Compare commits

...

977 Commits

Author SHA1 Message Date
Oliver Walters
f4fd84f50f Version number:
- Update to 0.14.0
2024-03-02 00:31:38 +00:00
Oliver
3b9fae6279 docker: push both multiple image tags (#6614)
* docker: push both multiple image tags

* Remove matrix strategy
2024-03-02 11:29:20 +11:00
Oliver
39ba25c5ed Render API docs (#6463)
* Render API docs

* Cleanup broken links

* Re-enable strict mode

* Change json to yaml

* Update docs/docs/api/schema.md

Co-authored-by: Matthias Mair <code@mjmair.com>

* Update docs/docs/api/schema.md

Co-authored-by: Matthias Mair <code@mjmair.com>

* Use neoteroi-mkdocs instead

- seems to render more reliably

* Fix SERVERS section for SPECTACTULAR_SETTINGS

* Script for splitting schema into smaller sections

* Generate an index file for the schema pages

* Move schema.md up one directory

* Fix formatting

* Remove tracked file

* Add hook for rebuilding API schema as part of RTD build

* Extract schema as RTD build step

* install invoke

* export env vars

* remove argparse

* Fix order of operations

* Compress env vars

* Remove custom env vars

- Now configured as part of RTD project

* Migrate db

* Revert "remove argparse"

This reverts commit 4665805340.

* Post-process generated schema file

* Fix file formatting

* Add note about schema repo

* no message

* Reduce schema overhead

* Ignore generated files

* Delete generated file

* Update .gitignore

* Add extra split for machine integration

* Remove schema files

- These will be auto-generated too

* Generate individual schema .md files

* Re-add .md files

- Need git commit log to work

* Update .gitignore

* Fix for CI test

* patch machine.api

* Revert previous change

* Formatting fix

* Adjust export step

* Bump API version

---------

Co-authored-by: Matthias Mair <code@mjmair.com>
2024-03-02 11:28:37 +11:00
Oliver
d39ab9c902 Mount db to volume (#6616)
- Do not bind externally
2024-03-02 10:49:20 +11:00
Oliver
dc03b33c35 Plugin table fix (#6613)
* Fix for plugin_table

- Catch potential undefined error
- Reported via sentry.io

* style fix

* further style fixes
2024-03-01 23:36:01 +00:00
Oliver
69871699c0 Details updates (#6605)
* Fix onClick behaviour for details image

* Moving items

- These are not "tables" per-se

* Refactoring for DetailsTable

* Skip hidden fields

* Cleanup table column widths

* Update part details

* Fix icons

* Add image back to part details

- Also fix onClick events

* Update stockitem details page

* Implement details page for build order

* Implement CompanyDetails page

* Implemented salesorder details

* Update SalesOrder detalis

* ReturnOrder detail

* PurchaseOrder detail page

* Cleanup build details page

* Stock location detail

* Part Category detail

* Bump API version

* Bug fixes

* Use image, not thumbnail

* Fix field copy

* Cleanup imgae hover

* Improve PartDetail

- Add more data
- Add icons

* Refactoring

- Move Details out of "tables" directory

* Remove old file

* Revert "Remove old file"

This reverts commit 6fd131f2a5.

* Fix files

* Fix unused import
2024-03-01 06:13:08 +00:00
Oliver
c8d6f2246b Fix postgresql container (#6610)
- Linked volume caused permission issues
2024-03-01 05:53:42 +00:00
Oliver
eb3d51f8a7 Adjust pkgr.yml (#6608)
* Adjust pkgr.yml

- Ref: https://github.com/inventree/InvenTree/issues/6154#issuecomment-1970536725
- Ref: https://doc.packager.io/documentation/customizing-the-build/#dependencies

* add in nginx config again

---------

Co-authored-by: Matthias Mair <code@mjmair.com>
2024-03-01 12:49:06 +11:00
Oliver
6962b61fff Fix docker tags (#6606)
- Revert to previous approach
2024-03-01 07:01:46 +11:00
Oliver
37c1fe1ccb BOM / Build Updates (#6604)
* Fix for build line table

- Prefill source location correctly

* Refactor API filtering for BomList

- Make use of RestFilter class

* Add "external stock" field to BomItem serializer

* Simplify custom filtering

* Add "structural" column to part table

* Update BOM tables:

- Display indication of "external stock"

* Annotate "external_stock" to part serializer

- Update PartTable [PUI]

* Annotate BuildLine serializer too

* BuildLine endpoint - filter available stock based on source build order

- If build order is specified, and has a source location, use that to filter available stock!

* Add message above build line table

* Update BuildLineTable

* Bump API version
2024-02-29 16:16:28 +11:00
Oliver
05e67d310a Details image tweaks (#6603)
* Changes for PartThumbTable:

- Limit use of custom styling
- Better display of images

* Fix background color

* Use <StylishText> for titles

* Cleanup details grid

- Use Mantine components
- Simplify structure

* Fix TableThumbProps
2024-02-29 11:18:08 +11:00
Oliver
cbd2794a7e New Crowdin updates (#6600)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix ru newlines

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-02-29 09:21:05 +11:00
Oliver
bf97804a0b Tweaks for SITE_URL setup (#6602) 2024-02-28 11:54:07 +00:00
Oliver
b192c44b61 Tweak for devcontainer docker compose (#6601)
- Do not use named volume
- Hardcode db path
2024-02-28 19:03:19 +11:00
Oliver
820d7c6a15 [PUI] Part parameter table (#6599)
* Refactor for PartTable

* Skeleton for ParametricPartTable

* Implement parametric part table

* Table updates
2024-02-28 06:43:36 +00:00
Oliver
a2a7b60d41 [PUI] Adds NewPart form (#6598) 2024-02-28 05:56:19 +00:00
Oliver
38d013ffe2 [PUI] Stock item forms (#6597)
* Implement "new stock item" button

* Fix adjustFilters for StockForm

* Fix imports
2024-02-28 05:43:18 +00:00
Oliver
2e81a304d1 Devcontainer postgresql (#6590)
* Working on devcontainer with postgresql

* Fix for docker-compose.yml

* Update postCreateCommand

* Tweak docker compose file

* Fix Dockerfile

- Do not use uv (breaks process)

* Update postCreateCommand.sh

- Skip database backup

* Tweak file

* Further improvements

- Remove 'devcontainer' Dockerfile target
- Fix postCreateCommand

* Further cleanup

* Reduce SQL errors

- Use filter().exists() rather than get()

* Set default SITE_URL

* Docs updates

* Fix hard-coded django version

* Update faq.md

* Typo fix: PluginObject -> PluginConfig

* Docs: strict mode

* docs: fix link

* docs: fix typo

* Fix error message

* Revert change to config_template default
2024-02-28 15:04:14 +11:00
Oliver
8f8b46e50d Translation fixes (#6595)
- Remove extraneous \n chars
2024-02-28 01:44:05 +00:00
Matthias Mair
1199291835 Refactor label/report template copying (#6582)
* [BUG] Inventree fiddles with files directly rather than using Django Storage api
Fixes #2585

* PEP fix

* clean diff

* move template discovery into central location

* more moving file operations

* fix paths

* and another path fixing

* more fixes

* fix typing

* switch config back to local

* revert locale stats

* add s3 support

* storages

* more adaptions

* use s3 switch to set storage backend

* fix reqs

* cleanup default_storage

* init in storage_backend

* move to storage classes everywhere

* fix call

* remove more S3 references

* move storage init

* fix startup error

* alsways use url

* ignore FileExistsError

* move s3 required url in

* remove S3 for now

* use Djangos defaults

* fix old import

* remove default_storage calls

* make labels/reports more similar

* expand functions out

* refactor to use refs where possible

* refactor copy section to be similar

* unify db lookup

* move shared code to generic section

* move ready out

* docstrings

* move even more functions out

* move references inline of the classes

* clean up refs

* fix init

* fix ensure dir

* remove unneeded tries

* cleanup diff

* more cleanup

* fix tests

* use SUBDIR
2024-02-28 09:25:01 +11:00
Oliver
f6123cc261 [WIP] docker / Caddy (#6551)
* remove docker-sqlite file

- Do not want to encourage use of sqlite

* Add Caddyfile

* Add default site URL to .env

- Matches Caddyfile

* Cleanup / simplify .env file

* Remove dev nginx conf file

* Further cleanup of .env file

* Update docker-compose.yml

- Use caddy image instead of nginx as proxy

* Set max body size

* gunicorn: enable external logging

* Update file structure

* Cleanup docker-compose file

* Update docker/docker-compose.yml

Co-authored-by: Matthias Mair <code@mjmair.com>

* Update docker/Caddyfile

Co-authored-by: Matthias Mair <code@mjmair.com>

* Fix for postgresql packages

- Need postgresql13-client to be installed, it contains pg_dump
- Without this, backup / restore *does not work*

* Create static_i18n dir if it does not exist

* Reduce output from collectstatic

* Revert gunicorn logging

- Want to see the logs in docker

* Fix trailing slash

Ref: https://github.com/inventree/InvenTree/pull/6551#issuecomment-1962423765

* tasks.py - pass 'nouv' option through

* Update package requirements:

- Allow installation of rapidfuzz without building

* Install uv as part of docker image

* Add environment variable to control downstream URL

* Do not use uv package manager by default

- Currently does not work "correctly" - ignores installed packages
- Requires further work to run reliably

* Fix docker-compose file

- Do not build locally

* Cleanup gunicorn file

- Remove unused lien

* Cleanup docker-compose.yml

- Simpler volume management

* Update Caddyfile

Add newline

* Update requirements.txt

Add newline

* Update tasks.py

Add missing blank line

* Simplify Caddyfile

* Adds option for customizing web port

* cleanup docker-compose.yml

- Better mapping of caddy data
- Cleaner volume setup

* Add django version template

- Ensure all docs links point to the current django version we are using

* docs: cleanup intro.md

* Cleanup serving_files.md

* Cleanup config.md

* docker install docs updates

* Enable code block copying

* Fix include file

* Fix link

* Update docker install docs

* Update docker.md

* Add info about demo dataset

* Tweak heading

* Update docs link checks

* Fix workflow

* Another fix

* More ignore pattearns

---------

Co-authored-by: Matthias Mair <code@mjmair.com>
2024-02-28 01:06:19 +11:00
Oliver
8a128a2bdd New Crowdin updates (#6567)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-02-27 07:10:57 +00:00
Oliver
bbace2b1c9 Silence duplicate template warnings (#6586) 2024-02-27 06:41:46 +00:00
Oliver
0c6334b3b4 Fix SITE_URL validator (#6585)
- Closes https://github.com/inventree/InvenTree/issues/6574
2024-02-27 16:46:48 +11:00
Oliver
405523881c Temperature units (#6584)
* Update unit conversion

- Set autoconvert_offset_to_baseunit attribute
- Add aliases for common temperature units
- Raise error if invalid target unit is provided

* Updated unit tests

- Checks for temperature conversion
- Checks for invalid units
2024-02-27 05:09:07 +00:00
Oliver
bb105d8c08 Tree fix (#6583)
* PartCategoryTree - add "subcategories" field

* Fix rendering of PartCategoryTree

* Implement similar fixes for StockLocationTree

* Bump API version

* Breadcrumb list tweak

- Limit to 7 items

* Fix bug for stock tree serializer

* Add unit tests
2024-02-27 03:00:32 +00:00
Oliver
de23023277 Tree fix (#6581)
* PartCategoryTree - add "subcategories" field

* Fix rendering of PartCategoryTree

* Implement similar fixes for StockLocationTree

* Bump API version
2024-02-27 01:00:11 +00:00
Oliver
8db769968f Allow currency symbol to be omitted from render_currency (#6580)
- Closes https://github.com/inventree/InvenTree/issues/6578
2024-02-26 23:16:54 +00:00
Matthias Mair
ea63a03fe4 Refactor docker CI for speed increases (#6562)
* push to ghcr (#168)

* Update docker.yaml

* Update docker.yaml

* Update docker.yaml

* use matrix to build for the plattforms (faster amd pub)

* try Buildjet

* switch to pyhton 3.9

* Update docker.yaml

* path python manually?

* bump python to 3.11

* Update Dockerfile

* step down vm

* cleanup workflow

* install docker on mac

* and docker compose too

* switch back to linux

* Docker tests (#175)

* push to ghcr (#168)

* Update docker.yaml

* Update docker.yaml

* Update docker.yaml

* use matrix to build for the plattforms (faster amd pub)

* try Buildjet

* switch to pyhton 3.9

* Update docker.yaml

* path python manually?

* bump python to 3.11

* Update Dockerfile

* step down vm

* cleanup workflow

* install docker on mac

* and docker compose too

* switch back to linux

* switch to uv

* fix uv install

* reintroduce checks

* cleanup diff
2024-02-27 09:50:44 +11:00
Matthias Mair
75c24fb8f4 Add config option to fully disable installing plugins (#6535)
* [FR] Add config option to fully disable installing plugins
Fixes #6531

* also restrict uninstalling

* Added test

* diff cleanup

* extend api to show if install was disabled

* PUI disable install buttons

* CUI disable install button if not available

* add config option

* Rephrase
2024-02-26 11:44:31 +00:00
Matthias Mair
85225538e6 Create test files in seperate folder & cleanup before image build (#6571)
* move ignore

* create testfiles in seperate folder

* add cleanup step to docker build

* use pathlib for paths
2024-02-26 11:23:49 +11:00
Bobbe
f441f672d6 Switch tests, docs, database settings from psycopg2 to psycopg (#6573)
* Fix isolation_level again, using the recommended method

* Use psycopg instead of psycopg2 in tests, docs
2024-02-26 10:58:32 +11:00
Oliver
18dcc60efd Update README.md (#6572)
Update sponsors
2024-02-26 09:50:42 +11:00
Bobbe
a3dc3bdbf4 Use psycopg2 constants for isolation_level again (#6569) 2024-02-26 09:46:57 +11:00
Bobbe
2df0fd8a67 Move get_scheduled_tasks call into register_tasks function (#6556)
* Move get_scheduled_tasks call into register_tasks function

* Adjust scheduled_tasks tests

* Set scheduled_tasks default, Fix test_function
2024-02-24 14:27:47 +11:00
Oliver
d689b95963 Adjust config template (#6561)
- Remove very advanced options from default configuration
2024-02-23 02:17:26 +00:00
Matthias Mair
1fa64dd8cc Fix tranlation pipeline (#6559)
* use default setup action

* small change to trigger CI

* use enviroment step

* define python version

* us uv a bit more

* clean diff

* globally define python version
2024-02-23 10:49:22 +11:00
Matthias Mair
6f0b2b31a8 Seperate CUI url paths and tests (#6543)
* move CUI JS files to CUI url section

* add flag to seperate CUI code and tests

* re-enable tests

* move urls back to backend patterns

* swap switch logic

* fix merge

* returning PUI paths if CUI not enabled

* revert test changes

* fix plugin settings url

* URL is not dependant on UI generation

* small fixes

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>

---------

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2024-02-22 22:56:50 +00:00
Matthias Mair
f5e02fd292 Switch to uv (#6499)
* test UV performance

* second dummy change

* fix CI

* set version - uv is already proving to be great

* dummy bump

* bump to newer uv

* use uv in more places

* revert uv

* add flag to disable UV

* fix vevn for CI

* bump uv version

* bump to uv-016

* bump

* bump

* switch to uv
2024-02-23 09:55:19 +11:00
Matthias Mair
5dbd3030d1 remove unused template tag (#6553) 2024-02-22 23:34:24 +11:00
Oliver
fdcbb68616 Fix for build output table (#6549)
- Correctly display passed test count
2024-02-22 00:05:46 +00:00
Oliver
6e713b15ae [Feature] Engineering Units (#6539)
* Conversion: Support conversion from "engineering notation"

* Add unit tests for scientific notation

* Update docs for unit conversion
2024-02-21 23:22:23 +00:00
Lavissa
8bf614607c Wrap theme parameters in memo (#6542) 2024-02-22 09:00:27 +11:00
Matthias Mair
c4d68aeef9 Dependabot/pip/cryptography 42.0.4 (#6545)
* Bump cryptography from 42.0.2 to 42.0.4

Bumps [cryptography](https://github.com/pyca/cryptography) from 42.0.2 to 42.0.4.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/42.0.2...42.0.4)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix merge

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-22 08:57:56 +11:00
Oliver
571b71f232 Retry table name query (#6540)
* Retry table name query

* Cleanup table names

* More cleanup
2024-02-21 14:11:56 +00:00
Oliver
defa03b83a Update PartParameterTemplate API (#6538)
* Update PartParameterTemplate API

- Expose number of "parts" using a particular template
- Improve filtering visibility
- Update PUI table

* Update API version
2024-02-21 12:59:02 +00:00
Oliver
0bfbd45cec API Tree filters (#6536)
* PartCategoryList: Add FilterSet class

* Update PartCategory filters

- Migrate all custom filtering to the FilterSet

* Logic updates

* Revert deleted code

* Implement similar filters for StockLocation list API

* Update API docs

* Unit test updates

* More fix

* Fix for StockLocation filterclass

* Fix PUI tables

* Cleanup CUI tables

* Updated unit tests
2024-02-21 23:34:20 +11:00
Oliver
a310437dc7 Bug fix for allocating items to build (#6532)
* Bug fix for allocating items to build

- Handle case where allocated quantity is less than 1
- Add unit tests
- Closes https://github.com/inventree/InvenTree/issues/6145

* Remove debug messgae
2024-02-21 10:35:26 +11:00
Lukas
7694092935 Added various PO fixes (#6483)
* Added various PO fixes

* Add auto-pricing and merge items functionality to PurchaseOrderLineItem

* Bump api version to v173

* Add po line item create/update tests
2024-02-21 09:03:32 +11:00
Oliver
55c64b546f Locales fix (#6528)
* Fix for language select translation

- Function rather than const variable
- Allows for recalculation

* Hide pseudo language if not in dev mode
2024-02-21 08:38:47 +11:00
Oliver
68ba9653ef Update devcontainer docs (#6527)
- Closes https://github.com/inventree/InvenTree/issues/6392
2024-02-20 21:03:26 +11:00
Oliver
567c7edbaf Test template disable (#6526)
* Add new field to PartTestTemplate model

- 'enabled' field
- default = True (backwards compatibility)
- Allows tests to be disabled
- Retains test results but disables test

* Update PartTestTemplate API

- Expose new field
- Enable filtering by field

* CUI updates

- Add to PartTestTemplate table

* PUI: Update PartTestTemplateTable

* Update getRequiredTests

- By default, filter out tests which are "disabled"

* Update StockItemTestResult table

- Only display "enabled" tests
- Update CUI
- UPdate PUI

* Update existing build output table

* Bump API version

* Docs updates

* Updated unit tests
2024-02-20 10:01:59 +00:00
Lavissa
3eb1914f1e Make API form Select Field theme-aware (#6521) 2024-02-20 11:33:56 +11:00
Lavissa
41f09f7578 Make Category tree theme-aware (#6523) 2024-02-20 11:32:19 +11:00
Matthias Mair
ebe01530e6 Bump CI/pre-commit versions (#6522)
* bump CI versions
and run new autofixes

* fix reqs
2024-02-20 10:47:57 +11:00
Oliver
6e932f85cf Fix for clearable form fields (#6518) 2024-02-19 06:41:31 +00:00
Oliver
ed95cf2a8f [CI] DB Migration Checks (#6517)
* Update imports

- Help prevent circular imports

* Run migration checks against 0.13.5
2024-02-19 06:30:40 +00:00
Oliver
3a52a1631d [PUI] Tweaks (#6516)
* Enable editing "part" in PartTestTemplate

* Hide part if user is not staff

* API: allow filtering of templates by key

* Update API docs
2024-02-19 06:05:36 +00:00
Oliver
3b11a01966 [PUI] Notifications (#6515)
* Add placeholder buttons

* Add callback functions

- Mark all notifications as read
- Delete all read notifications

* remove unused import
2024-02-19 16:06:21 +11:00
Oliver
a74b29f059 Fixes for test result updates (#6514)
* Fix ordering of "stock" column in StockItemTable

* Handle table.activeFilters

- Can be passed undefined value in some cases

* Fix legacy test result table

- Add in "pass test" button

* Improve logic for creating templates

- Only look at ancestor parts *above* the existing part

* Update migration

- Only look above!

* Improve matching in template

* New data migration

- Fixes (probably rare) edge case in previous data migration

* Table tweak

- Embolden required test templates

* Add assertion check to data migration

* Update API version

- Add filter for "has_results" on the PartTestTemplate API endpoint

* Logic fix
2024-02-19 16:04:10 +11:00
Oliver
7dab02555d Allow import of part images (#6513)
- Remove "readonly" attribute from image field
- Ref: https://github.com/inventree/InvenTree/issues/6511
2024-02-19 12:47:25 +11:00
Oliver
5f3932b7e1 Tweak PartParameterTable (#6512)
- Fix ordering of "name" field
2024-02-19 12:47:18 +11:00
Oliver
0f51127adf [WIP] Test result table (#6430)
* Add basic table for stock item test results

* Improve custom data formatter callback

* Custom data formatter for returned results

* Update YesNoButton functionality

- Add PassFailButton with custom text

* Enhancements for stock item test result table

- Render all data

* Add placeholder row actions

* Fix table link

* Add option to filter parttesttemplate table by "inherited"

* Navigate through to parent part

* Update PartTestTemplate model

- Save 'key' value to database
- Update whenever model is saved
- Custom data migration

* Custom migration step in tasks.py

- Add custom management command
- Wraps migration step in maintenance mode

* Improve uniqueness validation for PartTestTemplate

* Add 'template' field to StockItemTestResult

- Links to a PartTestTemplate instance
- Add migrations to link existing PartTestTemplates

* Add "results" count to PartTestTemplate API

- Include in rendered tables

* Add 'results' column to test result table

- Allow filtering too

* Update serializer for StockItemTestResult

- Include template information
- Update CUI and PUI tables

* Control template_detail field with query params

* Update ref in api_version.py

* Update data migration

- Ensure new template is created for top level assembly

* Fix admin integration

* Update StockItemTestResult table

- Remove 'test' field
- Make 'template' field non-nullable
- Previous data migrations should have accounted for this

* Implement "legacy" API support

- Create test result by providing test name
- Lookup existing template

* PUI: Cleanup table

* Update tasks.py

- Exclude temporary settings when exporting data

* Fix unique validation check

* Remove duplicate code

* CUI: Fix data rendering

* More refactoring of PUI table

* More fixes for PUI table

* Get row expansion working (kinda)

* Improve rendering of subtable

* More PUI updates:

- Edit existing results
- Add new results

* allow delete of test result

* Fix typo

* Updates for admin integration

* Unit tests for stock migrations

* Added migration test for PartTestTemplate

* Fix for AttachmentTable

- Rebuild actions when permissions are recalculated

* Update test fixtures

* Add ModelType information

* Fix TableState

* Fix dataFormatter type def

* Improve table rendering

* Correctly filter "edit" and "delete" buttons

* Loosen requirements for dataFormatter

* Fixtures for report tests

* Better API filtering for StocokItemTestResult list

- Add Filter class
- Add option for filtering against legacy "name" data

* Cleanup API filter

* Fix unit tests

* Further unit test fixes

* Include test results for installed stock items

* Improve rendering of test result table

* Fix filtering for getTestResults

* More unit test fixes

* Fix more unit tests

* FIx part unit test

* More fixes

* More unit test fixes

* Rebuild stock item trees when merging

* Helper function for adding a test result to a stock item

* Set init fix

* Code cleanup

* Cleanup unused variables

* Add docs and more unit tests

* Update build unit test
2024-02-18 23:26:01 +11:00
Miklós Márton
ad1c1ae604 Add option to disable the build output completion if are tests not passed (#6057)
* Add option to disable the build output completion if required tests not passed

Fixes #5037

* Fix review comments

* Added tests

* Add settinsg option to PUI

* Utilize F" string concatenation

* Add validation to serializer too to being able to generate proper error message in the case if multiple outputs having incomplete tests

* Fix other build tests failing because of the new stock items

* Remove len from array empty check

* Update serializers.py

* Update models.py

Simplify error message

* Update settings.py

Formatting fix

* Update models.py

More style fixes

* Update models.py

Remove empty line

---------

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2024-02-18 10:28:37 +11:00
Michael
7adf2e0835 Added doc for status_label to context variables (#6507) 2024-02-18 09:00:31 +11:00
Matthias Mair
a1eb3623ba fix bash syntax (#6505) 2024-02-16 18:33:29 +11:00
Oliver
7681cd2c44 Default site (#6503)
* Allow simpler setting for CSRF_TRUSTED_ORIGINS and CORS_ALLOWED_ORIGINS

- If these are not specified by the user, but a SITE_URL *is* specified, then use that

* Update docs

* Update config.md

Remove outdated notes
2024-02-16 15:14:55 +11:00
Oliver
e88defd026 Revert custom temp definition (#6502) 2024-02-16 01:22:05 +00:00
Oliver
d9b769d27b Fix broken URL (#6497) 2024-02-16 11:01:08 +11:00
Oliver
04261dbcac Provide a *copy* of the page context to pass to plugin (#6496) 2024-02-16 11:00:51 +11:00
Oliver
43457d4136 Support degree symbols for temperature units (#6498) 2024-02-16 11:00:17 +11:00
Oliver
21f209f7cc Forms actions fix (#6493)
* Handle case where OPTIONS.actions is not present

* Specify stock.change permission

* Hide table button based on user permission

* Fix for permission check class
2024-02-16 08:47:05 +11:00
Oliver
38fac47e39 New Crowdin updates (#6490)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Clean translation

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-02-15 23:17:24 +11:00
Lukas
8807492db6 Fix settings typing and use generics from standard collection (#6487)
* Fix settings typing and use generics from standard collection

* Fix docstring
2024-02-15 22:44:32 +11:00
Lukas
35577fad41 Added pdf2image kwargs (#6488)
* Added pdf2image kwargs

* Added direct use_cairo arg and make it default enabled

* Fix docs

* Fix docs

* Fix docs

* Fix docs
2024-02-15 20:53:54 +11:00
Oliver
5af6b92f90 Fix retry time for Q_CLUSTER (#6489)
- Ensure it is *greater* than timeout
2024-02-15 11:36:14 +11:00
Oliver
5ca007a184 Move CurrencyTable to admin center (#6484) 2024-02-14 14:49:24 +00:00
Oliver
0e1923a90c Handle error on logout (#6482)
* Handle error on logout

- Logout failure redirects to login page

* Handle case if user is undefined when fetching role data

* Cleanup error messages

* More error message cleanup
2024-02-14 14:17:01 +00:00
Lukas
aa7eaaab3a Machine integration (#4824)
* Added initial draft for machines

* refactor: isPluginRegistryLoaded check into own ready function

* Added suggestions from codereview

* Refactor: base_drivers -> machine_types

* Use new BaseInvenTreeSetting unique interface

* Fix Django not ready error

* Added get_machines function to driver

- get_machines function on driver
- get_machine function on driver
- initialized attribute on machine

* Added error handeling for driver and machine type

* Extended get_machines functionality

* Export everything from plugin module

* Fix spelling mistakes

* Better states handeling, BaseMachineType is now used instead of Machine Model

* Use uuid as pk

* WIP: machine termination hook

* Remove termination hook as this does not work with gunicorn

* Remove machine from registry after delete

* Added ClassProviderMixin

* Check for slug dupplication

* Added config_type to MachineSettings to define machine/driver settings

* Refactor helper mixins into own file in InvenTree app

* Fixed typing and added required_attributes for BaseDriver

* fix: generic status import

* Added first draft for machine states

* Added convention for status codes

* Added update_machine hook

* Removed unnecessary _key suffix from machine config model

* Initil draft for machine API

* Refactored BaseInvenTreeSetting all_items and allValues method

* Added required to InvenTreeBaseSetting and check_settings method

* check if all required machine settings are defined and refactor: use getattr

* Fix: comment

* Fix initialize error and python 3.9 compability

* Make machine states available through the global states api

* Added basic PUI machine admin implementation that is still in dev

* Added basic machine setting UI to PUI

* Added machine detail view to PUI admin center

* Fix merge issues

* Fix style issues

* Added machine type,machine driver,error stack tables

* Fix style in machine/serializers.py

* Added pui link from machine to machine type/driver drawer

* Removed only partially working django admin in favor of the PUI admin center implementation

* Added required field to settings item

* Added machine restart function

* Added restart requird badge to machine table/drawer

* Added driver init function

* handle error functions for machines and registry

* Added driver errors

* Added machine table to driver drawer

* Added back button to detail drawer component

* Fix auto formatable pre-commit

* fix: style

* Fix deepsource

* Removed slug field from table, added more links between drawers, remove detail drawer blur

* Added initial docs

* Removed description from driver/machine type select and fixed disabled driver select if no machine type is selected

* Added basic label printing implementation

* Remove translated column names because they are now retrieved from the api

* Added printer location setting

* Save last 10 used printer machine per user and sort them in the printing dialog

* Added BasePrintingOptionsSerializer for common options

* Fix not printing_options are not properly casted to its internal value

* Fix type

* Improved machine docs

* Fix docs

* Added UNKNOWN status code to label printer status

* Skip machine loading when running migrations

* Fix testing?

* Fix: tests?

* Fix: tests?

* Disable docs check precommit

* Disable docs check precommit

* First draft for tests

* fix test

* Add type ignore

* Added API tests

* Test ci?

* Add more tests

* Added more tests

* Bump api version

* Changed driver/base driver naming schema

* Added more tests

* Fix tests

* Added setting choice with kwargs and get_machines with initialized=None

* Refetch table after deleting machine

* Fix test

---------

Co-authored-by: Matthias Mair <code@mjmair.com>
2024-02-14 14:13:47 +00:00
Oliver
aed7754bc2 Fix for AttachmentTable (#6481)
* Fix for AttachmentTable

- Rebuild actions when permissions are recalculated

* Update examples.md
2024-02-15 00:45:33 +11:00
Michael
d86f964fb1 Add BOM creation to the sofa example (#6477)
* Add BOM creation to ther sofa example

* Fixed typo

* Update examples.md

Cleanup wording a bit

---------

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2024-02-14 17:28:13 +11:00
Oliver
3637b28da7 Remove maintenance mode wrapper from plugin loading (#6479) 2024-02-14 13:22:38 +11:00
Matthias Mair
825366f684 fix schema path (#6474) 2024-02-13 08:01:37 +01:00
Oliver
f3ca704e97 Maintenance mode update (#6473)
- Add check for exiting maintenance mode after reloading plugin registry
2024-02-13 14:04:00 +11:00
Matthias Mair
0c2f1cceb6 Add API change detection (#6440)
* Add basic task for generating apidocs

* Fix SPECTACTULAR_SETTINGS

- Some provided options were not correct

* Update .gitignore

* Fix for duplicated API path

- `/api/plugins/activate` routed to PluginActivate view
- Must be associated with a specific plugin ID

* By default, fail if warnings are raised

* Use GenericAPIView for GetAuthToken

* Use GenericAPIView for RolesDetail endpoint

* Refactor more endpoints to use GenericApiView

* More API cleanup

* Add extra type hints for exposed methods

* Update RoleDetails endpoint

- Specify serializer
- Use RetrieveAPI class type

* More type hints

* Export API docs as part of CI

* add more api views docs

* even more docs

* extend tests to api-version

* simplify serializer

* and more docs

* fix serializer

* added more API docs

* clean diff

* Added APISearch base

* do not assume you know the user
he might be anonymously creating the schema ;-)

* set empty serializer where no input is needed

* Use dummy model for schema generation

* fix OpenAPI docs section

* only run if needed

* remove schema task

* Add version check

* pin version

* fix QC order

* fix assign

* refactor order

* optimize compile times

* fix assign

* onyl use install

* more fixing

* use full update cycle

* revert python change

* use api_version

* run py

* why?

* clean up output

* only check for api version diff if api_version was not touched

* add schema check again

* use seperate filter for API

* increment api_version

* Added push step

* seperate publishing step to lower complexity

* fix naming

* fix file ending

* Update qc_checks.yaml

---------

Co-authored-by: Oliver Walters <oliver.henry.walters@gmail.com>
2024-02-13 12:57:25 +11:00
Bobbe
b372db8960 Fix APICallMixin url_args handling (#6468) 2024-02-12 08:01:28 +11:00
Bobbe
824aa8138b Use re_path instead of deprecated url function in plugin docs (#6466) 2024-02-12 08:00:06 +11:00
Bobbe
e6e6473503 Fix uncaught Company.DoesNotExist error in SupplierBarcodeMixin (#6467) 2024-02-12 07:58:57 +11:00
Oliver
bf2111ef9d New Crowdin updates (#6459)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* ru: Remove newline chars

* Simplify translation

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-02-11 13:24:07 +11:00
Oliver
903c65d08a Maintenance Mode Update (#6462)
* Adjust maintenance mode backend

- Save a timestamp to the setting, after which maintenance mode is not active
- Fallback to account for possibility that race condition / exception leaves maintenance mode active

* Update docstring

* Remove unused import

* Add unit tests for maintenance mode
2024-02-10 14:33:45 +11:00
Oliver
9c93130224 PluginValidationMixin : fix full_clean method (#6461)
- Accept *args and **kwargs
2024-02-10 10:57:51 +11:00
Lavissa
0ba16bbbbd Fix string capitalization (#6460) 2024-02-10 08:41:14 +11:00
Oliver
325841dbf1 Log plugin event errors (#6455)
* Log plugin event errors

- Log errors from plugins running process_event function

* Catch exception on custom validation

* Catch some more errors

* Fix circular imports
2024-02-08 17:31:53 +11:00
Oliver
af4d888b1b Generate API docs (#6319)
* Add basic task for generating apidocs

* Fix SPECTACTULAR_SETTINGS

- Some provided options were not correct

* Update .gitignore

* Fix for duplicated API path

- `/api/plugins/activate` routed to PluginActivate view
- Must be associated with a specific plugin ID

* By default, fail if warnings are raised

* Use GenericAPIView for GetAuthToken

* Use GenericAPIView for RolesDetail endpoint

* Refactor more endpoints to use GenericApiView

* More API cleanup

* Add extra type hints for exposed methods

* Update RoleDetails endpoint

- Specify serializer
- Use RetrieveAPI class type

* More type hints

* Export API docs as part of CI

* add more api views docs

* even more docs

* extend tests to api-version

* simplify serializer

* and more docs

* fix serializer

* added more API docs

* clean diff

* Added APISearch base

* do not assume you know the user
he might be anonymously creating the schema ;-)

* set empty serializer where no input is needed

* Use dummy model for schema generation

* fix OpenAPI docs section

* Update .github/workflows/qc_checks.yaml

Co-authored-by: Matthias Mair <code@mjmair.com>

* REmove duplicate commands

* Ignore warnings in CI

---------

Co-authored-by: Matthias Mair <code@mjmair.com>
2024-02-08 16:19:57 +11:00
Oliver
a99ba75fed Report helper (#6454)
* Adds helper function for formatting numbers in reports

* Update docs
2024-02-08 15:01:47 +11:00
Oliver
226dc82cfd Update Proxy support for django 4.2 (#6453)
* Update settings.py to support more django settings

- Now required by 4.2
- Prevents running behind proxy
- CSRF_TRUSTED_ORIGINS
- USE_X_FORWARDED_HOST
- USE_X_FORWARDED_PORT
- Update config template file also.

* Update settings / docs

* Update settings.py

Remove dirt
2024-02-08 14:44:58 +11:00
Oliver
b55e840050 (Yet another) Fix for dockerfile (#6452)
* Fix for dockerfile

- Ensure that core DB packages are kept around
- We can purge the dev packages after building python modules

* Revert "Fix for dockerfile"

This reverts commit 8522ff243e.

* Update requirements.txt

- Remove psycopg-c
- Not supported unless postgresql-libs are installed as a system package
- Prints a warning message in logs
- Works without it
2024-02-08 14:38:55 +11:00
Oliver
633fbd37bd Maintenance Mode Improvements (#6451)
* Custom migration step in tasks.py

- Add custom management command
- Wraps migration step in maintenance mode

* Rename custom management command to "runmigrations"

- Add command to isRunningMigrations

* Add new data checks

* Update database readiness checks

- Set maintenance mode while performing certain management commands

* Remove unused import

* Re-add syncdb command

* Log warning msg

* Catch another potential error vector
2024-02-08 12:47:49 +11:00
Oliver
8b62f7b2c0 Label printing errors (#6450)
* Enhance error handling for label printing

* Better feedback in forms

* Raise error to user - no need to log
2024-02-08 11:37:06 +11:00
dependabot[bot]
4e58f0a3c7 Bump django from 4.2.9 to 4.2.10 (#6448)
Bumps [django](https://github.com/django/django) from 4.2.9 to 4.2.10.
- [Commits](https://github.com/django/django/compare/4.2.9...4.2.10)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-08 10:57:20 +11:00
Oliver
d77cbb4c9b New Crowdin updates (#6447)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix newlines

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-02-08 10:56:41 +11:00
Oliver
59c78460c8 Update tasks.py (#6446)
* Update tasks.py

- Exclude temporary settings when exporting data

* Remove duplicate code
2024-02-07 23:26:51 +11:00
Oliver
22af3e2f59 Postgresql fix (#6441)
* Assume maintenance mode is *on* if database is inaccessible

* Specify ash shell

* Update psycopg requirements

* Style fixes

* style fix - backends.py
2024-02-07 22:33:37 +11:00
Oliver
edd6f25411 Fix for issue 6442 (#6443)
- Add null option to status field for stock adjustment
- Prevent stock adjustments from setting status to OK
2024-02-07 20:11:46 +11:00
Oliver
fdd70263ea Week number (#6439)
* Support week number for batch generation

* Bug fix - hour is not minute

* Update docs
2024-02-07 11:36:20 +11:00
Oliver
55949e5321 Maintenance Mode Fix (#6422)
* Custom maintenance mode backend

* Implement check and retries

* Update debug formatting
2024-02-07 10:52:10 +11:00
Oliver
df5a3013e6 Fix for build.js (#6437)
- Catch potential empty build_detail attribute
2024-02-07 10:30:08 +11:00
Miklós Márton
045af50f99 Mention the API version bump in CONTRIBUTION.md (#6436)
* Mention the API version bump in CONTRIBUTION.md

* Update CONTRIBUTING.md

---------

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2024-02-07 09:18:19 +11:00
Oliver
c0c4e9c226 [WIP] Plugin Updates (#6400)
* Add method to extract "install name" from a plugin

* Include more information in plugin meta serializer

* Implement better API filtering for PluginConfig list

* Add an "update" button to the plugin table row actions

- Only for "package" plugins

* Adds method to update a plugin:

- Add plugin.installer.update_plugin method
- Add error logging to existing methods
- Add API endpoint and serializer
- Integrate into PUI table

* Implement lazy loading for plugin tables

* Extract package information on registry load

- Info is already available via entrypoint data
- Significantly faster as introspection operation is expensive
- Less code is good code

* Frontend updates

* Add accordion to plugin page

* Add setting to control periodic version check updates

* Update API version info

* Add "package_name" field to PluginConfig

- When the plugin is loaded, save this name to the PluginConfig model
- Update the admin view

* Update API serializer

* Refactor plugin installer code

- Add common functions

* Adds API endpoint for uninstalling an installed plugin

* Allow uninstall of plugin via API

- Add API endpoints
- Add UI elements

* Tweak for admin list display

* Update plugin

* Refactor "update" method

- Just use the "install" function
- Add optional "version" specifier
- UI updates

* Allow deletion of PluginConfig when uninstalling plugin

* Add placeholder for deleting database tables

* Revert code change

- get_object() is required

* Use registry.get_plugin()

- Instead of registry.plugins.get()
- get_plugin checks registry hash
- performs registry reload if necessary

* Add PluginValidationMixin class

- Allows the entire model to be validated via plugins
- Called on model.full_clean()
- Called on model.save()

* Update Validation sample plugin

* Fix for InvenTreeTree models

* Refactor build.models

- Expose models to plugin validation

* Update stock.models

* Update more models

- common.models
- company.models

* Update more models

- label.models
- order.models
- part.models

* More model updates

* Update docs

* Fix for potential plugin edge case

- plugin slug is globally unique
- do not use get_or_create with two lookup fields
- will throw an IntegrityError if you change the name of a plugin

* Inherit DiffMixin into PluginValidationMixin

- Allows us to pass model diffs through to validation
- Plugins can validate based on what has *changed*

* Update documentation

* Add get_plugin_config helper function

* Bug fix

* Bug fix

* Update plugin hash when calling set_plugin_state

* Working on unit testing

* More unit testing

* Fix typo (installing -> uninstalling)

* Reduce default timeout

* set default timeout as part of ApiDefaults

* revert changes to launch.json

* Remove delete_tables field

- Will come back in a future PR

* Fix display of nonFIeldErrors in ApiForm.tsx

* Allow deletion of deleted plugins

- PluginConfig which no longer matches a valid (installed) plugin

* Cleanup

* Move get_plugin_config into registry.py

* Move extract_int into InvenTree.helpers

* Fix log formatting

* Update model definitions

- Ensure there are no changes to the migrations

* Update PluginErrorTable.tsx

Remove unused var

* Update PluginManagementPanel.tsx

remove unused var

* Comment out format line

* Comment out format line

* Fix access to get_plugin_config

* Fix tests for SimpleActionPlugin

* More unit test fixes

* Update plugin/installer.py

- Account for version string
- Remove on uninstall

* Fix
2024-02-07 02:08:30 +11:00
Oliver
cd803640a9 Fix display of allocated stock items in build table (#6427)
- Include stock_detail in BuildLine serializer by default
2024-02-06 23:28:06 +11:00
Oliver
2b9816d1a3 [Plugin] Enhanced custom validation (#6410)
* Use registry.get_plugin()

- Instead of registry.plugins.get()
- get_plugin checks registry hash
- performs registry reload if necessary

* Add PluginValidationMixin class

- Allows the entire model to be validated via plugins
- Called on model.full_clean()
- Called on model.save()

* Update Validation sample plugin

* Fix for InvenTreeTree models

* Refactor build.models

- Expose models to plugin validation

* Update stock.models

* Update more models

- common.models
- company.models

* Update more models

- label.models
- order.models
- part.models

* More model updates

* Update docs

* Fix for potential plugin edge case

- plugin slug is globally unique
- do not use get_or_create with two lookup fields
- will throw an IntegrityError if you change the name of a plugin

* Inherit DiffMixin into PluginValidationMixin

- Allows us to pass model diffs through to validation
- Plugins can validate based on what has *changed*

* Update documentation

* Add get_plugin_config helper function

* Bug fix

* Bug fix

* Update plugin hash when calling set_plugin_state

* Working on unit testing

* More unit testing

* Move get_plugin_config into registry.py

* Move extract_int into InvenTree.helpers

* Fix log formatting

* Update model definitions

- Ensure there are no changes to the migrations

* Comment out format line

* Fix access to get_plugin_config

* Fix tests for SimpleActionPlugin

* More unit test fixes
2024-02-06 22:00:22 +11:00
Oliver
dce2954466 Update docker package requirements (#6423)
- Replace psycopg2 with psycopg[binary]
- Ref: https://learndjango.com/tutorials/psycopg3-binary-and-django-42-installation-quick-t
2024-02-06 21:22:21 +11:00
Matthias Mair
e3fb12dc03 Bump deps (#6421)
* bump deps

* cleanup
2024-02-06 19:59:56 +11:00
dependabot[bot]
341f638e5d Bump cryptography from 41.0.7 to 42.0.0 (#6419)
Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.7 to 42.0.0.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/41.0.7...42.0.0)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-06 07:54:03 +01:00
Matthias Mair
d36cf358f8 Bump to Dj 4.x (#6173)
* bump to dj >4.2

* switch to experimental git release

* bump django-import_export

* bump mptt

* replace is_ajax, which was removed
https://docs.djangoproject.com/en/3.1/releases/3.1/#id2

* Save before accessing values in m2m/fk fields

* move plugin init

* use dev version of django for fix

* update deps

* fix deps

* use django smaller 4.2

* fix reqs

* fix merge

* remove moved code

* another merge fix

* fix ajax call

* fix refs

* change python min v

* fix deps

* bump deps

* fix deps

* pin pillow

* dj 4.1 upgrades

* make diff smaller

* bump all deps

* drop down to py3.9

* bump versions

* merge fix

* fix diff

* more bumping

* diff cleanup

* bump deps

* fix reqs

* use accurate state for model migrations
using apps the historically correct state is used

* try import

* added more logs

* add try here too

* clean up rebuilds

* Dj 4.2 (#161)

* autochanges

* bump

* fix diff

* fix diff

* bump deps

* fix req

* remove select_related to test error influence

* switch to mptt fork

* fix reqs for upstream

* move tracking ensureance into save

* optimize check frequency

* use psycopg instead of psycopg2

* fix header

* just use the values

* switch to dj < 4.2

* fix req

* another req fix

* switch to 4.2 again

* fix merge error

* Check for null pk in calculate_total_price

Cannot access self.lines if pk is Null

* use patched mptt

* try psycopg2 again

* Remove tree rebuild from migrations

* Prevent notify_users if importing or migrating

* Add order_by() to subquery annotations

- Ref: https://stackoverflow.com/a/629691

* Update stock filters

- Append order_by()

* fix error if running without timezones in testing

* add logging to figure this out

* remove tz from self.creation if TZ is off

* add tz?

* move around?

* only run the test i am trying to figure out
not reproducible on my machine

* only run the test i am trying to figure out
not reproducible on my machine

* run all tests again

---------

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2024-02-06 13:30:50 +11:00
Oliver
676bb02f6e Prevent notify_users if importing or migrating (#6415)
* Prevent notify_users if importing or migrating

* Comment out format line
2024-02-05 13:53:55 +00:00
Matthias Mair
4869d0dab8 Add mermaid to docs (#5405)
* move devcontainer docs

* rename bare metal pages

* fix backlinks

* Add getting started for devs

* add mermaid

* include contrib in docs

* use another plugin

* include everything

* update doc checks

* fix install command

* Update mkdocs.yml

* Update mkdocs.yml

* Update starting.md

* Update starting.md

* Update starting.md

* extend requirements

* Update mkdocs.yml

* clean diff

* clean diff + check if it works

* fix CI check for custom tags

* clean md
2024-02-05 21:40:38 +11:00
Oliver
fc86064bd1 Handle null case for delivery_methods (#6411) 2024-02-05 21:24:59 +11:00
Oliver
f88d5577d1 [Docker] Pin postgresql version (#6407)
* Update docker build

- Pin postgresql libs to v13

* Fix typo
2024-02-05 18:25:14 +11:00
Oliver
e9c6dd8273 Use registry.get_plugin() (#6408)
- Instead of registry.plugins.get()
- get_plugin checks registry hash
- performs registry reload if necessary
2024-02-05 15:51:45 +11:00
Oliver
3a48af6bd4 Fix contentexcludes in tasks.py (#6406)
- Include 'socialaccount.socialtoken'
2024-02-05 15:23:41 +11:00
Oliver
77fd6b6bb3 [PUI] Tweaks and refactor for "part details" page (#6405)
* Add getModelInfo helper function

- Extract model definition from provided modeltype

* Improvements for details.tsx

- Use defined URL functions, not strings
- Catch potential errors

* Fix PartDetail page

- Use modeltype definitions
- URL fixes
2024-02-05 14:25:29 +11:00
Oliver
7483fd203d New Crowdin updates (#6367)
* updated translation base

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix ru/django.po

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-02-05 13:42:57 +11:00
Oliver
e207606686 Simplify translation (#6404)
* Simplify translation

* style fix
2024-02-05 13:42:49 +11:00
Miklós Márton
74d0eb729c Allow orders' creation date edit via API (#6178)
* Add creation_date to the serializers of the Orders (to make it changeable by API)

Fixes #6159

* Add API tests for the creation_date field

* Fix missing , in test_api.py

* Allow null to be passed (transleted to current date later) for creation_date of the Orders in the serializers too
2024-02-04 20:09:17 +11:00
Lavissa
c4a71a991a Remove devcontainer Git Feature (#6398) 2024-02-04 09:28:52 +11:00
Oliver
2a924ec85b Fix for filter adjustment in related field (#6396)
- Cannot compare two objects directly
2024-02-03 23:24:24 +11:00
Oliver
5bc00298c6 [WIP] Site ID Fixes (#6390)
* Fix docs for INVENTREE_SITE_URL

* Adjust default SITE_ID

* Optional support for multi-site

- Disable by default

* Prevent site setting from being changed if set by config parameter

* Update site url setting on server launch

* Update log messages

* Update RULESET_MODELS

* Update unit tests

* More fixes for unit tests

* Update docs

* Update SSO image
2024-02-03 22:51:29 +11:00
Oliver
538ff9be7b Error message fix (#6393)
* Remove debug error

* Hide errors
2024-02-02 22:20:38 +11:00
Matthias Mair
be30cec2ad [PUI] SSO Support (#6333)
* Add sso buttons
Fixes #5753

* Added more icons

* fix callback url

* made heading dynamic

* allow either sso or normal reg

* Added SSO registration

* added divider

* added preferred ui API

* fix test

* fix update function

* refactor

* fix naming

* fix import

* add coverage ignore

* more ignore

* fixed missing key

* renamed button

* revert coverage statements

* set prefered mode before sso login

* added dynamic login redirect

* fixed test assert

* use API Endpoints instead of hardcoding

* fix lookup
2024-02-02 20:15:13 +11:00
Oliver
c9c93bce39 BuildOrder: Fix bug where test templates are requested for a non-trackable output (#6387) 2024-02-02 14:58:11 +11:00
Oliver
f97cdef9fc [PUI] Login / Logout State Fixes (#6368)
* Fix API endpoint URLs

* Adds "authenticated" field to root API endpoint

* Load global status data separately

- Create new global state manager
- Load *after* login
- Prevents auth popup dialog and failure messages

* Add launch config for frontend dev

* Update docs

* Clear token auth if no token is defined

* remove unneeded import

* Revert format of InfoView endpoint

* Remove "authenticated" from InfoView

* Refactor is_staff token check

- Using new get_token_from_request method

* Cleanup code

- return early

* URL fixes

- More fixes for incorrect api calls

* Better tracking of authenticated status

- track an internal flag in apiState

* Prioritize token auth

* Only fetch userState if authenticated

* Force unauthenticated state on first launch

* Updates to login procedure

- Rename doClassicLogin to doBasicLogin (reflecting "basic" auth)
- Add "loggedIn" attribute to sessionState
- Cleanup procedure for securing a token

* Abort early on checkLoginState

- Prevent failed calls to user_me

* Refactoring

- Simpler to just track token state
- No need for separate status tracker
- Works much cleaner this way

* Remove debug messages

* Cleanup unused imports

* Fix unused variable

* Revert timeout to 2000ms

* Rename doClassicLogout -> doLogout

* Improvements for checkLoginState

- Account for the presence of a CSRF session cookie
- If available, use it to fetch a token

* Clear CSRF cookie on logout

- Forces logout from session
- Tested, works well!
- Clean up notifications

* Cleanup setApiDefaults method

* fix global logout (PUI -> CUI)

---------

Co-authored-by: Matthias Mair <code@mjmair.com>
2024-02-02 12:02:55 +11:00
Oliver
ec2a66e7a5 Request column names from server when loading table (#6375)
* Request column names from server when loading table

- As these are translated by the server, we can make use of them on the client side!
- Reduces duplication of translated titles

* Move tables up one level

* Cache translated table column names

* Clear table column cache whenever we change locale context

* Fix cache behavioue

* Enforce titleCase for table column names

* Refactor BuildOrder table

* Refactor Company table

* Refactor order tables

* More refactoring

- Refactoring more columns

* Update more tables

* Refactor BomTable

* Remove toTitleCase

- We will rely on the server-side translations!

* Remove debug messages

* Update src/frontend/src/tables/InvenTreeTable.tsx

Co-authored-by: Matthias Mair <code@mjmair.com>

* Cleanup

* Update docstring

* Cleanup

* Support nested fields with dot notation

- Powerful code reuse is powerful

* Complete refactor for BuildLine table

- Add some missing field descriptors on the backend

* Cleanup hooks a bit

* Update BomTable

* Allow table filters to make use of the same info

* Add ReferenceColumn helper

* Use queryParams when fetching table OPTIONS

- Required to ensure the same columns are fetched

* More refactoring

* Fix ProjectCodeTable

* Code cleanup

* Fix unused var

---------

Co-authored-by: Matthias Mair <code@mjmair.com>
2024-02-02 10:11:37 +11:00
Lukas
577185cd98 Fix urls loading for plugin testing (#6383)
* Fix urls for plugin testing

* Fix pre-commit

* Only load urls if testing setup is true
2024-02-02 06:16:47 +11:00
Oliver
9197517f38 API URL fixes (#6381)
- Part of https://github.com/inventree/InvenTree/issues/5697
- Cherry picking just the API fixes
2024-02-01 21:03:44 +11:00
Oliver
e1e63fa644 Fix for tasks.py - docker (#6380)
* Instrument tasks.py

* Enforce no_frontend

* Run docker CI step if tasks.py changes
2024-02-01 12:25:24 +11:00
Oliver
e85dd73f62 Fix for invoke update (#6379)
- If frontend option was skipped during update, then "static" step would not be performed
2024-02-01 11:03:04 +11:00
Oliver
7fe8207463 Forms initial data (#6365)
* Use PATCH for edit form

* Add "localhost:8000" server option

* Add initialData property for forms

- Allows user to specify an initial dataset to override default values

* Override field values when constructing form props

* Remove debug messages

* Wrap ApiForm in FormProvider

- Allows lower elements to access all form data without rebuild
- Pass all form data through to adjustFilters routine

* Fixes for RelatedModelField

- Ensure that the saved data are cleared when filters change

* Fix debug message for token creation

* Fix address rendering for modals

* Refactor "address" forms

- Use new "hook" form structure

* Update Contacts table

* Prevent related model fields from fetching on initial form open

- Only fetch when the drop-down is actually opened
- Significantly reduces the number of API calls

* Fix for ChoiceField

- Display label / description / placeholder text

* Fix for DateInput

- Correct conversion of datatype

* Implement "new purchase order" form

- Uses modal form hook
- Supply initial data
- Adjust filters according to selected company

* Add new company from company table

* Edit company from detail page

* More table updates

- StockLocation
- PartCategory

* Update more tables / forms:

- PartParameter table
- PartParameterTemplate table
- Cleanup unused imports

* Update ProjectCode table

* CustomUnits table

* Update RelatedPart table

* Update PartTestTemplate table

* Cleanup PartParameterTable

* Add "IPN" column to PartParameterTable

* Update BuildOrder table

* Update BuildDetail page

* PurchaseOrderLineItem table

* Simplify

- Move fields which are only used in one location

* Create new salesorder with context

- Also consolidate translated strings
- Also improve consistency of inline rendering (with missing image)

* Revert change to RenderInlineModel

* Fix for build table

- Use apiUrl wrapper around ApiEndpoint

* Fix parameter for PurchaseOrderTable

* Adjust server selector

- Only show localhost:8000 if in dev mode

* Tweak URL

* Add extra test to playground

- Check initial value works for nested field

* Cleanup playground

* Cleanup unused vars

* memoize fields

* Fix typo

host -> host

* Fix part editing

* Cleanup unused

* update group table
2024-02-01 00:38:59 +11:00
Oliver
2557383892 Disable cache for report helpers (#6370)
- Can lead to unintended consequences where REPORT_DEBUG_MODE toggles
2024-01-31 15:52:01 +11:00
Lavissa
fb71e847bb [PUI] Details Panel components (#6040)
* Add default_location to part filters

* Add Detail components

* Add Detail Image V1

* Remove piggyback change from different branch

* Remove unused code

* Add remove image modal

* Basic part image selection form

* Add Part Image selector Modal and fix PartThumb API pagination

* imports

* Add Image Upload modal

* Typescript and translation cleanup

* .

* Revert temporary workaround for existing_image

* Start adding fields

* .

* Modre fields and Icon manager

* Add most part detail fields

* .

* Final draft

* Remove unused TS

* More cleanup

* .

* Bump API version

* .

* Docstring oopsie
2024-01-31 10:37:42 +11:00
Oliver
3bfde82394 Tracing improvements (#6353)
* Prevent tracing in worker thread

* Tweak logic

* Further improvements

* Adds invoke command to launch gunicorn server

* Update docstring

* Add explicit check for migrations or data import

* Update tracing.py

Allow tracing in worker thread
2024-01-31 10:29:56 +11:00
Oliver
282ecebc39 [PUI] API Endpoint refactor (#6358)
* Cleanup SupplierPartTable

* Show PurchaseOrder table on SupplierPart page

* Perform edit actions as PATCH requests

* Implement ManufacturerPartParameter table

* Fix link

* supplier part link fix

* Add new ApiEndpoints enumeration

* Refactor calls to ApiState

* Revert previous change

* remove unused imports
2024-01-30 16:36:32 +11:00
Oliver
b42f3de357 Bug fix for javascript rendering (#6362)
* Check template name when rendering also

* Update i18n.py

Enforce stringiness
2024-01-30 10:51:23 +11:00
Oliver
b29d86403e Remove proxy support in vite server (#6359)
- Does not support OPTIONS requests
- No point keeping it around
2024-01-29 22:18:25 +11:00
Oliver
9a215f97f5 [PUI] Tables (#6357)
* Cleanup SupplierPartTable

* Show PurchaseOrder table on SupplierPart page

* Perform edit actions as PATCH requests

* Implement ManufacturerPartParameter table

* Fix link

* supplier part link fix

* Revert code which introduced bug

* Remove unused import
2024-01-29 17:51:29 +11:00
Oliver
0f7d385755 URL nav improvements (#6356)
* Implement getDetailUrl method

* Add nav link for PurchaseOrderLineItem table

* URL cleanup

- Replace hard-coded URLs with lookup
2024-01-29 16:51:54 +11:00
Oliver
b42f8a620b Token tweaks (#6354)
- Adjust to allow "bearer" token type
2024-01-29 15:58:56 +11:00
Oliver
cf7a20e1b7 Add support for Slovak (#6351) 2024-01-29 10:56:58 +11:00
Oliver
f6ba180cc4 Build order improvements (#6343)
* Auto-fill project code

When creating a new child build order, copy project code from parent build

* Auto-fill project code for sales orders

* Annotate "building" quantity to BuildLine serializer

- So we know how many units are in production

* Display building quantity in build line table

* Update API version info

* Skeleton for BuildLineTable

- No content yet (needs work)

* Refactor part hovercard

* Navigate to part

* Add actions for build line table

* Display more information for "available stock" column

* More updates

* Fix "building" filter

- Rename to "in_production"

* Add filters

* Remove unused imports
2024-01-29 10:56:34 +11:00
Oliver
1272b89839 New Crowdin updates (#6340)
* updated translation base

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix faulty translation

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-01-29 10:45:23 +11:00
Oliver
0f2675c139 [PUI] Child item table (#6334)
* Add child stock item table

* Fix stock item splitting bug

- StockItem tree was not being rebuilt correctly
- Add unit tests

* Annotate StockItem serializer with "child_items" count

* Show or hide "child items" panel

* Account for case where tree_id is zero
2024-01-24 12:33:34 +00:00
Oliver
65ecb975c6 [PUI] Assigned stock table (#6337)
* Display "assigned stock" table

* Add BuildOrder table to SalesOrder page
2024-01-24 12:24:02 +00:00
Oliver
fb0baa9e7a Fix stock item splitting bug (#6335)
* Fix stock item splitting bug

- StockItem tree was not being rebuilt correctly
- Add unit tests

* Account for zero tree_id value
2024-01-24 12:09:33 +00:00
Oliver
f07d8a7a80 [PUI] Add missing pages (#6326)
* Add placeholder page for SupplierPartDetail

- All empty at this stage

* Adds ManufacturerPartDetail page

* Further updates to supplierpart and manufacturerpart support

* Cleanup unused vars

* More cleanup
2024-01-24 09:39:37 +11:00
Oliver
76410ef68d New Crowdin updates (#6307)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-01-24 09:15:55 +11:00
Philipp Fruck
0a94758d63 fix(docker): SELinux volume labels (#6330)
When mounting volumes into containers with SELinux
enabled on the host the z option must be specified
2024-01-24 09:01:21 +11:00
Oliver
d8f69c0609 Specify empty OIDC prefix (#6324)
* Specify empty OIDC prefix

Ref: https://github.com/inventree/InvenTree/discussions/6273

* Add extra comment around version information

* Update InvenTree/InvenTree/settings.py

Co-authored-by: Philipp Fruck <dev@p-fruck.de>

---------

Co-authored-by: Philipp Fruck <dev@p-fruck.de>
2024-01-23 13:14:11 +00:00
Matthias Mair
8f6893a6b2 [PUI] Auth behaviour controls (#6314)
* optimize login layout

* move auth/reg up

* [PUI] Registration
Fixes #6282

* [PUI] Registration
Fixes #6282

* fix type

* Add auth settings to API state

* control showing of registration via state

* small cleanups

* Added registration and password forgotten to auth endpoint

* control password forgotten via state

* cleanup imports

* bump api version
2024-01-23 23:27:41 +11:00
dependabot[bot]
f35ce29612 Bump pillow from 10.1.0 to 10.2.0 (#6323)
Bumps [pillow](https://github.com/python-pillow/Pillow) from 10.1.0 to 10.2.0.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/10.1.0...10.2.0)

---
updated-dependencies:
- dependency-name: pillow
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-23 10:03:07 +11:00
Oliver
d502d93380 [PUI] Small updates (#6320)
* Ensure .ts files are generated

- "yarn run compile" before "yarn run dev"
- ensures that .ts locale files are all generated

* Implement "Add Part Category" button

* Create new stock location

* Rename customActionGroups to tableActions

* Rename customFilters to tableFilters

* Edit category from table

* Edit stock location from table

* Add some placeholder buttons

* More placeholders
2024-01-22 13:55:44 +00:00
Oliver
ab921ccb31 [PUI] Logout Fixes (#6318)
* Refactor method to extract token from request

* Reimplement error-report API endpoint

- Removed in previous commit - b8b3dfc90e
- Adds unit tests to ensure it doesn't happen again

* Adds custom logout view for API

- Ensure correct token gets deleted
- Our new custom token setup is incompatible with default dj-rest-auth
2024-01-22 12:07:35 +00:00
Oliver
e7d926f983 Reimplement error-report API endpoint (#6317)
- Removed in previous commit - b8b3dfc90e
- Adds unit tests to ensure it doesn't happen again
2024-01-22 12:03:58 +00:00
Oliver
f85b773a50 Only import tracing module if tracing is enabled (#6316) 2024-01-22 22:14:55 +11:00
Matthias Mair
d64fbfc254 [PUI] Registration (#6309)
* optimize login layout

* move auth/reg up

* [PUI] Registration
Fixes #6282

* [PUI] Registration
Fixes #6282

* fix type

* style: cleaned imports
2024-01-22 21:39:06 +11:00
Oliver
2fbb8c757f [PUI] Test template table (#6311)
* Add PartTestTemplateTable

* Update PartTestTemplate API

- Improve filtering options
- Add sorting options
- Add search options

* Update table

* Add placeholders for editing and deleting test templates

* Update calls to DescriptionColumn

* CORS fixes:

- Update CORS headers in settings.py

* Add / edit / delete templates

* Fix for partId
2024-01-22 04:49:33 +00:00
Oliver
edad000d8e CORS fixes: (#6310)
* CORS fixes:

- Update CORS headers in settings.py

* Remove dead code
2024-01-22 04:48:58 +00:00
Lavissa
917a88c6f4 Add check for KeyError on reading plugin commit (#6300)
* Add check for KeyError on reading plugin commit

* .

* .
2024-01-22 06:56:08 +11:00
Oliver
428a4c0386 SSO Error Improvememts (#6246)
* Improve exception handling

* Extract extra information from SSO auth failure

* Revert order of ignore check
2024-01-21 23:31:47 +11:00
Oliver
4b5fcd4e69 Remove {% jstrans %} tag (#6298)
* Revert {% jstrans %} for .js files

- file extension is enough

* Remove custom jstrans templatetag

* Replace jstrans calls for .html files

- Add "escape=True"

* Allow for custom "escape" argument

* Update custom trans tag

- Cannot pass kwargs in the same way
- Add the "escape" attribute

* Update js translations in html files
2024-01-21 22:47:47 +11:00
Oliver
c5d0902379 django-allauth==0.6.1 (#6301) 2024-01-20 22:33:41 +00:00
Oliver
e3f6624cf9 SSO config docs (#6297)
* Fix django-allauth URLs

* Update SSO docs
2024-01-20 15:31:17 +11:00
dependabot[bot]
22068da3fa Bump vite from 4.4.12 to 4.5.2 in /src/frontend (#6296)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.4.12 to 4.5.2.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.2/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.2/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 02:40:52 +00:00
Oliver
4f8dddc597 JS translation fix (#6288)
* Implement {% jstrans %} template

- Forces string escaping in translated text
- Required for js written in templates

* Fix part_base.html

* Update .html files

- Change any templated javascript code to use {% jstrans %}

* Update .js files

- Explicitly use {% jstrans %}

* Update CI checks
2024-01-19 01:16:17 +00:00
Matthias Mair
914743627b [PUI] Fix global login (#6287)
* Global login PUI -> CUI
Fixes #6285

* ensure session is always set

* Check if user is already logged in CUI->PUI

* reduce diff
2024-01-19 00:57:00 +00:00
Oliver
a0b595de6e Tracing tweaks (#6278)
* Tweaks for OpenTelemetry tracing:

- Add logger info message if enabled
- Explicit kwargs to setup_tracing method
- Change is_http to True by default (as per internal docs)

* Allow operation without specifying tracing.headers

* Clean up log message
2024-01-19 11:55:48 +11:00
Matthias Mair
357f715789 [PUI] Fix logout (#6284)
* fix path

* changed PUI to use logout route
Fixes #5968
2024-01-19 11:30:19 +11:00
Matthias Mair
64dbf8c1e3 [FR] Add tracing support (#6211)
* [FR] Add tracing support
Fixes #6208

* move checks out of manage.py

* fixed reqs

* small cleanup

* move tracing init to settings
similar to how sentry is handled

* rephrase

* clean up imports

* added argument regarding console debugging

* fix typing

* added auth section

* remove empty headers

* made protocol configurable

* rename vars & cleanup template

* more docs for template

* add docs
2024-01-18 17:50:05 +11:00
Oliver
89e458bcba Import Fix (#6274)
* Fix check for static dir

* Fix export price field for SalesOrderLineItem

* Automatically detect which non-nullable fields need conversion

* Fix bug during import

- fulfilled_quantity and allocated_quantity must have a pk
- Cannot work before imported!
2024-01-18 17:25:25 +11:00
Oliver
1d3a23ca4e News updates (#6275)
* Set default ordering

* Order table by "most recent entries" by default
2024-01-18 17:25:15 +11:00
Matthias Mair
b8b3dfc90e Move more paths to basic path (#6251)
* move more paths to basic path

* changed url route to only match fully - fixed test

* revert path changes on labelprint pages

* fix not found/redirect

* revert test change
2024-01-18 17:05:10 +11:00
Oliver
c3a5d777b1 Do not create owners when importing data (#6271)
- Prevent issues due to duplicate Owner objects
2024-01-18 10:31:43 +11:00
Oliver
053df0f59d Update README.md (#6270)
Add new sponsor

(Thanks @markus-k :) )
2024-01-18 09:37:54 +11:00
Lavissa
0892de8c99 Fix news feed task timeout (#6250) 2024-01-17 17:52:52 +11:00
Oliver
eb7fd4de2b New Crowdin updates (#6258)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-01-17 16:40:17 +11:00
Oliver
b983a8636c Report copy fix (#6264)
* Update process for copying default report templates

- Overwrite if the file hash has changed

* Cleanup inline code
2024-01-17 16:40:05 +11:00
Oliver
7d36049ac9 [React] UI Translation Updates (#6257)
* Move locales definition into separate file

- Cleanup settings.py a bit

* Update docstring

* Expose 'default_locale' to info API endpoint

* Validate settings.LANGUAGE_CODE

* Fix bug in BuildDetail page

* Use selected language when making API queries

* Translate more strings

* Tweak variable name

* Update locale config

* Remove duplicate code

* Remove compiled messages.ts translation files

* Fixes for LanguageContext.tsx

* Update messages.d.ts for sr locale

* Ensure compiled files are served by django runserver

* Amend changes to STATICFILES_DIRS

* Cleanup prerender.py

* Refetch status codes when locale is changed

* Fix log msg

* Clear out old static files
2024-01-17 16:29:06 +11:00
Oliver
386aa5952c Update invoke docs (#6263)
- Minimum invoke version is 2.0.0
2024-01-17 12:36:24 +11:00
Oliver
75f75ed820 Tasks API Endpoint (#6230)
* Add API endpoint for background task overview

* Cleanup other pending heartbeat tasks

* Adds API endpoint for queued tasks

* Adds API endpoint for scheduled tasks

* Add API endpoint for failed tasks

* Update API version info

* Add table for displaying pending tasks

* Add failed tasks table

* Use accordion

* Annotate extra data to scheduled tasks serializer

* Extend API functionality

* Update tasks.py

- Allow skipping of static file step in "invoke update"
- Allows for quicker updates in dev mode

* Display task result error for failed tasks

* Allow delete of failed tasks

* Remove old debug message

* Adds ability to delete pending tasks

* Update table columns

* Fix unused imports

* Prevent multiple heartbeat functions from being added to the queue at startup

* Add unit tests for API
2024-01-17 07:10:42 +11:00
miggland
5c7d3af150 Add tests for report API (#6214)
* Add tests for report api

* Add test for delete endpoint

* Use pk in reverse call

* Allow kwargs in APITestCase methods

* Data is in response.data

* Fix comment

* Use expected_code argument instead of separate check

* Test with superuser

* Negative indexing not allowed, try with this alternative

* Looking for what went wrong..

* APITestCase does not work like Python requests - change POST calls

* Upload success is code 201

* In PATCH method, change test file and description as well

* Add test for get_api_url method

* Change format parameter for file upload

* Copy tests for report API over to label API

* Add test model for BuildLineLabel

* Update tests

* Make example template valid

* Add methods for testing label printing
2024-01-17 06:55:55 +11:00
Oliver
21ff17332d Do not translate string (#6248) 2024-01-16 22:12:59 +11:00
Oliver
7cd62527c0 Prevent creation of new PluginConfig during data import (#6247)
* Prevent creation of new PluginConfig during data import

- Fixes race condition which can occur sometimes

* typo fix
2024-01-16 22:12:51 +11:00
Oliver
716e577916 Update rebuild_thumbnails command (#6254)
* Update rebuild_thumbnails command

- Skip if images already exist
- Prevents significant clutter in the logs
- Speeds up the command too

* Refactoring
2024-01-16 22:12:38 +11:00
Oliver
8eec2f32c0 Panel index fix (#6255)
* Fix panel routing for part category index

* Fix routing for stock location index
2024-01-16 22:12:28 +11:00
Oliver
fa28697799 Import data fix (#6253)
* Use '--natural-foreign' when exporting dataset

- Uses "natural keys" (model names) instead of ContentType ID

* Update task options

- Change 'include_plugins' to 'exclude_plugins'
- Change 'delete_temp' to 'retain_temp'

* Split data import into two-step process

- First, import auth models
- Second, import data
- Ensures auth.user and auth.group are in place before users.owner is loaded

* Adjust temp file name

* Touch apps.py

- Just so the proper CI checks run
2024-01-16 21:32:51 +11:00
Oliver
829e01dd33 Updates to data import/export steps (#6244)
* Updates to data import/export steps

- Allow import/export of users.owner model
- Allow export of API tokens (optional, default=False)

* Exclude plugin configuration data by default

* Add option to exclude socialaccount information from exported data
2024-01-16 01:26:57 +11:00
Oliver
576bef5d82 Add new setting PURCHASEORDER_AUTO_COMPLETE (#6245)
* Add new setting PURCHASEORDER_AUTO_COMPLETE

- Controls whether purchase orders are automatically closed
- Default to True

* Tweak setting component
2024-01-16 00:51:03 +11:00
Oliver
3511450b3d Update callback when base currency is adjusted (#6243)
* Update callback when base currency is adjusted

- Schedule recalculation of part pricing

* Revert old changes
2024-01-15 17:12:16 +11:00
Oliver
f396642d16 Fix escape codes in translated strings (#6234)
* Fix escape codes in translated strings

- Only add escape characters for javascript files

* import fix

* more import fix
2024-01-14 13:29:36 +11:00
Bobbe
d2d59e0709 Add tests for get_purchase_orders logic (#6236) 2024-01-14 12:44:34 +11:00
Oliver
f27503b1af Cleanup about and version dialogs (#6235) 2024-01-14 10:10:34 +11:00
Oliver
9070eaad60 New Crowdin updates (#6229)
* updated translation base

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-01-14 08:36:45 +11:00
Oliver
4254b7dda3 Add settings for return orders (#6231) 2024-01-14 07:52:13 +11:00
Oliver
544c7d389c Fix display of error notifications (#6232) 2024-01-14 07:52:02 +11:00
Oliver
ef679b1663 Error API (#6222)
* Adds API endpoint for fetching error information

* Bump API version

* Implement table for displaying server errors

* Add support for "bulk delete" in new table component

* Update API version with PR

* Fix unused variables

* Enable table sorting

* Display error details
2024-01-13 19:27:47 +11:00
Oliver
5180d86388 Change log message from info to debug (#6227)
- Reduces log verbosity for message which is not typically useful
2024-01-13 19:27:07 +11:00
Oliver
9b1a310ffe Fix rendering of supplier part pack quantity (#6226)
- Fixes https://github.com/inventree/InvenTree/issues/6224
2024-01-13 08:06:47 +11:00
Oliver
213a63318d Fix placeholder text values (#6223) 2024-01-13 01:29:12 +11:00
Oliver
a04b22b090 Fix URL regex (#6218)
URL regex for build report detail view was incorrect
2024-01-12 23:49:50 +11:00
Oliver
b4a7787447 New Crowdin updates (#6207)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-01-12 23:16:58 +11:00
Oliver
1b054c5a9c Patch purchase_order.js (#6215)
* Patch purchase_order.js

Small tweak to catch potential undefined variable

* Cleanup

* Remove trailing whitespace
2024-01-12 22:17:39 +11:00
Matthias Mair
b8369fb726 fixed depreceated is_ajax (#6210) 2024-01-12 10:06:25 +11:00
dependabot[bot]
8a764c2c05 Bump jinja2 from 3.1.2 to 3.1.3 (#6209)
Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.2 to 3.1.3.
- [Release notes](https://github.com/pallets/jinja/releases)
- [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/jinja/compare/3.1.2...3.1.3)

---
updated-dependencies:
- dependency-name: jinja2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-12 09:38:05 +11:00
Bobbe
082b5d1c80 Fix reassigning supplier barcodes (#6162)
* Ignore 3rd party barcode plugins when assigning barcode

* Use single quotes
2024-01-12 09:37:04 +11:00
Oliver
7f231cb6c1 Specify ForeignKey widget for importing destination field (#6205)
- Fixes https://github.com/inventree/InvenTree/issues/6201
2024-01-11 22:17:14 +11:00
Oliver
df8b480abb Style fixes (#6203) 2024-01-11 15:16:03 +11:00
Matthias Mair
9d0264c319 [CI] docstrings (#6172)
* Squashed commit of the following:

commit 52d7ff0f65
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 23:03:20 2024 +0100

    fixed lookup

commit 0d076eaea8
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 23:03:08 2024 +0100

    switched to pathlib for lookup

commit 473e75eda2
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 22:52:30 2024 +0100

    fix wrong url response

commit fd74f8d703
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 21:14:38 2024 +0100

    switched to ruff for import sorting

commit f83fedbbb8
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 21:03:14 2024 +0100

    switched to single quotes everywhere

commit a92442e60e
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 20:58:23 2024 +0100

    added autofixes

commit cc66c93136
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 20:56:47 2024 +0100

    enable autoformat

commit 1f343606ec
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 20:42:14 2024 +0100

    Squashed commit of the following:

    commit f5cf7b2e78
    Author: Matthias Mair <code@mjmair.com>
    Date:   Sun Jan 7 20:36:57 2024 +0100

        fixed reqs

    commit 9d845bee98
    Author: Matthias Mair <code@mjmair.com>
    Date:   Sun Jan 7 20:32:35 2024 +0100

        disable autofix/format

    commit aff5f27148
    Author: Matthias Mair <code@mjmair.com>
    Date:   Sun Jan 7 20:28:50 2024 +0100

        adjust checks

    commit 47271cf1ef
    Author: Matthias Mair <code@mjmair.com>
    Date:   Sun Jan 7 20:28:22 2024 +0100

        reorder order of operations

    commit e1bf178b40
    Author: Matthias Mair <code@mjmair.com>
    Date:   Sun Jan 7 20:01:09 2024 +0100

        adapted ruff settings to better fit code base

    commit ad7d88a6f4
    Author: Matthias Mair <code@mjmair.com>
    Date:   Sun Jan 7 19:59:45 2024 +0100

        auto fixed docstring

    commit a2e54a760e
    Author: Matthias Mair <code@mjmair.com>
    Date:   Sun Jan 7 19:46:35 2024 +0100

        fix getattr useage

    commit cb80c73bc6
    Author: Matthias Mair <code@mjmair.com>
    Date:   Sun Jan 7 19:25:09 2024 +0100

        fix requirements file

    commit b7780bbd21
    Author: Matthias Mair <code@mjmair.com>
    Date:   Sun Jan 7 18:42:28 2024 +0100

        fix removed sections

    commit 71f1681f55
    Author: Matthias Mair <code@mjmair.com>
    Date:   Sun Jan 7 18:41:21 2024 +0100

        fix djlint syntax

    commit a0bcf1bcce
    Author: Matthias Mair <code@mjmair.com>
    Date:   Sun Jan 7 18:35:28 2024 +0100

        remove flake8 from code base

    commit 22475b31cc
    Author: Matthias Mair <code@mjmair.com>
    Date:   Sun Jan 7 18:34:56 2024 +0100

        remove flake8 from code base

    commit 0413350f14
    Author: Matthias Mair <code@mjmair.com>
    Date:   Sun Jan 7 18:24:39 2024 +0100

        moved ruff section

    commit d90c48a0bf
    Author: Matthias Mair <code@mjmair.com>
    Date:   Sun Jan 7 18:24:24 2024 +0100

        move djlint config to pyproject

    commit c5ce55d511
    Author: Matthias Mair <code@mjmair.com>
    Date:   Sun Jan 7 18:20:39 2024 +0100

        added isort again

    commit 42a41d23af
    Author: Matthias Mair <code@mjmair.com>
    Date:   Sun Jan 7 18:19:02 2024 +0100

        move config section

    commit 8569233181
    Author: Matthias Mair <code@mjmair.com>
    Date:   Sun Jan 7 18:17:52 2024 +0100

        fix codespell error

    commit 2897c6704d
    Author: Matthias Mair <code@mjmair.com>
    Date:   Sun Jan 7 17:29:21 2024 +0100

        replaced flake8 with ruff
        mostly for speed improvements

* enable docstring checks

* fix docstrings

* fixed D417 Missing argument description

* Squashed commit of the following:

commit d3b795824b
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 22:56:17 2024 +0100

    fixed source path

commit 0bac0c19b8
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 22:47:53 2024 +0100

    fixed req

commit 9f61f01d9c
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 22:45:18 2024 +0100

    added missing toml req

commit 91b71ed24a
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 20:49:50 2024 +0100

    moved isort config

commit 12460b0419
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 20:43:22 2024 +0100

    remove flake8 section from setup.cfg

commit f5cf7b2e78
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 20:36:57 2024 +0100

    fixed reqs

commit 9d845bee98
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 20:32:35 2024 +0100

    disable autofix/format

commit aff5f27148
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 20:28:50 2024 +0100

    adjust checks

commit 47271cf1ef
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 20:28:22 2024 +0100

    reorder order of operations

commit e1bf178b40
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 20:01:09 2024 +0100

    adapted ruff settings to better fit code base

commit ad7d88a6f4
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 19:59:45 2024 +0100

    auto fixed docstring

commit a2e54a760e
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 19:46:35 2024 +0100

    fix getattr useage

commit cb80c73bc6
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 19:25:09 2024 +0100

    fix requirements file

commit b7780bbd21
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:42:28 2024 +0100

    fix removed sections

commit 71f1681f55
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:41:21 2024 +0100

    fix djlint syntax

commit a0bcf1bcce
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:35:28 2024 +0100

    remove flake8 from code base

commit 22475b31cc
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:34:56 2024 +0100

    remove flake8 from code base

commit 0413350f14
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:24:39 2024 +0100

    moved ruff section

commit d90c48a0bf
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:24:24 2024 +0100

    move djlint config to pyproject

commit c5ce55d511
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:20:39 2024 +0100

    added isort again

commit 42a41d23af
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:19:02 2024 +0100

    move config section

commit 8569233181
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:17:52 2024 +0100

    fix codespell error

commit 2897c6704d
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 17:29:21 2024 +0100

    replaced flake8 with ruff
    mostly for speed improvements

* fix pyproject

* make docstrings more uniform

* auto-format

* fix order

* revert url change
2024-01-11 14:40:07 +11:00
Lavissa
9db3efa085 Enable existing_image on PATCH requests (#6126)
* Enable existing_image on PATCH requests

* Fix CI problems

* Solution

* Change check to whitelist and add unit tests
2024-01-11 12:03:12 +11:00
Matthias Mair
4b14986591 [CI] Enable python autoformat (#6169)
* Squashed commit of the following:

commit f5cf7b2e78
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 20:36:57 2024 +0100

    fixed reqs

commit 9d845bee98
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 20:32:35 2024 +0100

    disable autofix/format

commit aff5f27148
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 20:28:50 2024 +0100

    adjust checks

commit 47271cf1ef
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 20:28:22 2024 +0100

    reorder order of operations

commit e1bf178b40
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 20:01:09 2024 +0100

    adapted ruff settings to better fit code base

commit ad7d88a6f4
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 19:59:45 2024 +0100

    auto fixed docstring

commit a2e54a760e
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 19:46:35 2024 +0100

    fix getattr useage

commit cb80c73bc6
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 19:25:09 2024 +0100

    fix requirements file

commit b7780bbd21
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:42:28 2024 +0100

    fix removed sections

commit 71f1681f55
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:41:21 2024 +0100

    fix djlint syntax

commit a0bcf1bcce
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:35:28 2024 +0100

    remove flake8 from code base

commit 22475b31cc
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:34:56 2024 +0100

    remove flake8 from code base

commit 0413350f14
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:24:39 2024 +0100

    moved ruff section

commit d90c48a0bf
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:24:24 2024 +0100

    move djlint config to pyproject

commit c5ce55d511
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:20:39 2024 +0100

    added isort again

commit 42a41d23af
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:19:02 2024 +0100

    move config section

commit 8569233181
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:17:52 2024 +0100

    fix codespell error

commit 2897c6704d
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 17:29:21 2024 +0100

    replaced flake8 with ruff
    mostly for speed improvements

* enable autoformat

* added autofixes

* switched to single quotes everywhere

* switched to ruff for import sorting

* fix wrong url response

* switched to pathlib for lookup

* fixed lookup

* Squashed commit of the following:

commit d3b795824b
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 22:56:17 2024 +0100

    fixed source path

commit 0bac0c19b8
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 22:47:53 2024 +0100

    fixed req

commit 9f61f01d9c
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 22:45:18 2024 +0100

    added missing toml req

commit 91b71ed24a
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 20:49:50 2024 +0100

    moved isort config

commit 12460b0419
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 20:43:22 2024 +0100

    remove flake8 section from setup.cfg

commit f5cf7b2e78
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 20:36:57 2024 +0100

    fixed reqs

commit 9d845bee98
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 20:32:35 2024 +0100

    disable autofix/format

commit aff5f27148
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 20:28:50 2024 +0100

    adjust checks

commit 47271cf1ef
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 20:28:22 2024 +0100

    reorder order of operations

commit e1bf178b40
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 20:01:09 2024 +0100

    adapted ruff settings to better fit code base

commit ad7d88a6f4
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 19:59:45 2024 +0100

    auto fixed docstring

commit a2e54a760e
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 19:46:35 2024 +0100

    fix getattr useage

commit cb80c73bc6
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 19:25:09 2024 +0100

    fix requirements file

commit b7780bbd21
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:42:28 2024 +0100

    fix removed sections

commit 71f1681f55
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:41:21 2024 +0100

    fix djlint syntax

commit a0bcf1bcce
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:35:28 2024 +0100

    remove flake8 from code base

commit 22475b31cc
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:34:56 2024 +0100

    remove flake8 from code base

commit 0413350f14
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:24:39 2024 +0100

    moved ruff section

commit d90c48a0bf
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:24:24 2024 +0100

    move djlint config to pyproject

commit c5ce55d511
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:20:39 2024 +0100

    added isort again

commit 42a41d23af
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:19:02 2024 +0100

    move config section

commit 8569233181
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 18:17:52 2024 +0100

    fix codespell error

commit 2897c6704d
Author: Matthias Mair <code@mjmair.com>
Date:   Sun Jan 7 17:29:21 2024 +0100

    replaced flake8 with ruff
    mostly for speed improvements

* fix coverage souce format

---------

Co-authored-by: Oliver Walters <oliver.henry.walters@gmail.com>
2024-01-11 11:28:58 +11:00
Oliver
9715af564f Fix for mobile app documentation URL (#6198)
Closes https://github.com/inventree/InvenTree/issues/6140
2024-01-11 09:02:43 +11:00
Bobbe
36bb3c5645 Fix supplier barcode order numbers (#6158)
* Add tme barcode CPO field

* Fix LCSC order number field

* Fix mouser order number field

* Fix get_purchase_orders logic

* Refine get_purchase_orders logic

* Slightly refactor get_purchase_orders logic
2024-01-11 08:14:07 +11:00
Oliver
d1cc81fc9f Remove support for django-debug-toolbar (#6196)
* Remove support for django-debug-toolbar

- Will not be effective with new react interface
- Performance is too slow for use anyway

* Fix requirements-dev.txt
2024-01-11 07:18:20 +11:00
Oliver
5135d6b5d1 Allow plugin load in shell (#6195) 2024-01-11 01:09:48 +11:00
Matthias Mair
4f4852e2e7 Add optional link to CUI detail pages (#6129)
to make transition easier linking to these should also be enabled
2024-01-11 00:54:20 +11:00
dependabot[bot]
fbc12fb496 Bump follow-redirects from 1.15.3 to 1.15.4 in /src/frontend (#6192)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-11 00:45:33 +11:00
Oliver
e2f78829ec Sponsors fix (#6193)
* Remove sponsors.yml

- Will update sponsors manually

* Update README.md
2024-01-11 00:45:22 +11:00
Oliver
445551e6f3 Ready fix (#6191)
* Update "isRunningMigrations" method

* Update other apps.py files
2024-01-11 00:35:25 +11:00
Matthias Mair
e1b670ba57 CI updates (#6167)
* replaced flake8 with ruff
mostly for speed improvements

* fix codespell error

* move config section

* added isort again

* move djlint config to pyproject

* moved ruff section

* remove flake8 from code base

* remove flake8 from code base

* fix djlint syntax

* fix removed sections

* fix requirements file

* fix getattr useage

* auto fixed docstring

* adapted ruff settings to better fit code base

* reorder order of operations

* adjust checks

* disable autofix/format

* fixed reqs

* remove flake8 section from setup.cfg

* moved isort config

* added missing toml req

* fixed req

* fixed source path
2024-01-11 00:16:13 +11:00
Oliver
73cc39bb68 Fix resource classes for order models (#6188)
- PurchaseOrder
- ReturnOrder
- SalesOrder

Fixes https://github.com/inventree/InvenTree/issues/6155
2024-01-10 23:46:55 +11:00
Oliver
9f01962c4e Handle case where ref_url is invalid (#6186) 2024-01-10 23:37:29 +11:00
Oliver
a23235400d Improve data import for PartParameterTemplate (#6182)
- Create Resource class which uses InvenTreeResource base
- Ensure 'units' field is converted to string if empty
- Handle null choices field in PartParameterTemplate model
2024-01-10 22:41:52 +11:00
Oliver
53ac6c724d Re-implement no-frontend flag in "invoke update" (#6183)
- Allows old installers to work once more
- Ref: https://github.com/inventree/InvenTree/issues/6177
2024-01-10 22:41:22 +11:00
Oliver
b5b6d75e23 Typo fix (#6181)
exsist -> exist
2024-01-10 22:19:37 +11:00
Oliver
ec9bff9be4 Simplify translations (#6180)
- Do not include HTML tags
2024-01-10 21:59:41 +11:00
Matthias Mair
dfaee0ea96 Added update notification (#6165)
* added update notification

* added more logging and checks
2024-01-08 06:36:19 +11:00
Matthias Mair
93df90d295 [FR] Support creating reading initial superuser account password from file (#6144)
* factored out user creation step

* [FR] Support creating reading initial superuser account password from file
Fixes #5471

* added docs

* use env too with password file

* do not warn if passwordfile is set
2024-01-06 07:38:53 +11:00
Oliver
6a6a5932f3 New Crowdin updates (#6142)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-01-04 02:24:47 +04:00
Oliver
a35e03671c New Crowdin updates (#6118)
* updated translation base

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-12-29 15:59:00 +01:00
Matthias Mair
88f86efd4c Cancel purchase order - Error object (#6137)
Fixes #6136

Error introduced in https://github.com/inventree/InvenTree/pull/6017
2023-12-27 11:50:46 +01:00
Matthias Mair
1cdcac09be Remove py2 exception handeling and move file to dj4 standard (#6131)
* remove py2 exception handeling and move file to dj4 standard

* re-add coverage exception
2023-12-24 07:04:20 +01:00
Matthias Mair
f3efabeeb8 [PUI] Move UI rendering out of App.tsx (#6130)
* move UI rendering out of App.tsx
this makes bundles that just use API components (eg. to render out PUI settings controls for inclusion CUI) much easier

* fixes pre-commit warning

* Revert "fixes pre-commit warning"

This reverts commit 712f27bb1c.
2023-12-24 07:03:20 +01:00
mcollins-DL
fc5645a9a5 Fix to clear SO Allocations on receipt of Return Order Line Item (#6117)
* Added stock_item.clearAllocations() to order.models.py

* Update models.py ReturnOrder clear allocations on line receipt
2023-12-21 09:00:06 +04:00
Oliver
be1820fb94 [PUI] Implement manufacturer part table (#6115)
* Fix supplier part form

- Ensure manufacturer parts get filtered by "part" instance

* Implement simple ManufacturerPart table

* Add table actions

* Fix unused imports
2023-12-19 15:28:27 +11:00
Oliver
a63529a9cf Update django-allauth version (#6099)
* Patch for django-allauth 0.55.0

- Some breaking changes here
- Add logger error if auth provider cannot be imported
- Fix for API endpoints

* Only provide URLs for configured plugins

* Update for django-allauth 0.56.0

- Remove support for keycloak
- Remove example from configuration template

* Update django-allauth in requirements.txt

* Update requirements.in

* Refactor SSO functions into common file

* Update config template file

* Update docs

* Fix template files

* Log SSO exceptions to the database

- WIll help greatly with debugging installs

* Add note about error handling in docs
2023-12-18 17:35:36 +11:00
Oliver
148bf0764b Remove --no-frontend option from installer (#6111)
- This swtich is no longer present in `invoke update`
- Frontend installation is off by default
- Fixes https://github.com/inventree/InvenTree/discussions/6109
2023-12-18 10:44:15 +11:00
Oliver
7058696030 [PUI] Price range formatting (#6107)
* Implement price range formatter

* Remove error message logging

* Fix unused variable issue

* Clean up more error messages
2023-12-16 21:48:14 +11:00
Oliver
bee3e93162 Update RowActions (#6106)
* Update RowActions

- Show text labels (again)
- Revert to Menu.Item
- Adjust placement
- Add optional tooltip

* Remove unused import
2023-12-16 21:45:03 +11:00
Oliver
95d29f18e9 Small tweak for log output (#6102) 2023-12-16 11:40:35 +11:00
Oliver
43836b3450 Add information on auto-update param to docs (#6100) 2023-12-16 07:20:48 +11:00
Oliver
36bfd62c93 Update version.py (#6097)
Update to 0.14.0 dev
2023-12-15 23:04:00 +11:00
Oliver
301afaa9df Update version.py (#6095)
Bump version to 0.13.0
2023-12-15 21:38:43 +11:00
Oliver
ad97ea41a0 Another attempt at fixing unit test (#6096) 2023-12-15 21:37:00 +11:00
Oliver
2c45d88d15 Fix unit test (#6084)
* Add debug print statements

* Add more debug prints

* Upate unit test

- Use global variables to ensure correct error is raised
- collect() does not always collect in the same "order"
2023-12-15 17:23:33 +11:00
Oliver
64671dce20 Table Hook Updates (#6093)
* Update useTable hook

- Storage for "selected rows"
- Aim is to make row selection available outside table component

* Use updated hook to manage row selection

* Add more table data to useTable:

- hidden column selection
- search terms

* Remove unused import

* Remove unused function

* Cleanup
2023-12-15 13:53:46 +11:00
Oliver
72167630ac Add support for serbian language (#6091) 2023-12-15 10:06:41 +11:00
Oliver
2aa993bcf5 Add item key (#6089)
- Adds "key" to FilterItem component
2023-12-15 09:01:56 +11:00
Oliver
aecdc0739d Revert "[PUI] URl / panel fix (#6078)" (#6088)
This reverts commit 902eafcc75.
2023-12-15 08:38:30 +11:00
Oliver
cd3e7037c5 Update devcontainer docs (#6085)
- Much better performance running from WSL filesystem
2023-12-14 16:53:09 +11:00
Oliver
17068ca5a2 Add docs on using editable install for plugin development (#6083)
* Add docs on using editable install for plugin development

* Add info
2023-12-14 14:24:00 +11:00
Oliver
99c92ff655 Sales order barcode allocate (#6072)
* Bug fix for BarcodePOReceive endpoint

- Existing scan must match "stockitem" to raise an error

* bug fix: barcode.js

- Handle new return data from barcode scan endpoint

* Add barcode endpoint for allocating stock to sales order

* Improve logic for preventing over allocation of stock item to sales order

* Test for sufficient quantity

* Bump API version

* Bug fix and extra check

* Cleanup unit tests

* Add unit testing for new endpoint

* Add blank page for app sales orders docs

* Add docs for new barcode features in app

* Fix unit tests

* Remove debug statement
2023-12-14 11:13:50 +11:00
Oliver
3410534f29 Bump django-maintenance-mode (#6079)
Fixes https://github.com/inventree/InvenTree/issues/6066
2023-12-13 22:57:27 +11:00
Oliver
68c3014232 FAQ - removeSuffix error (#6081)
* Add FAQ entry about removeSuffix error

- Closes https://github.com/inventree/InvenTree/issues/6080

* Check python version in tasks.py
2023-12-13 22:20:50 +11:00
Oliver
902eafcc75 [PUI] URl / panel fix (#6078)
* Add debug messages for auth - trying to track down issues

* Add explicit PartIndex page

* Remove debug statements

* Simplify panel state management

- Do not encode in URL
- Reduce number of component loads
- Ensure a valid panel is always selected

* Implement StockIndex page
2023-12-13 21:37:41 +11:00
Oliver
6cb66a7a70 New Crowdin updates (#6069)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-12-13 13:03:02 +11:00
Andre Iwers
f66cc9d8c2 Allow plugin loading when calling 'showmigrations' (#6076)
- Otherwise cannot see what migrations have been run for a plugin

Co-authored-by: afkiwers <andre@currawong.aero>
2023-12-13 12:43:06 +11:00
Oliver
5b547f777d Handle IntegrityError when saving PartPricing object (#6070) 2023-12-11 15:58:27 +11:00
Philipp Fruck
9ecaea2c12 chore(docker): Remove intermediate stage (#6068)
removes the prebuild stage from the final image
which solves the issue that build dependencies
where not cleaned up properly. Saves a couple
of MB in image size.
2023-12-11 09:57:37 +11:00
Philipp Fruck
b59cf0f4a4 fix(SSO): log error message when EMAIL_HOST is empty (#6067)
fixes #4553
2023-12-11 07:49:50 +11:00
Oliver
676a97b709 New Crowdin updates (#6048)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-12-11 07:46:57 +11:00
Oliver
8d46521cab Plugin loading improvements (#6056)
* Add API endpoint to reload plugin registry

* Tweak debug

* Add elements for CUI

* Update API version

* Reload plugins from PUI
2023-12-11 07:46:41 +11:00
Oliver
256f44b44e Handle "showmigrations" command (#6062)
- If provided, do not load custom app ready code
- Ref: https://github.com/inventree/InvenTree/issues/5804#issuecomment-1785562383
2023-12-11 07:46:27 +11:00
Lavissa
5d42fb3f88 [PUI] Table loading refactor (#6014)
* Pre-commit alterations

* Change PK for ID on panels only using ID

* Remove '-1' calls to Tables

* Remove unused imports
2023-12-08 15:03:47 +11:00
Oliver
5b5df279c0 Installer CLI fix (#6049)
* Update heroku procfile

- Fix command to run on ubuntu 20.04
- Add "invoke" command (duplicate of "cli")

* Update docs for installer

* Fix typo

* Update root_commands.sh
2023-12-08 13:10:44 +11:00
Lavissa
ca909f9d5a Add asterisk to Build route (#6054) 2023-12-08 12:48:54 +11:00
Oliver
63a0d4d223 Prevent check for low stock when creating new items (#6055) 2023-12-08 12:12:52 +11:00
Oliver
048a06ce19 [PUI] refactor table filter selector (#6047)
* Add FilterSelectDrawer component

* Add descriptions for build order table filters

* Pass active filters through via UseTable hook

* Remove old FilterGroup component

* Add callback to remove selected filter

* Implement interface for adding new filters

* Prevent duplication of filters

* Hide "add filter" elements after creating new filter

* Improved rendering

* Implement more filters for stock item table

* Add some filters for stock location table

* Refactor filter choice method

- Add StatusFilterOptions callback
- Update filters for existing tables

* purchase order table filters

* Implement more table filters

* Fix unused imports

* Render display value, not raw value

* Cleanup

* UI improvements
2023-12-08 11:56:35 +11:00
Miklós Márton
3a7b1510b3 Disable prefetch on part when saving stock items (#6051)
Contributes to #6013
2023-12-08 07:19:48 +11:00
Oliver
2dc164634d Add information on copying templates to installation directory when developing a plugin (#6050) 2023-12-07 16:49:12 +11:00
Matthias Mair
974ea1ead3 State transition support for generic states (#6017)
* Added state transition support to generic states

* make can_cancel a property everywhere

* add check if method is defined

* add unit tests

* extend tests

* fixed loading of broken classes

* added test to ensure transition functions are called

* added cleaning step for custom classes

* change description texts

* added state transitions to SalesOrder, ReturnOrder

* renamed internal functions

* reduced diff

* fix keyword def

* added return funcion

* fixed test assertation

* replace counting with direct asserting

* also pass kwargs

* added sample for transition plugin
2023-12-07 14:48:09 +11:00
Oliver
12cbfcbd95 Add INVENTREE_STRICT_URLS to PUI (#6046) 2023-12-06 23:54:19 +11:00
Oliver
5387615d4f New Crowdin updates (#6032)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-12-06 17:17:24 +11:00
Oliver
238902e4f3 Url validation (#6045)
* Add new setting INVENTREE_STRICT_URLS

- Enforce schema prefix to URL validation
- Default  = True

* Implement new validation

* Also implement for DRF serializers
2023-12-06 17:17:15 +11:00
Oliver
c778c067f5 Tweak installer docs (#6043)
- Small improvements for installer documentation
2023-12-06 14:13:12 +11:00
dependabot[bot]
e7852980ad Bump vite from 4.4.11 to 4.4.12 in /src/frontend (#6041)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.4.11 to 4.4.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.4.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.4.12/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-06 11:10:30 +11:00
Oliver
2aef05c1c3 Fix validation for Build model (#6038)
* Fix validation for Build model

- Move checks into clean() method
- Allows errors to be handled cleanly when editing from the admin interface

* Fix unit test
2023-12-06 09:48:49 +11:00
Oliver
f70a049102 Notify when packager build fails (#6039) 2023-12-06 09:22:56 +11:00
Oliver
e6e1a66b34 Fix for table double loading (#6035)
- Closes https://github.com/inventree/InvenTree/issues/6031
2023-12-05 23:20:53 +11:00
Oliver
22800d6b10 Api image fix (#6034)
* Replace invalid image with skeleton

* Simplify ApiImage

* Flag unused variable
2023-12-05 23:20:41 +11:00
Oliver
8605832693 Admin section refactor (#6033)
* Remove plugin warning

- No longer needed as plugins are automatically reloaded

* Move ProjectCodeTable to admin interface

* Move CustomUnits table

* Transfer PartParameterTemplateTable

* Tweak user menu

* Fix unused imports
2023-12-05 19:03:09 +11:00
Lavissa
03a8190195 [API] Add default location filter to part endpoint (#6016)
* Add default_location to part filters

* Move to PartFilter class
2023-12-05 12:20:36 +11:00
Oliver
f96055a33f Refactor docker image for devcontainer (#6029)
* Make base image configurable

* Refactor dockerfile

- Place extra packages into extra shell script
- Add pre-build stage where packages are installed, and then cache removed
- Run same script in dev target, but do not remove

* Run docker workflow whenever package requirements change

* Replace devcontainer dockerfile

* Install base packages in devcontainer

* --no-cache-dir
2023-12-05 07:05:48 +11:00
Lukas
15f58b965e PUI: navigation/admin center improvements (#5953)
* First draft for refactoring the api forms including modals

* Fix merging errors

* Fix deepsource

* Fix jsdoc

* trigger: deepsource

* Try to improve performance by not passing the whole definition down

* First draft for switching to react-hook-form

* Fix warning log in console with i18n when locale is not loaded

* Fix: deepsource

* Fixed RelatedModelField initial value loading and disable submit if form is not 'dirty'

* Make field state hookable to state

* Added nested object field to PUI form framework

* Fix ts errors while integrating the new forms api into a few places

* Fix: deepsource

* Fix some values were not present in the submit data if the field is hidden

* Handle error while loading locales

* Fix: deepsource

* Added few general improvements

* Fix missig key prop

* Fix storage deprecation warnings

* Save panel state in url params

* Improved admin center

* Delete unused file

* Fix: deepsource

* Improve user drawer with descriptions

* Fix api bug with plugin settings and added links to notification entries

* Make plugin related settings work

* Added a lot more plugin actions

* Move InfoItem into own component

* Use Paper for setting item to make have some border radius according to theme

* Fix: deepsource

* Proposal: unify system settings and admin center to one central place

* Fix: deepsource

* Dont add install plugin if plugins are not enabled on this instance

* Fix switch links

* Revert: 'Proposal: unify system settings and admin center to one central place'

* Fix related model settings field

* Fix key error in plugin error table

* Make plugin panels loadables

* Remove user/group edit modal and open the detail drawer instead
2023-12-04 23:09:53 +11:00
Oliver
f034d86c3f Bug fix for label-skip implementation (#6030)
- Previous implementation skipped the first n labels
- We want to skip cells, but print all labels
- New implementation is also cleaner
2023-12-04 23:06:40 +11:00
Oliver
3c9e3d1252 Add facility to skip first "n" cells when printing labels (#6028) 2023-12-04 16:06:32 +11:00
Oliver
c3acb1753e Ensure template string is cached (#6027) 2023-12-04 15:15:39 +11:00
Oliver
025cc47b81 Do not autoescape part full-name template (#6024)
* Do not autoescape part full-name template

* Add skipqc

* Adjust skipqc

* Provide custom select_autoescape

* Remove comment
2023-12-04 13:29:07 +11:00
Oliver
6373f6077f New Crowdin updates (#6026)
* updated translation base

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-12-04 13:19:10 +11:00
Oliver
dc5508ba81 New Crowdin updates (#6010)
* updated translation base

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-12-03 19:37:01 +11:00
Matthias Mair
bbf4d2f206 enable passing of free-form urls to apiUrl (#6012) 2023-12-02 22:35:12 +11:00
Oliver
fb42878c11 Give the people what they want (#6021)
* Add 'existing_image' field to part API serializer

* Ensure that the specified directory exists

* Fix serializer

- Use CharField instead of FilePathField
- Custom validation
- Save part with existing image

* Add unit test for new feature

* Bump API version
2023-12-02 18:52:50 +11:00
Oliver
a7728d31ab Check for null part instance (#6011)
- Error reported via sentry.io
2023-11-30 16:31:07 +11:00
mechanarchy
ae4c4242dd LDAP group membership support (#6003)
* LDAP group support

* config_template LDAP group support

* LDAP group docs

* Typo

* Fix import

Transcription error

* Linter fix

* Linter fix

* Linter fix
2023-11-30 16:11:05 +11:00
Matthias Mair
b343ef337d made StatusRenderer less strict to allow usage of custom states (#6008) 2023-11-30 15:54:45 +11:00
Lukas
0cd66fd16c Fix reloading on viewport size change (#6005) 2023-11-30 15:53:32 +11:00
Oliver
e4c5bfc2fe More tables (#5999)
* Move CompanyTable

* Add "Contacts" table to company detail page

* Typo fix

* Remove 'notes' column

* Add preFormWarning and preFormSuccess to API form

- Refactor existing deletion forms

* Adds "address" table for company view

* Prevent wrapping on inline model rendereing

* Refactor new tables

* Fix unused imports
2023-11-29 14:05:22 +11:00
Oliver
f0f72a8742 New Crowdin updates (#5991)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-11-29 13:25:30 +11:00
dependabot[bot]
875f3df2b8 Bump cryptography from 41.0.5 to 41.0.6 (#6002)
* Bump cryptography from 41.0.5 to 41.0.6

Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.5 to 41.0.6.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/41.0.5...41.0.6)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update requirements.txt

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2023-11-29 13:25:17 +11:00
Oliver
c3aeadda4a Add useTable hook (#6000)
* Add new hook - useTable

- Will replace useTableRefresh
- More extensible (further functionality to follow)
- Pass entire state through to final table
- Defined interface for return type

* Update BomTable

* Update UsedInTable

* Refactor attachment table

* Update remaining tables

* Clean StockItemTable

* Fix NotificationTable

* Remove unused import
2023-11-28 23:15:08 +11:00
Matthias Mair
c7bf1348e7 Update CODEOWNERS (#5996)
remove @matmair
2023-11-28 10:23:54 +11:00
Matthias Mair
2509c8f48b [PUI] Template helpers (#5993)
* added helper to render an instance for an url

* made manifest loading more error tolerant (to the user)

* refactored return string composition

* made manifest path configurable

* made django app relative

* fix typing for py 3.9

* move css up
2023-11-27 22:13:06 +11:00
mechanarchy
1994b0f3b2 Fix 'Maximimum' typo (#5990) 2023-11-27 13:01:01 +11:00
Matthias Mair
fac66b289b [PUI] URL fixes (#5989)
* added helper to render an instance for an url

* removed unneeded declaration

* restructed PUI urls to improve behaviour:
- removes dead routes
- adds percausion to make sure asset-matching path is always the same
- makes sure subpaths of the PUI path also match to PUI (fixing a current bug with fully reload PUI)

* clean up diff
2023-11-27 11:51:53 +11:00
Matthias Mair
87df9245eb [PUI] Helper to render an instance for an url (#5977)
* added helper to render an instance for an url

* cleanup unsued imports
2023-11-27 10:53:41 +11:00
Michael
e9fe394d94 Store data from example panel (#5986)
* Fix errors in variable names of examples

* Added drop down example

* Added drop down example

* Fixed picture name

* Fixed context variable in purchase order report

* Store data from example panel

* Store data from example panel

* Store data from example panel
2023-11-27 09:48:41 +11:00
miggland
43434fc530 Extend API for labels and reports to allow creating new items (#5982)
* Extend API for labels and reports to allow creating new items

* Increment API_VERSION

* Missed something..
2023-11-26 20:52:03 +11:00
Oliver
90e6938eb6 New Crowdin updates (#5980)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-11-26 10:31:45 +11:00
Oliver
2185bf0437 Translation test : Add context (#5987)
* Translation test : Add context

- Adds extra context to the SKU field
- Ref: https://github.com/inventree/InvenTree/issues/5984

* remove trailing comma
2023-11-26 10:31:32 +11:00
Oliver
e11725078f Translation string fixes (#5985)
* Capitalize string

* Fix typo: Bebug - Debug
2023-11-25 12:32:43 +11:00
Oliver
064caafebb API filter fix (#5979)
* Simplify custom date filter

* Add creation date data to test fixture

* Add 'creation_date' to part serializer

* Add unit test

* Update API version
2023-11-24 10:34:56 +11:00
Lavissa
44614493e9 Forgot to add the new page to nav :) (#5978) 2023-11-24 09:41:34 +11:00
Oliver
b9424a9284 Improve handling of checkUserRole (#5976) 2023-11-24 00:06:30 +11:00
Oliver
a3f62c750e New Crowdin updates (#5973)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-11-23 19:59:47 +11:00
Lavissa
f4ba7a1698 Documentation update; New frontend docs, additional info on devcontainer (#5974)
* Add Frontend dev docs, and expand devcontainer docs

* Grammatical fixes
2023-11-23 12:21:09 +11:00
Oliver
f7a8707a9a add ability to export part image data (#5971) 2023-11-23 10:41:10 +11:00
Oliver
cfddfa526e Add custom InvenTreeDateFilter class (#5970)
- Fixes issues with naive datetimei object
- Suppress warning
- Closes https://github.com/inventree/InvenTree/issues/5223
2023-11-23 10:36:15 +11:00
Matthias Mair
7dec1fcb25 Fixed a few modeltype links (#5969) 2023-11-23 09:18:50 +11:00
Oliver
a9d5b24702 New Crowdin updates (#5955)
* updated translation base

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-11-22 00:43:30 +11:00
Oliver
15b2437392 Docker CI Updates (#5909)
* Update docker image - base python version

- Also, build docker image on PR if any docker-related files have changed

* Update setuptools

* Update base level packages for docker image

* Reduce version

* Include docker workflow in filter

* Revert to python 3.10

* Remove call to upgrade setuptools

* Try newer version of setuptools

* Remove reliance on "minimal"

* Fix package URL

* Whoops. Fix typo
2023-11-22 00:25:13 +11:00
Oliver
2ccddd8f2e Project Responsible (#5944)
* Add "responsible owner" to project code table

* Update project code serializer

* Update CUI project code table

* Update PUI project code table

* Update API version
2023-11-21 23:13:20 +11:00
Oliver
6090ddfdf3 Part pricing override (#5956)
* Add override fields for part pricing

* Allow pricing override values to be specified via the API

* Fix serializer

* Update pricing docs

* Add UI elements for manually overriding pricing data

* Increment API version
2023-11-21 14:53:45 +11:00
Oliver
dabd95db85 PO barcode- add line (#5949)
* Enable custom barcode support for 'manufacturerpart' model

* Adds API endpoint for scanning parts into a purchase order

* Update API version

* Activate API endpoint

* Refactor 'format_matched_response'

- Move to instance level
- Use existing mixin class

* Refactor get_supplier_part method

* Fix BarcodePOReceive serializer

* Update API version with link to PR

* Updates to fix existing unit tests

* Fix API version
2023-11-21 08:26:32 +11:00
Lukas
264dc9d27a PUI general improvements (#5947)
* First draft for refactoring the api forms including modals

* Fix merging errors

* Fix deepsource

* Fix jsdoc

* trigger: deepsource

* Try to improve performance by not passing the whole definition down

* First draft for switching to react-hook-form

* Fix warning log in console with i18n when locale is not loaded

* Fix: deepsource

* Fixed RelatedModelField initial value loading and disable submit if form is not 'dirty'

* Make field state hookable to state

* Added nested object field to PUI form framework

* Fix ts errors while integrating the new forms api into a few places

* Fix: deepsource

* Fix some values were not present in the submit data if the field is hidden

* Handle error while loading locales

* Fix: deepsource

* Added few general improvements

* Fix missig key prop

* Fix storage deprecation warnings
2023-11-21 08:24:00 +11:00
Lavissa
333e2ce993 Add owner toggle setting to new frontend (#5952) 2023-11-21 07:10:18 +11:00
Oliver
317c2666b8 Improvements for part.full_name (#5946)
* Improvements for part.full_name

- Compile and cache the template
- Reduces typical render time from ~20ms to ~0.2ms

* Force autoescape
2023-11-21 00:12:25 +11:00
Lukas
cb537780dc Make modals/forms more reactive (#5897)
* First draft for refactoring the api forms including modals

* Fix merging errors

* Fix deepsource

* Fix jsdoc

* trigger: deepsource

* Try to improve performance by not passing the whole definition down

* First draft for switching to react-hook-form

* Fix warning log in console with i18n when locale is not loaded

* Fix: deepsource

* Fixed RelatedModelField initial value loading and disable submit if form is not 'dirty'

* Make field state hookable to state

* Added nested object field to PUI form framework

* Fix ts errors while integrating the new forms api into a few places

* Fix: deepsource

* Fix some values were not present in the submit data if the field is hidden

* Handle error while loading locales

* Fix: deepsource
2023-11-21 00:00:44 +11:00
Oliver
0d7b4f2f17 Fixes call to processField (#5943)
* Fixes call to processField

- Previously used options.fields?
- Should use fields?

* Update forms.js
2023-11-20 22:03:08 +11:00
Oliver
65531f7611 API bug fix: Distinct query (#5940)
* Fix "company" filter for StockList

- distinct() was in the wrong spot
- Added a new unit test to cover this

* Update stocklist API filter

- Move custom filtering into FilterSet class
- Exposes available filters to API documentation
- Improved readability / field validation

* Further improvements for StockList API

* For for order extra line item serializer

- 'title' is not a valid field
2023-11-20 18:25:52 +11:00
Matthias Mair
70a96942c1 [PUI] Api state extension/cleanup (#5934)
* added option to replace path parts

* use function instead of f-string

* remove doube defintion

* added user pk

* Update src/frontend/src/states/ApiState.tsx

Co-authored-by: Lukas <76838159+wolflu05@users.noreply.github.com>

* fix typing issue

---------

Co-authored-by: Lukas <76838159+wolflu05@users.noreply.github.com>
2023-11-20 13:54:04 +11:00
Oliver
829d427a76 Improve error handling when updating currency exchange (#5939)
- Suppress output for expected error conditions
2023-11-20 13:14:16 +11:00
Oliver
52b01b09bf Refactor existing barcode API endpoints (#5937)
* Refactor existing barcode API endpoints

- Expose fields using proper DRF serializers
- API endpoints are now self documenting
- Validation is handled by serializer models
- Serializers and endpoints are extensible
- Extended existing unit tests

* Catch errors if db not yet loaded

* Tweak unit tests
2023-11-20 12:51:49 +11:00
Oliver
8cb2ed3bd6 New Crowdin updates (#5926)
* updated translation base

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix translation string manually

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-11-20 10:53:19 +11:00
Matthias Mair
d30ff8a291 Bump weasyprint (#5885)
* bumped weasyprint

* factored reused section out (reduce DB access)

* added pdf-context testing

Co-authored-by: miggland <miggland@users.noreply.github.com>

* switched to pdfminer.six

* make test more resilient

---------

Co-authored-by: miggland <miggland@users.noreply.github.com>
2023-11-20 09:35:11 +11:00
Matthias Mair
a0841088ae only run sposnor action if on upstream repo (#5931) 2023-11-17 08:16:52 +11:00
Lavissa
e2b29cbd95 Toggle full name display of users (#5927)
* Add option to display full names instead of usernames

* InvenTree Setting added to allow for admins to choose full names
  over usernames for display in the web GUI

* Fix FA icon for settings page

* Remove debug print
2023-11-16 13:18:01 +11:00
Lukas
0b168c1d9a CUI dependent/nested fields (#5924)
* Added backend changes to support printing options

* Pass printing options seperatly via kwargs for easier api refactor later

* Implemented printing options in CUI

* Fix js linting

* Use translations for printing dialog

* Support nested fields in CUI

* Added docs

* Remove plugin and template fields from send printing options

* Fix docs

* Added tests

* Fix tests

* Fix options response and added test for it

* Fix tests

* Bump api version

* Update docs

* Apply suggestions from code review

* Fix api change date

* Added dependent field and improved nested object fields on CUI

* Fix: cui js style

* Fix process field implementation if the 'old' __ syntax is used for nested fields
2023-11-16 11:58:51 +11:00
Matthias Mair
acb31927f1 Factored typecasting out (#5925)
* factored typecasting out

* fixed nameing conflict
2023-11-16 10:03:18 +11:00
Oliver
df0da18d2f Refactoring supplier barcode support (#5922)
* Refactoring supplier barcode support

- Add a set of standard field name strings
- Map from custom fields to standard fields
- Helper functions for returning common field data
- Updated unit tests

* Update unit tests

* Fix unit test

* Add more unit tests'

* Improve error messages
2023-11-15 23:35:31 +11:00
Oliver
538a01c500 Error handling (#5923)
* More error handling when loading label app

* More error handling when loading report app
2023-11-15 23:11:38 +11:00
Oliver
928e00e19c New Crowdin updates (#5911)
* updated translation base

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-11-15 22:55:20 +11:00
Oliver
f42fc77cd5 Refactoring (#5921)
- Move all supplier barcode functions into SupplierBarcodeMixin
- Look for specific mixin class when scanning
2023-11-15 14:38:58 +11:00
Bobbe
cf3d96a265 Fix some issues with supplier barcode plugins (#5919)
* Remove unnecessary logging imports from supplier plugins

* Fix MPN filtering in get_supplier_parts

* Ensure digikey barcodes contain a SKU

* Keep running other barcode plugins if an error occured in one of them

* Fix typo (replace plugin with current_plugin)

* Fix class name
2023-11-15 13:40:30 +11:00
Oliver
dedf50da62 Quick fix for unit test (#5920)
- Sometimes the items are returned in a different order, maybe?

(cherry picked from commit 849f52a1af)
2023-11-15 13:18:49 +11:00
Oliver
2234357e63 Fix some issues in report docs. (#5918)
Ref: https://github.com/inventree/InvenTree/issues/5893
2023-11-15 10:42:57 +11:00
Oliver
fe0d9c1923 Distinct query fix (#5916)
* Fix "allocated" query for stock items

* Use distinct() elsewhere

* Add unit test to check distinct query results
2023-11-15 10:18:26 +11:00
Matthias Mair
5be6bc8940 Added order cancel notifications (#5912)
* Squashed commit of the following:

commit 5e8ea099068475fd257d8c172348dc6f3edf9bcf
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 09:22:38 2023 +0200

    Update ui_plattform.spec.ts

commit 49da3312beff7fd6837ea741e621df221c445d19
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 07:56:25 2023 +0200

    more logging

commit 5337be4c3990051b805a6fce2e79ca4030b4afe5
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 07:56:11 2023 +0200

    added filter method for undefined settings that overwrite defaults

commit 5df8a0b3e77cd5dcf04c39ad7638ac845df75e4c
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 03:05:06 2023 +0200

    you do not need to string a string

commit 0650d3b3a0132889c2a76de38db38224e974d205
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 03:04:34 2023 +0200

    fix things that were borken for no good reason

commit a40dbfd1364cf01465037350184f59d2a2a8afab
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 02:39:34 2023 +0200

    reduce unneeded blocking timeouts

commit bf9046a5361ae919e70662e717d6156434b6fe43
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 02:34:10 2023 +0200

    catch server fetching errors

commit aa01e67e8c8e789fdf755ac4481e730fe5ea4183
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 02:33:29 2023 +0200

    move init as things are now plugged together different

commit 290c33bd3125d50779497d6fc5981d5813b58f5d
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 01:49:32 2023 +0200

    do not log a failed automatic login try - why would you?

* Added notifications for cancled orders

* cleanup submodules

* added notification to build

* move import?
2023-11-15 09:31:24 +11:00
Oliver
247ad4dcf9 Revert "Form improvements (#5837)" (#5913)
This reverts commit 0d193d8cff.
2023-11-14 14:49:45 +11:00
Oliver
2fcd6ae0b9 Report image rendering fix (#5907)
* Allow different image variations to be rendered in when using a part image in a report

* Use preview image in default test report

* Fix api_version

- Missed in https://github.com/inventree/InvenTree/pull/5906

* Update docstring

* Add similar functionality for company_image tag

* Update report documentation

* base-64 encode images for rendering in reports

- Allows image manipulation operations to be performed on the images
- Avoids any file pathing issues

* Update docs

* Fix unit tests

* More unit test fixes

* More unit test

* Handle missing file

* Instrument unit test

- Trying to determine what is going on here

* Fix for image resize

* Translate error messages

* Update default report templates

- Specify image size
2023-11-14 12:08:18 +11:00
Oliver
3e063750b5 Improve RenderOwner function (#5910) 2023-11-13 23:12:37 +11:00
Lukas
0d193d8cff Form improvements (#5837)
* Added backend changes to support printing options

* Pass printing options seperatly via kwargs for easier api refactor later

* Implemented printing options in CUI

* Fix js linting

* Use translations for printing dialog

* Support nested fields in CUI

* Added docs

* Remove plugin and template fields from send printing options

* Fix docs

* Added tests

* Fix tests

* Fix options response and added test for it

* Fix tests

* Bump api version

* Update docs

* Apply suggestions from code review

* Fix api change date

* Added dependent field and improved nested object fields on CUI

* Fix: cui js style
2023-11-13 22:49:45 +11:00
Michael
17ae1a780d Added dropdown menu to the example panel (#5898)
* Fix errors in variable names of examples

* Added drop down example

* Added drop down example

* Fixed picture name

* Fixed context variable in purchase order report
2023-11-13 22:17:52 +11:00
Oliver
ac26f61ecd Owner list API updates (#5906)
* Implement better search matching for owner list API

* Update UserSerializer

-Add filterset fields to various API endpoints

* Allow owner list to be filtered by "is_active" status

- Only applies to users (not groups)

* Add ability to filter "owner" list by "is_active" status

- Only applies to users

* Use "is_active" filter for "responsible" form field
2023-11-13 15:06:45 +11:00
Oliver
8972dcb506 New Crowdin updates (#5899)
* updated translation base

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-11-13 14:33:49 +11:00
Oliver
ff4138e8de Revert "bumped docker base image (#5889)" (#5905)
This reverts commit 41296e4574.
2023-11-13 12:58:32 +11:00
Matthias Mair
33c02fcd78 Added first UI components for user managment (#5875)
* Added first UI components for user managment
Ref #4962

* Add user roles to table and serializer

* added key to AddItem actions

* added ordering to group

* style text

* do not show unnecessary options

* fix admi / superuser usage

* switched to use BooleanColumn

* Added active column

* added user role change action

* added user active change action

* Added api change log

* fixed logical error

* added admin center to navigation

* added groups to user serializer

* added groups to the uI

* Added user drawer

* fixed active state

* remove actions as they are not usable after refactor

* move functions to drawer

* added drawer lock state

* added edit toggle

* merge fix

* renamed values

* remove empty roles section

* fix settings header

* make title shorter to reducelayout shift when switching to server settings
2023-11-13 12:48:57 +11:00
Matthias Mair
41296e4574 bumped docker base image (#5889) 2023-11-13 12:44:31 +11:00
42CrMo4
a1ce2f53e1 correct Part uploadImage Parameter spelling (#5902) 2023-11-12 23:12:56 +11:00
Joshua Miller
078538add2 Minor documentation updates (#5900)
* Fixed typo

* Minor description adjustment
2023-11-12 22:55:12 +11:00
dependabot[bot]
2106eeb25a Bump axios from 1.5.1 to 1.6.0 in /src/frontend (#5901)
Bumps [axios](https://github.com/axios/axios) from 1.5.1 to 1.6.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.5.1...v1.6.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-12 08:38:43 +11:00
Oliver
e6db817c8f Model render refactor (#5894)
* Embiggen search drawer

* Refactor search drawer queries:

- Use proper permission checks

* Actually check user settings

* Move StatusRenderer

* Update renderers

- Improve in-line render for different order types

* Update stockitem renderere

* Remove old renderer functions

* Better data handling in UserState

* Tweaks for settings pages

* "Fix" scanning page

- Rendering is a bit broken currently, as the barcode scan does not send back the model data

* "Fix" scanning page

- Rendering is a bit broken currently, as the barcode scan does not send back the model data
- Required refactoring enumerations out into separate files
- Some strange race condition / import loop was happening

* Fix incorrect imports

* Fixing hover card

- Use unique key

* fixes

* Fix urls.md

* More udpates

* Fix unused import
2023-11-10 15:44:02 +11:00
Joshua Miller
9e2da947a9 Updated URL documentation with more information about panels (#5896)
* Fixed error "Line 39 in Markdown file: Encountered unknown tag 'load'."

I did not include the statements {% raw %} and {% endraw %} to escape the code. I have been able to set up a dev server and can confirm this new md file works. My apologies!

* Added more documentation about Panels, and their role in `page_base.html`

* Added more sidebar documentation, and documentation about panels

* Fixed `Line 73 in Markdown file: Encountered unknown tag 'trans'.` error.

Also, made minor clarity improvements

* Reworded some sentences for clarity improvements.

Additionally, added more information about `onPanelLoad`

* Minor syntax updates
2023-11-10 13:55:10 +11:00
Oliver
8caa4b427a Activate / deactivate plugins (#5895)
* Activate / deactivate plugins

- Uses new UI
- Nice progress indicators / notifications

* Fix unused variable
2023-11-10 13:11:33 +11:00
Oliver
5abe0eaaad Parameter table updates (#5892)
* Add some helper functions for role permission checks on frontend

* Update PartParameterTable

- Use new user role checks

* Fix up more table action permissions

* Add table for part parameter template

* Add edit and delete actions to new table

* Add ability to create new template from table

* Fix for BomTable

* Refactor RowActions

- Require icon
- Horizontal menu popout

* Refactor row actions for existing tables

* Fix BomTable

* Bug fix for notifications table

* Fix display of TableHoverCard

* Disable PanelGroup tooltip when expanded

* Fix unused variables
2023-11-09 21:50:17 +11:00
Oliver
0597ea9216 Token admin fix (#5891)
* Change available fields in admin

- Add 'key' when initially creating
- Stops key from being created twice

* Add MinLengthValidator to token key field
2023-11-09 13:59:44 +11:00
Oliver
02320e27c1 updated sponsors (#5890)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-11-09 11:07:30 +11:00
Oliver
7350f9887e Update sponsors action (#5887)
- Inspired by translations github workflow
2023-11-09 10:59:36 +11:00
Oliver
6276a00d20 New Crowdin updates (#5879)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-11-09 09:59:11 +11:00
Oliver
a0b1ba62a9 Label sheet printer (#5883)
* Add skeleton for new label sheet plugin

* Add custom printing options serializer

* Render individual label outputs to HTML

* Extract page size and column size

* Check label dimensions before printing

* Split labels into multiple pages / sheets

* Render out multiple labels onto a single sheet

* Cleanup base label template

- Allow @page style to *not* be generated
- Pass through as optional context variable
- Check that it still works for single label printing (default behaviour unchanged)
- Prevents multiple @page styles from being generated on label sheet output

* Fix stylesheets for part labels

* Cleanup stock location labels

* Cleanup more label templates

* Check if label can actually fit on page

* Generate output to PDF and return correct response

* Update panel.md

* Fix unit tests

* More unit test fixes
2023-11-09 09:00:23 +11:00
Michael
e674ca7437 Added a chapter for more complicated data (#5878) 2023-11-08 21:11:57 +11:00
Oliver
8c9bc4d1af Allow decimal values in new forms (#5881) 2023-11-08 20:45:09 +11:00
Oliver
907343c5db Pinned action column (#5880)
* Update mantine-datatable package

* use new pinLastColumn property
2023-11-08 08:30:05 +11:00
Oliver
5d05137630 [Platform] BOM Table (#5876)
* Add generic BooleanColumn for tables

* Edit BOM item

* Add 'building' quantity to BomItemSerializer

* Improve "available" column

* Fix yesnobutton

* Update 'available' and 'can_build' columns

* Delete BOM item

* Improve back-end ordering for BomItem list API

* Table tweaks

* Bump API version

* Tweak API notes
2023-11-08 07:37:17 +11:00
dependabot[bot]
26b2e90fcf Bump pillow from 9.5.0 to 10.0.1 (#5657)
* Bump pillow from 9.5.0 to 10.0.1

Bumps [pillow](https://github.com/python-pillow/Pillow) from 9.5.0 to 10.0.1.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/9.5.0...10.0.1)

---
updated-dependencies:
- dependency-name: pillow
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* Patch requirements files

* Update package requirements

* Improve offload_task function:

- Return True if the task runs or was offloaded
- Improved warning information

* Improve unit tests for task offloading

- Check return value of offload_task
- Check log output

* Bump django-stdimage

* Fix tasks.py

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2023-11-07 10:08:03 +11:00
Oliver
28399bed25 BOM tree fix (#5870)
* Extend protection against recurisve BOMs

- Prevent part variants from being use BOMs for other variants of the same part

* Add unit tests for new BOM validation checks

* Cleanup  urls.md

* Update unit tests

* Unit test update

* <ore unit test fixes
2023-11-07 09:48:04 +11:00
Matthias Mair
c34e14baec Expose API version descriptors for admins via API (#5865)
* Added endpoint to expose API version descriptors for admins

* Refactored tests and moved them to seperate file

* Switched to raw string

* add coverage exclusion

* ignore imports (possible fix to multiline imports)

* Add OpenApi Schema

* removed changes regarding coverage

* moved new functions to `version`
to keep api_version a pure static file

* clean up diff
2023-11-07 09:36:15 +11:00
Matthias Mair
0af08da6f8 Fully remove deepsource cov (#5874)
* Added first UI components for user managment
Ref #4962

* removed forgotten deepsource section
Follow up to #5851

* Revert "Added first UI components for user managment"

This reverts commit 624121ea29.
2023-11-07 09:35:08 +11:00
Oliver
63edd39e2d Ulugbek erkinov master (#5872)
* Remove stat context variables

* Revert "Remove stat context variables"

This reverts commit 0989c308d0.

* Allow longer timeout for image download tests

* fix: Adding specific quantity of a part as installed item(s

* fix: Adding specific quantity of a part as installed item(s

* Update serializers.py

* Mark field as not required

* If value not provided, revert to "default" behavior

* Fix serializer validation

* Update frontend forms to mach

* Bump API version

---------

Co-authored-by: Ulugbek Erkinov <ulugbekerkinov606@gmail.com>
2023-11-06 22:58:46 +11:00
Oliver
390906532e New Crowdin updates (#5839)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-11-06 21:45:06 +11:00
Oliver
4f8bca4ea4 PO auto fill fix (#5871)
* Take "on_order" quantity into account when ordering items

* Clean up panel.md

* update urls.d
2023-11-06 21:14:23 +11:00
Matthias Mair
25598144ed [PUI] Small nav additions (#5855)
* Added nav items from main nav

* Added system settings to nav

* removed duplicate entry

* renamed logo button to be clearer about it's intend

* change target of InvenTree icon in header to open nav

* added delay to not open hover on fast header click
2023-11-06 20:37:58 +11:00
Joshua Miller
a2b464e874 Fixed error "Line 39 in Markdown file: Encountered unknown tag 'load'." (#5867)
I did not include the statements {% raw %} and {% endraw %} to escape the code. I have been able to set up a dev server and can confirm this new md file works. My apologies!
2023-11-06 14:23:40 +11:00
Michael
77f39230c1 Added example with user data input to the panel mixin doc (#5848)
* Added example with user data input to the panel mixin doc

* Typo

* Added changes proposed by Lukas
2023-11-06 09:30:00 +11:00
Matthias Mair
f1e455178a [PUI] Added tests for PUI backend stuff (#5864)
* Added tests for PUI backend stuff

* Added coverage for error cases

* Added testing for frontend_settings

* Added more testing for `get_frontend_settings`
2023-11-06 09:27:07 +11:00
Matthias Mair
2d6a8a4bcc [PUI] Add more formatters (#5771)
* Added general data formatters

* Added usage of new date formatter

* Added usage of new date formatter

* Added usage of currency formatter

* style cleanup

* Moved to use real user and server settings

* fixed type

* Added in formatters again

* cleaned up unsued imports
2023-11-06 09:25:13 +11:00
Oliver
ea249c1dc5 Add translation support for Bulgarian (#5863)
* Enable Bulgarian for backend

* Build out initial backend translations

* Add integration for frontend
2023-11-05 21:28:14 +11:00
Oliver
dbf1baf0ed [React] Order tables (#5860)
* Factor out custom component for displaying project code information in a table

* Bump API version

* Update order serializers

- Annotate 'completed_lines' to each order type

* Build out columns for ReturnOrderTable

* Improvements to PurchaseOrderTable

* Building out SalesOrderTable

* Column tweaks

* Factor out project code column

* Factor out status column

* Factor out description column

* Factor out more columns

* More refactoring

* Center status labels

* Fix for PurchaseOrderLineItemTable

* Improve rendering

* Remove unused imports

* Refactor TotalPriceColumn

* Add generic currency column for rendering currency / money values
2023-11-05 12:57:38 +11:00
Matthias Mair
93f642c790 Added base for admin center (#5862) 2023-11-05 08:33:46 +11:00
Oliver
fb7020a85a [React] Build improvements (#5859)
* Add filters for BuildTable

* Add inline renderer for ProjectCode

* Pass form response data to onFormSuccess callback

* Create new build from build index

* Edit existing build order

* Add projectcode to list

* Bug fixess
2023-11-04 21:24:51 +11:00
Oliver
1640f605a0 [React] Currency table (#5858)
* Add basic table for displaying exchange rates

- Needed to extend current <InvenTreeTable> functionality for non-standard API endpoint data

* Add heading

* Add button to reload exchange rates
2023-11-04 17:47:39 +11:00
Oliver
361fc097a7 [React] PO detail pgae (#5847)
* Factor out common barcode actions

* Refactoring more icons

* Add PurchaseOrderLineItemTable component

* Improve renderer for SupplierPart

* Edit line item

* Table action column always visible

* Create <AddItemButton> component (refactoring)

* Table updates

- Improve actions column for table
- Move "download" button to right hand side

* Refactoring button components

* More cleanup

- Refactor <TableHoverCard> a bit
- Add placeholder for "receive items"

* Add ProgresBar component

* Make table columns switchable by default

- set switchable: false to disable

* Add project_code column to build table

* Fix row actions column for tables without actions

* Improve rendering for BuildOrderTable

* Cleanup unused imports

* Further fixes

* Remove another unused import
2023-11-04 14:02:13 +11:00
Matthias Mair
19810d0965 Use gpg for install script instead of apt-key (#5856)
* possible fix to #5838

* fixed syntax
2023-11-04 13:11:57 +11:00
Matthias Mair
7396b028d7 added pricing section (#5857) 2023-11-04 12:21:34 +11:00
Matthias Mair
0d99e97e1d Bump python dependencies (#5854)
* Bumped python dependencies

* Fixd for py3.9

* fixed for py 3.9
2023-11-04 09:51:48 +11:00
Matthias Mair
785b8eb602 Small size optimisation for devcontainer (#5852)
* avoid cache directory

* clean apt lists
2023-11-04 09:24:25 +11:00
Matthias Mair
67b70343c3 deactivated coverage analyzer (#5851)
and cleand up formatting
2023-11-04 08:15:33 +11:00
Oliver
30ef4383d1 Fix bug when user opts not to receive error reports (#5846) 2023-11-03 12:47:57 +11:00
Oliver
29a191e05e Prevent settings from writing to the database during import (#5844)
- Fixes conflicts which can occur due to importing duplicate settings from a data file
2023-11-03 11:58:22 +11:00
Matthias Mair
92336f6b32 [PUI] Settings simplification and restructure (#5822)
* unify file structure for settings

* fixed router

* clean up menu

* refactored user settings
mergerd profile and user setttings

* removed profile page entirely

* cleaned up account panels

* use more detailed link

* refactored settings page header

* fixed user settings save

* simplified user data handling

* fixed UserState invalidation after form submition

* removed username from account change
this can currently not be done safely

* Added basic security section

* Added way to remove SSO account

* Only show providers that are not in use

* Changed API to contain configuration change

* removed unused var

* Added email section to PUI

* Switched rending to vertical

* Added things for adding a new email

* removed sessions as we are not using that in PUI

* made rendering logic easier to understand

* alligned colums horizontally

* allign action buttons for email
2023-11-03 11:23:45 +11:00
Michael
7b9c618658 Addec context variables for contacts to docs (#5834)
* Corrected report mixin example

* Remove unused file

* Added context variables for company and address

* Repair link in file

* Repair link in file

* Repair link in file

* Removed blank

* Added context variables for contacts to docs

* Typo in link

* Update .pre-commit-config.yaml

Revert version

* Delete InvenTree/order/migrations/0099_auto_20231101_1747.py

* Delete InvenTree/part/migrations/0119_auto_20231101_1747.py

---------

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2023-11-02 17:40:40 +11:00
Oliver
f71322ecd3 [React] SupplierPart table (#5833)
* Fix TableHoverCard component

* Improving handling of very wide table cells

* Update panels for PartDetail

* Refactor <Thumbnail> component

* Add SupplierPart table

* Refactor forms

- Do not need to specify custom form name any more

* More fixes for modal forms

* Refactor forms field code

* Add generic row action components for edit and delete

* Add placeholder comments

* Add ability to edit supplier part from table

* Create supplier part

* Add missing import

* Revert scroll behaviour for wide cells

- Does not play nice on chrome

* Add placeholder panel for part manufacturers

* Fix inline renderer for manufacturerpart

* Cleanup unused imports

* Add icons to supplier part fields

* Increase size of form titles

* Another fix
2023-11-02 12:10:03 +11:00
Lukas
a11418398f Printing options (#5786)
* Added backend changes to support printing options

* Pass printing options seperatly via kwargs for easier api refactor later

* Implemented printing options in CUI

* Fix js linting

* Use translations for printing dialog

* Added docs

* Remove plugin and template fields from send printing options

* Fix docs

* Added tests

* Fix tests

* Fix options response and added test for it

* Fix tests

* Bump api version

* Update docs

* Apply suggestions from code review

* Fix api change date
2023-11-02 00:39:19 +11:00
Bobbe
59f17a9885 Bump django_money version back to 3.2.0, Fix #5825 (#5828)
* Bump django_money version back to 3.2.0

* Test building docker image

* Revert "Test building docker image"

This reverts commit b8e9d4f4c1.
2023-11-01 10:05:47 +11:00
Oliver
e18b6d38ef React updates (#5826)
* Add more panels to StockItem page

* Add some placeholder actions for StockItem page

* edit stock item

* Add info hover card to stocktable

* update extra info for part table

* Add extra columns to PurchaseOrder table

* Fix unused import
2023-11-01 07:32:40 +11:00
Oliver
2908ad0721 Recursive delete fix fix (#5819)
* Enable mysql checks as part of PR

* Add debug for CI

* Add delete_nodes method

- Ensure that the "parent" field is set to None before delete
- This means that we do not violate any ForeignKey constraints due to undefined order of operations
2023-10-31 22:58:43 +11:00
Bobbe
4913acda79 Update django money / py-moneyed dependencies (#5778)
* Replace moneyed format_money function

* Update django-money to 3.3.0 and py-moneyed to 3.0

* Add CurrencyField migrations

* Fix checking if decimal_places is set

* Add currency formatting test

* Test fixing deepsource test patterns

* Revert "Test fixing deepsource test patterns"

This reverts commit 398ef93cd6.

* FIx requirements.txt formatting

* Revert "FIx requirements.txt formatting"

This reverts commit bb554b0758.
2023-10-31 22:19:55 +11:00
Oliver
ebaa7d64a8 Skip plugin registry reload if performing django functions (#5823)
* Skip plugin registry reload if performing django functions

* Revert changes to pre-commit
2023-10-31 15:47:59 +11:00
Matthias Mair
782f36cd48 [PUI] Added AboutInventreeModal (#5813)
* updated typing to allow either link or action

* fixed typing

* made it possible to use an action instead of a link

* added ServerInfo Modal skeleton

* fixed anchor

* added content to ServerInfo

* Factored database lookup out

* Extended status API to CUI level

* extended ServerInfo to CUI level

* Made modal larger

* fixed default settings

* Refactored urls into seperate functions

* Refactored python version into seperate function

* Added endpoint and modal for PUI version modal

* switched to indirect imports to reduce imports

* Added copy button

* Added full copy button

* added default

* cleaned unused vars

* cleaned unused  vars

* Refactored auth check for InfoView

* implemented suggested changes

* fixed check logic
2023-10-31 10:02:28 +11:00
Oliver
43fac17796 Part table extra options (#5821)
* Render stock information on hover in part table

* Fix minimum_stock serializer field
2023-10-31 10:01:13 +11:00
Oliver
a83bc32fc7 [React] Stylish form titles (#5817)
* Use <StylishText> for form title

- Also removes useModalState
- This was clunky

* Remove unused improt
2023-10-31 00:05:24 +11:00
Oliver
3f7d05339b [React] BOM table (#5816)
* Add BuildOrderTable to part page

* Add UsedInTable

* Display SalesOrderTable on PartDetail page

* Fix horizontal overflow for panel group

* First pass at BomTable

* Adds <TableHoverCard> component

- Allows us to display more information in a table cell, without clutter

* Fix for row actions

- Prevent opening row actions menu from "clicking" the row
- Prevent selection of row action from "clicking" the row

* Further work on BOM table

- Column rendering
- Placeholder actions

* Prevent navigation to a panel group page and selection of invalid panel

* Fix unused references
2023-10-31 00:02:42 +11:00
Oliver
98d7c49ea5 Update .deepsource.toml (#5814)
Additional deepsource fix
2023-10-30 13:18:48 +11:00
Oliver
8c14614bb5 Update .deepsource.toml (#5812)
Fix test patterns for deepsource

Ref: https://github.com/inventree/InvenTree/pull/5778#issuecomment-1781903275
2023-10-30 12:41:17 +11:00
Matthias Mair
8aad3eeefa [PUI] Added Server Info Modal (#5810)
* updated typing to allow either link or action

* fixed typing

* made it possible to use an action instead of a link

* added ServerInfo Modal skeleton

* fixed anchor

* added content to ServerInfo

* Factored database lookup out

* Extended status API to CUI level

* extended ServerInfo to CUI level

* Made modal larger

* fixed default settings
2023-10-30 09:56:07 +11:00
Matthias Mair
7ff3f99dc9 Add deepsource code coverage (#5717)
* added deepsource coverage settings

* Ignore missing coverage

* trigger full CI run

* only run when upstream, master and push
2023-10-30 06:58:48 +11:00
Matthias Mair
5d6d847328 made default radius for UI smaller (#5809) 2023-10-30 06:58:06 +11:00
Oliver
fd0a57c4a1 Improve deletion behaviour for InvenTreeTree model (#5806)
* Improve deletion behaviour for InvenTreeTree model

- Remove recursive call to function
- Handle database operations as bulk queries
- Ensure child nodes have their pathstring updated correctly
- Remove old @receiver hook
- Refactor StockLocation.delete method
- Refactor PartCategory.delete method
- Atomic transactions potentially problematic here

* Add docstring

* Fix method name

* Use bulk-update instead of recursive save when pathstring changes

* Improvements for tree delete method

- Handle case where item has already been deleted

* Raise exception rather than simply logging

* Update unit tests

* Improvements to unrelated unit test

* Fix urls.md

* Fix typo
2023-10-30 06:57:40 +11:00
Oliver
a1f9260da6 Build part change fix (#5808)
* Add DiffMixin class

- Allows us to compute "diffs" against items stored in the database, when they are being updated

* Prevent build order part from being changed after creation

* Remove "part" field from buildorder edit form

* Add unit test
2023-10-29 23:13:38 +11:00
Michael
4cd4a84bac Added context variables for address (#5807)
* Corrected report mixin example

* Remove unused file

* Added context variables for company and address

* Repair link in file

* Repair link in file

* Repair link in file

* Removed blank
2023-10-29 22:07:21 +11:00
Joshua Miller
3c5e2a3541 Added Views, blocks, and templates documentation (#5805) 2023-10-29 12:11:10 +11:00
Oliver
f29a675824 [React] Tree navigation (#5800)
* Adds placehold "PartCategoryTree"

- Global navigation (within the "Part" context) for part categories
- Replaces multiple tree views from legacy UI

* FIx for ApiImage component

- Do not continuously request if the user is unauthorized

* Add StockLocation tree

* Add initial data to nav tree panels

* Display data

* Render tree view

* Fix unused variables
2023-10-28 00:35:52 +11:00
Michael
70578500ca Corrected example in report mixin (#5801)
* Corrected report mixin example

* Remove unused file
2023-10-27 23:08:30 +11:00
Matthias Mair
c47fa01965 [PUI] Raise translation/language support to CUI level (#5749)
* Added more languages

* Added translation stumps for PUI

* Exclude PUI translations from deepsource analysis

* Added comments to deepsource exlusions

* Modified language selection to use language names

* Refactored language mappings to be more efficient
following advice of @wolflu05

* Removed pseudo language from selection

* Translation cleanup

* Switched t/Trans useage

* switched back due to rendering limitations

* Revert "Merge branch 'master' of https://github.com/inventree/InvenTree into matmair/issue5729"

This reverts commit 9b6e8fff88, reversing
changes made to 481cd70aca.

* Revert "Revert "Merge branch 'master' of https://github.com/inventree/InvenTree into matmair/issue5729""

This reverts commit 143a531c1e.

* add translations back in

* add translations back in

* update translation postitions

* move language initialisation after loading
this means only en-gb for now

* update positions
2023-10-27 20:25:02 +11:00
Oliver
0acfaced83 React interface updates (#5798)
* Fix for <ActionDropdown> component

- Ensure component key is set properly

* Update <PageDetail> component

- Consolidate and simplify

* Update proxy settings for react development

* Fixes for StatusRenderer component

- Cannot use state hook inside function

* Add PurchaseOrderDetail page

* Tweak ApiImage component

* Add "ReceivedStock" table to PurchaseOrder detail page

* Add SalesOrderDetail page

* Add ReturnOrderDetail page

* Cleanup unused variables

* Remove import for unused icon
2023-10-27 14:39:17 +11:00
Oliver
22e9b14743 Update sponsors.yml (#5794)
Fix target branch
2023-10-27 14:06:57 +11:00
Oliver
6cba00c1d2 New Crowdin updates (#5769)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-10-27 09:10:35 +11:00
Matthias Mair
20d1fa847e Various style fixes (#5797)
* removed unused imports

* use f-string where applicable

* string format changes
2023-10-27 09:06:48 +11:00
Oliver
82b376de5b Improve WSL detection for vite.config.ts (#5796) 2023-10-26 22:10:02 +11:00
Matthias Mair
53c16510a1 [PUI] Render status labels (#5759)
* added deepsource coverage settings

* Ignore missing coverage

* trigger full CI run

* typo

* Added general status lookup endpoint

* Bumped API version

* cleaned up branch

* Fixed PlaygroundArea accordion behaviour

* Added dummy area for status labels

* Added StatusRenderer skeleton

* Fetch data from server

* Made server api state session persistant

* cleanup

* Added StatusLabel lookups based on ModelType

* Made use of translated status fields

* Added new ModelTypes

* Used new StatusRenderer

* Simplified renderer

* style fixes

* revert style change

* Squashed commit of the following:

commit 5e8ea099068475fd257d8c172348dc6f3edf9bcf
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 09:22:38 2023 +0200

    Update ui_plattform.spec.ts

commit 49da3312beff7fd6837ea741e621df221c445d19
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 07:56:25 2023 +0200

    more logging

commit 5337be4c3990051b805a6fce2e79ca4030b4afe5
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 07:56:11 2023 +0200

    added filter method for undefined settings that overwrite defaults

commit 5df8a0b3e77cd5dcf04c39ad7638ac845df75e4c
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 03:05:06 2023 +0200

    you do not need to string a string

commit 0650d3b3a0132889c2a76de38db38224e974d205
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 03:04:34 2023 +0200

    fix things that were borken for no good reason

commit a40dbfd1364cf01465037350184f59d2a2a8afab
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 02:39:34 2023 +0200

    reduce unneeded blocking timeouts

commit bf9046a5361ae919e70662e717d6156434b6fe43
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 02:34:10 2023 +0200

    catch server fetching errors

commit aa01e67e8c8e789fdf755ac4481e730fe5ea4183
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 02:33:29 2023 +0200

    move init as things are now plugged together different

commit 290c33bd3125d50779497d6fc5981d5813b58f5d
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 01:49:32 2023 +0200

    do not log a failed automatic login try - why would you?
2023-10-26 21:49:38 +11:00
Oliver
2ff2c0801a Check minimum python version before launching (#5795)
* Check minimum python version before launching

* Add link to docs
2023-10-26 21:45:23 +11:00
Oliver
b5bd45d891 Update sponsors.yml (#5793)
Set organisation flag (required)
2023-10-26 18:17:26 +11:00
Oliver
a1cb954cb0 Readme sponsors (#5789)
* Add sponsors action

- Automatically show list of project sponsors
- Ref: https://github.com/JamesIves/github-sponsors-readme-action

* Update README.md

* Pin workflow action versions
2023-10-26 17:35:18 +11:00
Matthias Mair
02d55b9c87 Squashed commit of the following: (#5785)
commit 5e8ea099068475fd257d8c172348dc6f3edf9bcf
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 09:22:38 2023 +0200

    Update ui_plattform.spec.ts

commit 49da3312beff7fd6837ea741e621df221c445d19
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 07:56:25 2023 +0200

    more logging

commit 5337be4c3990051b805a6fce2e79ca4030b4afe5
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 07:56:11 2023 +0200

    added filter method for undefined settings that overwrite defaults

commit 5df8a0b3e77cd5dcf04c39ad7638ac845df75e4c
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 03:05:06 2023 +0200

    you do not need to string a string

commit 0650d3b3a0132889c2a76de38db38224e974d205
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 03:04:34 2023 +0200

    fix things that were borken for no good reason

commit a40dbfd1364cf01465037350184f59d2a2a8afab
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 02:39:34 2023 +0200

    reduce unneeded blocking timeouts

commit bf9046a5361ae919e70662e717d6156434b6fe43
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 02:34:10 2023 +0200

    catch server fetching errors

commit aa01e67e8c8e789fdf755ac4481e730fe5ea4183
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 02:33:29 2023 +0200

    move init as things are now plugged together different

commit 290c33bd3125d50779497d6fc5981d5813b58f5d
Author: Matthias Mair <code@mjmair.com>
Date:   Tue Oct 24 01:49:32 2023 +0200

    do not log a failed automatic login try - why would you?
2023-10-26 07:55:52 +11:00
Oliver
e13b57669c Barcode app docs (#5788)
* Update settings page

* Update barcode action documentation

* Update app privacy information
2023-10-25 23:46:26 +11:00
Oliver
f280e0aeaf Update sales_order.js (#5784)
Typo fix
2023-10-25 12:24:30 +11:00
Oliver
dfc4fa7584 Fixes for table rendering (#5782)
- Handle case where related object does not exist in the dataset
2023-10-25 11:45:00 +11:00
Matthias Mair
8308f36923 bump action versions (#5776) 2023-10-25 09:18:53 +11:00
Oliver
679b49b4f7 Frontend server settings (#5765)
* Update ApiToken model

- Add metadata
- Remove unique_together requirement
- Add last_seen field

* Update admin page for token

* Store metadata against token on creation

* Track last-seen date

* Allow match against existing valid token

- If token is expired or revoked, create a new one
- Prevents duplication of tokens

* Update unit tests

* Fix default server

* Improve functionality for extracting frontend settings

* Update default server list

* Use f-strings

* Revert logger name

* Remove mark_safe warning
2023-10-23 22:33:16 +11:00
Oliver
39c499622d Part responsible owner (#5774)
* Add "responsible_owner" field to part model

- Will replace "responsible" field

* Data migration

- Adds 'responsible_owner' value for parts which have 'responsible' set
- Selects correct content type
- Performs reverse migratoin

* Update part serializer

- Point to the new field
- Rename to preserve compatibility
- OPTIONS metadata will take care of the rest

* Remove old 'responsible' field

* Bump API version

* Fix typo

* Fix serializer field
2023-10-23 21:35:51 +11:00
Oliver
2dfe2d97bc plugin hash - reduce error outputs (#5772)
* plugin hash - reduce error outputs

- If database is migrating, or not ready, don't log exception
- Ref: https://github.com/inventree/inventree-app/actions/runs/6604140572/job/17937959893

* Improve imports

* Further import improvements

* Typo fix
2023-10-23 10:01:28 +11:00
Oliver
d03927dea4 Fix imports in inventree_extras.py (#5770)
- Can cause a circular import
- Ref: https://github.com/inventree/inventree-app/actions/runs/6603924590/job/17937528806
2023-10-23 00:31:02 +11:00
Oliver
4d79c9f985 Add admin filters for ApiToken (#5768) 2023-10-21 22:48:41 +11:00
Oliver
b335728e29 Fix admin site - Custom admin URL (#5766)
* Update config template file

* Add entry to docs

* Add INVENTREE_ADMIN_ENABLED to settings.py

* Add helper functions for improving admin links

* Refactor existing admin links

* remove debug statements

* Fix custom admin URL

* Expand documentation

* Fix URL

* Improve wording in config_template.yaml

* Extend admin_url tag

- Allow lookup without pk
- Handle case where pk not found
2023-10-21 22:12:14 +11:00
Oliver
388b722de1 Handle case where 'title' does not exist (#5767) 2023-10-21 21:35:51 +11:00
Oliver
f0f4a20f4e More token tweaks (#5764)
* Update ApiToken model

- Add metadata
- Remove unique_together requirement
- Add last_seen field

* Update admin page for token

* Store metadata against token on creation

* Track last-seen date

* Allow match against existing valid token

- If token is expired or revoked, create a new one
- Prevents duplication of tokens

* Update unit tests
2023-10-21 15:10:11 +11:00
Oliver
3b6c941f65 Token tweaks (#5761)
* Ensure token name is trimmed

* Improve sanitizing of token name
2023-10-20 20:39:53 +11:00
Matthias Mair
47964d24c0 Fixed PlaygroundArea accordion behaviour (#5760) 2023-10-20 17:16:54 +11:00
Oliver
23ea746813 Api token updates (#5664)
* Create new APIToken model

- Has custom 'name' field
- Has custom expiry date

* Add data migration to port across any existing user tokens

* Adds 'revoked' field - tokens can be manually revoked

* Update API token - allow multiple tokens per user

* Custom token auth handler

- Correctly handles revoked tokens
- Correctly handles expired tokens

* Update AuthRequiredMiddleware

- Check for token active status

* Token API endpoint improvements

- Can return tokens with custom names
- Return more information on the token too

* Consolidate migrations

* When requesting a token, overwrite inactive token for authenticated user

- An authenticated user must receive a token
- Unauthenticated users cannot do this

* Fix

* Use token name for frontend

* Force token expiry, and generate default expiry date

* Force generation of a new token when requested

* Reduce data exposed on token API endpoint

* Display redacted token in admin site

* Log when new token is created for user

* Add default value for token

- Allows raw token to be viewed in the admin interface when created
- After created, no longer visible
- Also provides ability to generate token with static prefix

* Fixes for admin interface

- Prevent user and expiry from being edited after creation

* Implement unit tests for token functionality

* Fix content exclude for import/export

* Fix typo

* Further tweaks

- Prevent editing of "name" field after creation
- Add isoformat date suffix to token

* Longer token requires longer database field!

* Fix other API tokens

* Remove 'delete' method from token API endpoint

* Bump API version
2023-10-20 14:06:06 +11:00
Oliver
25138300ff New Crowdin updates (#5758)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-10-20 12:48:42 +11:00
Oliver
e57b69be48 React UI improvements (#5756)
* Add CompanyDetail page

* Add "edit company" button (and associated form)

* Implement dropdown actions for company page

* Update actions for PartDetail

* Adds overlay while modal is loading

- Lets the user know that something is at least happening in the background

* Update panels

* Implement separate pages for different company types

- Mostly for better breadcrumbs

* Placeholder actions for build detail

* Add stock table to company page

* typescript linting

* Fix unused variables

* remove dodgy improt
2023-10-20 00:23:58 +11:00
Bobbe
ae063d2722 Add basic support for scanning vendor barcodes (#5509)
* Add support for scanning digikey and mouser barcodes

* added small fixes if no part is found

* made small style cleanups

* Separate out ecia 2d barcode parser, Add quantity, PO number to response

* Use model instead of name for mouser supplier, add auto detection magic

* Add lcsc barcode support

* Move barcode plugins to new suppliers subdirectory

* Add get_supplier_part helper, Refactor plugins in preparation for #3791

* Add __init__.py to suppliers directory

* Improve formatting

* Add barcode integration tests

* Add api-barcode-po-receive endpoint

* Refactor supplier_barcode.py helpers into BarcodeMixin

* Implement the api-barcode-po-receive endpoint for all suppliers

* Always include lineitem in api response

* Fix location in response, only include quantity and location if set

* Check if barcode has already been assigned, Fix tests

* FIx quantity and location not being in lineitem reponse

* Use part.get_default_location() instead of part.default_location

* Fix fomatting again

* Fix type annotations for python 3.8

* Add get_supplier_part helper, check for barcode_data being a str

* Fix naming clash

* Clarify return type for scan_receive_item

* Improve model access using first() in two places

* Refactor a bunch of checks

* Improve selection of line item, if multiple line items match the SKU

* Add new api version for this PR

* Fix error if no line item exists

* Add debug print to investigate why tests are failing

* Remove the test print again

* Fix pre formatted log messages

* Test removing all plugins

* Test only with digikey plugin

* Test with all plugins, but without mouser "model" setting

* Test again without tests

* Test with simple tests

* Test with simple receive test

* Test with even more receive tests

* Test second receive test

* Test third receive test

* Test 4th receive test with debug prints

* Try deleting the stock item and stock locations

* Disable the test again

* Add SupplierBarcodeMixin to minimize shared code between plugins

* Add TME supplier barcode plugin

* Remove the TME tests again

* If this works the tests are broken, if this doesn't work the tests are broken too

* Add TME tests again

* Add back all tests again

* Fix TME purchase order number

* Fix TME qrcode regex

* Add documentation for this feature

* Fix TME qrcode regex

* Use Decimal instead of int for quantity

* Refactor get_supplier_parts, Add get_supplier method

* Improve docstrings

* Fix None type access

* FIx TME barcode detection, Improve supplier barcode handling

* Try to retrigger pipeline

* Refactor get_supplier_parts to not use lists

* Add DEFAULT_SUPPLIER_NAME to mouser plugin

* Add SUPPLIER_ID setting to other suppliers

* Fix supplier plugins not inheriting from settings mixin

---------

Co-authored-by: Matthias Mair <code@mjmair.com>
2023-10-19 23:28:21 +11:00
Oliver
2be5ec26f8 [React] Login improvements (#5752)
* Logout user if user query fails

* login / logout improvements

- Add timeout to request (otherwise hangs indefinitely)

* Improve "checking login" page

* Update login form

- Disable button if logging in
- Show loader

* Fixed unused vars
2023-10-19 13:42:12 +11:00
Oliver
566fef5309 [React] Purchasing and Sales (#5743)
* Skeleton layout for purchasing and sales

- Empty pages
- Nav links
- Tab panels

* Add <CompanyTable> component

- Customers
- Suppliers
- Manufacturers

* Table fxies

* Initial implementation of PurchaseOrderTable

* More tables

- Basic SalesOrderTable
- Basic ReturnOrderTable

* Improvements to InvenTreeTable component

- Handle incorrect return type from server
- Use state for recordCount

* Formatting fixes
2023-10-19 10:11:04 +11:00
Matthias Mair
543d50149b Added python test paths to deepsource (#5738)
* Added python test paths to deepsource

* Added test files

* added "tests" too
2023-10-19 07:43:50 +11:00
Oliver
3349013646 [React] Improvements to API forms (#5742)
* Remove read_only field from API OPTIONS

- 'read_only' is a reserved field for some Mantine components

* Fixing further errors for related field model

* Handle bad values for numericalValue

* Fix for default values for API forms

* Fix for choice field

- Do not set form default value when constructing element

* Tweak for PartDetail page
2023-10-18 14:45:03 +11:00
Oliver
cb33705e44 Adds 'A3' as report size option (#5733) 2023-10-18 14:27:07 +11:00
Matthias Mair
fda909ac59 Fix news dashboard (#5741)
* Fixes wrong news url

* Typed dashboard items

* Removed double trailing slashes
2023-10-18 09:07:13 +11:00
Matthias Mair
eb79bd1743 refactor: refactor unnecessary else / elif when if block has a return statement (#5735)
The use of `else` or `elif` becomes redundant and can be dropped if the last statement under the leading `if` / `elif` block is a `return` statement.
In the case of an `elif` after `return`, it can be written as a separate `if` block.
For `else` blocks after `return`, the statements can be shifted out of `else`. Please refer to the examples below for reference.

Refactoring the code this way can improve code-readability and make it easier to maintain.

Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
2023-10-18 09:06:54 +11:00
Matthias Mair
3f93151cca refactor: replace multiple == checks with in (#5737)
To check if a variable is equal to one of many values, combine the values into a tuple and check if the variable is contained `in` it instead of checking for equality against each of the values.
This is faster, less verbose, and more readable.

Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
2023-10-18 07:30:16 +11:00
Matthias Mair
faac6b6bf5 refactor: remove blank lines after docstring (#5736)
There shouldn't be any blank lines after the function docstring.
Remove the blank lines to fix this issue.

Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
2023-10-18 07:28:57 +11:00
Matthias Mair
158a209a0f Version bump (#5740)
* Updated all dependencies

* fixed some spelling errors

* More spelling corrections

* Fixes for changes in react-query
2023-10-18 07:27:28 +11:00
Bobbe
9f2c55d2d6 Allow stock items to be reallocated for build items (#5693)
* Allow stock items to be reallocated for build items

* Refactor create check

* Fix quantity calculation, Add tests
2023-10-18 00:48:30 +11:00
Oliver
8c10b98fe8 [React] Update part parameters table (#5731)
* Implement simple "PartVariantTable" component

- Not yet nested
- More work needed for table nesting

* Fix issue rendering same image multiple times

- Use useId hook to generate random key

* Update PartParameter list API endpoint

- Allow part_detail extra field
- Add FilterSet class
- Allow filter to include variants

* Update PartParameterTable

- Display part column
- Allow returned parts to include templates of base part
- Hide actions for templated parameters

* Fix some code smells
2023-10-18 00:41:05 +11:00
Oliver
997b2ad569 Remove --no-frontend option (#5568)
- Until the new frontend is fully ready to be shipped, adding this option only serves to confuse users (and break the install process potentially)
- Remove this option entirely from the invoke script
- Update documentation accordingly
2023-10-18 00:21:26 +11:00
Oliver
4bbf168393 Use full width (#5728)
- Wasting a lot of space on larger screen
2023-10-17 22:53:59 +11:00
Oliver
1b7f943946 Prefetch RuleSet objects for group (#5725)
* Prefetch RuleSet objects for group

- Prevent multiple unnecessary db queries

* Tweak

* Default to previous behavior

* Revert closer to original
2023-10-17 21:33:23 +11:00
Oliver
ccaece3634 Remove StockItemManager class (#5727)
- Not used anywhere
- Reference: https://github.com/inventree/InvenTree/issues/5586
2023-10-17 21:21:13 +11:00
Oliver
550d549325 Refactor plugin loading (#5726)
- Pre-fetch all PluginConfig objects
- Only update plugin config active status when changed
- Reference: https://github.com/inventree/InvenTree/issues/5586
2023-10-17 21:07:16 +11:00
Oliver
e3fef5b226 Improve checks for scheduled tasks at startup (#5724)
- Only update or create tasks if necessary
- Perform bulk_create and bulk_update operations
- Reduce to N(1) - independent of how many tasks we add in the future

Ref: https://github.com/inventree/InvenTree/issues/5586
2023-10-17 16:05:43 +11:00
Oliver
7ff5654448 Cache default currency (#5723)
- Prevents multiple (failing) database hits during server startup
- Prevents multiple (failing) database hits during migration checks
2023-10-17 15:50:05 +11:00
Oliver
8f75758c45 Fix money field kwargs (#5722)
- Reduces number of calls to currency_code_defaults()
- Thus reducing number of database hits

(cherry picked from commit 15ff99a24d2a4dcbe38cf4f2c7b886c8820952e8)
2023-10-17 15:22:19 +11:00
Oliver
a566ac67a7 BOM Table - Fix pricing when subassembly is open (#5721)
- Ignore pricing for sub-items
- Fixes https://github.com/inventree/InvenTree/issues/5286
2023-10-17 14:12:32 +11:00
Oliver
c74368f805 Fix typo (#5715)
* Fix typo

getSpashScren -> getSplashScreen

* Cleanup docs
2023-10-17 12:12:22 +11:00
Peter Kopias
40fb1329bb add /static/ relative comment and update splash image path in config_template.yaml (#5433)
* add /static/ relative comment and update splash image path in config_template.yaml

* Update config_template.yaml

Fix paths

---------

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2023-10-17 11:25:05 +11:00
Matthias Mair
40ff0e1060 Add deepsource for more security testing (#5714)
* Added deepsource for SAST testing and more
Closes https://github.com/inventree/org/issues/41

* Add deepsource badges to Readme
2023-10-17 11:23:19 +11:00
Oliver
8bc750bc06 [React] Settings interface (#5679)
* Add some dummy pages for settings

* Add some placeholder pages for settings

* Refactor 'useInstance' hook

- Allow use without a pk

* Make response status code available

* Cleanup user settings page

* Fill out (empty) panels for system settings

* Update URLs

* Add more user information to "me" API endpoint

* Implement global user context

- User information available globally
- Add placeholder page for plugin settings

* remove debug entry

* Add first-pass implementation of plugins table

* Add icon to plugin table

* plugin table improvements

* Add SettingsContext component

- Allows list of settings to be drilled down through props
- Also provides a way of reloading the settings list

* Update settings page

* Update settings detail API endpoints

- No longer case sensitive

* Display boolean values, and allow them to be edited

- Still some work to be done here
- Need to pass the base URL down through useContext

* Allow editing of non-boolean settings too

- Required some adjustment to existing forms interface

* Fix rendering of choice field within modal

* Display correct value for choice fields

* Expose settings units to API

* Updates

- Display units for setting (if available)
- Rename fieldType to field_type
- React does not like snakeCase props

* Improve form handling

* Add global server settings keys

* Add table for project codes

* Use cache to ensure that settings are not rebuilt too often

* Update api version notes

* Add username helper function to user state

* Remove SettingsContext

- Replace with global state manager
- Does not currently refresh properly

* Remove UserContext

* Update BaseContext

* Cleanup to match master

- Integrated many improvements from current master

* Get settings items working again

- Now integrates nicely with userSettingsState and globalSettingsState

* Improve generation of user name

* Handle user settings too

* url endpoint fix

* Add new table for custom unit management

* Update global settings
2023-10-17 10:28:46 +11:00
Michael
f409cd6894 Added example for barcode plugin (#5670)
* Added example for barcode plugin

* Add some words in stock location reports

* Correct error in link

* Correct error in link

* Typo in link

* Style in code example
2023-10-17 10:24:05 +11:00
Oliver
d98b1bb2c7 Fix <YesNoButton> (#5711)
- Add missing destructuring
2023-10-17 00:54:55 +11:00
Oliver
80a170d93c Improve useInstance hook (#5710)
- Allow use without primary key
- Allow default value to be customizedr
2023-10-16 23:54:28 +11:00
Oliver
9705521cd2 [React] Part parameters table (#5709)
* Track current panel selection in local storage

* Simplify part detail tabs

* Fix <PanelGroup> instances

* Handle missing model type for rendering

* Add some more API endpoints

* Add PartParameter table

* Add callback to create new part parameter

* Allow PartParameter list API endpoint to be searched

* More PanelGroup collapse tweaks

- Still requires more attention

* Fix logic for related part table

- Need to rebuild columns when part id changes

* Further fixes for related part table

* Re-implement change to PanelGroup

- useLocalStorage
- Change got clobbered in recent merge conflict

* Add part thumbnail to StockItemTable

* Add simple <YesNo> button

- Can be improved later

* Fix for PartTable

* Allow CORS requests to /static/ endpoint

* Updates to other existing tables

* Update URLs for dashboard items
2023-10-16 23:34:16 +11:00
Oliver
a9d18ba28f Improved implementation of panel collapse (#5708)
* Improved implementation of panel collapse

* Slight tweaks
2023-10-16 20:34:21 +11:00
Oliver
fa39ce8524 Fix API URLs for related fields (#5707) 2023-10-16 20:00:57 +11:00
Oliver
5b57685ae3 Adds toggle for text elements in panel tabs (#5706)
- Placeholder for now, UX could be better
- Just showing functionality for now
2023-10-16 19:44:28 +11:00
Oliver
4de51f4f4f [React] API Image Functionality (#5696)
* Improvements to API handling on react UI

- Do not force "/api/" prefix to the base URL of the server
- We will need to fetch media files from the server (at /media/)
- Extend API URL helper functions

* Update some more hard-coded URLs

* Fix search API endpoint

* Fix div for panel tab

* Fix debug msg

* Allow CORS request to /media/

* Add ApiImage component

- Used to fetch images from API which require auth
- Requires some tweaks to back-end CORS settings
- Otherwrise, image loading won't work on new API

* Update build order table

* Remove debug code

* Update part detail page
2023-10-16 19:44:09 +11:00
Oliver
65e9ba0633 Platform URL fixes (#5705)
* Fix API endpoints for attachment tables

* Fix API URLs for notifications

* Valid PK required for attachment table
2023-10-16 19:13:40 +11:00
Oliver
59efae0bfc Sentry: ignore NotAuthenticated errors (#5702)
- Causes a lot of unnecessary error reports
2023-10-16 14:37:13 +11:00
Oliver
01f30222eb Update docker production guide (#5701)
- Fixes https://github.com/inventree/InvenTree/issues/5655
2023-10-16 12:43:10 +11:00
Oliver
a547d6e064 New Crowdin updates (#5641)
* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin
2023-10-16 12:06:36 +11:00
Oliver
1fcb259c1a Improve sentry.io integration (#5700)
- Add "environment" string (required for release tracking)
- Add extra tags
2023-10-16 12:00:53 +11:00
Oliver
4ecd49e3eb Catch potential exception when performing unit conversion (#5699) 2023-10-16 12:00:44 +11:00
Oliver
598f0a5021 Improved BOM import process (#5698)
- Prevent issues due to query within @atomic block
- Speed up import using bulk_create
2023-10-16 10:53:31 +11:00
Oliver
814322e512 Api tweaks (#5690)
* Improvements to API handling on react UI

- Do not force "/api/" prefix to the base URL of the server
- We will need to fetch media files from the server (at /media/)
- Extend API URL helper functions

* Update some more hard-coded URLs

* Fix search API endpoint

* Fix div for panel tab

* Fix debug msg
2023-10-12 23:25:17 +11:00
Oliver
0c519c6b98 Add new "location_type" column to stock location table (#5688)
Ref: https://github.com/inventree/InvenTree/pull/5588
2023-10-12 01:06:19 +11:00
Oliver
0925fbbad2 [React] Settings state manager (#5687)
* Add interface definition for settings

* Fetch global and user settings as state object

* Cleanup dead code

* Fetch settings when performing token login
2023-10-12 00:36:53 +11:00
Oliver
bf7c1b43bd [React] Use typed paths (#5686)
* Use typed paths for all tables

* Refactor usage of useInstance hook

* Refactor URLs for existing forms

* More URL fixes

* Further URL fixes
2023-10-12 00:13:37 +11:00
Oliver
149e5c3696 User roles state (#5685)
* Update for useApiState

- Rename to useUserState
- Include role information

* Adds state method for checking user roles
2023-10-11 22:58:42 +11:00
Oliver
89faf8e59d Update settings API endpoints: (#5684)
* Update settings API endpoints:

- Use cache to prevent unnecessary hits to db
- Make settings detail endpoints case insensitive
- Update API version

* Remove is_admin and is_superuser

- Contaminated from other commit

* revert seralizers.py

* Revert breaking change to users/api.py
2023-10-11 22:58:11 +11:00
Lukas
e8e0b57cea Feature/location types (#5588)
* Added model changes for StockLocationTypes

* Implement icon for CUI

* Added location type to location table with filters

* Fix ruleset

* Added tests

* Bump api version to v136

* trigger: ci

* Bump api version variable too
2023-10-11 16:34:38 +11:00
Oliver
6f2dca729d Report orientation (#5678)
* Add helper functions for report generation

* Add new fields to Report model:

- page_size (default = A4)
- landscape (default = False)

* Add migration for InvenTree reports

* Enable landscape printing
2023-10-09 13:25:53 +11:00
Oliver
e9e505edd4 Import export fix (#5677)
* Update django-import-export

* Add custom handler to ensure data import is not too excessive
2023-10-09 11:48:17 +11:00
Matthias Mair
608ca75763 Type model information in SearchDrawer (#5597)
* moved search query to strongly typed model reference

* move title and link to reusable typed section

* typed ApiFormFieldType.model too

* renamed symbol

* switched to lookup
2023-10-08 20:20:32 +11:00
Kenneth Lorthioir
e76fa11e63 Open External Links in new window (#5674)
* Update clip_link.html

* Update item_base.html

* Add new_window to all external links
2023-10-07 15:41:33 +11:00
Joe Rogers
a779aa3a99 Add whitespace to fix markdown table rendering (#5675) 2023-10-07 15:41:10 +11:00
Oliver
7ab5ddcd7d Plugin auto migrate (#5668)
* Improved handling of race condition when saving setting value

* Improvements for managing pending migrations

- Inform user if there are outstanding migrations
- reload plugin registry (if necessary)

* Increase django-q polling time

According to this thread, should reduce multiple workers taking the same task:

https://github.com/Koed00/django-q/issues/183#issuecomment-239676084

* Revert default behavior

* Better logging

* Remove comment

* Update unit test

* Revert maintenance mode behaviour

* raise ValidationError in settings
2023-10-06 11:38:01 +11:00
Oliver
c7eb90347a Exchange rate plugin (#5667)
* Add plugin mixin class for supporting exchange rates

* Split some mixin classes out into their own files

- mixins.py is becoming quite bloated!

* Add some new settings for controlling currency updates

* Adds basic plugin implementation

* Refactor existing implementation

- Builtin plugin uses frankfurter.app API
- Better error / edge case handlign

* Add sample plugin for currency exchange

* Allow user to select which plugin to use for plugin updates

* Observe user-configured setting for how often exchange rates are updated

* Updates for some of the sample plugins

* Fix plugin slug

* Add doc page

* Document simple example

* Improve sample

* Add blank page for currency settings info

* More info in "config" page

* Update docs again

* Updated unit tests

* Fill out default settings values when InvenTree runs

* Add log messages

* Significant improvement in default settings speed

- Use bulk create
- Be efficient
- Dont' be inefficient

* More strict checks

* Refactor default values implementation

- Don't run at startup
- Run on list API
- Implement generic @classmethod
2023-10-05 21:19:28 +11:00
Matthias Mair
f5e8f27fcd Bump deps (#5666)
* bumped deps

* bumped frontend deps

* fixed reqs file for py 3.9
2023-10-05 09:48:53 +11:00
dependabot[bot]
25babf617c Bump postcss from 8.4.29 to 8.4.31 in /src/frontend (#5665)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.29 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.29...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-04 23:13:12 +02:00
Lukas
8ec04028d5 Native LDAP support (#5226)
* Added basic ldap support

* Added new settings to config template

* Added missing apk dependency to dockerfile

* update requirements.txt

* Remove requirements and added docs

* Update advanced.md

* Added more ldap options

* allow direct bind
* add ldap debug mode
* add ldap global_options
* add start_tls
* use get_boolean_setting

* Update advanced.md to use a warning box

* fix: style

* Update advanced.md

* Remove ldap install instructions for non docker installs
2023-10-05 07:52:36 +11:00
Oliver
a6dbe185c6 Allow reload of plugin registry when config changes (#5662)
* Allow reload of plugin registry when config changes

- Any global settings which control plugin behaviour trigger reload
- Plugin registry hash includes the value of these settings
- Plugin registry hash is now cached

* typo
2023-10-05 00:38:40 +11:00
Oliver
4b13f3b0de Fix location priority when receiving items (#5661)
* Fix location priority when receiving items

Fixes POReceiveBarcodeHandler

* Unit test adjustment
2023-10-05 00:31:50 +11:00
Lukas
9b4e1743c7 Feature/Tree picker (#5595)
* Show only the current modal over the backdrop, move others behind

* Added initial draft for tree picker

* Added filters to tree picker

* Added tree picker to more location fields

* Fixed bug with missing input group and filters side effect

* Added tree picker to part category inputs

* Added missing picker for part category parent input

* Fixed disabled items

* Fix js linting errors

* trigger: ci

* Bump api_version.py

* Update api_version.py
2023-10-05 00:11:49 +11:00
Oliver
3d8e3f1625 Bug fix for plugin reload mechanism (#5660)
- Save updated hash after reload
- Use actual *value* of active, not the function object!
2023-10-04 23:05:48 +11:00
Oliver
ffda700244 Improve unit conversion (#5658)
- In previous implementation, could not have a value "10k" for a unit of "m"
- Now, correct result is obtained (10k [m] = 10[km])
- Still passes all previous unit tests
- Simpler code, too
2023-10-04 17:28:20 +11:00
Joe Rogers
c18bc4e1d1 Fix: Allow allocation of variants to sales order lines (#5656)
* Add tests for allocating a variant to a sales order line

* Add check for variants to match template allocation

* Tweak fixtures for inventree-python, improve test checks

* Optimize check for item variants
2023-10-04 10:39:22 +11:00
Oliver
06eb948528 Plugin reload mechanism (#5649)
* Plugin reload mechanism

- Wrap reload_plugins with mutex lock
- Add methods for calculating plugin registry hash

* Perform plugin reload at critical entry points to the registry

- Background worker will correctly reload registry before performing tasks
- Ensures that the background worker plugin regsistry is up  to date
2023-10-04 09:00:11 +11:00
Oliver
78905a45c7 Plugin missing fix (#5653)
* Add 'is_installed' property to PluginConfig model

- Allow us to show users which plugins are "outdated"

* Cleanup plugin table

- Display clearly if a plugin is no longer installed

* Fixes for plugin table

* Revert change due to failing CI
2023-10-03 19:44:10 +11:00
Oliver
88314da7b3 Fix for plugin installation via web interface (#5646)
* Fix for plugin installation via web interface

- Previous implementation did not correctly catch error output
- Refactored into new file plugin.installer.py
- Provides better "output" when plugin does install correctly

* Remove unnecessary debug output

* Update unit test

* Remove placeholder code
2023-10-03 18:47:55 +11:00
Oliver
6521ce5216 Prevent plugin URL routing if ENABLE_PLUGINS_URL setting is disabled (#5648)
* Prevent plugin URL routing if ENABLE_PLUGINS_URL setting is disabled

* Fix for unit test

* Improve unit testing
2023-10-03 12:58:39 +11:00
Matthias Mair
30cf97d85b Exchange rate emergency fix (#5632)
* fixed LOG style errors

* Disabled exchange rate checks
See https://github.com/inventree/InvenTree/issues/5631

* Implement frakfurter.app as new exchange host
Closes #5631

* Revert "Disabled exchange rate checks"

This reverts commit 8ed7fbcba5.

* Revert "fixed LOG style errors"

This reverts commit 578da01a67.

* switched to requests

* Revert "Revert "fixed LOG style errors""

This reverts commit 78e5eb0e69.

* Revert "Revert "Revert "fixed LOG style errors"""

This reverts commit d21838959f.

* Revert "Revert "Revert "Revert "fixed LOG style errors""""

This reverts commit 3881923c0b.

* re-enable checks

* extended debug message

* add base cur

* reverted name change
2023-10-03 12:12:33 +11:00
Oliver
352fb4f6ff Allow plugin list to be filtered by "sample" or "builtin" status (#5647) 2023-10-03 10:39:24 +11:00
Oliver Lippert
6a48eaeec8 Don't ignore parts without stock in stocktake (#5627)
* don't ignore parts without stock in stocktake

#5621

* after including 0 stock parts, expect 14 parts to be in stocktake

---------

Co-authored-by: Oliver Lippert <oliver@lipperts-web.de>
2023-10-02 18:24:41 +11:00
Matthias Mair
e767516c80 Move broken plugin samples to disabled folders (#5642)
* move broken samples to disabled folders

* ensure there are no name clashes

* added option to collect plugins on load

* made tests aware of reloads

* reverted test name change

* added tests for broken samples

* fixed type signature
2023-10-02 10:39:17 +11:00
Oliver Lippert
46b0d77418 Let plugins decide if events should be processed or not (#5618)
* add wants_process_event to EventMixin

in register_event this function get called and the task is just offload if the plugin returns true

* add tests for wants_process_event

* fix comments for register_event after code changes

* add documentation for EventMixin.wants_process_event

* avoid pre-formatting log messages

---------

Co-authored-by: Oliver Lippert <oliver@lipperts-web.de>
2023-10-01 21:10:26 +11:00
Oliver Lippert
7fa0c6d280 Add worker task to VS Code (#5605)
* add worker task to vscode

* add documentation for devcontainer backgorund workers

* fix misphrased sentence ("want need" -> "need")

---------

Co-authored-by: Oliver Lippert <oliver@lipperts-web.de>
2023-10-01 21:06:34 +11:00
Oliver
3cd7ee9cd2 Improve formatting of log messages in settings.py (#5636) 2023-09-30 23:20:55 +10:00
Oliver
bc197a2906 New Crowdin updates (#5623)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-09-30 07:01:29 +10:00
Oliver
d4f7e1130e Fix docstring for @scheduled_task decorator (#5626) 2023-09-30 07:01:04 +10:00
Matthias Mair
240a2e91dd Fixed LOG style errors (#5628)
* fixed LOG style errors

* Disabled exchange rate checks
See https://github.com/inventree/InvenTree/issues/5631
2023-09-29 09:34:24 +10:00
Matthias Mair
71c416bafd Add flake8-logging linting (#5620)
* added flake8-logging to make logging more robust

* fixed LOG005 warnings

* fixed LOG008 warnings

* fixed LOG011 warnings

* fixed more LOG011 errors

* added ignores for intentional logger.error calls

* fixed even more LOG011 errors
2023-09-28 14:53:22 +10:00
Oliver
08e7190832 Exception handling (#5622)
* get_setting_object: Handle ProgrammingError

* Improved exception handling

- Address a few cases on application startup if the database is not ready (migrations not applied)
2023-09-27 13:42:20 +10:00
Oliver
2b0d81fd81 New Crowdin updates (#5573)
* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin
2023-09-25 21:52:49 +10:00
Oliver
dad240529d Adds optional config setting to enable database level logging (#5617) 2023-09-25 16:54:17 +10:00
Oliver
67b2d02a7f Allow Base URL to exist without TLD (#5615)
- Custom validator just for this setting
2023-09-25 16:18:12 +10:00
Oliver
87a8755e1b Fix bug in loading setting from cache (#5612)
* Fix bug in loading setting from cache

- Need to bring out "cache" key first, otherwise it gets included in the calculated cache key
- Reference: https://github.com/inventree/InvenTree/issues/5586

* Unit test fixes
2023-09-25 15:57:33 +10:00
Oliver
200bc5bd3d Use prefetch to improve query efficiency (#5613)
- Significantly improves performance of users.models.update_group_roles() function
- Ref: https://github.com/inventree/InvenTree/pull/5612
2023-09-25 13:24:39 +10:00
Oliver
b69342a3d2 Update minimum_stock field (#5609)
- Allow non-integer values
- Change column type
2023-09-25 07:26:26 +10:00
Oliver
7e6db65cac Interactive forms (#5608)
* Fix requery bug in related form field

* Fixes for RelatedModelField

- Better handling of null values

* StockItem form fix

- Clear 'supplier_part' field when 'part' field value changes

* Add adjustFilters callback function

- Allows query filters for a relatedfield to be changed on the fly
2023-09-24 23:36:34 +10:00
Oliver Lippert
2f0dbf9776 stop cascade update_pricing if prices didnt change (#5606)
Co-authored-by: Oliver Lippert <oliver@lipperts-web.de>
2023-09-24 23:02:09 +10:00
Oliver Lippert
c06047d026 fix controlling log level in devcontainer (#5604)
env now is INVENTREE_LOG_LEVEL

Co-authored-by: Oliver Lippert <oliver@lipperts-web.de>
2023-09-24 23:01:06 +10:00
Oliver
4e467c75d2 Update urls.py (#5603)
Remove double slash for favicon
2023-09-24 09:00:37 +10:00
Matthias Mair
00e7cd1e26 make idAccessor configurable (#5599) 2023-09-23 14:36:28 +10:00
Oliver
324d5929b5 Primary address fix (#5592)
* Improve management of primary address for a company

- Simplify approach (remove "confirm_primary" field)
- Remove @receiver hook
- Move all logic into Address.save() method

* Make address primary if it is the only one defined for a company

* Update frontend table

* Fix saving logic

* Actually fix it this time

* Fix for unit test

* Another test fix
2023-09-23 10:14:59 +10:00
Oliver
6cde460567 Attachment URL method (#5584)
* Attachment fully-qualified-url

Adds method to generate a fully-qualified URL for an attachment

* Construct absolute URL including host information
2023-09-21 13:55:14 +10:00
Oliver
0f8cddd23d Add validation check to "Convert to Variant" form (#5583)
* Add validation check to "Convert to Variant" form

- If a supplierpart is assigned, cannot convert stock item

* Fix failing unit test
2023-09-21 13:22:20 +10:00
Oliver
202dd6b4a1 Update context_variables.md (#5581)
Fix typo made when fixing a typo
2023-09-21 09:45:25 +10:00
Oliver
459d38cfc2 Update context_variables.md (#5580)
Fix typo "path_string" -> "pathstring"
2023-09-21 09:39:37 +10:00
Matthias Mair
8432297d3c [P UI] Make base url configurable (#5577)
* made url_base configurable for P UI

* fixed test for configurable base url
2023-09-21 09:39:05 +10:00
Matthias Mair
6c5b9e0108 [P UI] Router syntax switch (#5575)
* switched routes syntax

* switched to nested routes
2023-09-20 14:51:50 +10:00
Oliver
c6075a2064 Fix static url error for favicon.ico (#5574) 2023-09-20 14:51:04 +10:00
Oliver
47f341d2b5 Plugin loading fixes (#5572)
* Add config function to return external plugins dir

* Enable AppMixin support relative to external plugins directory

* Fix for urls.py

- URL patterns were causing custom app mixin plugins to fail reverse lookup in admin interface
- Brought admin URLs up one level

* simplify urls.py

* Fix plugin registry code which registers plugin URLs

- As we have updated InvenTree.urls.py we need to adjust this logic too

* Adds redirect for favicon.ico

* Handle empty plugins dir
2023-09-20 13:32:34 +10:00
Oliver
cf4df3402c New Crowdin updates (#5550)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-09-20 09:58:33 +10:00
Oliver
6f70f6d244 Improve breadcrumbs using new path API (#5570) 2023-09-19 21:25:59 +10:00
Oliver
c60efd9a1d Path detail API (#5569)
* Add "path_detail" to stock location serializer

- Requires ?path_detail=1 in URL query parameters
- Only avaialable on the StockLocation detail URL endpoint (not list, too expensive)

* Implement path_detail option for PartCategory detail API endpoint

* Add "path_detail" option to PartSerializer

* Add optional path_detail to StockItem serializer

* Cleanup

* Increment API version

* Add unit test for Part and PartCategory

* Remove debug statement
2023-09-19 17:44:06 +10:00
Oliver
314c93d55c Update readthedocs (#5565)
* Update readthedocs

* Tweak config file

* Cjange python version to 3.9

* Remove legacy option
2023-09-19 06:20:23 +10:00
Oliver
98eaa14eee Fix useInstance hook (#5567) 2023-09-18 23:24:05 +10:00
Oliver
981bfa344b [PUI] Stock location table (#5566)
* StockLocationTable component

* Build out stocklocation page

* Add extra columns

* Skeleton for stockitem page

* breadcrumbs

* Fix attachment table
2023-09-18 22:49:25 +10:00
Oliver
ad8df52b73 [PUI] Instance hook (#5564)
* Custom hook for fetching instance data from the server

* Refactor pages to use new hookr

* Allow useInstance hook to handle parameters
2023-09-18 21:07:01 +10:00
Firas Ben Mefteh
fe68598c1b Fix set password when no old password exists (#5562) 2023-09-18 20:51:17 +10:00
Oliver
a68c1d28c6 [PUI] Part category page (#5555)
* Replace PartIndex with CategoryDetail

- Can pass a category ID to show a single category
- Otherwise, show the top-level parts category

* Refactor <InvenTreeTable> component

- Simplify property passing
- Easier tableRefresh mechanism

* Refetch table data when base parameters change

* Correctly update pages when ID changes

* Notification panel cleanup

* Remove column from InvenTreeTableProps type

* more fancy

* Fix notification alert

* Implement useLocalStorage hook

* useLocalStorage hook for table filters too
2023-09-17 00:21:59 +10:00
Oliver
41cbe30db1 [PUI] Build detail page (#5554)
* Add skeleton for build order detail page

* Fill out some tabs

* Add PlaceholderPanel component

* Fix icon

* Add child build order table

* Add extra columns to build table

* Add header and breadcrumbs to build detail pgae

* Update part detail page

* Improve BuildIndex page

* Include part detail

* Add consumed stock table

* Add "completed outputs" table

* PanelGroup tweaks

* PartIndex tweaks
2023-09-16 15:11:50 +10:00
Oliver
cd0dfab17b Remove old debug messages (#5551) 2023-09-16 12:22:14 +10:00
Matthias Mair
ee64d12504 PUI: Scan Page (#5500)
* Added basic structure for scan

* Added function to scan page

* switched to enum and added source info

* Add QR code scanner

* added more actions and made qr more dynamic

* refactored Title and Doc combo

* cleand up structure and calls

* adjusted spacing

* cleaned up button section for QR codes

* switched to localStorage for Inputs

* fixed spacing

* added links to thumbnails

* added first renderers for objects

* removed reference col

* made open link action more selective

* added object renderers incl. APIs and matchers

* added handler for parts that do not match

* refactored object matcher

* fixed type
2023-09-16 08:20:36 +10:00
Oliver
2be2ea4f8f Docker CI test (#5544)
* Docker CI test

* Pin setuptools version

* Don't run docker build on pull-request
2023-09-15 22:44:54 +10:00
Oliver
17d227443d Copy notes (#5546)
* Add option to copy notes when duplicating a part

* Update front-end forms
2023-09-15 14:52:47 +10:00
Oliver
9deec91ec2 New Crowdin updates (#5535)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-09-15 10:41:07 +10:00
Oliver
c8021ec319 Update required parts for build orders (#5542)
- When a BomItem is created or edited, update any active build orders which use it
- Runs as a background task
- Fixes https://github.com/inventree/InvenTree/issues/5541
2023-09-15 10:04:54 +10:00
Oliver
56fdbc00c9 [PUI] Improve search panel (#5538)
* Cleanup links in search bar

* Add renderer for stockitem model

* Add model renderer for build order
2023-09-15 00:49:58 +10:00
Oliver
004dcd04d5 [PUI]: Basic notifications page (#5537)
* Add notification indicator to the navbar

* Fetch every 30 seconds

* Basic notifications drawer

* Simple page for rendering notification tables

* Implement notification table actions

* Move table component
2023-09-14 23:44:35 +10:00
Oliver
1ed3d21a00 Reset table pagination when search term changes (#5534) 2023-09-13 21:57:24 +10:00
Oliver
b3e8195e10 New Crowdin updates (#5520)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-09-12 16:35:37 +10:00
Oliver
666fe90c5c [PUI] Add simple "related parts" table (#5530)
* Add simple "related parts" table

- Still needs method to create a new related part
- Should allow user to click through to related parts
- Need to implement the "delete related part" functionality

* Fix image preview

* Add action to delete part relationship

* Implement function to add new related part

* Implement simple "click through" for the related parts table

- Will need to be improved later on

* fix
2023-09-12 16:34:39 +10:00
Oliver
816b60850d [PUI] Implement Notes editor (#5529)
* Add react-simplemde-editor

React wrapper for simplemde which we already use

* Barebones implementation of markdown editor field

* Implement notes editor

* Implement drag-and-drop image uplaod
2023-09-12 13:31:02 +10:00
Oliver
7e753523d1 [PUI] Attachment table (#5525)
* Basic AttachmentTable

* Add form for editing an attachment

* Fix columns for InvenTreeTable component

* Update part attachment table

* Add dropzone to attachments table

* Handle file upload with Dropzone

* Add header for panelgroup

* Improve rendering of attachment files

* Allow various attachment list API endpoints to be searched

* Determine available attachment actions based on user permissions

* Reload attachment table after upload

* Delete attachments via table

* ts fix

* Clip width of actions column

* More updates

- Add manual buttons for adding link or file
- Edit link or file

* Add tooltip for row actions

* Adds a custom hook for refreshing tables

- So much cleaner :)

* Change export type

* Disable row action column when checkbox selection is active

* Fix(?) for custom hook

* Badge tweak
2023-09-12 11:45:23 +10:00
Bobbe
f11a9e97d2 Fix for % (percent) unit (missing unary operator "%") (#5527)
* Fix `%` (percent) unit conversions

* Add tests for percent unit

* Fix formatting
2023-09-12 10:20:45 +10:00
Oliver
2ea3499c48 Fix related field (#5524)
- Bug fix for "related field" component
2023-09-11 11:36:08 +10:00
Oliver
a210e905dc [PUI] Part detail page (#5521)
* Very basic part detail page

- Simply displays the ID of the part (not any actual data)
- Navigate from the part table

* Implement generic PanelGroup component

- Used for displaying sets of panelized data
- Will be used a lot within the interface

* Reload part page after edit form

* Fix loading overlay for part page

* Fix search panel

* Add panels to part index page

* Fix icons

* Fix table row actions menu

* PanelGroup: allow active panel to be changed externally

* Fix SearchDrawer issue

- AbortController does not work as expected
- Might need to revisit this later

* Improve form loading indicator
2023-09-10 22:52:46 +10:00
Lukas
9a6c2d2953 Fix missing filters for get settings validator (#5480)
* Fix missing filters for get settings validator

* merge default model instance filters and kwargs

* Added tests for validators

* Give it a try without the kwargs passed to clean in save function

* Added string for identification for debug statement

* Added more debug comments

* Added more debug prints

* Fix test debug

* Modiefied workflow

* trigger ci

* Fix test and remove unused kwargs

* Added debug prints

* Only run one test in ci

* Added more debug code

* Remove all debug prints and reset workflow

* Reset overlooked file
2023-09-10 21:37:21 +10:00
Oliver
14a6330114 Refactor table columns (#5519)
- Improve show/hide
- Refactor "actions" colum
2023-09-09 08:34:27 +10:00
Oliver
528fa349b0 Transfer fields (#5518)
* Add extra API fields when performing stock adjustments

* Make optional fields actually optional

* Update API version

* Allow usage of optional fields when transferring stock items

* Update api_version.py
2023-09-09 00:34:30 +10:00
Oliver
baa9f3660b Modal api forms (#5355)
* Very basic form implementation

* Fetch field definition data via AP

* Add cancel and submit buttons

* Render basic field stack, and extract field data from API

* Extract specific field definition

* Handle text fields

* Add some more fields

* Implement boolean and number fields

* Add callback for value changes

* Use form state to update values

* Add skeleton for a 'related field'

* Framework for related field query manager

* Handle date type fields

* Make date input clearable

* Fix error messae

* Fix for optional callback function

* Use LoadingOverlay component

* Support url and email fields

* Add icon support

- Cannot hash react nodes!

* Create components for different form types

- Create
- Edit
- Delete

* Split ApiFormField into separate file

* Add support for pre-form and post-form content

* Don't render hidden fields

* Smaller spacing

* More demo data

* Add icon to clear text input value

* Account for "read only" property

* Framework for a submit data query

* Return 404 on API requests other than GET

- Other request methods need love too!

* Starting work on dynamically opening forms

* Check validity of OPTIONS response

* refactor

* Launch modal form with provided props

* Refactor tractor:

- Handle simple form submission
- Handle simple error messages

* Improve support for content pre and post form

* Allow custom content to be inserted between fields

* Pass form props down to individual fields

* Update playground page with API forms functionality

* Simplify form submission to handle different methods

* Handle passing of initial form data values

* Improve docstrings

* Code cleanup and add translations

* Add comment

* Ignore icon for checkbox input

* Add custom callback function for individual form fields

* Use Switch instead of Checkbox

* Add react-select

* Implement very simple related field select input

- No custom rendering yet
- Simple pk / name combination

* FIrst pass at retrieving data from API

* Updates:

- Implement "filters" for each form field
- Prevent duplicate searches from doing weird things

* Rearrange files

* Load initial values for related fields from the API

- Requires cleanup

* Display error message for related field

* Create some basic functions for construction field sets

* Display non-field-errors in form

* Improved error rendering

* Change field definition from list to Record type

- In line with current (javascript) implementation
- Cleaner / simpler to work with

* Correctly use default values on first form load

* Improve date input

* define a set of stockitem fields

* Implement "Choice" field using mantine.select

* Implement useForm hook for better performance

* Show permission denied error

* Improved callback "onChangeValue" functionality

- Define proper return type
- Access all form data

* Cleanup

* Implement components for rendering database model instance

- Not fully featured yet (still a lot of work to go)
- Porting code across from existing "model_renderers.js"

* Update packages

* Handle file input fields

* Improved loading overlay for form submission

* Utilize modal renderers in search results

* SearchDrawer cleanup

* Temporary fix for image pathing issue

* Cleanup table action buttons

- Now use a dropdown menu
- Implement "edit part" directly from the table
- This is only as an example for now

* Fix playground

* Generate random ID with useId hook

* Fix abortController to use ref

* Use AbortController for search panel

* Fix TableColumn type definition

* Improved generation of unique form ID values
2023-09-08 21:24:06 +10:00
Oliver
1e55fc8b6d Issued by filter (#5515)
* Construct dynamic user filter for issued-by

* Allow "build order" table to be filtered by "issued_by" field

* Bump API version

* Fix API version
2023-09-08 09:10:42 +10:00
Oliver
8a3477a406 New Crowdin updates (#5511)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-09-07 20:51:13 +10:00
Matthias Mair
683cf248ea Bump deps (#5487)
* bump deps

* bump again

* bump frontend as well

* fixed allauth version for now

* fixed requirements files

* fixed requirements files 2

* fixed changed lib path
2023-09-07 09:02:31 +10:00
Oliver
f917c91522 Optional error notification (#5506)
* Add user setting for receiving error notifications

* Ignore uses who don't want error notifications

* Add config to settings page
2023-09-05 15:23:14 +10:00
Oliver
2243f288ee Increase yarn timeout in dockerfile (#5505)
Closes https://github.com/inventree/InvenTree/issues/5494
2023-09-05 13:10:00 +10:00
Oliver
218618aa93 Paginate notifications on the server (#5503) 2023-09-05 13:00:50 +10:00
Oliver
71ad4a1c99 Login form fix (#5502)
* Handle login without supplier user

- Use custom login form
- Redirect back to login page
- No longer throws error

* Fix method return
2023-09-05 12:41:08 +10:00
saharshtapi
c741bfe39d Typo Fixed (#5501) 2023-09-04 23:44:45 +10:00
Oliver
e88a8e99a5 New Crowdin updates (#5476)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-08-30 10:25:40 +10:00
Matthias Mair
7d5ff1d5bd fixed allauth docs links (#5486) 2023-08-28 09:31:04 +10:00
Oliver
a5f6a05c1f Fix docker instructions (#5479) 2023-08-25 23:15:08 +10:00
Oliver
8da5d62c69 Url fix (#5472)
* Use urljoin function to construct absolute URL

* Add unit test
2023-08-24 16:02:22 +10:00
Matthias Mair
c8eb8b9a21 [FR/P_UI] Sentry.io integration for front-end (#5453)
Fixes #5313
2023-08-23 20:45:58 +10:00
Oliver
5851094620 Default expiry fix (#5466)
* Handle null values for expiry date

* Add unit test for new case

* Unit test fix

* More unit test fixes
2023-08-23 16:03:01 +10:00
Oliver
25a1380058 New Crowdin updates (#5456)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-08-23 11:53:20 +10:00
Oliver
481ae0fc76 Bug fix for daily stocktake check (#5455)
- Was not recording attempts against the right internal variable
- Update to use existing functionality
2023-08-16 16:20:20 +10:00
Matthias Mair
c796e79cf0 Use IS_DEV_OR_DEMO for playground as suggested in https://github.com/inventree/InvenTree/pull/5359#issuecomment-1654593807 (#5454)
Fixes #5452
2023-08-16 09:53:54 +10:00
Oliver
d25e9e2b2e New Crowdin updates (#5437)
* updated translation base

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Ignore frontend translation files

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-08-15 12:47:19 +10:00
Peter Kopias
58e06ba1cb Add comments and warnings to documentation so readers would follow the correct version (#5431)
* add warning to use stable docs for stable docker_prod.md

* add notification and documentation column to different versions in docker.md
2023-08-15 09:54:19 +10:00
Oliver
e7b5b145bf Fix html tag in template (#5445)
- Ensure <td> tag is closed correctly
2023-08-14 16:17:11 +10:00
Oliver
a2f614ad41 Disable "add rate" button in Admin interface (#5444)
- Does not work with custom backend
- Throws error if the button is pressed
- So, remove the button
2023-08-14 16:17:03 +10:00
Oliver
5731b88499 Raise error if no search term provided for search endpoint (#5441)
- Also adjust unit testing
2023-08-14 15:39:20 +10:00
Oliver
93e4dadb49 Catch IndexError when importing data (#5439)
* Catch IndexError when importing data

* Also handle TypeError
2023-08-14 15:21:41 +10:00
Oliver
a8118ed406 Fix for potential NoReverseMatch error (#5440)
- Check that the database model really does exist in the template code
2023-08-14 14:26:01 +10:00
Oliver
9a8c8961e4 Catch OperationalError when updating exchange rates (#5438)
- Can occur if the database is not yet ready
2023-08-14 14:16:11 +10:00
Peter Kopias
6707f18f73 Update migrate.md to handle exporting json via docker volume (#5428) 2023-08-11 13:04:09 +10:00
Oliver
e09c51a882 New Crowdin updates (#5425)
* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations messages.po from Crowdin

* Whitespace fixes for translation files
2023-08-11 10:32:14 +10:00
Oliver
dce565b4a3 Fix build output unallocate button (#5426) 2023-08-11 10:11:06 +10:00
Lukas
89795f632c [P_UI] Added django settings for p_ui (#5343)
* Added django settings for pui

* Fix: server version is not loaded on initial load

* Moved server version out of server selector icon

* Use polling only for WSL

* Added comment and extracted to constant instead of function

* Default show server selector to false if not in dev mode

* Refactored hostList settings

* Move json serialization into global scope

* Show server selector in netlify builds

* Use demo server for netlify

* Renamed netilfy mode to dev or demo mode

* Translate for netlify

* Dont use translation in main as the are not working there
2023-08-10 20:57:33 +10:00
Oliver
86ca0b27a4 Purchase history graph fix (#5421)
* Fix debug messages

* Fix bug in purchase history chart

- Use new pack_quantity_native attribute
2023-08-10 14:24:59 +10:00
Oliver
a1d2782137 Add optional "description" column to SalesOrderLineItem table (#5419)
- Provides more visibility for part information
2023-08-10 13:00:55 +10:00
Matthias Mair
ebbc27047b [P-UI] Home page (#5344)
* Add new start page

* [FR/P-UI] Home/Start page - widgets

* load widgets dynamic

* code cleanup

* remove lodash

* refactor and rename to WidgetLayout

* Add CSS serving

* removed unneeded complexity

* clean up UI; switch to menu for controls

* change signature

* added hotkey

* removed hover

* removed dummy widget

* Add translations

* fix test

* uses real data for getting started

* adapted style for GettingStartedCard

* added placeholder usage to GettingStartedCard
2023-08-09 22:32:53 +10:00
Oliver
62362455b8 Sales order variant stock (#5415)
* Annotate available variant stock to SalesOrderLine serializer

* Filter variant stock by:

- active = True
- salable = True

* Add 'salable' filter to StockList API

* Filter available stock in sales order table:

- Must be salable
- Must be active

* Update table display

* Bump API version
2023-08-09 21:45:12 +10:00
Lukas
1fe382e318 Fix plugin pickeling (#5412) 2023-08-09 21:18:21 +10:00
Ben Charlton
af14cef29c Add missing __init__.py to Inventree/web (#5390) 2023-08-08 15:58:11 +10:00
Oliver
e468227fbe Email fix (#5396)
* Construct absolute URL when sending email confirmation

* Implement required changes to auth URLs

* Fix unit test
2023-08-08 14:37:38 +10:00
Matthias Mair
131442b6c7 fixes small style error in panel.md (#5404) 2023-08-07 09:50:20 +10:00
Matthias Mair
6e13034bc7 Add playground (#5359) 2023-08-06 21:59:03 +10:00
Matthias Mair
2f224b3c19 Packages - force python 3.9 (#5206)
* specify python 3.9

* now support 12 too

* rmove 12

* change deps

* add dedicated wheel install

* add dev headers (for rappidfuzz)
2023-08-06 21:50:38 +10:00
Michael
e1abe5be40 Added docs on the panel plugin example (#5402)
* added docu and image for panel plugin example

* format
2023-08-06 21:49:29 +10:00
Matthias Mair
b6cb20ccfa increased settings value from 200 to 2000 (#5403)
Closes #5400
2023-08-06 21:47:14 +10:00
Matthias Mair
71026c338c Use error report fork (#5171)
* switch to django-error-report-2

* fix commit

* bump version

* bump error-report version
2023-08-06 21:41:57 +10:00
Matthias Mair
879d496e26 Bump deps (#5385)
* bump deps

* use isinstance where possible

* and another bump ;-)

* small fix for isinstance

* fixed wrong comparison

* fixed comparision

* use exact comparision
2023-08-06 21:39:15 +10:00
Oliver
54e0e47c6a Handle case where calculation may be performed on invalid values (#5394) 2023-08-03 14:17:09 +10:00
Oliver
73891f0b94 Hindi support (#5389)
* Add support for hindi language code

* Add initial translation file for hindi
2023-08-02 23:18:42 +10:00
Oliver
eeabc8d1f9 Pin mkdocs version to 1.4.3 (#5388)
- Updates to readthedocs policy mean that this needs to be pinned
- Moving to 1.5.0 breaks current documentation set
2023-08-02 22:52:01 +10:00
Oliver
c39ae80a13 Improve sorting of part column for BOM table (#5386) 2023-08-02 17:37:00 +10:00
Oliver
647c3ade20 Pint unit fix (#5381)
* Accept conversion of fractional values

- e.g. "1/10" is a valid input value
- pint dimensions returns strange results sometimes

* Add option (default) to return value without units

- Handles conversion for "stringish" input values

* Update unit tests

* Fix return from convert_physical_value method

* Update unit tests

* Improved checking for conversion code

* Call to_base_units first

* Conversion depends on whether units are supplied or not

* Updates to unit testing

* Handle conversion of units for supplier parts

- Includes some refactoring
2023-08-02 14:46:28 +10:00
Oliver
7394ddae33 Build output table sort (#5380)
* Implement sorting for "build output" column in build output table

* Implement sorting for the "required tests" column

* use isNan
2023-08-01 12:52:36 +10:00
Oliver
668dab4175 Fix auto-allocation of build outputs (#5378)
- Creation of BuildItem objects was using old model references
2023-08-01 11:23:01 +10:00
Hendrik v. Raven
ee5416719f Fix SSO check comparing id against name and extend log output (#5340)
* add error log on SSO check failure

* sso_check_provider: fix by comparing against id

the name is the pretty printed version which not necessarily is the same
as the provider id it is compared against. This fails e.g. for the
microsoft allauth extension where the id is microsoft, but the name is
"Microsoft Graph".

Closes: #5330
2023-08-01 10:38:29 +10:00
Oliver
90383ccb53 Fix for migration - updating from old version (#5372) 2023-07-31 12:40:04 +10:00
Oliver
36d669d70d Fix for label printing plugins (#5371)
- Don't pass pdf_file data to background process
- pre-rendered template data cannot be pickled
2023-07-31 10:07:12 +10:00
Oliver
b89a120f9e Return 404 on API requests other than GET (#5365)
- Other request methods need love too!

(cherry picked from commit 59ffdcaa19)
2023-07-28 21:48:37 +10:00
Oliver
87da286f2f Handle purchase price export for .xls files (#5362) 2023-07-28 15:28:50 +10:00
Oliver
5f3d3b28b3 Handle errors when printing reports (#5360)
- Re-throw as a ValidationError
- Results in a 400 error, not a 500
2023-07-28 13:47:46 +10:00
Oliver
7e7d4d01a2 Global search input drawer (#5346)
* First pass at global search input drawer

* Perform debounced query

* Build custom search query

* Render basic search results

* Flesh out more search types

* Add TODO

* Remove "bold" property

* Updates

* Improve rendering of search results

* Center loader

* Implement better "refresh" button

* Add alert on error

* Add "no results" alert

* Fix regex and whole word search
2023-07-27 13:05:39 +10:00
Matthias Mair
39464f645a Add netlify for fast previews (#5353)
* Add netlify to readme

* add redirect for testing

* fix command

* add SPA redirect

* run desktop lighthouse tests

* add base dir

* remove base dir - use app redirect

* remove unneeded redirect

* remove status

* remove status

* static redirects

* remove toml redirect

* use toml again

* make redirect conditional

* add debug msg

* remove check section
2023-07-27 11:17:44 +10:00
Oliver
339eae53a1 [WIP] Mantine datatables (#5218)
* Create dependency-review.yml

* Create scan.yml

* Create sonar-project.properties

* add option to use sections and refactro

* translate error messages

* remove unneeded vars

* move function code

* move data inside

* add global section

* add plugin section

* use translated section titles

* add translation strings

* rename scan action

* add user settings

* use ordered data

* fix settings url

* use debounced value for strings (not choices!)

* rename contex to context

* move i18n provider up

* move theme options into seperate context/ component

* renmae statrtup vars

* move translations out

* reactivate sentry

* move i18n provider to seperate context

* move langauge state completly out of App

* use theme out

* move theme context

* move LanguageContext

* move function into state

* make sentry optional for now

* add key to accordion

* init langauge context on top

* remove unneeded css files

* move errorpage to tsx

* add translation for error page

* Add error to title

* add typecast for error

* move type definition out

* remove todo -> type was already added

* upgrade deps

* add bootstrap

* remove @mantine/core

* readd core

* switch to bootstrap

* simplify import

* Add SPA views for react #2789

* split up frontend urls

* Add settings for frontend url loading

* add new UI scaffold

* remove tracking insert

* add platform app

* ensure static indexes work too

* add lingui

* add lingui config

* add mgmt tasks

* add base locales

* settings for frontend dev

* fix typo

* update deps

* add pre-commit

* add eslint

* add testing scaffold

* fix paths

* remove error - tests trip correctly

* merge workflow

* cleanup samples

* use name inline with other tests

* Add real worl frontend tests

* setup env

* tun migrations first

* optimize setup time

* setup demo dataset

* optimize run setup

* add test for class ui

* rename

* fix typo

* and another typo

* do install

* run migrations first

* fix name

* cleanup

* use other credentials

* use other credentials

* fix qc

* move envs to qc

* remove create_site

* reduce testing env

* fix test

* fix test call

* allaccess user

* add ui plattform check

* add better check

* remove unneeded env

* enable debug

* reduce wait time

* also build frontend on static

* add sekeleton

* fix various issues

* add locales

* clean output before building

* cleanup dir

* remove bootstrap

* clean up deps

* fix settings panel

* remove assets

* move logo

* split out router

* split up chunks

* fix zustand import syntax

* bundl

* update pre-render

* use vendor splitting

* maximes space usage

* enlarge breakpoints

* remove wired color changes

* cleanup tabs

* fix error

* update auth functions

* default to mail login

* add placeholder marking

* Add text to placeholder

* readd codespell

* add another test

* add sort plugin

* add sort plugin

* sort imports

* fix order

* Add mega menu

* run pre-commit fixes

* add node min version

* Docker container (#129)

* Fix allocation check for completing build order (#5199)

- Allocation check only applies to untracked line items

* docker dev

Install required node packages to docker development image

* add import order settings

* cleanout built ui

* Add "parttable" component

* Add task to serve front-end code dev

* remove default arg from build

* remove eslint

* optimize svg

* Adds generic function for rendering a table with server-side data

* Implement pagination and sorting

* Add more example columns

* Enable selection of table data rows

* add build step for plattform UI

* fix install command

* optional parameters

* Add simple stock table

* Add optional parameter for default sort

* Change "no records" text based on query result

* Translate

* Start writing some helper functions

* Add thumbnail component

* Fill out more columns for stock table

* Add simple skeleton for table search input

* Adjust default table properties

* Change loader variant

* Drop-down for selecting table columns

* Add search text callback

* use alpine commands

* do not use cache when creating image

* More updates for inventree table

- Fix search text entry
- Add "refresh" button
- Adjust variable names

* Search input improvements

- Add button to clear search input

* Enable mantine notification system

* Add "not yet implemented" notification message

* Add download action button

* Adds ButtonMenu component

- Button which expands to show other actions
- Add hooks for adding action menus to tables

* Add basic build order list table

* Add custom filters button for table

* Allow columns to be toggled

* Column visibility saved across table loads

* Adds display for table filters

- Define interface for table filter definition
- Add component for displaying filters
- Cleanup for part table

* Cleanup

* Define type for controlling column data

* Allow custom ordering term for table column

- Replaces "sortName" concept from bootstrap-table

* Improve build order table

- Fancy progress bars

* Reimplement invoke task to serve frontend files via yarn

* Update package files with mantine

* Implement callback when record selection is changed

* Adds generic "actionbutton" component

* Remove duplicate form components

* Remove tracked files in web/static

* Remove a bunch of files

- tracked in from the wrong original branch

* More page fixes

* Revert changes to reqiurements-dev.txt

* Spelling fix

* Component updates

* Cleanup components

* Cleanup

* Use spread operator

* Add some new dummy pages for testing

* Cleanup / simplify stockitem table

* Cleanup for part table

* Cleanup build order table

* Cleanup column toggle function

* Remove hard-coded URL

* Format updates

* Update deps

* npm required for inventree-python checks

* Fix search input

- Better debouncing
- Cleaner code

* Update package files

* vite polling fixes

* Implementation for download button

- Dropdown menu with file format options

* Implement callback for download of table data

* Better state management for hidden columns

* Implement state framework for active custom filters

* Silence some errors

* Revert change to vite config

* Implement collapsible filter list group

- Save active filters to local storage
- Add some example filters to the part table
- Add FilterBadge component

* Fix page names

* Simplify search input

- useDebouncedValue

* linting

* Refactor

* Remove debug msg

* Simplify search state

* Refactor function for constructing API query

* Add tooltip

* Update icons

* Add modal for selecting filter options

* Add more table filters for part table

* render custom item for filter select

* Complete implementation for selectable filters

- Allow choices to be specified as attribute
- Allow choices to be specified as function
- Handle state management for filter choice form

* Tweak badge

* Cleanup top-level yarn and npm files

* Less roundy

---------

Co-authored-by: Matthias Mair <code@mjmair.com>
2023-07-27 10:10:07 +10:00
Oliver
941451203a Exclude some common fields from django-import-export (#5349)
- Add "get_fields()" method to InvenTreeResource
- Override default behaviour and exclude some common fields
- Will flow down to any inheriting classes
2023-07-26 16:54:02 +10:00
Oliver
6660508326 Allow duplicate BOM items when duplicating a part (#5347) 2023-07-26 16:47:59 +10:00
Matthias Mair
c614b78faf Add old dashboard page for now (#5325) 2023-07-26 10:10:18 +10:00
Lukas
073a275d89 Skip ready functions if not in main thread or plugins are not loaded yet (#5005)
* Skip ready functions if not in main thread or plugins are not loaded yet

* Debug integration tests

* Update ready.py

* Update ready.py

* Fix isInMainThread and isPluginRegistryLoaded ready functions

* Preload gunicorn app to only invoke the appconfig ready functions once

* debug: test prints for statistics

* Remove debug print

* Test without

* Revert "Test without"

This reverts commit 1bc1872893.

* Second test

* Add checks back to part, label, user model

* Add checks back to inventree, plugin apps

* log server output for debugging

* hopefully I can get the log this time+

* Next test

* Test with --noreload

* Next test

* trigger: ci, because session expired

* block the second ready execution instead of the first

* fix: load order

* Fix test and revert gh actions workflow change

* Added all_apps method to reload machanism

* Changed detect reload mechanism

* Also trigger ready on reload

* Add skipping second reload back for testing mode

* Added doc string back

* Update InvenTree/plugin/base/integration/AppMixin.py
2023-07-26 08:33:13 +10:00
Seth Price
60f344a360 Remove container-name:s from Compose file (#5339)
* Remove `container-name:`s from Compose file

* Remove `container-name:`s from dev Compose file

I forgot this one existed, oops.
2023-07-25 12:51:37 +10:00
Oliver
bfedb9cf87 Email config fix (#5336)
* Change for DEFAULT_FROM_EMAIL

- Use USERNAME if not specified

(cherry picked from commit 487ac917c90e9fe3da4effaa9326b707ceecd321)

* Email configuration fails if DEFAULT_FROM_EMAIL not set

(cherry picked from commit 01e573c3a2702e7c21ed13b0cb44280c89d3dee1)

* Docs update
2023-07-25 11:41:17 +10:00
Ulices
c3cdb9cd7e Fix "your" word duplicated (#5335) 2023-07-25 08:02:40 +10:00
Matthias Mair
13b9b57698 P-UI: Dependency bumps (#5298)
* add option to use sections and refactro

* translate error messages

* remove unneeded vars

* move function code

* move data inside

* add global section

* add plugin section

* use translated section titles

* add translation strings

* rename scan action

* add user settings

* use ordered data

* fix settings url

* use debounced value for strings (not choices!)

* rename contex to context

* move i18n provider up

* move theme options into seperate context/ component

* renmae statrtup vars

* move translations out

* reactivate sentry

* move i18n provider to seperate context

* move langauge state completly out of App

* use theme out

* move theme context

* move LanguageContext

* move function into state

* make sentry optional for now

* add key to accordion

* init langauge context on top

* remove unneeded css files

* move errorpage to tsx

* add translation for error page

* Add error to title

* add typecast for error

* move type definition out

* remove todo -> type was already added

* upgrade deps

* add bootstrap

* remove @mantine/core

* readd core

* switch to bootstrap

* simplify import

* Add SPA views for react #2789

* split up frontend urls

* Add settings for frontend url loading

* add new UI scaffold

* remove tracking insert

* add platform app

* ensure static indexes work too

* add lingui

* add lingui config

* add mgmt tasks

* add base locales

* settings for frontend dev

* fix typo

* update deps

* add pre-commit

* add eslint

* add testing scaffold

* fix paths

* remove error - tests trip correctly

* merge workflow

* cleanup samples

* use name inline with other tests

* Add real worl frontend tests

* setup env

* tun migrations first

* optimize setup time

* setup demo dataset

* optimize run setup

* add test for class ui

* rename

* fix typo

* and another typo

* do install

* run migrations first

* fix name

* cleanup

* use other credentials

* use other credentials

* fix qc

* move envs to qc

* remove create_site

* reduce testing env

* fix test

* fix test call

* allaccess user

* add ui plattform check

* add better check

* remove unneeded env

* enable debug

* reduce wait time

* also build frontend on static

* add sekeleton

* fix various issues

* add locales

* clean output before building

* cleanup dir

* remove bootstrap

* clean up deps

* fix settings panel

* remove assets

* move logo

* split out router

* split up chunks

* fix zustand import syntax

* bundl

* update pre-render

* use vendor splitting

* maximes space usage

* enlarge breakpoints

* remove wired color changes

* cleanup tabs

* fix error

* update auth functions

* default to mail login

* add placeholder marking

* Add text to placeholder

* readd codespell

* add another test

* add sort plugin

* add sort plugin

* sort imports

* fix order

* Add mega menu

* run pre-commit fixes

* add node min version

* Docker container (#129)

* Fix allocation check for completing build order (#5199)

- Allocation check only applies to untracked line items

* docker dev

Install required node packages to docker development image

* add import order settings

* cleanup settings

* cleanup dashboard

* clanup part tab

* refactor header to only use 1 line

* cleanup reqs for py3.9

* remove compiled UI

* revert reqs change

* cleanup tasks

* cleanout built ui

* remove default user

* cleanup package.json

* fix doctip

* remove sentry

* optimize loading

* reset versions

* clean

* factor out menu items

* refactor Navtabs

* refactor HoverMenu

* remove part

* remove prettier

* remove default arg from build

* remove eslint

* Merge branch 'plattform' of https://github.com/matmair/InvenTree into platform-ui_base

* optimize svg

* add build step for plattform UI

* fix install command

* fix test

* remove extra test

* set default host if none is set

* set nicer names

* fix tests

* fix logged-in test

* update translations

* ensure more path matches

* make loading of serverrefs dynamic

* use default radius

* fix issues / code smells

* clean code smell

* fix password reset

* fix error messages

* detect small screens

* use loader meachanism for views

* refactor structure

* move auth functions out

* use text

* refactor defaults

* Add email login
FR] Add email link based logins Closes #3531

* fix name error

* fix reqs

* fix backend for magic login

* fix frontend

* remove unused route

* remove now unneeded test section

* cleanup code

* add navigation header

* fix logo component for nesting

* factor out menu

* refactor style

* clean code

* Translate items and use unique ids

* use alpine commands

* increase margins to remove drawer scroller

* only render plugins if they are defined

* remove sample content

* fix assertation

* open on hover

* refactor

* merge fix

* cleanup navigation drawer

* change dependencies for UI testing

* add highlight filter

* Add correct menu items

* move design component out

* move pre-commit out

* move deps again

* move js styles in

* revert CI changes

* remove unneeded exclution

* changed placeholder user

* refactored EditButton to component

* refactored app loading to useEffect

* moved color lookup into global scope

* reafactored UserPanel render block

* marked placeholder pages in doc links

* made doc tooltip optional for MenuLink

* changed MenuLinkItem names

* fixes missing Link item in MenuLink

* fixed merge error

* bump deps

* bump python

* downgrade for py3.9

* bump further

---------

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2023-07-24 08:32:18 +10:00
Matthias Mair
da4e506411 Added install step to trans (#5327) 2023-07-24 08:31:19 +10:00
Marcel Pörner
9b377ccfbf Fix admin url to point to right model (#5319) 2023-07-23 22:32:14 +10:00
Miklós Márton
25334c0d91 Fix selecting the default stock item template (#5318) 2023-07-23 19:42:56 +10:00
Miklós Márton
feeccc5c91 Fix selecting default label template (#5317) 2023-07-23 17:09:41 +10:00
Matthias Mair
4a211dd48d P UI: Show server version on auth (#5314)
* added language toggle component

* added language and theme controls onto start page

* moved host selection out of auth

* optimized rendering

* make server option less obvious

* changed EditButton save symbol

* longer welcome text

* removed ColorToggle color schema

* reduced code

* disabled host selection when options are changing

* fix type error

* use GH reporter

* fix tests?

* compile frontend

* fix assertation

* revert unneeded change

* split up into more components

* add server version info

* fix merge

* fix merge
2023-07-23 12:22:54 +10:00
Oliver
e6cd0b05ce Remove error message (#5315)
- Polluting logs without benefit
2023-07-23 12:21:33 +10:00
Matthias Mair
507205ca78 Add DIO identifier (#5312)
* Add info for installer, branch, publisher

* include version info

* fix permissions

* fix typo

* add more info

* use GH url

* fix node

* remove publisher

* remove more logging

* Add info to version view

* enable branch discovery when not set

* fix ref to github version

* add branch to about dialog

* use precise plattform information - if no env is set

* fix ref names

* load VERSION info

* rename plattform to target

* extend installer var

* add generic plattform info

* add docs for version info

* add more info

* add installer codes

* Add DIO code
2023-07-23 09:09:28 +10:00
Oliver
4e0f22aa00 Typo fix (#5307)
* successfull -> successful

* Fix backtick
2023-07-22 22:54:01 +10:00
Matthias Mair
f227315ad1 P-UI: Fast language / theme / server selection (#5301)
* added language toggle component

* added language and theme controls onto start page

* moved host selection out of auth

* optimized rendering

* make server option less obvious

* changed EditButton save symbol

* longer welcome text

* removed ColorToggle color schema

* reduced code

* disabled host selection when options are changing

* fix type error

* use GH reporter

* fix tests?

* compile frontend

* fix assertation

* revert unneeded change

* split up into more components

* separated functions / use cases for LanguageToggle more

* moved color toggle to profile

* moved language out of main menu into profile

* remapped settings link
2023-07-22 22:19:19 +10:00
Oliver
f70294b247 Stock installed table fix (#5305)
* Prevent installed items from being hidden

* Fix parent / child relationship
2023-07-21 23:53:58 +10:00
Miklós Márton
6b4717cb28 Show built in label printing plugin even if the external plugins are disabled (#5304) 2023-07-21 21:55:51 +10:00
Oliver
45aa8c39b3 React tweaks (#5300)
* Add .gitignore for static web files

* Add invoke task to start frontend dev server
2023-07-21 09:46:44 +10:00
Matthias Mair
9fe77b20e4 P-UI: Adapt translation CI (#5299)
* Fixes up translation for P-UI
Closes [FR/P-UI] Translation integration #5282

* ammended crowdin settings
2023-07-21 09:00:37 +10:00
Matthias Mair
2fc82fd0e3 Adds trigger when plugins are loaded (#5260)
* added trigger when plugins are loaded

* only trigger if db is ready
2023-07-21 08:56:19 +10:00
Matthias Mair
1ab772e66d P UI: Basic UI & Auth setup (#5207)
* update deps

* remove @mantine/rte

* add icons again

* Create dependency-review.yml

* Create scan.yml

* Create sonar-project.properties

* add option to use sections and refactro

* translate error messages

* remove unneeded vars

* move function code

* move data inside

* add global section

* add plugin section

* use translated section titles

* add translation strings

* rename scan action

* add user settings

* use ordered data

* fix settings url

* use debounced value for strings (not choices!)

* rename contex to context

* move i18n provider up

* move theme options into seperate context/ component

* renmae statrtup vars

* move translations out

* reactivate sentry

* move i18n provider to seperate context

* move langauge state completly out of App

* use theme out

* move theme context

* move LanguageContext

* move function into state

* make sentry optional for now

* add key to accordion

* init langauge context on top

* remove unneeded css files

* move errorpage to tsx

* add translation for error page

* Add error to title

* add typecast for error

* move type definition out

* remove todo -> type was already added

* upgrade deps

* add bootstrap

* remove @mantine/core

* readd core

* switch to bootstrap

* simplify import

* Add SPA views for react #2789

* split up frontend urls

* Add settings for frontend url loading

* add new UI scaffold

* remove tracking insert

* add platform app

* ensure static indexes work too

* add lingui

* add lingui config

* add mgmt tasks

* add base locales

* settings for frontend dev

* fix typo

* update deps

* add pre-commit

* add eslint

* add testing scaffold

* fix paths

* remove error - tests trip correctly

* merge workflow

* cleanup samples

* use name inline with other tests

* Add real worl frontend tests

* setup env

* tun migrations first

* optimize setup time

* setup demo dataset

* optimize run setup

* add test for class ui

* rename

* fix typo

* and another typo

* do install

* run migrations first

* fix name

* cleanup

* use other credentials

* use other credentials

* fix qc

* move envs to qc

* remove create_site

* reduce testing env

* fix test

* fix test call

* allaccess user

* add ui plattform check

* add better check

* remove unneeded env

* enable debug

* reduce wait time

* also build frontend on static

* add sekeleton

* fix various issues

* add locales

* clean output before building

* cleanup dir

* remove bootstrap

* clean up deps

* fix settings panel

* remove assets

* move logo

* split out router

* split up chunks

* fix zustand import syntax

* bundl

* update pre-render

* use vendor splitting

* maximes space usage

* enlarge breakpoints

* remove wired color changes

* cleanup tabs

* fix error

* update auth functions

* default to mail login

* add placeholder marking

* Add text to placeholder

* readd codespell

* add another test

* add sort plugin

* add sort plugin

* sort imports

* fix order

* Add mega menu

* run pre-commit fixes

* add node min version

* Docker container (#129)

* Fix allocation check for completing build order (#5199)

- Allocation check only applies to untracked line items

* docker dev

Install required node packages to docker development image

* add import order settings

* cleanup settings

* cleanup dashboard

* clanup part tab

* refactor header to only use 1 line

* cleanup reqs for py3.9

* remove compiled UI

* revert reqs change

* cleanup tasks

* cleanout built ui

* remove default user

* cleanup package.json

* fix doctip

* remove sentry

* optimize loading

* reset versions

* clean

* factor out menu items

* refactor Navtabs

* refactor HoverMenu

* remove part

* remove prettier

* remove default arg from build

* remove eslint

* Merge branch 'plattform' of https://github.com/matmair/InvenTree into platform-ui_base

* optimize svg

* add build step for plattform UI

* fix install command

* fix test

* remove extra test

* set default host if none is set

* set nicer names

* fix tests

* fix logged-in test

* update translations

* ensure more path matches

* make loading of serverrefs dynamic

* use default radius

* fix issues / code smells

* clean code smell

* fix password reset

* fix error messages

* detect small screens

* use loader meachanism for views

* refactor structure

* move auth functions out

* use text

* refactor defaults

* Add email login
FR] Add email link based logins Closes #3531

* fix name error

* fix reqs

* fix backend for magic login

* fix frontend

* remove unused route

* remove now unneeded test section

* cleanup code

* add navigation header

* fix logo component for nesting

* factor out menu

* refactor style

* clean code

* Translate items and use unique ids

* use alpine commands

* increase margins to remove drawer scroller

* only render plugins if they are defined

* remove sample content

* fix assertation

* open on hover

* refactor

* merge fix

* cleanup navigation drawer

* change dependencies for UI testing

* add highlight filter

* Add correct menu items

* move design component out

* move pre-commit out

* move deps again

* move js styles in

* revert CI changes

* remove unneeded exclution

* changed placeholder user

* refactored EditButton to component

* refactored app loading to useEffect

* moved color lookup into global scope

* reafactored UserPanel render block

* marked placeholder pages in doc links

* made doc tooltip optional for MenuLink

* changed MenuLinkItem names

* fixes missing Link item in MenuLink

* fixed merge error

---------

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2023-07-21 08:54:57 +10:00
Oliver
9f96620e12 New Crowdin updates (#5253)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-07-20 18:33:56 +10:00
Matthias Mair
2259de7f31 P UI: Added frontend publishing for bare-metal (git) (#5277)
* Add SPA views for react #2789

* split up frontend urls

* Add settings for frontend url loading

* add new UI scaffold

* remove tracking insert

* add platform app

* ensure static indexes work too

* add lingui

* add lingui config

* add mgmt tasks

* add base locales

* settings for frontend dev

* fix typo

* update deps

* add pre-commit

* add eslint

* add testing scaffold

* fix paths

* remove error - tests trip correctly

* merge workflow

* cleanup samples

* use name inline with other tests

* Add real worl frontend tests

* setup env

* tun migrations first

* optimize setup time

* setup demo dataset

* optimize run setup

* add test for class ui

* rename

* fix typo

* and another typo

* do install

* run migrations first

* fix name

* cleanup

* use other credentials

* use other credentials

* fix qc

* move envs to qc

* remove create_site

* reduce testing env

* fix test

* fix test call

* allaccess user

* add ui plattform check

* add better check

* remove unneeded env

* enable debug

* reduce wait time

* also build frontend on static

* add sort plugin

* fix order

* run pre-commit fixes

* add node min version

* Docker container (#129)

* Fix allocation check for completing build order (#5199)

- Allocation check only applies to untracked line items

* docker dev

Install required node packages to docker development image

* add import order settings

* cleanout built ui

* remove default arg from build

* remove eslint

* optimize svg

* add build step for plattform UI

* fix install command

* use alpine commands

* do not use cache when creating image

* Added release pipeline

* trigger: ci

* Fix ci

* Fix ci

* Fix ci

* fix: workflow

* fix: workflow

* fix: doubble zipping

* fix: doubble zipping

* fix: doubble zipping

* fix: doubble zipping

* fix: doubble zipping

* Added frontend-download helper to tasks.py

* revert unrelated change

* Add frontend step to update task

* add frontend stuff to version info

* small change to trigger ci

* keep terminal output clean

* return found versions

* fix suggested command

* revert small change

* move to multiline

* add flag to stop frontend compile

* make node building optional on static

* add node trans to transalte task

* ammend commands to use new flag

* remove unneeded flag

* add warning

* add yarn bypass

* docstrings

* check for docker env

---------

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
Co-authored-by: wolflu05 <76838159+wolflu05@users.noreply.github.com>
2023-07-20 10:12:08 +10:00
Oliver
3baa640d70 Fix company index page title (#5288) 2023-07-20 08:12:31 +10:00
Matthias Mair
be59d1eb05 P UI: Rearrange CI (#5276)
* rearrange CI

* small name change
2023-07-19 21:50:26 +10:00
Oliver
cd3d3834be Update translate task (#5283)
- Allow for skipping of building static files
- CI workflow does not have node / etc / installed
2023-07-19 10:43:40 +10:00
Ulices
d012f8f08c Update InvenTree demo url (#5281) 2023-07-19 10:12:59 +10:00
dependabot[bot]
09464699e3 Bump semver from 6.3.0 to 6.3.1 in /src/frontend (#5272)
Bumps [semver](https://github.com/npm/node-semver) from 6.3.0 to 6.3.1.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/v6.3.1/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v6.3.0...v6.3.1)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-19 08:23:47 +10:00
dependabot[bot]
0ae81f3a21 Bump word-wrap from 1.2.3 to 1.2.4 (#5279)
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-19 08:23:26 +10:00
Oliver
1125bf4396 Mantine (#5274)
* Add .gitignore for compiles web assets

* Update react dependencies

- Add mantine/core
- Add mantine-datatable

* Add more packages

- mantine/dropzone
- mantine/notifications

* Fix for development docker image
2023-07-19 06:28:00 +10:00
Oliver
6d3978ea28 Add database model for defining custom units (#5268)
* Add database model for defining custom units

- Database model
- DRF serializer
- API endpoints

* Add validation hook

* Custom check for the 'definition' field

* Add settings page for custom units

- Table of units
- Create / edit / delete buttons

* Allow "unit" field to be empty

- Not actually required for custom unit definition

* Load custom unit definitions into global registry

* Docs: add core concepts page(s)

* Add some back links

* Update docs

* Add unit test for custom unit conversion

* More unit testing

* remove print statements

* Add missing table rule
2023-07-19 06:24:16 +10:00
Oliver
81e2c2f8fa Update django-allauth-2fa to 0.11.1 (#5273)
- 0.11.0 was yanked from pypi (rude)
2023-07-18 23:56:52 +10:00
Matthias Mair
3e37469350 Platform UI - React integration (#5011)
* Add SPA views for react #2789

* split up frontend urls

* Add settings for frontend url loading

* add new UI scaffold

* remove tracking insert

* add platform app

* ensure static indexes work too

* add lingui

* add lingui config

* add mgmt tasks

* add base locales

* settings for frontend dev

* fix typo

* update deps

* add pre-commit

* add eslint

* add testing scaffold

* fix paths

* remove error - tests trip correctly

* merge workflow

* cleanup samples

* use name inline with other tests

* Add real worl frontend tests

* setup env

* tun migrations first

* optimize setup time

* setup demo dataset

* optimize run setup

* add test for class ui

* rename

* fix typo

* and another typo

* do install

* run migrations first

* fix name

* cleanup

* use other credentials

* use other credentials

* fix qc

* move envs to qc

* remove create_site

* reduce testing env

* fix test

* fix test call

* allaccess user

* add ui plattform check

* add better check

* remove unneeded env

* enable debug

* reduce wait time

* also build frontend on static

* add sort plugin

* fix order

* run pre-commit fixes

* add node min version

* Docker container (#129)

* Fix allocation check for completing build order (#5199)

- Allocation check only applies to untracked line items

* docker dev

Install required node packages to docker development image

* add import order settings

* cleanout built ui

* remove default arg from build

* remove eslint

* optimize svg

* add build step for plattform UI

* fix install command

* use alpine commands

* do not use cache when creating image

---------

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2023-07-18 22:45:49 +10:00
Oliver
b717011f06 Docker build: Update python deps (#5270)
* Update python deps

* Update requirements.in

* Fix requirements-dev.txt
2023-07-18 19:43:45 +10:00
Oliver
7a8e59305f Allow label printing when custom plugins not enabled (#5266)
- Will still allow "builtin" plugins to function
2023-07-18 10:34:50 +10:00
Sébastien APP
5862c70b24 Update python.md (#5264)
Just a typo corrected in a script sample.
2023-07-18 09:04:58 +10:00
Oliver
e8d16298a4 Label plugin refactor (#5251)
* Add skeleton for builtin label printing plugin

* Force selection of plugin when printing labels

* Enhance LabelPrintingMixin class

- Add render_to_pdf method
- Add render_to_html method

* Enhance plugin mixin

- Add class attribute to select blocking or non-blocking printing
- Add render_to_png method
- Add default method for printing multiple labels
- Add method for offloding print job

* Simplify print_label background function

- All arguments now handled by specific plugin

* Simplify label printing API

- Simply pass data to the particular plugin
- Check result type
- Return result

* Updated sample plugin

* Working on client side code

* Cleanup

* Update sample plugin

* Add new model type

- LabelOutput model
- Stores generated label file to the database
- Makes available for download

* Update label printing plugin mixin

* Add background task to remove any old label outputs

* Open file if response contains filename

* Remove "default printer" option which does not specify a plugin

* Delete old labels after 5 days

* Remove debug statements

* Update API version

* Changed default behaviour to background printing

* Update label plugin mixin docs

* Provide default printer if none provided (legacy)

* Update unit test

* unit test updates

* Further fixes for unit tests

* unit test updates
2023-07-17 21:39:53 +10:00
Matthias Mair
4d7fb751eb remove unneeded gunicorn (#5248) 2023-07-17 20:53:02 +10:00
Oliver
8daff0bff4 Support traditional Chinese (zh-hant) locale (#5261) 2023-07-17 20:45:13 +10:00
Matthias Mair
df77305d60 Fix settings function callback (#5259)
* fix settings function callback

* merge instance filters and passed keys
2023-07-17 20:19:32 +10:00
Oliver
20b59c3575 Fix language code for pt-br (#5256)
- Has to be lowercase in settings.py to work correctly
2023-07-16 19:35:03 +10:00
Oliver
370021bc7c Update docs to match reality (#5255) 2023-07-15 23:55:22 +10:00
Oliver
2e35b861ef New Crowdin updates (#5221)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-07-15 08:39:13 +10:00
Oliver
034cc4b983 Translation stat fix (#5250)
* Add helper function for translation coverage

- Handle case where locale name does not match exactly

* remove debug statement

* Fixes
2023-07-15 08:38:39 +10:00
Lukas
4a46e0321f fix: make protected setting again changeable through admin ui (#5245) 2023-07-14 08:57:19 +10:00
Oliver
f4f39e274f Add https comment to nginx file (#5243) 2023-07-14 08:53:37 +10:00
Lukas
0aee2a11c4 Fix weasyprint links (#5244)
* Fix weasyprint links

* Update report.md
2023-07-13 18:31:05 +02:00
Oliver
41167f22c9 Only update theme if value provided (#5240)
- Handles case where null or invalid value provided
2023-07-13 20:34:41 +10:00
Oliver
bd1689095d fix typo (#5236) 2023-07-13 10:30:19 +10:00
Oliver
01f2aa5f74 Protected settings fix (#5229)
* Hide protected setting in settings view

* Implement custom serializer for setting value

- Return '***' if the setting is protected

* Implement to_internal_value

* Stringify

* Add protected setting to sample plugin

* Unit tests for plugin settings API

* Update unit test
2023-07-12 16:29:08 +10:00
Lukas
ee274739a6 Added required attribute to settings/plugins, refactor: allValues (#5224)
* Added required attribute to settings/plugins, refactor: allValues

- added 'required' attribute to InvenTreeBaseSetting
- added 'check_all_settings'
- added 'all_settings' to get a list of all defined settings
- refactored 'allValues' to use new 'all_settings' function
- added docs for new 'check_setting' function on plugin SettingsMixin

* Fix typing to be compatible with python 3.9

* trigger: ci

* Fixed **kwargs bug and added tests
2023-07-12 08:19:19 +10:00
Oliver
b3dcc28bd9 Add missing callback for attachment delete button (#5219) 2023-07-11 10:29:33 +10:00
Matthias Mair
a9a8ac1c70 Add email login (#5209)
* Add email login
FR] Add email link based logins Closes #3531

* fix reqs

* fix backend code

* Add tests for magic login
2023-07-11 08:13:35 +10:00
Oliver
92b0a19270 Docker: add missing reporting libs (#5213)
* Docker: add missing reporting libs

* Fix pango / cairo libs

Ref: https://github.com/Kozea/WeasyPrint/issues/699

* Move to where it will actually do something

* Remove continuation

* Remove dev packages
2023-07-10 21:12:17 +10:00
Lukas
4a1f73331f Refactor: Dockerimage (#5007)
* refactor docker image to reduce size

* Added required deps for database drivers

* Added alpine test dockerfile

* Readded image libs to Dockerfile

* Fix link in dockerfile

* Replaced Dockerfile

* Remove bash from image

* Added base target to Dockerfile

* Added missing mysql dev headers

* Add dev target back to Dockerfile

* fix: Dockerfile style

* Add old comments back to dockerfile

* Removed wrong comment

* Added back piwheels repo
2023-07-10 19:52:33 +10:00
Oliver
8fb7612894 Fix link to SalesOrder in stock history table (#5210) 2023-07-10 13:13:35 +10:00
Matthias Mair
ae046c778e support debian 12 (#5205) 2023-07-09 08:40:53 +10:00
Matthias Mair
d4fad4f5c8 fix cli on 22.04 (#5204) 2023-07-09 08:40:27 +10:00
Lukas
b37b8a8610 fix: generic status import (#5192) 2023-07-09 08:39:26 +10:00
Oliver
1f81daadf6 Fix allocation check for completing build order (#5199)
- Allocation check only applies to untracked line items
2023-07-07 13:27:35 +10:00
Lukas
24ae21b342 Fix 404 link in docker_prod.md (#5193) 2023-07-07 08:12:50 +10:00
Oliver
4b40fe5ea1 New Crowdin updates (#5172)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-07-06 13:09:49 +10:00
Oliver
e9a0b02d3d Fix range filtering for serial number (#5189)
* Fix range filtering for serial number

- Needs to point to "serial_int" field to work

* Bump API version

(cherry picked from commit c3cb47ec15bbba1bf5d6d46374ac29b627f5bf34)
2023-07-06 13:08:08 +10:00
Oliver
773dd3b210 Fix quantity aggregation for stock table (#5188)
* Fix quantity aggregation for stock table

- Stock quantity can only be added together if units are the same

* Add stock total footer to part table
2023-07-06 12:54:05 +10:00
Oliver
2d2a084866 Shorten string fix (#5187)
* Fix for model renderer code

- Handles string shortening without corrupting HTML

* Fix for BOM table
2023-07-06 12:37:40 +10:00
Oliver
35defe78c0 Stocktake location filter (#5185)
* Pass specified location to "perform_stocktake"

* Separately track total stocktake and location stocktake data

* Catch any exception
2023-07-06 12:30:28 +10:00
Oliver
dd4f5d4630 Add 'available' filter for BuildLine API endpoint (#5186)
* Add 'available' filter for BuildLine API endpoint

- Fixes missing filter
- Closes https://github.com/inventree/InvenTree/issues/1839

* Bump API version
2023-07-06 12:18:26 +10:00
Oliver
9abcc0ec34 Param fix (#5183)
* Handle AttributeError in convert_physical_value

* Added new unit test
2023-07-06 11:02:18 +10:00
Oliver
831693e941 Stocktake external (#5182)
* Add 'location' filtering option for part.stock_entries

* Add "exclude_external" field to stocktake report

* Add "stocktake_exclude_external" default option

* Implement setting to exclude external stock

* Split stocktake functionality out into separate file

* Change name of internal setting

* Refactoring

* Add 'exclude_external' field to stocktake form
2023-07-05 22:53:44 +10:00
Oliver
c91fbdbc48 use django-q2 (#5167)
* use django-q2

- Modern fork of django-q

* Update requirements.txt
2023-07-05 22:53:32 +10:00
Oliver
3bea809823 Unit check option (#5175)
* Add option to control parameter units

* Check setting before validation

* Update part parameter settings page

* Update unit tests

* Update docs
2023-07-05 11:11:19 +10:00
Christoph
cf0d30b11c add report feature for stock locations (#5134)
* add report feature for stock locations

* fix flake 8 errors

* run pre-commit run --all-files to fix style errors

* add new model

* create default stock location
2023-07-05 10:19:13 +10:00
Oliver
9cf629ccc5 Add migration check for 0.12.0 database (#5174) 2023-07-05 10:07:48 +10:00
Matthias Mair
aec56d4b34 Bump general deps (#5170)
* bump deps

* down to py 3.9

* pin pillow
2023-07-05 07:20:56 +10:00
Matthias Mair
f16c8a5444 Update 2fa lib (#5169)
* remove CustomTwoFactorRemove

* add setting
2023-07-05 07:20:38 +10:00
Matthias Mair
83afa74927 Bump django - fix CVE-2023-36053 (#5168)
* bump deps - fix CVE-2023-36053

* downgrade to 3.9

* fix typo
2023-07-05 07:19:53 +10:00
Oliver
7ba26ebfbb Task improvements (#5159)
* Ignore triggered events if plugin events are not enabled

* Ensure that plugin tasks are handled by the background worker

* Allow shell access

* Don't force async if testing

* Enable plugin events as part of CI

* fix
2023-07-04 23:26:19 +10:00
Oliver
4c3dc6ddbd Restrict "recently updated" stock to "available" (#5165) 2023-07-04 22:53:46 +10:00
Oliver
8b730884d7 Handle exception when creating default labels (#5163)
* Handle exception when creating default labels

- Running workers in parallel may cause race conditions
- Catch any exception which is raised

* Prevent password from being logged

* Update default timeout for docker
2023-07-04 22:46:03 +10:00
Oliver
9ebbc2f9f6 Do not enforce unit type conversion for part parameters (#5160)
* Do not enforce unit type conversion for part parameters

- Still convert to "native value" (if possible)

* update unit tests
2023-07-04 21:57:43 +10:00
Oliver
d52a839cf2 Handle case where git head is detached (#5161)
- branch information does not exist in this case
- Fixes https://github.com/inventree/InvenTree/issues/5133
2023-07-04 21:57:23 +10:00
Oliver
637a0ca7a5 Remove old method (#5156)
- Now handled entirely by API / frontend
- Very inefficient, even if it was still being used
2023-07-04 20:52:42 +10:00
Oliver
42fd4c7ee6 Query improvements for BuildLine table (#5153)
- Prefetch / preselect related records
- Improve query speed
2023-07-04 19:24:36 +10:00
Oliver
17c2070503 Order table improvements (#5151)
- prevent "double loading" of order tables
2023-07-04 16:20:03 +10:00
spike77453
ace94d5c18 Fix link to background tasks documentation (#5150) 2023-07-04 14:30:15 +10:00
Oliver
aab7911a2b Contributing docs (#5147)
* Add more info for invoke test

* Update code style docs

* link fix
2023-07-04 13:30:33 +10:00
Oliver
5f61b5f120 Rendering fix for build allocation table (#5145)
- Fix link to part
- Fix link to stock item
2023-07-04 13:30:26 +10:00
Oliver
5408ced564 New Crowdin updates (#5107)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-07-04 13:22:59 +10:00
Ritchie Poh
3ea5a36044 Update config.md (#5144)
Possible spelling error for INVENTREE_TIMEZONE env variable
2023-07-04 11:39:49 +10:00
Alexandre NICOLAIE
7e6409cd92 Update config.md (#5141)
* Change the description of `File Storage Locations` variables

* Remove `INVENTREE_BACKUP_DIR` that is already explain on the backup documentation and bellow in this documentation
2023-07-04 10:57:02 +10:00
Mark Oude Elberink
2e7c86ff92 fix: add missing build model property (#5127)
* fix: add missing virtual build property

* chore: improve docstring
2023-07-03 09:55:58 +10:00
Oliver
a81ac89603 Update version.py (#5124)
Bump to 0.13.0 dev
2023-06-30 14:50:56 +10:00
Oliver
1d18b487f3 Update version.py (#5123)
Bump version to 0.12.0
2023-06-30 14:22:43 +10:00
Oliver
7955d1f579 Get git log fix (#5113)
* Simplify extraction of git repo

- Replace Repo.discover() with Repo()
- Ensure provided path is directory

* Remove profiling code
2023-06-30 13:43:54 +10:00
Oliver
c8642bedcd remove old method (#5120)
- not used
2023-06-29 22:38:37 +10:00
Oliver
752fb97d2f Transfer stock location (#5117)
* Hide info messages if no data

* pre-fill location when transferring stock

* Change iteration variable

* Measure twice, cut once

* js improvements
2023-06-28 21:59:47 +10:00
Oliver
a78b26f93a Child items table (#5114)
* Template cleanup

- Remove "buttons" attribute (outdated)

* Fix filters for "child stock items" table

* Fix 'ancestor' stock filter
2023-06-28 15:22:23 +10:00
Oliver
53e120cdb3 Fix for index page badge (#5115)
- If table was not paginated, totalRows did not exist
- Handle this case by looking at length of actual data
2023-06-28 15:22:08 +10:00
Lavissa
940fa74365 Update docker dev docs (#4743)
* Change docker dev server install

* Added --rm to docker compose run commands
  This deletes the one-time-use container after exiting

* Added --dev to setup-test. This runs setup-dev as a part of
  setup-test.

* Revisions

* Updates

* Add context

* Update docs

* Tyops :)

* Remove reference to action that has not happened yet
2023-06-26 17:10:58 +10:00
Oliver
8b6abe1505 Fix for stock status change (#5105)
- Recent refactor introduced bug
- Update stock.js
2023-06-26 12:14:44 +10:00
Oliver
256c9cb751 New Crowdin updates (#5101)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-06-26 09:44:10 +10:00
Matthias Mair
73e0d03abf Merge workflows (#5103)
* merge workflows
from https://github.com/inventree/InvenTree/pull/5087

* syntax fix
2023-06-26 09:43:57 +10:00
Matthias Mair
a83a71b3a3 Add Notifications shortcut (#5104)
* add notify_users shortcut

* make html template optional
2023-06-26 09:43:07 +10:00
Oliver
0458b5c53a Variant low stock (#5102)
* Handle FileNotFoundError

* Adjust "low_stock" query filter

- Include variant stock
2023-06-24 22:31:52 +10:00
Oliver
5dfc389c06 New Crowdin updates (#5083)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-06-24 14:36:29 +10:00
Lavissa
a5c8d86530 Synergise label filter help texts (#5100)
* Synergise label filter help texts

* Fix QR code alt text

* Change label alt text from QC or QR
2023-06-24 08:40:36 +10:00
Oliver
24b554a8d2 PartParameter API improvements (#5094)
* PartParameter: Include template_detail by default

* PartParameter API updates

- Allow sorting by template name
- Refactor with mixin class

* Bug fixes

* Bump API version
2023-06-24 01:04:11 +10:00
Oliver
fab738cd75 Scheduling api fix (#5093)
* Fix query for part scheduling API

* Add unit test for scheduling endpoint

* Remove length check
2023-06-23 23:55:52 +10:00
Oliver
603aef5da9 Catch Part.DoesNotExist (#5092)
- Can be thrown when importing Part instances
- Fixes https://github.com/inventree/InvenTree/issues/5090
2023-06-23 21:30:51 +10:00
Oliver
198245d0d2 Pin mysqlclient lib for docker (#5091)
- Recent updates have caused breakage
2023-06-23 20:21:26 +10:00
Oliver
3b4e20b54a Unit Test Improvements (#5087)
* Disable migration testing

- Compare how long the unit tests take

* Change file

- To get unit tests to run

* Fix format

* Consolidate tasks.py

- Remove coverage task
- Add --coverage flag to test task

* Fix typo

* Run migration unit tests if migration files are updated

* Fix

* Touch migration file

- Should cause migration unit tests to be run

* Force migration checks for docker build

* Prevent default report creation in unit tests

- Should save some time

* Add simple profiling for plugin loading

- Display time taken to load each plugin

* Fix to invoke test

* Disable get_git_log (for testing)

* Disable get_git_path in CI

- Might remove this entirely?
- For now, bypass for unit testing

* Add debug for unit registry

- Display time taken to load registry

* Don't full-reload unit registry

* Adjust migration test workflow

- env var updates
- change paths-filter output

* Fix for migration_test.yaml

- Actually need to set the output

* env fix

* db name

* Prevent sleep if in test mode

* Reduce sleep time on wait_for_db
2023-06-23 17:25:59 +10:00
Oliver
693d24b4b6 Table filters refactor (#5086)
- Fix a couple of bugs
- Code cleanup
2023-06-22 10:29:14 +10:00
Oliver
deffcc2814 Update FUNDING.yml (#5085)
Fix paypal link
2023-06-21 22:35:25 +10:00
Oliver
3001471706 README Updates (#5084)
* Update funding.yml

* Cleanup README.md

* More cleanup
2023-06-21 22:30:30 +10:00
Oliver
7f5e844367 Fix for loadPartTable (#5082)
- Recent changes introduced a bug related to filters
- Only really visible on index page
2023-06-21 16:42:10 +10:00
Oliver
66ec82d4d1 Add migration test workflow (#4973)
* Add migration test workflow

* Adds dummy migration

- For testing only
- Will be removed

* Improvements to workflow

* Update workflow

* Use manual git step

* Don't install twice

* Let's try again

* Does v3 work?

* Try https repo

* Fix repo address

* Once more with feeling

* Fix location of test database

* Revert change to wrong file

* Fix DB location

* Hard-code database path

* debugging

* More updates grrr

* Adjust permissions on database file

* Manual git operation

* Updates for migration test workflow

* Fix database names

* Remove dummy migration file

* Fix filters

* Fix path filter

* Explicit pathing

* Fix formatting error

* Another formatting error
2023-06-20 22:37:30 +10:00
Oliver
e2800b19ef New Crowdin updates (#5061)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-06-20 22:27:16 +10:00
Oliver
303305e05f Fix constraint for address model (#5076)
* Fix constraint for address model

- Do not handle at database level
- Add a "validate_unique" method to the address model
- Fixes https://github.com/inventree/InvenTree/issues/5070

* Remove unique constraint rule

* Unit test fix

- Adjust unit test around new code

* Further unit test updates
2023-06-20 16:57:20 +10:00
Matthias Mair
b700b44c53 Add dj-rest-auth (#4187)
* Add dj-rest-auth
[FR] User registration via API
Fixes #3978

* add jwt support for API

* check for old password

* Add check if registration is allowed

* make email mandatory if selected

* lower postgres version?

* update req

* revert psql change

* move form options out

* Update reqs

* Add handlers for most OAuth2

* refactor and add logging

* make error message more actionable

* add handler for twitter

* add keycloak endpoint

* warning for legacy apps

* remove legacy twitter support

* rename file

* move url to sub

* make JWT optional (default off)

* Add var to config template

* Add API endpoint to list available providers

* fix url pattern
2023-06-20 16:26:02 +10:00
Oliver
09cc654530 Required for order fix (#5077)
* Fix part tables on index page

- Stop table filters overriding each other

* Refactor "needed for build" table

- Now shows amount actually needed

* Fix typo

* js fix

* linting

* Fix warning messages on index page

* js linting
2023-06-20 12:39:18 +10:00
Oliver
4c9d4add2c Table custom buttons (#5075)
* Add generic implementation for barcode actions

- Commonize code against tables
- Cleaner UI
- Better code
- Will make future react refactor easier

* Add permissions.js

- Separate .js file for dynamically checking permissions

* Update stock table to use client-side actions

* API endpoint for bulk category adjustment

* Bug fix for purchase_order.js

- Prevent some really strange API calls

* Refactor actions for part table

- Now done dynamically

* Refactor actions for the attachment tables

* Refactor actions for build output table

* Increment API version

* Cleanup janky button

* Refactor supplier part table

* Refactor manufacturer part table

* Remove linkButtonsToSelection

- no longer needed
- Cleanup, yay!

* Cleanup purchase order line table

* Refactor BOM table buttons

* JS linting

* Further cleanup

* Template cleanup

- remove extra div elements

* js linting

* js fix
2023-06-20 07:45:35 +10:00
Lavissa
13389845b1 Add Contacts to admin panel (#5065)
* Contact model added to admin panel
2023-06-19 22:42:02 +10:00
Oliver
021a5a4081 Fixes for purchase order receive buttons (#5072) 2023-06-19 20:01:00 +10:00
Matthias Mair
617ad6c233 decouble ruleset migration (#5067) 2023-06-19 16:56:26 +10:00
Oliver
2e8fb2a14a Stock status change API (#5064)
* Add API endpoint for changing stock item status

- Change status for multiple items simultaneously
- Reduce number of database queries required

* Perform bulk update in serializer

* Update 'updated' field

* Add front-end code

* Bump API version

* Bug fix and unit test
2023-06-18 07:40:47 +10:00
Oliver
f6420f98c2 Simplify release notes docs (#5063) 2023-06-17 21:56:26 +10:00
Lavissa
bf707766b6 [Feature] Company Addresses (#4732)
* Add initial model structure

* Initial Address model defined

* Add migration and unit tests

* Initial migration for Address model generated

* Unit tests for Address model added

* Move address field to new model

* Added migration to move address field to Address model

* Implement address feature to backend

* API endpoints for list and detail implemented

* Serializer class for Address implemented

* Final migration to delete old address field from company added

* Tests for API and migrations added

* Amend migration file names

* Fix migration names in test

* Add address property to company model

* Iinital view and JS code

* Fix indents

* Fix different things

* Pre-emptive change before merge

* Post-merge fixes

* dotdotdot...

* ...

* iDots

* .

* .

* .

* Add form functionality and model checks

* Forms require a confirmation slider to be checked to submit
  if address is selected as primary

* Backend resets primary address before saving if new address
  is designated as primary

* Fix pre-save logic to enforce primary uniqueness

* Fix typos

* Sort out migrations

* Forgot one

* Add admin entry and small fixes

* Fix migration file name and dependency

* Update InvenTree/company/models.py

Co-authored-by: Matthias Mair <code@mjmair.com>

* Update InvenTree/company/models.py

Co-authored-by: Matthias Mair <code@mjmair.com>

* Correct final issues

* .

---------

Co-authored-by: Matthias Mair <code@mjmair.com>
2023-06-17 21:55:25 +10:00
Oliver
61d2f452b2 Homepage Improvements (#5057)
* remove STOCK_RECENT_COUNT parameter

- Now "recent" is set by date
- Tables are paginated by the server

* Display total row count

* remove PART_RECENT_COUNT

- Replace with date filter
- Update Part.api

* Bump API version
2023-06-17 08:21:25 +10:00
Oliver
31ff3599eb Parameter by name (#5055)
* Add method get_parameter

- Return a parameter for a part, on name

* Add unit test for new method

* Adds template tag to retrieve parameter based on name

* Update docs
2023-06-16 12:14:17 +10:00
Oliver
51cece9e07 custom panel fix (#5049)
- Handle case where get_custom_panels returns None
2023-06-15 18:59:50 +10:00
Oliver
62faaf01c5 Stock installed items (#5030)
* Cleanup

* Add "installed_items" count to StockItem serializer

- Add queryset annotation
- Add API filter for list endpoint

* js code

* More js updates

* Load installed items on demand

* Make option configurable
2023-06-15 16:42:52 +10:00
Matthias Mair
013d206b91 Add metadata to plugin configs (#5019)
* add metadata to plugin

* Api version bump

* exclude metadata from admin to fix test
2023-06-15 16:17:05 +10:00
Matthias Mair
2322a98068 replace assertation with more targeted logging check (#5045) 2023-06-15 07:34:00 +10:00
Oliver
be6ab14c9b Improve StockItem API speed (#5042)
- Removes child detail fields which cannot be effectively annotated
- Prefetch required fields
- Add unit test method for checking query count
2023-06-14 18:33:49 +10:00
Oliver
8d16abcefb Build line labels (#5034)
* Adds BuildLineLabel model

- New type of label for printing against BuildLine objects

* Add serializer for new model

* Add API endpoints for new label type

* Add hooks to BuildLine table

* Create default label

- Create an example BuildLineLabel object

* Add admin integration

* Fix js code

* Use two-tiered template

- Allows base template to be updated

* Improve default label

* Add docs pages for labels

* Update nav

* Documentation for new label

* Add permission role

* Bump API version
2023-06-14 13:07:18 +10:00
Oliver
a3940cfc41 Improve error logging (#5039)
- Truncate path to < 200 chars
- Prevents exception when creating new Error object
2023-06-14 13:07:05 +10:00
Oliver
00bb740216 Add ProjectCode support to build orders (#4808)
* Add "project_code" field to Build model

* Add "project_code" field to Build model

* build javascript updates

(cherry picked from commit 3e27a3b739)

* Update table filters

(cherry picked from commit 196c675585)

* Adds API filtering

* Bump API version

* Hide project code field from build form if project codes not enabled

(cherry picked from commit 4e210e3dfa)

* refactoring to attempt to fix circular imports

* Upgrade django-test-migrations package

* Fix broken import

* Further fixes for unit tests

* Update unit tests for migration files

* Fix typo in build.js

* Migration test updates

- Need to specify MPTT stuff

* Fix build.js

* Fix migration order

* Update API version
2023-06-14 11:23:35 +10:00
Oliver
c8365ccd0c Update app docs (#5032) 2023-06-13 21:04:17 +10:00
Oliver
6ba777d363 Build Order Updates (#4855)
* Add new BuildLine model

- Represents an instance of a BOM item against a BuildOrder

* Create BuildLine instances automatically

When a new Build is created, automatically generate new BuildLine items

* Improve logic for handling exchange rate backends

* logic fixes

* Adds API endpoints

Add list and detail API endpoints for new BuildLine model

* update users/models.py

- Add new model to roles definition

* bulk-create on auto_allocate

Save database hits by performing a bulk-create

* Add skeleton data migration

* Create BuildLines for existing orders

* Working on building out BuildLine table

* Adds link for "BuildLine" to "BuildItem"

- A "BuildItem" will now be tracked against a BuildLine
- Not tracked directly against a build
- Not tracked directly against a BomItem
- Add schema migration
- Add data migration to update links

* Adjust migration 0045

- bom_item and build fields are about to be removed
- Set them to "nullable" so the data doesn't get removed

* Remove old fields from BuildItem model

- build fk
- bom_item fk
- A lot of other required changes too

* Update BuildLine.bom_item field

- Delete the BuildLine if the BomItem is removed
- This is closer to current behaviour

* Cleanup for Build model

- tracked_bom_items -> tracked_line_items
- untracked_bom_items -> tracked_bom_items
- remove build.can_complete
- move bom_item specific methods to the BuildLine model
- Cleanup / consolidation

* front-end work

- Update javascript
- Cleanup HTML templates

* Add serializer annotation and filtering

- Annotate 'allocated' quantity
- Filter by allocated / trackable / optional / consumable

* Make table sortable

* Add buttons

* Add callback for building new stock

* Fix Part annotation

* Adds callback to order parts

* Allocation works again

* template cleanup

* Fix allocate / unallocate actions

- Also turns out "unallocate" is not a word..

* auto-allocate works again

* Fix call to build.is_over_allocated

* Refactoring updates

* Bump API version

* Cleaner implementation of allocation sub-table

* Fix rendering in build output table

* Improvements to StockItem list API

- Refactor very old code
- Add option to include test results to queryset

* Add TODO for later me

* Fix for serializers.py

* Working on cleaner implementation of build output table

* Add function to determine if a single output is fully allocated

* Updates to build.js

- Button callbacks
- Table rendering

* Revert previous changes to build.serializers.py

* Fix for forms.js

* Rearrange code in build.js

* Rebuild "allocated lines" for output table

* Fix allocation calculation

* Show or hide column for tracked parts

* Improve debug messages

* Refactor "loadBuildLineTable"

- Allow it to also be used as output sub-table

* Refactor "completed tests" column

* Remove old javascript

- Cleans up a *lot* of crusty old code

* Annotate the available stock quantity to BuildLine serializer

- Similar pattern to BomItem serializer
- Needs refactoring in the future

* Update available column

* Fix build allocation table

- Bug fix
- Make pretty

* linting fixes

* Allow sorting by available stock

* Tweak for "required tests" column

* Bug fix for completing a build output

* Fix for consumable stock

* Fix for trim_allocated_stock

* Fix for creating new build

* Migration fix

- Ensure initial django_q migrations are applied
- Why on earth is this failing now?

* Catch exception

* Update for exception handling

* Update migrations

- Ensure inventreesetting is added

* Catch all exceptions when getting default currency code

* Bug fix for currency exchange rates update

* Working on unit tests

* Unit test fixes

* More work on unit tests

* Use bulk_create in unit test

* Update required quantity when a BuildOrder is saved

* Tweak overage display in BOM table

* Fix icon in BOM table

* Fix spelling error

* More unit test fixes

* Build reports

- Add line_items
- Update docs
- Cleanup

* Reimplement is_partially_allocated method

* Update docs about overage

* Unit testing for data migration

* Add "required_for_build_orders" annotation

- Makes API query *much* faster now
- remove old "required_parts_to_complete_build" method
- Cleanup part API filter code

* Adjust order of fixture loading

* Fix unit test

* Prevent "schedule_pricing_update" in unit tests

- Should cut down on DB hits significantly

* Unit test updates

* Improvements for unit test

- Don't hard-code pk values
- postgresql no likey

* Better unit test
2023-06-13 20:18:32 +10:00
Oliver
98bddd32d0 Skip pricing updates when importing data (#5026)
* Skip pricing updates when importing data

- Depending on migration state, pricing table might not exist
- post-save hooks can call update_pricing
- So, ignore if running data migration or import

* Typo fix
2023-06-13 12:40:21 +10:00
Oliver
9117c2234b Migration fixes (#5025)
* Catch exception on is_worker_running

- Exception may occur if table is not yet available
- If it *does* then we can assume the worker is no running

* General error catch in offload_task

* Pick an earlier migration to run from?

* Update initial common migration

- Handle error on table duplication

* Change target migration file

- Ensure that part MPTT migrations have been applied!

* Fix migration ref

- Need 0025
- Price field needs to be available
2023-06-13 07:34:56 +10:00
Oliver
89ad8312ce Fix migration order (#5027)
- Ensure stock.0094 runs before company.0059
- Ref https://github.com/inventree/InvenTree/pull/4984
- Ideally addresses historical migration issues
2023-06-13 07:34:41 +10:00
Oliver
8ca02cb105 Catch exception (#5008)
* Catch exception

* Update settings.py

Don't print out exception message, just log error

* Update settings.py

Style fixes

* Update settings.py

Remove error message

* Update settings.py

Remove logger
2023-06-12 21:03:08 +10:00
Matthias Mair
2f98ed7022 remove signatur checking (we do not use included git anymore) (#5018) 2023-06-12 20:10:13 +10:00
Matthias Mair
9f56ee1023 [FR] Add Feature flags (#4982)
* make currency choices independend

* Remove check for field, just try to get rid of it

* Add IF EXISTS to avoid error (works in postgres)

* Look for operational error, not programming error

* Use variants, depending on errors caused

* [FR] Add Feature flags
Fixes #4965

* Add option to define custom flags

* Revert "make currency choices independend"

This reverts commit ab84a7ff83.

* try fixing mysql

* more safeguards

* fix executioner call

* a fck

* use migrations. syntax

* and another round for mysql

* revert print change

* use UTC for datetime

* Update part.migrations.0112

- Add custom migration class which handles errors

* Add unit test for migration

- Ensure that the new fields are added to the model

* Update reference to PR

* fix ruleset for missing_models

* fix ruleset for flags_flagstate

* add API endpoints for flags

* add tests for new API endpoints

* fix tests

* fix merge

* fix tests

---------

Co-authored-by: martin <martin@iggland.com>
Co-authored-by: Oliver Walters <oliver.henry.walters@gmail.com>
2023-06-12 13:13:53 +10:00
Matthias Mair
15ab911da6 Add more version information (#5014)
* Add info for installer, branch, publisher

* include version info

* fix permissions

* fix typo

* add more info

* use GH url

* fix node

* remove publisher

* remove more logging

* Add info to version view

* enable branch discovery when not set

* fix ref to github version

* add branch to about dialog

* use precise plattform information - if no env is set

* fix ref names

* load VERSION info

* rename plattform to target

* extend installer var

* add generic plattform info

* add docs for version info

* add more info

* add installer codes

* Ammend navigation
2023-06-12 00:32:03 +10:00
Matthias Mair
f3a13fc625 Add SBOM to container (#5017)
* Add sbom to docker

* Enable manual release

* remove dispatch
2023-06-11 23:37:03 +10:00
Oliver
aebff26ad3 New Crowdin updates (#4976)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-06-10 19:56:44 +10:00
Matthias Mair
d710efb64b remove concurrency checks (#5002)
Fixes https://github.com/inventree/InvenTree/issues/4999
2023-06-09 10:30:03 +10:00
Matthias Mair
5d1d8ec889 Refactor states/status (#4857)
* add file for states

* move general definition out

* add some tests and docs

* add tests for invalid definitions

* make status_label tag generic

* move templatetags

* remove unused tag

* rename test file

* make status label a lookup

* rename tags

* move import structure

* add missing tag

* collect states dynamically

* fix context function

* move api function out

* add tests for tags

* rename tests

* refactor imports

* Add test for API function

* improve errors and add tests for imporved errors

* make test calls simpler

* refactor definitions to use enums

* switch to enum

* refactor definitions to use enums

* fix lookup

* fix tag name

* make _TAG lookup a function

* cleanup BaseEnum

* make _TAG definition simpler

* restructure status codes to enum

* reduce LoC

* type status codes as int

* add specific function for template context

* Add definition for lookups

* fix filter lookup

* TEST: "fix" action lookup

* Add missing migrations

* Make all group code references explict

* change default on models to value

* switch to IntEnum

* move groups into a seperate class

* only request _TAG if it exsists

* use value and list

* use dedicated groups

* fix stock assigment

* fix order code

* more fixes

* fix borked change

* fix render lookup

* add group

* fix import

* fix syntax

* clenup

* fix migrations

* fix typo

* fix wrong value usage

* fix test

* remove group section

* remove group section

* add more test cases

* Add more docstring

* move choices out of migrations

* change import ordeR?

* last try before I revert

* Update part.migrations.0112

- Add custom migration class which handles errors

* Add unit test for migration

- Ensure that the new fields are added to the model

* Update reference to PR

---------

Co-authored-by: Oliver Walters <oliver.henry.walters@gmail.com>
2023-06-09 10:27:26 +10:00
Matthias Mair
005c8341bf Improve devdocs (#4813)
* move devcontainer docs

* rename bare metal pages

* fix backlinks

* Add getting started for devs

* add mermaid

* include contrib in docs

* use another plugin

* include everything

* update doc checks

* fix install command

* remove mermaid

* remove inclusion tag

* remove empty list item

* readd include-markdown

* use non-conflicting syntax for include

* fix table rendering in mkdocs

* make controbuting embedable

* re-add mermaid

* remove empty section

* resturcture

* remove mermaid again
2023-06-09 10:16:30 +10:00
Oliver
280f6241dd Migration fix (#5006)
- Ensure initial django_q migrations are applied
- Why on earth is this failing now?
2023-06-09 10:09:02 +10:00
Oliver
f1031efa93 CSS fix for 2FA token (#5000)
- Enforce white background for the QR code
2023-06-09 01:09:12 +10:00
Matthias Mair
e807339c55 respect timezone when handeling git commit times (#4997) 2023-06-08 22:00:54 +10:00
Oliver
bae1c239e8 Fix for forms.js (#4996)
- Fixes subtle bug introduced in recent javascript linting fix
- Prevented error messages from displaying on modal forms
- The whole thing needs to be razed and rebuilt
2023-06-08 21:30:10 +10:00
Oliver
842d7a93d5 Fix for faulty data migrations (#4987)
* Update part.migrations.0112

- Add custom migration class which handles errors

* Add unit test for migration

- Ensure that the new fields are added to the model

* Update reference to PR
2023-06-08 21:12:57 +10:00
Matt Brown
a4b4df5ff4 Place uninstalled items back in stock (#4994)
reset the consumed_by field as well as belongs_to, so an uninstalled
item can be reused.

Fixes: #4992
2023-06-08 20:22:50 +10:00
Oliver
81413e02c4 Update CONTRIBUTING.md (#4988)
Add notes about "migration" tag
2023-06-07 22:15:59 +10:00
Matthias Mair
d7d3d8aa26 Currency migrations - stop migrations when defaults change (#4975)
* make currency choices independend

* replace hard coded default currency

* use function for psql?

* use callable default

* revert some fields

* also migrat all currency codes
2023-06-07 20:05:37 +10:00
Oliver
192c1ecb21 Add note on SSO behind proxy (#4985) 2023-06-07 11:55:02 +10:00
Oliver
a3150d9cb3 Refactor label API code (#4978)
* Refactor label API code

- Add common base class for serializers

* More import cleanup
2023-06-06 18:49:19 +10:00
Oliver
f65281c801 Migration bug fix 2 (#4977)
* Additional migration fix:

- In #4961 we did not notice that the migration files had been renamed
- There is still a chance that a production db (running from master) has a corrupted set of migrations
- Check if the duplicate columns already exist
- If they do, delete them first

* Typo fix

* Add PR reference
2023-06-06 15:45:24 +10:00
Oliver
ba24ff570a SSO bug fix (#4972)
* Catch SSO error

- If social application is not assigned to at least one site, errors happen
- Check if at least one site is enabled

* Docs updates

* Typo fix
2023-06-05 21:03:16 +10:00
Oliver
3ba1d10fc4 New Crowdin updates (#4950)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-06-05 20:04:16 +10:00
Oliver
a4be6bc90b New tags (#4971)
- feature
- experimental
2023-06-05 19:41:42 +10:00
Matthias Mair
58a33c2e67 [FR] Switch from git to dulwich (#4966)
* [FR] Switch from pure git to dulwich Fixes #4942

* fix lenght

* change length again

* change length again
2023-06-05 19:27:46 +10:00
dependabot[bot]
2ed7eefa27 Bump cryptography from 40.0.1 to 41.0.0 (#4955)
* Bump cryptography from 40.0.1 to 41.0.0

Bumps [cryptography](https://github.com/pyca/cryptography) from 40.0.1 to 41.0.0.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/40.0.1...41.0.0)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* bump everything

* do not build for linux/arm/v7
see https://github.com/inventree/InvenTree/pull/4955#issuecomment-1575909025

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matthias Mair <code@mjmair.com>
2023-06-05 19:27:27 +10:00
Oliver
d8965c6c2b Prevent div-by-zero error (#4967)
- Div-by-zero could occur when calculating how many items can be built for a part
- Might result if (somehow) the BomItem has a quantity of zero
2023-06-05 13:40:50 +10:00
Oliver
45ec7b9728 Enable and disable plugins via the API (#4964)
* Cleanup plugin settings page

- Template adjustments

* Activate plugin directly via API

* Update plugin activate endpoint

- Allow plugin to be deactivated also
- Default value = True if not provided

* Update front-end / js

- Allow same JS method to either enable or disable a plugin

* Hide info for plugins which are not active

* remove duplicated column

* Tweak serializer docstring

* Fix typo

* Add extra data to plugin serializer

- is_builtin
- is_sample

* Some backend cleanup

- Don't stringify null values
- Don't replace None with "Unavailable"

* front-end table for rendering plugins

* Change default sorting

- Show active plugins first

* Fix button callback

* Remove old template

* JS linting

* More linting
2023-06-05 12:19:56 +10:00
Matthias Mair
0c47552199 Add openssf badge (#4558) 2023-06-04 00:47:06 +10:00
Oliver
2ca9e0e574 Fix for improper migrations (#4961)
- Order of migrations had been changed
- Resulted in conflicting database state
- Never ever do this!
- Ref: https://github.com/inventree/InvenTree/pull/4898
2023-06-04 00:26:18 +10:00
Matthias Mair
21ed4b2081 Fix common spelling mistakes (#4956)
* add codespell

* first fixes

* doc fixes

* fix docstrings and comments

* functional changes

* docstrings again

* and docs again

* rename args

* add ignore

* use pre-commit for filtering instead

* ups

* fix typo in filter
2023-06-04 00:04:52 +10:00
Oliver
5e2bfaa43a Update CONTRIBUTING.md (#4960)
Add documentation on new PR tags
2023-06-03 23:16:39 +10:00
Oliver
b0338e181e Parameter validation via plugin (#4958)
* Expose part parameter validation to plugins

- Allow ValidationMixin plugins to validate part parameter values

* Update sample plugin

* Catch and re-throw error

* Update docs

* Improve plugin docs

* Simplify validation sample

* Calculate numeric value first
2023-06-03 21:27:31 +10:00
miggland
1d85b70313 Add Metadata to more models (#4898)
* Update models: add MetadataMixin

* Fix name of model in Metadata API definition

* Add API endpoints

* Update API version

* Fix syntax

* Add API endpoint for RO, RO line, RO line extra item

* Add Metadata to Contacts

* Fix link in API version

* Fix name of model

* Fix error?

* Fix error?

* Fix all errors, hopefully..

* Add tests for order, line, extraline metadata

Extend for PO, SO

* Add tests for metadata for Company-related models

* Fix spelling

* Consolidate metadata test for all part models into one test

* Add test for all Stock metadata

* Update stock test_api

* Add all metadata tests for orders

* Fix various errors in tests

* Fix model name

* Add migration files

* Update tests for metadata

* Resolve conflict around API version number

* Rename migration file

* Rename migration file

* Will Contact edit endpoint work better?

* Revert changes in URL definitions

* Remove test, duplicate

* Fix tests with fixed PK, not from fixtures, to use a dynamic PK

* Fix migration overlap
2023-06-02 19:26:20 +10:00
Oliver
c0dafe155f Fix for 'available' filter (#4952)
- Available filter also requires "in stock"
2023-06-02 16:37:16 +10:00
Lukas
1df97a7607 Add "can reproduce" checkbox to bug template (#4953)
* Added can-reproduce selection

* Update bug_report.yaml

* Update bug_report.yaml

* Update bug_report.yaml

* Update bug_report.yaml

* Update bug_report.yaml

* Update bug_report.yaml

* Update bug_report.yaml
2023-06-02 16:35:17 +10:00
Oliver
812b256e08 Shorten displayed linked by default (#4951) 2023-06-02 14:27:15 +10:00
Oliver
2c3ba6e528 Update action.md (#4949)
Fix path for action plugin docs
2023-06-02 09:35:56 +10:00
miggland
936c8ad7fc Metadata bugfixes (#4947)
* Fix name of model in Metadata API definition

* Add API endpoint for RO, RO line, RO line extra item

* Update InvenTree/order/api.py

Thanks @matmair - missed that I changed this later on

Co-authored-by: Matthias Mair <code@mjmair.com>

---------

Co-authored-by: Matthias Mair <code@mjmair.com>
2023-06-02 07:54:55 +10:00
Oliver
60f2f1ea86 Report files (#4946)
* Adds more report tags

- uploaded_file: Load qualified path for a media file
- encode_svg_image: Encode an svg image as base64 data

* Docs updates
2023-06-02 07:54:15 +10:00
miggland
11c5ce5f80 Test flexibility (#4945)
* Add option to specify which tests to run in invoke test

* Add information on testing in CONTRIBUTING.md
2023-06-01 23:54:06 +10:00
Lukas
4d9e92011e Fix/settings bugs and added model SettingKeyType typing (#4944)
* fix .gitignore and spelling issues

* Fix setting get not cached correctly

* Add model to settings key type

* Fix plugin setting slug url

* Fix typo

* Fix resetting of related setting field

* Improved model comment
2023-06-01 23:53:06 +10:00
Oliver
037654610e Better rendering for parameter table (#4943)
- Use trueFalseLabel
2023-06-01 20:30:53 +10:00
Miklós Márton
46a808c064 Add user settings to remember the last/favourite label template (#4938)
* Add user settings to remember the last/favourite label template

Fixes #4932

* Remove settings_value from translated templates

Thanks Oliver for the hint!
2023-06-01 20:01:48 +10:00
Oliver
18d9ecd0f4 Tweak svg data cleaning: (#4941)
- Decode data if passed as bytes
2023-06-01 16:47:47 +10:00
Oliver
cb0f0e34d9 Improvements for parameteric part table (#4940)
- Initially only display columns which have data
2023-06-01 16:09:13 +10:00
Oliver
2c58b2fd36 New Crowdin updates (#4939)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-06-01 15:39:39 +10:00
Oliver
e21a5e62b8 Parameter types (#4935)
* Add fields to PartParameterTemplateModel

- checkbox: Is the field a 'checkbox'
- choices: List of valid options

* Update javascript

* Adds unit test for PartParameterTemplate

- Checkbox cannot have units
- Checkbox cannot have choices
- Choices must be unique

* Improve API filtering

- Add "has_choices" filter
- Add "has_units" filter

* Prune dead code

* Update js functions for creating / editing parameters

* Update part parameter form

- Rebuild the "data" field based on the selected template
- Supports "string" / "boolean" / "select"

* Adjust data input based on parameter type

- Choice displays available options
- Checkbox displays boolean switch
- Otherwise displays text input
- Adds more unit testing
- Updates to forms.js for improved functionality

* Calculate numeric value for boolean parameters

* Update docs

* Bump API version
2023-06-01 07:20:11 +10:00
Oliver
2c05e3e74d Improve docs for report helpers (#4933) 2023-05-31 22:05:13 +10:00
Oliver
685cc1fd77 BOM table: remove quantity footer (#4930)
- Summing quantities of different parts does not make sense
- Especially when different parts have units
- i.e. "3 m + 22 litres = ??"
2023-05-31 17:25:51 +10:00
Lukas
99d122baa9 Refactor model helpers into own file (#4927)
* Refactor model helpers into own file to allow helper import when apps not loaded yet

* Import helper functions at module level

* Added missing imports where vscode couldnt help because its no explicit import
2023-05-31 09:18:42 +10:00
Matthias Mair
a196f443a1 Bump deps (#4911)
* bump deps

* Update requirements.txt

* fix pint

* fix reqs

* fix dev reqs
2023-05-31 09:18:02 +10:00
Oliver
92930d475c Fix JS linting workflow (#4915)
* Add bad .js code

- Call function which has not been defined
- Should throw JS lint error

* update eslint

- Move to newer version of eslint
- Change default rules

* Fixes for tables.js

* Fixes for table_filters.js

* Fixes for stock.js

* Fix for sales_order.js and search.js

* Fix return_order.js

* Fixes for purchase_order.js

* More updates

- part.js
- plugin.js
- pricing.js

* Updates

- order.js

* Even morerer updates

- label.js
- modals.js
- model_renderers.js
- news.js
- notification.js

* More, MORE!

- build.js
- company.js
- charts.js
- filters.js
- forms.js
- helpers.js

* Final?

- api.js
- attachment.js
- barcode.js
- bom.js

* Fix 'useless-escape'

* Disable no-useless-escape rule
2023-05-30 22:55:53 +10:00
Oliver
0808382d06 Fix ReturnOrder serializer (#4923)
- Add in missing fields
2023-05-30 21:00:03 +10:00
Oliver
eca2172624 Add Finnish language support (#4921) 2023-05-29 22:05:38 +10:00
Oliver
3205527ebe Change target of PurchaseOrder notification (#4905)
* Change target of PurchaseOrder notification

- Triggered when order is placed
- No longer when order is created

* unit test fixes
2023-05-29 21:04:31 +10:00
Ulices
32331875fe Fix Unink to Unlink (#4919) 2023-05-29 15:56:58 +10:00
Oliver
95755c5453 js fixes (#4918)
- Do not call formatDecimal on a string
2023-05-29 11:36:12 +10:00
Oliver
b842d5ea67 New Crowdin updates (#4910)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-28 22:13:15 +10:00
Oliver
637b195a68 Add name to backport workflow (#4914) 2023-05-28 20:26:39 +10:00
Oliver
60f79a0a23 Add workflow for backporting PRs (#4912)
* Add workflow for backporting PRs

- Time saving for backporting bug fixes to stable branches
- Apply to PRs before closing

* Cleanup .json file
2023-05-28 18:55:06 +10:00
Miklós Márton
0b4a06ae7e Fix completeSalesOrderShipment typo in sales_order.js (#4908) 2023-05-27 09:48:14 +10:00
Miklós Márton
21dafdee8e - Bump djLint to 1.29.0 (from 1.25.0) in the CI linting scripts (#4907)
- Add alt attribute to the img tags in the templates (required by the bump)
2023-05-27 08:52:10 +10:00
Oliver
11f816a787 Fix for migration file (#4901)
- Catch *all* exceptions when migrating

(cherry picked from commit bcd068fc23637d815d7899d549f83b8bd8a26057)
2023-05-26 19:44:32 +10:00
Oliver
01e2376748 New Crowdin updates (#4875)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-26 17:01:21 +10:00
miggland
4d76708bee Add API Calendar for Return Orders (#4899)
* Add RO to calendar output

* Update title and description to be more descriptive

* Add documentation

* Add simple test for RO calendar

* Lint/syntax

* Lint

* Changes to title, description
2023-05-26 17:01:04 +10:00
Oliver
5dd6f18495 Part units (#4854)
* Add validation to part units field

* Add "pack_units" field to the SupplierPart model

* Migrate old units to new units, and remove old field

* Table fix

* Fixture fix

* Update migration

* Improve "hook" for loading custom unit database

* Display part units column in part table

- Also allow ordering by part units
- Allow filtering to show parts which have defined units

* Adds data migration for converting units to valid values

* Add "pack_units_native" field to company.SupplierPart model

* Clean pack units when saving a SupplierPart

- Convert to native part units
- Handle empty units value
- Add unit tests

* Add background function to rebuild supplier parts when a part is saved

- Required to ensure that the "pack_size_native" is up to date

* Template updates

* Sort by native units first

* Bump API version

* Rename "pack_units" to "pack_quantity"

* Update migration file

- Allow reverse migration

* Fix for currency migration

- Handle case where no currencies are provided
- Handle case where base currency is not in provided options

* Adds unit test for data migration

* Add unit test for part.units data migration

- Check that units fields are updated correctly

* Add some extra "default units"

- each / piece
- dozen / hundred / thousand
- Add unit testing also

* Update references to "pack_size"

- Replace with "pack_quantity" or "pack_quantity_native" as appropriate

* Improvements based on unit testing

* catch error

* Docs updates

* Fixes for pricing tests

* Update unit tests for part migrations · 1b6b6d9d

* Bug fix for conversion code

* javascript updates

* JS formatting fix
2023-05-26 16:57:23 +10:00
Matthias Mair
717bb07dcf Possible fix for git messages (#4882)
* yank git stuff out

* fix tests

* Add check for git to reduce warning logs
Fixes #4428

* reverse git removal

* and more resetting
2023-05-26 08:17:32 +10:00
Oliver
433ea4d0de Documentation Improvements (#4894)
* Improvements for version banner

- Display at the bottom of the page
- Simplify text

* Use mkdocs version provider

* use 'navigation.instant'

* Support modern mkdocs features

- Much better navigation and improved features

* Content changes

* Add code to find previous versions

* Remove modern python syntax

* display output codes

* Hide version banner

* Extra debug message

* update terminology.md
2023-05-25 22:42:16 +10:00
Oliver
8268b9b105 Fix for data migration (#4892)
* Fix for data migration

- Catch *all* exceptions
- We don't want an unhandled exception to break data migration

* Add more exception handling

* Fix typo
2023-05-25 10:59:55 +10:00
Matthias Mair
fdd4169cd7 Improve cli experience (#4881)
* fix cli call to get a fully running invoke

* use relativ import
This is importend when imported from outside

* Add version command

* Add more information to version command

* make print easier to understand
2023-05-24 16:34:36 +10:00
Oliver
4079224658 Fix for PurchaseOrder template (#4885)
- Fixes bug which removes javascript incorrectly
2023-05-24 16:18:54 +10:00
Oliver
4d00c471e1 Change debug message for plugin events (#4884)
- Change from .info to .debug for less verbosity
2023-05-24 15:32:49 +10:00
Oliver
09e99e5f75 Remove debug message (#4883)
- Removes an old debug message which was left in by mistake
2023-05-24 13:48:38 +10:00
Matthias Mair
aa2f5e330a Make nginx more clear that changing the port is 'dangerous' (#4880)
* Make nginx more clear that changing the port is 'dangerous'
See https://github.com/inventree/InvenTree/issues/4859#issuecomment-1560025566

* also add note to prod docker-compose
2023-05-24 09:05:16 +10:00
Oliver
91d79dc3ed Improved error handling for email support (#4862)
* Improved error handling for email support

- Prevent email sending if email not configured
- Check for tx email address before sending

(cherry picked from commit de541f811ede030ea5eb3136132731e1dafccc31)

* Update InvenTree/email.py

Co-authored-by: Matthias Mair <code@mjmair.com>

* Update InvenTree/email.py

Co-authored-by: Matthias Mair <code@mjmair.com>

* Fix location of file email.py

* Allow dummy emails in testing

* Provide default email in testing mode

* Fix to get test working

---------

Co-authored-by: Matthias Mair <code@mjmair.com>
2023-05-24 07:33:31 +10:00
Oliver
96b7845d84 Fix for currency migration (#4873)
- Handle case where no currencies are provided
- Handle case where base currency is not in provided options

(cherry picked from commit b1bf086ae9)
2023-05-22 22:59:10 +10:00
Oliver
f76059b2b4 Reorganize FAQ section (#4872) 2023-05-22 20:02:42 +10:00
Oliver
59cbf17b02 Faq docker (#4871)
* Remove stat context variables

* Revert "Remove stat context variables"

This reverts commit 0989c308d0.

* Allow longer timeout for image download tests

* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* docs: Update FAQ section

- Add note regarding permissions for docker directories

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-22 17:17:59 +10:00
Oliver
5992dcdfda Handle ValidationError when creating a new setting (#4868)
- Duplicate keys can occur in some race conditions
- Catch and pass ValidationError
2023-05-22 12:12:02 +10:00
Oliver
63da2ae9f7 New Crowdin updates (#4852)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-22 00:36:23 +10:00
Oliver
8dc45e49cd Fix category filtering for parameteric part table (#4861) 2023-05-21 23:17:28 +10:00
Oliver
9e77b9fc56 Parameter filtering (#4823)
* adds new field 'parameter type' to PartParameterTemplate model

* Move part parameter settings onto their own page

* Add "choices" and "regex" template types

* Adds validation for PartParameter based on template type

* javascript cleanup

* Fix for serializers.py

* Add unit testing for parameter validation

* Add filters

* Rename "type" field to "param_type"

- Should have seen that one coming

* Coerce 'boolean' value to True/False

* table update

* js linting

* Add requirement for "pint" package

* Add validator for physical unit types

- Revert a previous migration which adds "parameter type" and "validator" fields
- These will get implemented later, too much scope creep for this PR
- Add unit test for validation of "units" field

* Update PartParameter model

- Add data_numeric field (will be used later)
- Add MinLengthValidator to data field

* Run validation for part parameter data

- Ensure it can be converted to internal units

* Update admin interface to display partparameter values inline for a part

* Adds validation of part parameter data value

- Also converts to base units, and stores as "numeric" value
- Display "numeric" value in tables
- Create new file conversion.py for data conversion

* Update unit tests and fix some bugs

* Update docstring

* Add units to parameter columns in parameteric part table

* Allow part list to be ordered by a particular parameter value

- Annotate queryset with new "order_by_parameter" method
- Skeleton method for future work

* Bump API version

* Adds unit testing for sorting parts by parameter value

* Update historical data migrations

- Turns out RunPython.noop is a thing?

* Cache the unit registry

- Creating the unit registry takes a significant amount of time
- Construct when first called, and then cache for subsequent hits
- Massive improvement in performance

* Throw error on empty values when converting between units

* Data migration for converting existing part parameter values

* Handle more error cases

* Show parameteric table on top-level part page too

* Unit test for data migration

* Update credits in docs

* Improved error checking

* WIP docs updates

* Fix parameteric table filtering

* remove zoom property

* Fix for import path

* Update parameter docs

* Run background task to rebuild parameters when template changes

* Make "data_numeric" field nullable

- Defaulting to zero is not appropriate, as the actual value may be zero
- Sorting still seems to work just fine

* Fixes for unit test

* More unit test fixes

* Further fixes for unit tests

---------

Co-authored-by: Matthias Mair <code@mjmair.com>
2023-05-19 13:24:55 +10:00
Lukas
cb8ae10280 Refactor: BaseInvenTreeSetting (#4834)
* Added typing for settings

* Refactored common.BaseInvenTreeSetting model to make it more generic

* Use older syntax for union types

* Added protected option to typing

* Remove now unused code

* Remove old 'get_kwargs' method as it is replaced by 'get_filters_for_instance'

* Trigger ci
2023-05-19 09:51:30 +10:00
Oliver
61481b4eb0 New Crowdin updates (#4842)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-18 20:51:44 +10:00
Oliver
327381357b Adds ability to partially scrap build outputs (#4846)
* BuildOrder updates:

- Use batch code generation when creating a new build output
- Allow partial scrapping of build outputs

* Fixes for stock table

* Bump API version

* Update unit tests
2023-05-18 14:04:57 +10:00
IvoLealMCS
120a710ad4 Add InvenTreePluginViewMixin to PartIndex (#4843) 2023-05-18 01:04:37 +10:00
Oliver
d5caa98936 Report helper docs (#4845)
* Add docs for more helper functions

* add docs about dot notation lookup
2023-05-18 01:04:19 +10:00
Oliver
b732b4ceb5 Add docs for more helper functions (#4844) 2023-05-18 00:07:19 +10:00
Oliver
4785f465e8 Cleanup / consolidate unit testing code (#4831)
- Move testing code out of helpers.py
- Create new file unit_test.py
2023-05-17 07:35:26 +10:00
Oliver
f85b378115 New Crowdin updates (#4810)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-17 00:38:05 +10:00
Oliver
5c7303fd53 Update mkdocs.yml (#4828)
Fix spelling error in docs
2023-05-16 22:56:07 +10:00
miggland
98d87c84e3 Shipment delivery date (#4825)
* Add delivery date setting to Shipment model

* Add delivery_date to serializer

* Correct test for is_delivered

* Add relevant fields to API and forms

* Add test

* Increment API version

* Move migration file because of conflict
2023-05-16 21:28:09 +10:00
Oliver
397419f365 Build consume stock (#4817)
* Adds "consumed_by" field to the StockItem model.

- Points to a BuildOrder instance which "consumed" this stock
- Marks item as unavailable
- Allows filtering against build order

* Allow API filtering

* Adds table of "consumed stock items" to build order page

* Update stock table to show "consumed by" stock status

* Add "consumed_by" link to stock item detail

* Optionally add 'buildorder' details to installStockItem method

* Update methodology for completing a build item

- Instead of deleting stock, mark as "consumed by"

* Fix history entry for splitting stock

* Bug fix

* track "consumed_by" field for tracked items also

* Update build docs

* Update allocation documentation

* Update terminology.md

* Unit test updates

* Fix conflicting migrations

* revert change
2023-05-16 21:25:02 +10:00
Oliver
368f615d71 Bug fix for improperly configured SSO provider (#4822)
* Add sso template tags

* Check if SSO provider is valid on login page

* Add warning if SSO method is not correctly configured

* Template tweaks
2023-05-15 15:09:14 +10:00
Oliver
065f3e2404 Small visual tweaks for notifications tab (#4819) 2023-05-15 12:15:01 +10:00
Oliver
3e0b57f10a Add "description" field to extra line items (#4815) 2023-05-15 11:13:38 +10:00
Oliver
caa7b84c3e Allow tag fields to be empty (#4816)
Without this change, editing items (e.g. via the admin interface) fails if the tag field is not empty
2023-05-15 10:57:12 +10:00
Oliver
e2505433a2 Docker build fix (#4811)
* Attempt to fix crypto build on raspbian

* Run CI if requirements files change
2023-05-15 08:27:26 +10:00
Matthias Mair
1b94a271b6 Add faq entry for https://github.com/inventree/InvenTree/issues/4802#issuecomment-1546767130 (#4812) 2023-05-15 07:47:36 +10:00
Oliver
b04053d9b5 New Crowdin updates (#4801)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-14 19:12:53 +10:00
Oliver
1d384572ec Update CSS for table filter tags (#4803) 2023-05-13 22:44:04 +10:00
Oliver
b2ceac2c4a [Feature] Scrap Build Outputs (#4800)
* Update docs for status codes

* Adds API endpoint for scrapping individual build outputs

* Support 'buildorder' reference in stock tracking history

* Add page for build output documentation

* Build docs

* Add example build order process to docs

* remove debug statement

* JS lint cleanup

* Add migration file for stock status

* Add unit tests for build output scrapping

* Increment API version

* bug fix
2023-05-13 22:19:35 +10:00
miggland
634daa2161 Add flag to API which allows using pack size (#4741)
* Add flag to API which allows using pack size when adding stock items manually

* Check for use_pack_size before pop

* Add test data and tests

* Improve data handling

* Add form field for use_pack_size when adding stock

* Add description of pack size to docs

* Don't check for supplier part if it is None

* Move form field to after supplier part, for better logic

* Fix wrong function

* Fix tests

* Adjust purchase price when using pack size

* Adjust help text for purchase price

* Adjust help text for purchase price some more

* Fix tests for purchase price of added stock

* Update api_version.py
2023-05-13 21:32:25 +10:00
Matthias Mair
017ccaa27a Cleanup plugin mixin registry (#4790)
* collect mixins dynamically

* remove unfinsihed option to reorder mixins

* clean up settings

* fix text

* fix mixin lookup

* stupid error

* fix assertations

* use regustered function instead of private dict

* switch to dict for reg

* fix test

* makke sure mixins also works with class

* cleanup

* fix reqs

* fix test assertations
2023-05-12 22:00:25 +10:00
Lukas
17057f4266 Docs/devcontainer (#4787)
* Added empty problemMatchers to prevent vscode from asking

* Added first draft for devcontainer docs

* Add 3rd space to tips

* Fix wording

* Add 4rd space to tips

* Refphased intro text

* Fixed spelling mistakes and added note

* Added dynamic variables for devcontainer

* Added missing containerWorkspaceFolder vars other devcontainer files

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>

* Added note for inventree core intelicense for plugin devs

* Added where is dev data stored question to faq

* Update docs/docs/start/devcontainer.md

* update toc

---------

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
Co-authored-by: Matthias Mair <code@mjmair.com>
2023-05-12 17:13:48 +10:00
Oliver
41cef1a190 Add reload functionality for build item table (#4799)
* Add reload functionality for build item table

* Update requirements.txt file

(cherry picked from commit c976d06ec75d17e4c2fd4e557eb0bda858ae391e)
2023-05-12 14:29:29 +10:00
dependabot[bot]
306f36bff8 Bump django from 3.2.18 to 3.2.19 (#4795)
Bumps [django](https://github.com/django/django) from 3.2.18 to 3.2.19.
- [Commits](https://github.com/django/django/compare/3.2.18...3.2.19)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-10 08:21:59 +10:00
Oliver
57502a1ad8 New Crowdin updates (#4760)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-08 19:09:50 +10:00
miggland
84f8e33269 Add list of pending shipment to home page, with corresponding settings for user (#4775) 2023-05-08 00:25:51 +10:00
Jakob Haufe
89dfb6186f Restore shopping cart icon in BO/Allocate Parts (#4780)
The shopping cart icon for parts already on order was originally
implemented in 1b421fb59a but got broken in 27aa16d55d as the return
value of `makeIconBadge` is discarded.

Additionally, the FontAwesome JS renderer doesn't seem to like
non-empty content for this, so even when adding it back to `icons`,
it didn't get rendered properly. Instead, the count has to be added
to the title.
2023-05-07 22:47:10 +10:00
Lukas
404113d739 Fix metadatalookup (#4784) 2023-05-07 10:02:11 +10:00
Ulices
4510cf2dd6 fix strarting -> starting (#4786) 2023-05-07 10:01:04 +10:00
Oliver
3b3ce81d11 Sales order button fix (#4782)
* Add (empty) set of API filters for contact table

* Fix typo
2023-05-06 18:49:21 +10:00
Oliver
5886415aa7 Extend docs for report filename generation (#4781) 2023-05-06 09:49:32 +10:00
Oliver
09083d2de1 Build filters (#4773)
* Add optional callback to table filtering function

* Add custom filters to build item table
2023-05-06 08:39:10 +10:00
Ulices
434a00b55f fix caluclations->calculations (#4774) 2023-05-06 08:38:48 +10:00
Matthias Mair
1fc22359c7 add missing triage:not-checked to documentation issue (#4778) 2023-05-06 07:46:45 +10:00
Lukas
d416e57ee3 Add option to hide plugin setting from auto-generated plugin setting page (#4767)
* Add option to hide plugin setting from auto-generated plugin setting page

* Change hide to hidden

* Added small note to docs about hidden settings

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>

---------

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2023-05-06 01:14:39 +10:00
Oliver
35d04c0357 Fix for "used in" calculation (#4770)
* Simplify query filtering for determining list of parts which require a component to build

* Fix .devcontainer file

(cherry picked from commit d4bd8ea0a9)

* Catch ValueError
2023-05-06 00:44:49 +10:00
Oliver
d7f75d3ab3 Project code docs (#4772)
* Add page for project code docs

* Add details on project codes

* more docs
2023-05-05 23:59:30 +10:00
Oliver
a0f18d82cb Improve documentation on currency rendering (#4768)
* Improve documentation on currency rendering

* Fix .devcontainer file
2023-05-05 23:43:12 +10:00
Oliver
1c3d037baf Bug fix for zero quantity pricing (#4765)
- Fix default value for formatPriceRange method
- Display empty value in table
2023-05-05 23:08:33 +10:00
Lukas
7793b3505d Fix various devcontainer issues (#4761)
* Fix various devcontainer issues

- fix dubious git ownership during postCreateCommand
- fix gpg signing in container
- fix local gitconfig in container
- add gitlens extension to devcontainer
- enable plugins in devcontainer

* fix: spelling mistake
2023-05-05 22:34:42 +10:00
Oliver
9920c3fd9c Metadata fix (#4725)
* Add 'clean' method to MetadataMixin class

- Ensure that the "metadata" is a valid dict object

* Add "overwrite" option for set_metadata method

* Update unit tests

* full_clean -> clean

* Cleanup

* Fix for MetadataMixin

* Updates for unit tests

* Test
2023-05-05 14:06:19 +10:00
Oliver
c45e66935a Stock transfer same location (#4757)
* Allow stock items to be transferred into the same location

* Add new code when moving into same location

* Update unit test

* Further unit test fixes
2023-05-05 12:55:31 +10:00
Oliver
e7317522a6 New Crowdin updates (#4750)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-05 07:09:24 +10:00
Matthias Mair
f5c2591fd4 Add 'Tag' management (#4367)
* 'Tag' management
Fixes #83

* Add for ManufacturerPart, SupplierPart

* Add tags for StockLocation, StockItem

* fix serializer definition

* add migrations

* update pre-commit

* bump dependencies

* revert updates

* set version for bugbear

* remove bugbear

* readd bugbear remove isort

* and remove bugbear again

* remove bugbear

* make tag fields not required

* add ruleset

* Merge migrations

* fix migrations

* add unittest for detail

* test tag add

* order api

* reduce database access

* add tag modification test

* use overriden serializer to ensuer the manager is always available

* fix typo

* fix serializer

* increae query thershold by 1

* move tag serializer

* fix migrations

* content_types are changing between tests - removing them

* remove unneeded fixture

* Add basic docs

* bump API version

* add api access to the docs

* add python code

* Add tags to search and filters for all models
2023-05-04 09:02:48 +10:00
Oliver
baaa147fd0 Ignore variant stock when performing stocktake (#4752)
* Ignore variant stock when performing stocktake

- Including variant stock can cause duplicate counts of items
- Causes valuation numbers to be incorrect

* Fix unit tests

* Further unit test fixes
2023-05-02 23:57:40 +10:00
Oliver
6da108e031 Bug fix for label printing (#4751)
* Bug fix for label printing

- Do not use "debug mode" when printing using a plugin
- Fixes https://github.com/inventree/InvenTree/issues/4745

* Fix inverted logic
2023-05-02 23:09:41 +10:00
Oliver
3ff217d229 Fix for sales order tables (#4753)
- Allow line items to be allocated after partial shipment
- Fixes https://github.com/inventree/InvenTree/issues/4734
2023-05-02 22:55:53 +10:00
Oliver
be735e4568 Update FUNDING.yml (#4755)
Fix typo
2023-05-02 22:28:32 +10:00
Oliver
52321af962 Add github link to funding.yml (#4754) 2023-05-02 22:25:26 +10:00
Oliver
61d613ff34 Base URL configuration options (#4749)
* Improve construct_absolute_url method

- Look for hard-coded site URL if provided
- Otherwise look for specified site URL
- Otherwise look at the provided request object

* Refactor existing code which used base URL setting

* Update docs

* Validate that a provided base URL is valid
2023-05-02 22:14:57 +10:00
Matthias Mair
10c3d101e8 Replace mklint with djlint (#4746)
* remove markuplint

* remove dedicated html step - will be done by pre-commit

* add djlint for django template linting

* Fix T003: Endblock should have name

* Fix H013: Img tag should have an alt attribute

* Fix H014: Found extra blank lines

* Fix T003: Endblock should have name

* Fix H013: Img tag should have an alt attribute

* small fixes

* Fix T001: Variables should be wrapped in a single whitespace

* Fix T003: Endblock should have name

* small fixes

* fix form method

* add entry to contributing

* fix template changes

* another fix

* use current version
2023-05-02 20:03:52 +10:00
Oliver
09fabff551 Fix filters for part variant table (#4748)
- Slight adjustment to HTML tags
- Buttons were not displaying correctly inline
2023-05-02 14:47:02 +10:00
Oliver
149c4df231 Remove restriction on manual stock item installation (#4747)
- Allow stock item to be manually inside an item which is iteself installed in an item
- No practical reason for this limitation to exist
2023-05-02 14:46:54 +10:00
Oliver
d9864fce69 New Crowdin updates (#4737)
* updated translation base

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-02 14:46:46 +10:00
Oliver
20d8c2b4e6 Include error type when an error occurs updating exchange rates (#4728)
- Will allow us to further introspect errors received by sentry
2023-05-02 09:34:03 +10:00
Lavissa
6bd95f3b15 Link changes and in-table clipboards (#4697)
* Add clipboard to tables and external link changes

* Clipboard icon added to tables for screens >1200px wide. Enables
  copying of SKU/MPN/IPN from table cells where these otherwise are
  hyperlinks

* External links now open in new tabs with noreferrer

* Move external links into separate template

* All statically rendered external links have been moved out to a new
  template.
2023-05-02 08:13:50 +10:00
Miklós Márton
0b8feb2c4a Display proper printing target name in the label printing modal dialog (#4727)
Fix #4717
2023-05-02 07:59:54 +10:00
Matthias Mair
e5e1a09b45 CI cleanup (#4744)
* merge doc checks into main QC checks

* rename

* well both APIs are closed now so this would be useless

* make header more concise

* add check if server code changed - make run conditional

* reformat
2023-05-02 07:58:57 +10:00
Matthias Mair
21e0679cb9 [FR] Remove Gitpod integration (#4740)
Fixes #4729
2023-05-02 07:39:38 +10:00
Matthias Mair
5e99e54bbc Update README.md with digitalocean info (#4742)
* Update README.md with digitalocean info

* update link for DO in docs
2023-05-01 19:42:14 +02:00
Oliver
be856c3682 Scan in items fix (#4738)
* Clean up comments

* Bug fix

- Pass options.modal through if provided when creating new barcodeDialog

* Fix for scanning from item context into location
2023-05-01 23:50:20 +10:00
Oliver
ad4acef459 Plugin metadata docs (#4724)
* Add page for model metadata

* Add intro section

* Add info on direct access

* more updates
2023-05-01 23:33:44 +10:00
Matthias Mair
08c4aa4998 Make sure plugins are always on a new line (#4721)
* "Install Plugin" via GUI fails to add plugin to plugins.txt correctly for first plugin
Fixes #4719

* remove old change as it was not the root issue

* make sure plugins are always written on a new line
2023-05-01 23:26:23 +10:00
Oliver
c5ba632463 Use coveralls github action (#4735) 2023-05-01 22:13:35 +10:00
Matthias Mair
abee2cee88 Code style improvements (#4683)
* fix list comps

* mopre comp fixes

* reduce computing cost on any() calls

* add bugbear

* check for clean imports

* only allow limited relative imports

* fix notification method lookup

* fix notification method assigement

* rewrite assigment

* fix upstream changes to new style

* fix upstream change to new coding style
2023-04-28 20:49:53 +10:00
Matthias Mair
660a4f8e39 bump deps (#4692)
* bump deps

* add markuplint setting to disable character-reference
2023-04-28 20:12:13 +10:00
Oliver
f6831558a4 Fix export of order data (#4714)
* Adds extra unit test for exporting sales orders

- Exporting sales orders to .xls currently throws exception

* Fix 'total_price' field when exporting orders

* Fix for unit test
2023-04-28 06:54:50 +10:00
Oliver
f6021c4749 sentry.io improvements (#4712)
* Write function to catch sentry.io events before sending

- Will let us ignore certain types of errors which we are not interested in

* Cleanup

* Include release info

* Allow sentry reporting in debug mode

* Consolidate DRF sentry code into InvenTree/sentry.py

* Add more error types to ignore

* update docs
2023-04-28 06:54:31 +10:00
miggland
8d28fc06be Improve text when no sub-categories found (#4711)
Avoids confusion when looking for parts
2023-04-27 22:57:16 +10:00
Oliver
e2c3b28640 Company docs updates (#4710)
* Refactor order documentation into simpler consolidated pages

* Reorganize images

* Add documentation on "Contact"

* Add imgae
2023-04-27 22:27:21 +10:00
Oliver
bb860227c8 New Crowdin updates (#4705)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-27 14:43:54 +10:00
Oliver
82e98dffb8 Add custom template loader class (#4706)
- Specifically to ignore caching for generated reports and labels
- Fixes https://github.com/inventree/InvenTree/issues/4611
2023-04-27 13:28:00 +10:00
Matthias Mair
3975a85742 Check if doc links are broken (#4686)
* add check for links

* fix style

* call silently

* add dep

* add path

* ignore readme as it contains necesarry broken links

* fix syntax

* check recursevly

* run in sync

* use linkcheckmd instaead

* use sync

* add requests

* revert fix to check if checks still work

* fix lookup

* fix old link

* run check verbose

* run async

* remove recurse

* introduce error for test

* fix link
2023-04-27 07:24:26 +10:00
Oliver
608f96c723 Add python example for deleting a part (#4703)
* Add python example for deleting a part

* Ignore PRs which are just docs from release notes
2023-04-26 23:18:03 +10:00
Oliver
5fcab2aec3 Specify order currency (#4698)
* Add 'order_currency' to the various external order models

- By default will use the currency specified for the supplier (or customer)
- Can be specified per order, also

* Display order currency on order pgae

* Add 'order_currency' field

* Enable "blank" currency option (to default to the currency specified by the referenced company

* Fix default currency code when adding line items

* Remove 'total_price_currency' serializer field

- Now replaced with 'order_currency' for greater flexibility

* Bump api_version.py

* Update default order report templates

* Updated docs

* More docs updaes

* Adjust unit tests

* Use 'order_currency' in order tables

* Update purchase order api unit tests
2023-04-26 17:35:15 +10:00
Oliver
36d17c082b Homepage hide inactive (#4700)
* Add user setting for including "inactive" parts in results displayed on homepage

* Adds user setting to hide inactive parts on the homepage

Closes https://github.com/inventree/InvenTree/issues/4688
2023-04-26 15:45:14 +10:00
Oliver
f382d7ef21 Disable httpstat.us tests (#4699)
- Uptime of the eternal service is not great
- For now, disable tests
- Potentially reintroduce these at some point in the future
2023-04-26 14:27:13 +10:00
Oliver
eaa518852c Mark "supplier" field as required on PurchaseOrder serializer (#4693)
- Fixes https://github.com/inventree/InvenTree/issues/4687
2023-04-26 12:35:55 +10:00
Lavissa
011b5915e1 Fix missed translation of overallocation string (#4681)
* Fix missed translation of overallocation string

* Added missing translation prefix on string for Overallocation REJECT choice

* Fix missing translation in part admin panel

* Added missing translation prefix on string for "variant of" column in
  admin panel
2023-04-25 22:41:31 +10:00
Matthias Mair
d7bdcd95a6 Fix plugin meta lookup (#4684)
* fix website lookup

* use the raw data on error
2023-04-24 08:28:50 +10:00
Oliver
5411cf0878 Update README.md (#4679)
Add documentation badge
2023-04-23 00:20:41 +10:00
Jakob Haufe
7537fd1278 Fix sorting by location in stock item table (#4658)
Currently, when sorting the stock item table by location, it gets sorted
by location id.

This changes the sorting criterion to pathstring.
2023-04-23 00:12:03 +10:00
Oliver
8df207d8e1 Extend ReportMixin class to support labels (#4678)
* Extend ReportMixin context to shim label templates also

* Update docs
2023-04-22 23:46:43 +10:00
Oliver
50cbaff76d Fixes for docs building (#4659) 2023-04-22 23:35:25 +10:00
Oliver
2ffd2354eb Documentation integration (#4653)
* Add documentation under docs/ directory

* Add CI workflow for mkdocs configuration checking

* Add documentation issue template

* update pip-tools?

* Update .gitignore files

* Fix .gitignore rules

* Improve release notes page

* remove references to old repo
2023-04-22 22:40:29 +10:00
Oliver
20f01e8741 New Crowdin updates (#4648)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-22 09:11:33 +10:00
Oliver
ad545bad24 Update to report plugin API (#4649)
explicitly add the model instance when allowing plugins to add context data
2023-04-21 15:28:31 +10:00
Oliver
9198b52398 Add ordering and search to ProjectCodeList API endpoint (#4645) 2023-04-20 22:51:47 +10:00
Oliver
0f2fd2f678 New Crowdin updates (#4641)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-20 22:49:31 +10:00
Oliver
91189fbb77 Fix for "focus" field in forms (#4644)
- Reimplements ability to auto-focus fields when launching modal forms
- Can specify with the "focus" option
- Otherwise, will focus on the first available field
2023-04-20 22:40:08 +10:00
Oliver
7bc4de6a92 Fix persist buttons (#4640)
Fixes "persist form" buttons for the following models:

- Part
- PartCategory
- StockItem
- StockLocation

Closes https://github.com/inventree/InvenTree/issues/4491
2023-04-20 22:12:43 +10:00
Oliver
c64ff9d569 Refactor table buttons for printing (#4639)
- Make them to look like the other printing buttons
2023-04-20 22:12:33 +10:00
Oliver
68d1682000 Report Plugins (#4643)
* Add plugin mixin class for extending reports

* Expose report context to the plugin system

* Add an example mixin for adding context data to a report

* Add the 'request' object to the plugin code
2023-04-20 14:21:22 +10:00
Lavissa
a020548c8e Fix typo in Update Check Interval setting (#4642)
Change will create a new translation key
The following locales have added strings to the old key:

DE
Intervall für die Suche nach Updates

HU
Frissítés keresés gyakorisága

PT
Atualizar Verificação de Intervalo
2023-04-20 11:15:01 +10:00
Oliver
070e2afcea Project code support (#4636)
* Support image uploads in the "notes" markdown fields

- Implemented using the existing EasyMDE library
- Copy / paste support
- Drag / drop support

* Remove debug message

* Updated API version

* Better UX when saving notes

* Pin PIP version (for testing)

* Bug fixes

- Fix typo
- Use correct serializer type

* Add unit testing

* Update role permissions

* Typo fix

* Update migration file

* Adds a notes mixin class to be used for refactoring

* Refactor existing models with notes to use the new mixin

* Add helper function for finding all model types with a certain mixin

* Refactor barcode plugin to use new method

* Typo fix

* Add daily task to delete old / unused notes

* Add ProjectCode model

(cherry picked from commit 382a0a2fc32c930d46ed3fe0c6d2cae654c2209d)

* Adds IsStaffOrReadyOnly permissions

- Authenticated users get read-only access
- Staff users get read/write access

(cherry picked from commit 53d04da86c4c866fd9c909d147d93844186470b4)

* Adds API endpoints for project codes

(cherry picked from commit 5ae1da23b2eae4e1168bc6fe28a3544dedc4a1b4)

* Add migration file for projectcode model

(cherry picked from commit 5f8717712c65df853ea69907d33e185fd91df7ee)

* Add project code configuration page to the global settings view

* Add 'project code' field to orders

* Add ability to set / edit the project code for various order models

* Add project code info to order list tables

* Add configuration options for project code integration

* Allow orders to be filtered by project code

* Refactor table_filters.js

- Allow orders to be filtered dynamically by project code

* Bump API version

* Fixes

* Add resource mixin for exporting project code in order list

* Add "has_project_code" filter

* javascript fix

* Edit / delete project codes via API

- Also refactor some existing JS

* Move MetadataMixin to InvenTree.models

To prevent circular imports

(cherry picked from commit d23b013881eaffe612dfbfcdfc5dff6d729068c6)

* Fixes for circular imports

* Add metadata for ProjectCode model

* Add Metadata API endpoint for ProjectCode

* Add unit testing for ProjectCode API endpoints
2023-04-20 00:47:07 +10:00
Oliver
eafd2ac966 Catch error if notes directory does not exist (#4638) 2023-04-19 22:41:19 +10:00
Matthias Mair
1b8ad70fb6 [FR] Refactor plugin registry (#4340)
* add mixin order ref

* move import

* fix import order

* reorder import

* move activation/deactivation to mixins

* move loaded/unloaded mixins out into seperate modules

* fix deactivation sequence

* switch to classmethods for loading

* only run (de)activation if defined for mixin
Fixes #4184

* fix deactivating

* move reloading back to registry

* fix merge error

* move app mixin deactivation

* fix migration reloading

* reverse deactivation sequence

* Revert "reverse deactivation sequence"

This reverts commit aff17dd07d.
2023-04-19 20:54:42 +10:00
Oliver
5cd74c4190 Support image uploads in the "notes" markdown fields (#4615)
* Support image uploads in the "notes" markdown fields

- Implemented using the existing EasyMDE library
- Copy / paste support
- Drag / drop support

* Remove debug message

* Updated API version

* Better UX when saving notes

* Pin PIP version (for testing)

* Bug fixes

- Fix typo
- Use correct serializer type

* Add unit testing

* Update role permissions

* Typo fix

* Update migration file

* Adds a notes mixin class to be used for refactoring

* Refactor existing models with notes to use the new mixin

* Add helper function for finding all model types with a certain mixin

* Refactor barcode plugin to use new method

* Typo fix

* Add daily task to delete old / unused notes

* Bug fix for barcode refactoring

* Add unit testing for function
2023-04-19 13:08:26 +10:00
Matthias Mair
2623c22b7e Remove hashes from dev requirements (#4627)
* Remove hashes from dev requirements
Fixes #4616

* rollback https://github.com/inventree/InvenTree/pull/4617
2023-04-19 07:10:02 +10:00
Matthias Mair
9d5522c18c [FR] Update to OpenAPI from CoreAPI (#4178)
* [FR] Update to OpenAPI from CoreAPI
Fixes #3226

* factor request function out

* add schema export task

* add api-docs

* add action to check if diff occured

* also wait for docstyle

* use full command

* add envs for inventree

* update inventree before running

* use relative path

* remove schema action

* remove tags to fit 3.0 parsers

* fix url base name for reloads

* revert change in plugin resolver

* remove unused tags

* add rapidoc too

* declare api regex

* fix as suggested by @martonmiklos in
https://github.com/inventree/InvenTree/pull/4178#discussion_r1167279443

* set inventree logo

* remove Rapidoc
2023-04-18 23:08:36 +10:00
Oliver
b0f6021002 Implement pagination for stock history tracking API (#4629) 2023-04-18 22:40:31 +10:00
Oliver
ae05c68417 New Crowdin updates (#4624)
* updated translation base

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

* Fix: New translations django.po from Crowdin

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-18 09:40:59 +10:00
Oliver
3e53b60cac Mark as 0.12.0 dev (#4622) 2023-04-18 00:50:51 +10:00
Oliver
f6b9b12745 Translation fixes (#4621)
* Remove HTML tags from translateable string

* Improve another string

* Improve strings for plugin version check

* Cleanup duplicate serials msg

* JS translations

* fmt
2023-04-18 00:12:14 +10:00
1592 changed files with 593921 additions and 169452 deletions

33
.deepsource.toml Normal file
View File

@@ -0,0 +1,33 @@
version = 1
exclude_patterns = [
"docs/docs/javascripts/**", # Docs: Helpers
"docs/ci/**", # Docs: CI
"InvenTree/InvenTree/static/**", # Backend: CUI static files
"ci/**", # Backend: CI
"InvenTree/**/migrations/*.py", # Backend: Migration files
"src/frontend/src/locales/**", # Frontend: Translations
]
test_patterns = ["**/test_*.py", "**/test.py", "**/tests.py"]
[[analyzers]]
name = "shell"
[[analyzers]]
name = "javascript"
[analyzers.meta]
plugins = ["react"]
[[analyzers]]
name = "python"
[analyzers.meta]
runtime_version = "3.x.x"
[[analyzers]]
name = "docker"
[[analyzers]]
name = "test-coverage"
enabled = false

View File

@@ -1,47 +0,0 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.241.1/containers/python-3/.devcontainer/base.Dockerfile
# [Choice] Python version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.10, 3.9, 3.8, 3.7, 3.6, 3-bullseye, 3.10-bullseye, 3.9-bullseye, 3.8-bullseye, 3.7-bullseye, 3.6-bullseye, 3-buster, 3.10-buster, 3.9-buster, 3.8-buster, 3.7-buster, 3.6-buster
ARG VARIANT="3.10-bullseye"
FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT}
# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10
ARG NODE_VERSION="none"
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
# [Optional] If your pip requirements rarely change, uncomment this section to add them to the image.
# COPY requirements.txt /tmp/pip-tmp/
# RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \
# && rm -rf /tmp/pip-tmp
# [Optional] Uncomment this section to install additional OS packages.
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && \
apt-get -y install --no-install-recommends \
git gcc g++ gettext gnupg libffi-dev \
# Weasyprint requirements : https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#debian-11
poppler-utils libpango-1.0-0 libpangoft2-1.0-0 \
# Image format support
libjpeg-dev webp \
# SQLite support
sqlite3 \
# PostgreSQL support
libpq-dev \
# MySQL / MariaDB support
default-libmysqlclient-dev mariadb-client && \
apt-get autoclean && apt-get autoremove
# [Optional] Uncomment this line to install global node packages.
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1
# Update pip
RUN pip install --upgrade pip
# Install required base-level python packages
COPY ./docker/requirements.txt base_requirements.txt
RUN pip install --disable-pip-version-check -U -r base_requirements.txt
# preserve command history between container starts
# Ref: https://code.visualstudio.com/remote/advancedcontainers/persist-bash-history
# Folder will be created in 'postCreateCommand' in devcontainer.json as it's not preserved due to the bind mount
RUN echo "export PROMPT_COMMAND='history -a' && export HISTFILE=/workspaces/InvenTree/dev/commandhistory/.bash_history" >> "/home/vscode/.bashrc"
WORKDIR /workspaces/InvenTree

View File

@@ -1,19 +1,11 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.241.1/containers/python-3
{
"name": "InvenTree",
"build": {
"dockerfile": "Dockerfile",
"context": "..",
"args": {
// Update 'VARIANT' to pick a Python version: 3, 3.10, 3.9, 3.8, 3.7, 3.6
// Append -bullseye or -buster to pin to an OS version.
// Use -bullseye variants on local on arm64/Apple Silicon.
"VARIANT": "3.10-bullseye",
// Options
"NODE_VERSION": "lts/*"
}
},
"name": "InvenTree devcontainer",
"dockerComposeFile": "docker-compose.yml",
"service": "inventree",
"overrideCommand": true,
"workspaceFolder": "/home/inventree/",
// Configure tool-specific properties.
"customizations": {
@@ -21,15 +13,13 @@
"vscode": {
// Set *default* container specific settings.json values on container create.
"settings": {
"python.defaultInterpreterPath": "/workspaces/InvenTree/dev/venv/bin/python",
"python.defaultInterpreterPath": "${containerWorkspaceFolder}/dev/venv/bin/python",
"python.linting.enabled": true,
"python.linting.pylintEnabled": false,
"python.linting.flake8Enabled": true,
"python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8",
"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
"python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf",
"python.linting.banditPath": "/usr/local/py-utils/bin/bandit",
"python.linting.flake8Path": "/usr/local/py-utils/bin/flake8",
"python.linting.mypyPath": "/usr/local/py-utils/bin/mypy",
"python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle",
"python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle",
@@ -40,49 +30,41 @@
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"batisteo.vscode-django"
"batisteo.vscode-django",
"eamodio.gitlens"
]
}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [8000],
"forwardPorts": [5173, 8000, 8080],
"portsAttributes": {
"5173": {
"label": "Vite Server"
},
"8000": {
"label": "InvenTree server"
"label": "InvenTree Server"
},
"8080": {
"label": "mkdocs server"
}
},
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "./.devcontainer/postCreateCommand.sh",
"postCreateCommand": ".devcontainer/postCreateCommand.sh",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode",
"features": {
"git": "os-provided",
"github-cli": "latest"
},
"containerUser": "vscode",
"remoteEnv": {
// InvenTree config
"INVENTREE_DEBUG": "True",
"INVENTREE_DEBUG_LEVEL": "INFO",
"INVENTREE_DB_ENGINE": "sqlite3",
"INVENTREE_DB_NAME": "/workspaces/InvenTree/dev/database.sqlite3",
"INVENTREE_MEDIA_ROOT": "/workspaces/InvenTree/dev/media",
"INVENTREE_STATIC_ROOT": "/workspaces/InvenTree/dev/static",
"INVENTREE_BACKUP_DIR": "/workspaces/InvenTree/dev/backup",
"INVENTREE_CONFIG_FILE": "/workspaces/InvenTree/dev/config.yaml",
"INVENTREE_SECRET_KEY_FILE": "/workspaces/InvenTree/dev/secret_key.txt",
"INVENTREE_PLUGIN_DIR": "/workspaces/InvenTree/dev/plugins",
"INVENTREE_PLUGIN_FILE": "/workspaces/InvenTree/dev/plugins.txt",
// Python config
"PIP_USER": "no",
// used to load the venv into the PATH and avtivate it
// used to load the venv into the PATH and activate it
// Ref: https://stackoverflow.com/a/56286534
"VIRTUAL_ENV": "/workspaces/InvenTree/dev/venv",
"PATH": "/workspaces/InvenTree/dev/venv/bin:${containerEnv:PATH}"
"VIRTUAL_ENV": "${containerWorkspaceFolder}/dev/venv",
"PATH": "${containerWorkspaceFolder}/dev/venv/bin:${containerEnv:PATH}"
}
}

View File

@@ -0,0 +1,40 @@
version: "3"
services:
db:
image: postgres:13
restart: unless-stopped
expose:
- 5432/tcp
volumes:
- inventreedatabase:/var/lib/postgresql/data:z
environment:
POSTGRES_DB: inventree
POSTGRES_USER: inventree_user
POSTGRES_PASSWORD: inventree_password
inventree:
build:
context: ..
target: dev
args:
base_image: "mcr.microsoft.com/vscode/devcontainers/base:alpine-3.18"
data_dir: "dev"
volumes:
- ../:/home/inventree:z
environment:
INVENTREE_DEBUG: True
INVENTREE_DB_ENGINE: postgresql
INVENTREE_DB_NAME: inventree
INVENTREE_DB_HOST: db
INVENTREE_DB_USER: inventree_user
INVENTREE_DB_PASSWORD: inventree_password
INVENTREE_PLUGINS_ENABLED: True
INVENTREE_PY_ENV: /home/inventree/dev/venv
depends_on:
- db
volumes:
inventreedatabase:

View File

@@ -1,14 +1,22 @@
#!/bin/bash
# create folders
mkdir -p /workspaces/InvenTree/dev/{commandhistory,plugins}
cd /workspaces/InvenTree
# Avoiding Dubious Ownership in Dev Containers for setup commands that use git
git config --global --add safe.directory /home/inventree
# create venv
python3 -m venv dev/venv
. dev/venv/bin/activate
python3 -m venv /home/inventree/dev/venv --system-site-packages --upgrade-deps
. /home/inventree/dev/venv/bin/activate
# setup InvenTree server
pip install invoke
inv update
inv setup-dev
# Run initial InvenTree server setup
invoke update -s
# Configure dev environment
invoke setup-dev
# Install required frontend packages
invoke frontend-install
# remove existing gitconfig created by "Avoiding Dubious Ownership" step
# so that it gets copied from host to the container to have your global
# git config in container
rm -f /home/vscode/.gitconfig

View File

@@ -4,7 +4,7 @@ env:
es2021: true
jquery: true
extends:
- google
- eslint:recommended
parserOptions:
ecmaVersion: 12
rules:
@@ -19,6 +19,8 @@ rules:
valid-jsdoc: off
no-multiple-empty-lines: off
comma-dangle: off
no-unused-vars: off
no-useless-escape: off
prefer-spread: off
indent:
- error

10
.github/CODEOWNERS vendored
View File

@@ -1,12 +1,2 @@
# General owner is the maintainers team
* @SchrodingersGat
# plugins are co-owned
/InvenTree/plugin/ @SchrodingersGat @matmair
/InvenTree/plugins/ @SchrodingersGat @matmair
# Installer functions
.pkgr.yml @matmair
Procfile @matmair
runtime.txt @matmair
/contrib/ @matmair

4
.github/FUNDING.yml vendored
View File

@@ -1,2 +1,4 @@
patreon: inventree
github: inventree
ko_fi: inventree
patreon: inventree
custom: [paypal.me/inventree]

View File

@@ -52,6 +52,13 @@ body:
label: "Version Information"
description: "The version info block."
placeholder: "You can get this by going to the `About InvenTree` section in the upper right corner and clicking on the `copy version information` button"
- type: checkboxes
id: can-reproduce
attributes:
label: "Please verify if you can reproduce this bug on the demo site."
description: "You can sign in at [InvenTree Demo](https://demo.inventree.org) with admin:inventree. Note that this instance runs on the latest dev version, so your bug may be fixed there."
options:
- label: "I can reproduce this bug on the demo site."
- type: textarea
id: logs
attributes:

View File

@@ -0,0 +1,15 @@
name: "Documentation"
description: "Create an issue to improve the documentation"
labels: ["documentation", "triage:not-checked"]
body:
- type: markdown
attributes:
value: |
Create a new issue regarding the InvenTree documentation
- type: textarea
id: repro
attributes:
label: Body of the issue
description: Please provide one distinct thing to fix or a clearly defined enhancement
validations:
required: true

View File

@@ -1,5 +1,5 @@
name: 'Migration test'
description: 'Run migration test sequenze'
description: 'Run migration test sequence'
author: 'InvenTree'
runs:

View File

@@ -1,5 +1,5 @@
name: 'Setup Enviroment'
description: 'Setup the enviroment for general InvenTree tests'
description: 'Setup the environment for general InvenTree tests'
author: 'InvenTree'
inputs:
python:
@@ -35,12 +35,12 @@ runs:
using: 'composite'
steps:
- name: Checkout Code
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
# Python installs
- name: Set up Python ${{ env.python_version }}
if: ${{ inputs.python == 'true' }}
uses: actions/setup-python@13ae5bb136fac2878aff31522b9efb785519f984 # pin@v4.3.0
uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # pin@v4.7.1
with:
python-version: ${{ env.python_version }}
cache: pip
@@ -48,21 +48,24 @@ runs:
if: ${{ inputs.python == 'true' }}
shell: bash
run: |
python3 -m pip install pip==23.0.1
pip3 install invoke wheel
python3 -m pip install -U pip
pip3 install invoke wheel uv
- name: Set the VIRTUAL_ENV variable for uv to work
run: echo "VIRTUAL_ENV=${Python_ROOT_DIR}" >> $GITHUB_ENV
shell: bash
- name: Install Specific Python Dependencies
if: ${{ inputs.pip-dependency }}
shell: bash
run: pip3 install ${{ inputs.pip-dependency }}
run: uv pip install ${{ inputs.pip-dependency }}
# NPM installs
- name: Install node.js ${{ env.node_version }}
if: ${{ inputs.npm == 'true' }}
uses: actions/setup-node@969bd2663942d722d85b6a8626225850c2f7be4b # pin to v3.5.0
uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # pin to v3.8.2
with:
node-version: ${{ env.node_version }}
cache: 'npm'
- name: Intall npm packages
- name: Install npm packages
if: ${{ inputs.npm == 'true' }}
shell: bash
run: npm install
@@ -79,12 +82,12 @@ runs:
- name: Install dev requirements
if: ${{ inputs.dev-install == 'true' ||inputs.install == 'true' }}
shell: bash
run: pip install -r requirements-dev.txt
run: uv pip install -r requirements-dev.txt
- name: Run invoke install
if: ${{ inputs.install == 'true' }}
shell: bash
run: invoke install
run: invoke install --uv
- name: Run invoke update
if: ${{ inputs.update == 'true' }}
shell: bash
run: invoke update
run: invoke update --uv

5
.github/release.yml vendored
View File

@@ -4,6 +4,7 @@ changelog:
exclude:
labels:
- translation
- documentation
categories:
- title: Breaking Changes
labels:
@@ -15,7 +16,11 @@ changelog:
- title: New Features
labels:
- Semver-Minor
- feature
- enhancement
- title: Experimental Features
labels:
- experimental
- title: Bug Fixes
labels:
- Semver-Patch

37
.github/workflows/backport.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
# Backport tagged issues to a stable branch.
#
# To enable backporting for a pullrequest, add the label "backport" to the PR.
# Additionally, add a label with the prefix "backport-to-" and the target branch
name: Backport
on:
pull_request_target:
types: [ "labeled", "closed" ]
jobs:
backport:
name: Backport PR
runs-on: ubuntu-latest
if: |
github.event.pull_request.merged == true
&& contains(github.event.pull_request.labels.*.name, 'backport')
&& (
(github.event.action == 'labeled' && github.event.label.name == 'backport')
|| (github.event.action == 'closed')
)
steps:
- name: Backport Action
uses: sqren/backport-github-action@f54e19901f2a57f8b82360f2490d47ee82ec82c6 # pin@v9.2.2
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
auto_backport_label_prefix: backport-to-
add_original_reviewers: true
- name: Info log
if: ${{ success() }}
run: cat ~/.backport/backport.info.log
- name: Debug log
if: ${{ failure() }}
run: cat ~/.backport/backport.debug.log

View File

@@ -8,6 +8,9 @@ on:
branches:
- l10
env:
python_version: 3.9
jobs:
check:
@@ -21,22 +24,15 @@ jobs:
INVENTREE_MEDIA_ROOT: ./media
INVENTREE_STATIC_ROOT: ./static
INVENTREE_BACKUP_DIR: ./backup
python_version: 3.9
steps:
- name: Checkout Code
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- name: Set Up Python ${{ env.python_version }}
uses: actions/setup-python@13ae5bb136fac2878aff31522b9efb785519f984 # pin@v4.3.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Environment Setup
uses: ./.github/actions/setup
with:
python-version: ${{ env.python_version }}
cache: 'pip'
- name: Install Dependencies
run: |
sudo apt-get update
sudo apt-get install gettext
pip3 install invoke
invoke install
install: true
apt-dependency: gettext
- name: Test Translations
run: invoke translate
- name: Check Migration Files

View File

@@ -20,31 +20,51 @@ on:
push:
branches:
- 'master'
# pull_request:
# branches:
# - 'master'
pull_request:
branches:
- 'master'
jobs:
paths-filter:
name: Filter
runs-on: ubuntu-latest
outputs:
docker: ${{ steps.filter.outputs.docker }}
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50 # pin@v2.11.1
id: filter
with:
filters: |
docker:
- .github/workflows/docker.yaml
- docker/**
- docker-compose.yml
- docker.dev.env
- Dockerfile
- requirements.txt
- tasks.py
# Build the docker image
build:
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}
cancel-in-progress: true
runs-on: ubuntu-latest
needs: paths-filter
if: needs.paths-filter.outputs.docker == 'true' || github.event_name == 'release' || github.event_name == 'push'
permissions:
contents: read
packages: write
id-token: write
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
python_version: 3.9
python_version: "3.11"
runs-on: ubuntu-latest # in the future we can try to use alternative runners here
steps:
- name: Check out repo
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Set Up Python ${{ env.python_version }}
uses: actions/setup-python@13ae5bb136fac2878aff31522b9efb785519f984 # pin@v4.3.0
uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # pin@v4.7.1
with:
python-version: ${{ env.python_version }}
- name: Version Check
@@ -56,14 +76,12 @@ jobs:
echo "git_commit_date=$(git show -s --format=%ci)" >> $GITHUB_ENV
- name: Build Docker Image
# Build the development docker image (using docker-compose.yml)
run: |
docker-compose build --no-cache
run: docker-compose build --no-cache
- name: Update Docker Image
run: |
docker-compose run inventree-dev-server invoke update
docker-compose run inventree-dev-server invoke setup-dev
docker-compose up -d
docker-compose run inventree-dev-server pip install --upgrade setuptools
docker-compose run inventree-dev-server invoke wait
- name: Check Data Directory
# The following file structure should have been created by the docker image
@@ -81,26 +99,30 @@ jobs:
run: |
echo "GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}" >> docker.dev.env
docker-compose run inventree-dev-server invoke test --disable-pty
docker-compose run inventree-dev-server invoke test --migrations --disable-pty
docker-compose down
- name: Clean up test folder
run: |
rm -rf InvenTree/_testfolder
- name: Set up QEMU
if: github.event_name != 'pull_request'
uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # pin@v2.1.0
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # pin@v3.0.0
- name: Set up Docker Buildx
if: github.event_name != 'pull_request'
uses: docker/setup-buildx-action@95cb08cb2672c73d4ffd2f422e6d11953d2a9c70 # pin@v2.1.0
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # pin@v3.0.0
- name: Set up cosign
if: github.event_name != 'pull_request'
uses: sigstore/cosign-installer@7cc35d7fdbe70d4278a0c96779081e6fac665f88 # pin@v2.8.0
uses: sigstore/cosign-installer@11086d25041f77fe8fe7b9ea4e48e3b9192b8f19 # pin@v3.1.2
- name: Login to Dockerhub
if: github.event_name != 'pull_request'
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # pin@v2.1.0
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # pin@v3.0.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log into registry ghcr.io
if: github.event_name != 'pull_request'
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # pin@v2
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # pin@v3.0.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -109,7 +131,7 @@ jobs:
- name: Extract Docker metadata
if: github.event_name != 'pull_request'
id: meta
uses: docker/metadata-action@12cce9efe0d49980455aaaca9b071c0befcdd702 # pin@v4.1.0
uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # pin@v5.0.0
with:
images: |
inventree/inventree
@@ -118,11 +140,13 @@ jobs:
- name: Build and Push
id: build-and-push
if: github.event_name != 'pull_request'
uses: docker/build-push-action@c56af957549030174b10d6867f20e78cfd7debc5 # pin@v3.2.0
uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # pin@v5.0.0
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7
platforms: linux/amd64,linux/arm64
push: true
sbom: true
provenance: false
target: production
tags: ${{ env.docker_tags }}
build-args: |
@@ -133,5 +157,4 @@ jobs:
if: ${{ false }} # github.event_name != 'pull_request'
env:
COSIGN_EXPERIMENTAL: "true"
run: cosign sign ${{ steps.meta.outputs.tags }}@${{
steps.build-and-push.outputs.digest }}
run: cosign sign ${{ steps.meta.outputs.tags }}@${{ steps.build-and-push.outputs.digest }}

View File

@@ -1,15 +1,12 @@
# Checks for each PR / push
name: QC checks
name: QC
on:
push:
branches-ignore:
- l10*
branches-ignore: [ 'l10*' ]
pull_request:
branches-ignore:
- l10*
branches-ignore: [ 'l10*' ]
env:
python_version: 3.9
@@ -25,28 +22,43 @@ env:
INVENTREE_BACKUP_DIR: ../test_inventree_backup
jobs:
pep_style:
name: Style [Python]
runs-on: ubuntu-20.04
paths-filter:
name: Filter
runs-on: ubuntu-latest
outputs:
server: ${{ steps.filter.outputs.server }}
migrations: ${{ steps.filter.outputs.migrations }}
frontend: ${{ steps.filter.outputs.frontend }}
api: ${{ steps.filter.outputs.api }}
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- name: Enviroment Setup
uses: ./.github/actions/setup
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50 # pin@v2.11.1
id: filter
with:
dev-install: true
- name: Run flake8
run: flake8 InvenTree --extend-ignore=D
filters: |
server:
- 'InvenTree/**'
- 'requirements.txt'
- 'requirements-dev.txt'
migrations:
- '**/migrations/**'
- '.github/workflows**'
api:
- 'InvenTree/InvenTree/api_version.py'
frontend:
- 'src/frontend/**'
javascript:
name: Style [JS]
name: Style - Classic UI [JS]
runs-on: ubuntu-20.04
needs: pep_style
needs: [ 'pre-commit' ]
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- name: Enviroment Setup
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Environment Setup
uses: ./.github/actions/setup
with:
npm: true
@@ -60,32 +72,16 @@ jobs:
python InvenTree/manage.py prerender
npx eslint InvenTree/InvenTree/static_i18n/i18n/*.js
html:
name: Style [HTML]
runs-on: ubuntu-20.04
needs: pep_style
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- name: Enviroment Setup
uses: ./.github/actions/setup
with:
npm: true
install: true
- name: Check HTML Files
run: npx markuplint **/templates/*.html
pre-commit:
name: Style [pre-commit]
runs-on: ubuntu-20.04
needs: pep_style
needs: paths-filter
if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.frontend == 'true'
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Set up Python ${{ env.python_version }}
uses: actions/setup-python@13ae5bb136fac2878aff31522b9efb785519f984 # pin@v4.3.0
uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # pin@v4.7.1
with:
python-version: ${{ env.python_version }}
cache: 'pip'
@@ -96,6 +92,117 @@ jobs:
pip install requests
python3 ci/version_check.py
mkdocs:
name: Style [Documentation]
runs-on: ubuntu-20.04
needs: paths-filter
steps:
- name: Checkout Code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Set up Python ${{ env.python_version }}
uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # pin@v4.7.1
with:
python-version: ${{ env.python_version }}
- name: Check Config
run: |
pip install pyyaml
pip install -r docs/requirements.txt
python docs/ci/check_mkdocs_config.py
- name: Check Links
uses: gaurav-nelson/github-action-markdown-link-check@v1
with:
folder-path: docs
config-file: docs/mlc_config.json
check-modified-files-only: 'yes'
use-quiet-mode: 'yes'
schema:
name: Tests - API Schema Documentation
runs-on: ubuntu-20.04
needs: paths-filter
if: needs.paths-filter.outputs.server == 'true'
env:
INVENTREE_DB_ENGINE: django.db.backends.sqlite3
INVENTREE_DB_NAME: ../inventree_unit_test_db.sqlite3
INVENTREE_ADMIN_USER: testuser
INVENTREE_ADMIN_PASSWORD: testpassword
INVENTREE_ADMIN_EMAIL: test@test.com
INVENTREE_PYTHON_TEST_SERVER: http://localhost:12345
INVENTREE_PYTHON_TEST_USERNAME: testuser
INVENTREE_PYTHON_TEST_PASSWORD: testpassword
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Environment Setup
uses: ./.github/actions/setup
with:
apt-dependency: gettext poppler-utils
dev-install: true
update: true
- name: Export API Documentation
run: invoke schema --ignore-warnings --filename InvenTree/schema.yml
- name: Upload schema
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # pin@v3.1.3
with:
name: schema.yml
path: InvenTree/schema.yml
- name: Download public schema
if: needs.paths-filter.outputs.api == 'false'
run: |
pip install requests >/dev/null 2>&1
version="$(python3 ci/version_check.py only_version 2>&1)"
echo "Version: $version"
url="https://raw.githubusercontent.com/inventree/schema/main/export/${version}/api.yaml"
echo "URL: $url"
curl -s -o api.yaml $url
echo "Downloaded api.yaml"
- name: Check for differences in API Schema
if: needs.paths-filter.outputs.api == 'false'
run: |
diff --color -u InvenTree/schema.yml api.yaml
diff -u InvenTree/schema.yml api.yaml && echo "no difference in API schema " || exit 2
- name: Check schema - including warnings
run: invoke schema
continue-on-error: true
- name: Extract version for publishing
id: version
if: github.ref == 'refs/heads/master' && needs.paths-filter.outputs.api == 'true'
run: |
pip install requests >/dev/null 2>&1
version="$(python3 ci/version_check.py only_version 2>&1)"
echo "Version: $version"
echo "version=$version" >> "$GITHUB_OUTPUT"
schema-push:
name: Push new schema
runs-on: ubuntu-20.04
needs: [paths-filter, schema]
if: needs.schema.result == 'success' && github.ref == 'refs/heads/master' && needs.paths-filter.outputs.api == 'true'
env:
version: ${{ needs.schema.outputs.version }}
steps:
- uses: actions/checkout@v4
with:
repository: inventree/schema
token: ${{ secrets.SCHEMA_PAT }}
- name: Download schema artifact
uses: actions/download-artifact@v3
with:
name: schema.yml
- name: Move schema to correct location
run: |
echo "Version: $version"
mkdir export/${version}
mv schema.yml export/${version}/api.yaml
- uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "Update API schema for ${version}"
python:
name: Tests - inventree-python
runs-on: ubuntu-20.04
@@ -114,16 +221,16 @@ jobs:
INVENTREE_PYTHON_TEST_PASSWORD: testpassword
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- name: Enviroment Setup
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Environment Setup
uses: ./.github/actions/setup
with:
apt-dependency: gettext poppler-utils
dev-install: true
update: true
npm: true
- name: Download Python Code For `${{ env.wrapper_name }}`
run: git clone --depth 1 https://github.com/inventree/${{ env.wrapper_name }}
./${{ env.wrapper_name }}
run: git clone --depth 1 https://github.com/inventree/${{ env.wrapper_name }} ./${{ env.wrapper_name }}
- name: Start InvenTree Server
run: |
invoke delete-data -f
@@ -136,57 +243,44 @@ jobs:
invoke check-server
coverage run -m unittest discover -s test/
docstyle:
name: Style [Python Docstrings]
runs-on: ubuntu-20.04
needs: pre-commit
continue-on-error: true
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- name: Enviroment Setup
uses: ./.github/actions/setup
with:
install: true
- name: Run flake8
run: flake8 InvenTree --statistics
coverage:
name: Tests - DB [SQLite] + Coverage
runs-on: ubuntu-20.04
needs: [ 'javascript', 'html', 'pre-commit' ]
needs: [ 'pre-commit' ]
continue-on-error: true # continue if a step fails so that coverage gets pushed
env:
INVENTREE_DB_NAME: ./inventree.sqlite
INVENTREE_DB_ENGINE: sqlite3
INVENTREE_PLUGINS_ENABLED: true
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- name: Enviroment Setup
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Environment Setup
uses: ./.github/actions/setup
with:
apt-dependency: gettext poppler-utils
dev-install: true
update: true
- name: Coverage Tests
run: invoke coverage
- name: Data Export Test
uses: ./.github/actions/migration
- name: Test Translations
run: invoke translate
- name: Check Migration Files
run: python3 ci/check_migration_files.py
- name: Coverage Tests
run: invoke test --coverage
- name: Upload Coverage Report
run: coveralls
uses: coverallsapp/github-action@3dfc5567390f6fa9267c0ee9c251e4c8c3f18949 # pin@v2.2.3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
postgres:
name: Tests - DB [PostgreSQL]
runs-on: ubuntu-20.04
needs: [ 'javascript', 'html', 'pre-commit' ]
needs: [ 'pre-commit' ]
env:
INVENTREE_DB_ENGINE: django.db.backends.postgresql
@@ -213,12 +307,12 @@ jobs:
- 6379:6379
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- name: Enviroment Setup
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Environment Setup
uses: ./.github/actions/setup
with:
apt-dependency: gettext poppler-utils libpq-dev
pip-dependency: psycopg2 django-redis>=5.0.0
pip-dependency: psycopg django-redis>=5.0.0
dev-install: true
update: true
- name: Run Tests
@@ -230,8 +324,7 @@ jobs:
name: Tests - DB [MySQL]
runs-on: ubuntu-20.04
needs: [ 'javascript', 'html', 'pre-commit' ]
if: github.event_name == 'push'
needs: [ 'pre-commit' ]
env:
# Database backend configuration
@@ -252,14 +345,13 @@ jobs:
MYSQL_USER: inventree
MYSQL_PASSWORD: password
MYSQL_ROOT_PASSWORD: password
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s
--health-retries=3
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
ports:
- 3306:3306
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- name: Enviroment Setup
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Environment Setup
uses: ./.github/actions/setup
with:
apt-dependency: gettext poppler-utils libmysqlclient-dev
@@ -270,3 +362,155 @@ jobs:
run: invoke test
- name: Data Export Test
uses: ./.github/actions/migration
migration-tests:
name: Tests - Migrations [PostgreSQL]
runs-on: ubuntu-latest
needs: paths-filter
if: github.ref == 'refs/heads/master' && needs.paths-filter.outputs.migrations == 'true'
env:
INVENTREE_DB_ENGINE: django.db.backends.postgresql
INVENTREE_DB_NAME: inventree
INVENTREE_DB_USER: inventree
INVENTREE_DB_PASSWORD: password
INVENTREE_DB_HOST: '127.0.0.1'
INVENTREE_DB_PORT: 5432
INVENTREE_DEBUG: info
INVENTREE_PLUGINS_ENABLED: false
services:
postgres:
image: postgres:14
env:
POSTGRES_USER: inventree
POSTGRES_PASSWORD: password
ports:
- 5432:5432
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Environment Setup
uses: ./.github/actions/setup
with:
apt-dependency: gettext poppler-utils libpq-dev
pip-dependency: psycopg
dev-install: true
update: true
- name: Run Tests
run: invoke test --migrations --report
migrations-checks:
name: Tests - Full Migration [SQLite]
runs-on: ubuntu-latest
needs: paths-filter
if: github.ref == 'refs/heads/master' && needs.paths-filter.outputs.migrations == 'true'
env:
INVENTREE_DB_ENGINE: sqlite3
INVENTREE_DB_NAME: /home/runner/work/InvenTree/db.sqlite3
INVENTREE_DEBUG: info
INVENTREE_PLUGINS_ENABLED: false
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
name: Checkout Code
- name: Environment Setup
uses: ./.github/actions/setup
with:
install: true
- name: Fetch Database
run: git clone --depth 1 https://github.com/inventree/test-db ./test-db
- name: Latest Database
run: |
cp test-db/latest.sqlite3 /home/runner/work/InvenTree/db.sqlite3
chmod +rw /home/runner/work/InvenTree/db.sqlite3
invoke migrate
- name: 0.10.0 Database
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
- name: 0.12.0 Database
run: |
rm /home/runner/work/InvenTree/db.sqlite3
cp test-db/stable_0.12.0.sqlite3 /home/runner/work/InvenTree/db.sqlite3
chmod +rw /home/runner/work/InvenTree/db.sqlite3
invoke migrate
- 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
platform_ui:
name: Tests - Platform UI
runs-on: ubuntu-20.04
timeout-minutes: 60
needs: [ 'pre-commit', 'paths-filter' ]
if: needs.paths-filter.outputs.frontend == 'true'
env:
INVENTREE_DB_ENGINE: sqlite3
INVENTREE_DB_NAME: /home/runner/work/InvenTree/db.sqlite3
INVENTREE_DEBUG: True
INVENTREE_PLUGINS_ENABLED: false
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Environment Setup
uses: ./.github/actions/setup
with:
npm: true
install: true
update: true
- name: Set up test data
run: invoke setup-test -i
- name: Install dependencies
run: inv frontend-compile
- name: Install Playwright Browsers
run: cd src/frontend && npx playwright install --with-deps
- name: Run Playwright tests
run: cd src/frontend && npx playwright test
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # pin@v3.1.3
if: always()
with:
name: playwright-report
path: src/frontend/playwright-report/
retention-days: 30
platform_ui_build:
name: Build - UI Platform
runs-on: ubuntu-20.04
timeout-minutes: 60
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Environment Setup
uses: ./.github/actions/setup
with:
npm: true
- name: Install dependencies
run: cd src/frontend && yarn install
- name: Build frontend
run: cd src/frontend && npm run build
- name: Zip frontend
run: |
cd InvenTree/web/static
zip -r frontend-build.zip web/
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # pin@v3.1.3
with:
name: frontend-build
path: InvenTree/web/static/web

View File

@@ -13,15 +13,39 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Checkout Code
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Version Check
run: |
pip install requests
python3 ci/version_check.py
- name: Push to Stable Branch
uses: ad-m/github-push-action@4dcce6dea3e3c8187237fc86b7dfdc93e5aaae58 # pin@master
uses: ad-m/github-push-action@d91a481090679876dfc4178fef17f286781251df # pin@v0.8.0
if: env.stable_release == 'true'
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: stable
force: true
publish-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Environment Setup
uses: ./.github/actions/setup
with:
npm: true
- name: Install dependencies
run: cd src/frontend && yarn install
- name: Build frontend
run: cd src/frontend && npm run build
- name: Zip frontend
run: |
cd InvenTree/web/static/web
zip -r ../frontend-build.zip *
- uses: svenstaro/upload-release-action@1beeb572c19a9242f4361f4cee78f8e0d9aec5df # pin@2.7.0
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: InvenTree/web/static/frontend-build.zip
asset_name: frontend-build.zip
tag: ${{ github.ref }}
overwrite: true

View File

@@ -1,35 +0,0 @@
# Runs on releases
name: Publish release notes
on:
release:
types: [ published ]
jobs:
tweet:
runs-on: ubuntu-latest
steps:
- uses: Eomm/why-don-t-you-tweet@5936bb1fd0096b1c2bbbb7518746638261bb4dae # pin@v1.0.1
with:
tweet-message: "InvenTree release ${{ github.event.release.tag_name }} is out
now! Release notes: ${{ github.event.release.html_url }} #opensource
#inventree"
env:
TWITTER_CONSUMER_API_KEY: ${{ secrets.TWITTER_CONSUMER_API_KEY }}
TWITTER_CONSUMER_API_SECRET: ${{ secrets.TWITTER_CONSUMER_API_SECRET }}
TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }}
TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
reddit:
runs-on: ubuntu-latest
steps:
- uses: bluwy/release-for-reddit-action@4b2d034b5c86a24db24363f1064149a8c2db69b4 # pin@v1.2.0
with:
username: ${{ secrets.REDDIT_USERNAME }}
password: ${{ secrets.REDDIT_PASSWORD }}
app-id: ${{ secrets.REDDIT_APP_ID }}
app-secret: ${{ secrets.REDDIT_APP_SECRET }}
subreddit: InvenTree
title: "InvenTree version ${{ github.event.release.tag_name }} released"
comment: "${{ github.event.release.body }}"

View File

@@ -14,11 +14,10 @@ jobs:
pull-requests: write
steps:
- uses: actions/stale@5ebf00ea0e4c1561e9b43a292ed34424fb1d4578 # pin@v6.0.1
- uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # pin@v8.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue seems stale. Please react to show this is still
important.'
stale-issue-message: 'This issue seems stale. Please react to show this is still important.'
stale-pr-message: 'This PR seems stale. Please react to show this is still important.'
stale-issue-label: 'inactive'
stale-pr-label: 'inactive'

View File

@@ -5,6 +5,10 @@ on:
branches:
- master
env:
python_version: 3.9
node_version: 16
jobs:
build:
@@ -21,20 +25,15 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- name: Set up Python 3.9
uses: actions/setup-python@13ae5bb136fac2878aff31522b9efb785519f984 # pin@v4.3.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Environment Setup
uses: ./.github/actions/setup
with:
python-version: 3.9
- name: Install Dependencies
run: |
sudo apt-get update
sudo apt-get install -y gettext
pip3 install invoke
invoke install
install: true
npm: true
apt-dependency: gettext
- name: Make Translations
run: |
invoke translate
run: invoke translate
- name: Commit files
run: |
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
@@ -43,7 +42,7 @@ jobs:
git add "*.po"
git commit -m "updated translation base"
- name: Push changes
uses: ad-m/github-push-action@4dcce6dea3e3c8187237fc86b7dfdc93e5aaae58 # pin@master
uses: ad-m/github-push-action@d91a481090679876dfc4178fef17f286781251df # pin@v0.8.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: l10

View File

@@ -9,14 +9,13 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3.1.0
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Setup
run: pip install -r requirements-dev.txt
- name: Update requirements.txt
run: pip-compile --output-file=requirements.txt requirements.in -U
- name: Update requirements-dev.txt
run: pip-compile --generate-hashes --output-file=requirements-dev.txt
requirements-dev.in -U
run: pip-compile --generate-hashes --output-file=requirements-dev.txt requirements-dev.in -U
- uses: stefanzweifel/git-auto-commit-action@fd157da78fa13d9383e5580d1fd1184d89554b51 # pin@v4.15.1
with:
commit_message: "[Bot] Updated dependency"

24
.gitignore vendored
View File

@@ -26,6 +26,7 @@ var/
*.egg-info/
.installed.cfg
*.egg
*.DS_Store
# Django stuff:
*.log
@@ -38,15 +39,6 @@ local_settings.py
# Files used for testing
inventree-demo-dataset/
inventree-data/
dummy_image.*
_tmp.csv
inventree/label.pdf
inventree/label.png
inventree/my_special*
_tests*.txt
# Sphinx files
docs/_build
# Local static and media file storage (only when running in development mode)
inventree_media
@@ -69,6 +61,7 @@ secret_key.txt
.idea/
*.code-workspace
.bash_history
.DS_Store
# https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
.vscode/*
@@ -100,7 +93,18 @@ node_modules/
maintenance_mode_state.txt
# plugin dev directory
plugins/
InvenTree/plugins/
# Compiled translation files
*.mo
messages.ts
# Generated API schema file
api.yaml
# web frontend (static files)
InvenTree/web/static
# Generated docs files
docs/docs/api/*.yml
docs/docs/api/schema/*.yml

View File

@@ -1,44 +0,0 @@
tasks:
- name: Setup django
before: |
export INVENTREE_DB_ENGINE='sqlite3'
export INVENTREE_DB_NAME='/workspace/InvenTree/dev/database.sqlite3'
export INVENTREE_MEDIA_ROOT='/workspace/InvenTree/inventree-data/media'
export INVENTREE_STATIC_ROOT='/workspace/InvenTree/dev/static'
export INVENTREE_BACKUP_DIR='/workspace/InvenTree/dev/backup'
export PIP_USER='no'
sudo apt install -y gettext
python3 -m venv venv
source venv/bin/activate
pip install invoke pyyaml
mkdir dev
invoke update
invoke setup-test --ignore-update --path inventree-data
gp sync-done start_server
- name: Start server
init: gp sync-await start_server
command: |
gp sync-await start_server
export INVENTREE_DB_ENGINE='sqlite3'
export INVENTREE_DB_NAME='/workspace/InvenTree/dev/database.sqlite3'
export INVENTREE_MEDIA_ROOT='/workspace/InvenTree/inventree-data/media'
export INVENTREE_STATIC_ROOT='/workspace/InvenTree/dev/static'
export INVENTREE_BACKUP_DIR='/workspace/InvenTree/dev/backup'
source venv/bin/activate
inv server
ports:
- port: 8000
onOpen: open-preview
github:
prebuilds:
master: true
pullRequests: false
pullRequestsFromForks: true
addBadge: true
addLabel: gitpod-ready
addCheck: false

View File

@@ -1,7 +1,7 @@
name: inventree
description: Open Source Inventory Management System
homepage: https://inventree.org
notifications: false
notifications: true
buildpack: https://github.com/mjmair/heroku-buildpack-python#v216-mjmair
env:
- STACK=heroku-20
@@ -15,10 +15,13 @@ env:
- INVENTREE_PLUGIN_FILE=/opt/inventree/plugins.txt
- INVENTREE_CONFIG_FILE=/opt/inventree/config.yaml
after_install: contrib/packager.io/postinstall.sh
before:
- contrib/packager.io/before.sh
dependencies:
- curl
- python3
- python3-venv
- "python3.9 | python3.10 | python3.11"
- "python3.9-venv | python3.10-venv | python3.11-venv"
- "python3.9-dev | python3.10-dev | python3.11-dev"
- python3-pip
- python3-cffi
- python3-brotli

View File

@@ -3,38 +3,71 @@
exclude: |
(?x)^(
InvenTree/InvenTree/static/.*|
InvenTree/locale/.*
InvenTree/locale/.*|
src/frontend/src/locales/.*|
.*/migrations/.*
)$
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: mixed-line-ending
- repo: https://github.com/pycqa/flake8
rev: '6.0.0'
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.2.2
hooks:
- id: flake8
additional_dependencies: [
'flake8-docstrings',
'flake8-string-format',
'pep8-naming ',
]
- repo: https://github.com/pycqa/isort
rev: '5.12.0'
hooks:
- id: isort
- repo: https://github.com/jazzband/pip-tools
rev: 6.12.3
- id: ruff-format
args: [--preview]
- id: ruff
args: [
--fix,
--preview
]
- repo: https://github.com/matmair/ruff-pre-commit
rev: 830893bf46db844d9c99b6c468e285199adf2de6 # uv-018
hooks:
- id: pip-compile
name: pip-compile requirements-dev.in
args: [--generate-hashes, requirements-dev.in, -o, requirements-dev.txt]
args: [requirements-dev.in, -o, requirements-dev.txt, --python-version=3.9]
files: ^requirements-dev\.(in|txt)$
- id: pip-compile
name: pip-compile requirements.txt
args: [requirements.in, -o, requirements.txt]
args: [requirements.in, -o, requirements.txt,--python-version=3.9]
files: ^requirements\.(in|txt)$
- repo: https://github.com/Riverside-Healthcare/djLint
rev: v1.34.1
hooks:
- id: djlint-django
- repo: https://github.com/codespell-project/codespell
rev: v2.2.6
hooks:
- id: codespell
exclude: >
(?x)^(
docs/docs/stylesheets/.*|
docs/docs/javascripts/.*|
docs/docs/webfonts/.* |
src/frontend/src/locales/.* |
)$
- repo: https://github.com/pre-commit/mirrors-prettier
rev: "v4.0.0-alpha.8"
hooks:
- id: prettier
files: ^src/frontend/.*\.(js|jsx|ts|tsx)$
additional_dependencies:
- "prettier@^2.4.1"
- "@trivago/prettier-plugin-sort-imports"
- repo: https://github.com/pre-commit/mirrors-eslint
rev: "v9.0.0-beta.0"
hooks:
- id: eslint
additional_dependencies:
- eslint@^8.41.0
- eslint-config-google@^0.14.0
- eslint-plugin-react@6.10.3
- babel-eslint@6.1.2
- "@typescript-eslint/eslint-plugin@latest"
- "@typescript-eslint/parser"
files: ^src/frontend/.*\.(js|jsx|ts|tsx)$

9
.vscode/launch.json vendored
View File

@@ -14,13 +14,20 @@
"justMyCode": true
},
{
"name": "Python: Django - 3rd party",
"name": "InvenTree Server - 3rd party",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/InvenTree/manage.py",
"args": ["runserver"],
"django": true,
"justMyCode": false
},
{
"name": "InvenTree Frontend - Vite",
"type": "chrome",
"request": "launch",
"url": "http://localhost:5173",
"webRoot": "${workspaceFolder}/src/frontend"
}
]
}

20
.vscode/tasks.json vendored
View File

@@ -1,52 +1,70 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
// the problemMatchers should prevent vscode from asking how it should check the output
"version": "2.0.0",
"tasks": [
{
"label": "worker",
"type": "shell",
"command": "inv worker",
"problemMatcher": [],
},
{
"label": "clean-settings",
"type": "shell",
"command": "inv clean-settings",
"problemMatcher": [],
},
{
"label": "delete-data",
"type": "shell",
"command": "inv delete-data",
"problemMatcher": [],
},
{
"label": "migrate",
"type": "shell",
"command": "inv migrate",
"problemMatcher": [],
},
{
"label": "server",
"type": "shell",
"command": "inv server",
"problemMatcher": [],
},
{
"label": "setup-dev",
"type": "shell",
"command": "inv setup-dev",
"problemMatcher": [],
},
{
"label": "setup-test",
"type": "shell",
"command": "inv setup-test --path dev/inventree-demo-dataset",
"command": "inv setup-test -i --path dev/inventree-demo-dataset",
"problemMatcher": [],
},
{
"label": "superuser",
"type": "shell",
"command": "inv superuser",
"problemMatcher": [],
},
{
"label": "test",
"type": "shell",
"command": "inv test",
"problemMatcher": [],
},
{
"label": "update",
"type": "shell",
"command": "inv update",
"problemMatcher": [],
},
]
}

View File

@@ -1,203 +1,6 @@
Hi there, thank you for your intrest in contributing!
Please read the contribution guidelines below, before submitting your first pull request to the InvenTree codebase.
### Contributing to InvenTree
## Quickstart
Hi there, thank you for your interest in contributing!
Please read our contribution guidelines, before submitting your first pull request to the InvenTree codebase.
The following commands will get you quickly configure and run a development server, complete with a demo dataset to work with:
### Bare Metal
```bash
git clone https://github.com/inventree/InvenTree.git && cd InvenTree
python3 -m venv env && source env/bin/activate
pip install invoke && invoke
pip install invoke && invoke setup-dev --tests
```
### Docker
```bash
git clone https://github.com/inventree/InvenTree.git && cd InvenTree
docker compose run inventree-dev-server invoke install
docker compose run inventree-dev-server invoke setup-test
docker compose up -d
```
Read the [InvenTree setup documentation](https://docs.inventree.org/en/latest/start/intro/) for a complete installation reference guide.
### Setup Devtools
Run the following command to set up all toolsets for development.
```bash
invoke setup-dev
```
*We recommend you run this command before starting to contribute. This will install and set up `pre-commit` to run some checks before each commit and help reduce the style errors.*
## Branches and Versioning
InvenTree roughly follow the [GitLab flow](https://docs.gitlab.com/ee/topics/gitlab_flow.html) branching style, to allow simple management of multiple tagged releases, short-lived branches, and development on the main branch.
### Version Numbering
InvenTree version numbering follows the [semantic versioning](https://semver.org/) specification.
### Master Branch
The HEAD of the "main" or "master" branch of InvenTree represents the current "latest" state of code development.
- All feature branches are merged into master
- All bug fixes are merged into master
**No pushing to master:** New featues must be submitted as a pull request from a separate branch (one branch per feature).
### Feature Branches
Feature branches should be branched *from* the *master* branch.
- One major feature per branch / pull request
- Feature pull requests are merged back *into* the master branch
- Features *may* also be merged into a release candidate branch
### Stable Branch
The HEAD of the "stable" branch represents the latest stable release code.
- Versioned releases are merged into the "stable" branch
- Bug fix branches are made *from* the "stable" branch
#### Release Candidate Branches
- Release candidate branches are made from master, and merged into stable.
- RC branches are targetted at a major/minor version e.g. "0.5"
- When a release candidate branch is merged into *stable*, the release is tagged
#### Bugfix Branches
- If a bug is discovered in a tagged release version of InvenTree, a "bugfix" or "hotfix" branch should be made *from* that tagged release
- When approved, the branch is merged back *into* stable, with an incremented PATCH number (e.g. 0.4.1 -> 0.4.2)
- The bugfix *must* also be cherry picked into the *master* branch.
## Environment
### Target version
We are currently targeting:
| Name | Minimum version |
|---|---|
| Python | 3.9 |
| Django | 3.2 |
### Auto creating updates
The following tools can be used to auto-upgrade syntax that was depreciated in new versions:
```bash
pip install pyupgrade
pip install django-upgrade
```
To update the codebase run the following script.
```bash
pyupgrade `find . -name "*.py"`
django-upgrade --target-version 3.2 `find . -name "*.py"`
```
## Credits
If you add any new dependencies / libraries, they need to be added to [the docs](https://github.com/inventree/inventree-docs/blob/master/docs/credits.md). Please try to do that as timely as possible.
## Migration Files
Any required migration files **must** be included in the commit, or the pull-request will be rejected. If you change the underlying database schema, make sure you run `invoke migrate` and commit the migration files before submitting the PR.
*Note: A github action checks for unstaged migration files and will reject the PR if it finds any!*
## Unit Testing
Any new code should be covered by unit tests - a submitted PR may not be accepted if the code coverage for any new features is insufficient, or the overall code coverage is decreased.
The InvenTree code base makes use of [GitHub actions](https://github.com/features/actions) to run a suite of automated tests against the code base every time a new pull request is received. These actions include (but are not limited to):
- Checking Python and Javascript code against standard style guides
- Running unit test suite
- Automated building and pushing of docker images
- Generating translation files
The various github actions can be found in the `./github/workflows` directory
## Code Style
Sumbitted Python code is automatically checked against PEP style guidelines. Locally you can run `invoke style` to ensure the style checks will pass, before submitting the PR.
Please write docstrings for each function and class - we follow the [google doc-style](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) for python. Docstrings for general javascript code is encouraged! Docstyles are checked by `invoke style`.
## Documentation
New features or updates to existing features should be accompanied by user documentation. A PR with associated documentation should link to the matching PR at https://github.com/inventree/inventree-docs/
## Translations
Any user-facing strings *must* be passed through the translation engine.
- InvenTree code is written in English
- User translatable strings are provided in English as the primary language
- Secondary language translations are provided [via Crowdin](https://crowdin.com/project/inventree)
*Note: Translation files are updated via GitHub actions - you do not need to compile translations files before submitting a pull request!*
### Python Code
For strings exposed via Python code, use the following format:
```python
from django.utils.translation import gettext_lazy as _
user_facing_string = _('This string will be exposed to the translation engine!')
```
### Templated Strings
HTML and javascript files are passed through the django templating engine. Translatable strings are implemented as follows:
```html
{% load i18n %}
<span>{% trans "This string will be translated" %} - this string will not!</span>
```
## Github use
### Tags
The tags describe issues and PRs in multiple areas:
| Area | Name | Description |
|---|---|---|
| Triage Labels | | |
| | triage:not-checked | Item was not checked by the core team |
| | triage:not-approved | Item is not green-light by maintainer |
| Type Labels | | |
| | breaking | Indicates a major update or change which breaks compatibility |
| | bug | Identifies a bug which needs to be addressed |
| | dependency | Relates to a project dependency |
| | duplicate | Duplicate of another issue or PR |
| | enhancement | This is an suggested enhancement or new feature |
| | help wanted | Assistance required |
| | invalid | This issue or PR is considered invalid |
| | inactive | Indicates lack of activity |
| | question | This is a question |
| | roadmap | This is a roadmap feature with no immediate plans for implementation |
| | security | Relates to a security issue |
| | starter | Good issue for a developer new to the project |
| | wontfix | No work will be done against this issue or PR |
| Feature Labels | | |
| | API | Relates to the API |
| | barcode | Barcode scanning and integration |
| | build | Build orders |
| | importer | Data importing and processing |
| | order | Purchase order and sales orders |
| | part | Parts |
| | plugin | Plugin ecosystem |
| | pricing | Pricing functionality |
| | report | Report generation |
| | stock | Stock item management |
| | user interface | User interface |
| Ecosystem Labels | | |
| | demo | Relates to the InvenTree demo server or dataset |
| | docker | Docker / docker-compose |
| | CI | CI / unit testing ecosystem |
| | setup | Relates to the InvenTree setup / installation process |
Refer to our [contribution guidelines](https://docs.inventree.org/en/latest/develop/contributing/) for more information!

View File

@@ -9,14 +9,19 @@
# - Runs InvenTree web server under django development server
# - Monitors source files for any changes, and live-reloads server
FROM python:3.9-slim as inventree_base
ARG base_image=python:3.11-alpine3.18
FROM ${base_image} as inventree_base
# Build arguments for this image
ARG commit_tag=""
ARG commit_hash=""
ARG commit_date=""
ARG commit_tag=""
ARG data_dir="data"
ENV PYTHONUNBUFFERED 1
ENV PIP_DISABLE_PIP_VERSION_CHECK 1
ENV INVOKE_RUN_SHELL="/bin/ash"
ENV INVENTREE_LOG_LEVEL="WARNING"
ENV INVENTREE_DOCKER="true"
@@ -24,7 +29,7 @@ ENV INVENTREE_DOCKER="true"
# InvenTree paths
ENV INVENTREE_HOME="/home/inventree"
ENV INVENTREE_MNG_DIR="${INVENTREE_HOME}/InvenTree"
ENV INVENTREE_DATA_DIR="${INVENTREE_HOME}/data"
ENV INVENTREE_DATA_DIR="${INVENTREE_HOME}/${data_dir}"
ENV INVENTREE_STATIC_ROOT="${INVENTREE_DATA_DIR}/static"
ENV INVENTREE_MEDIA_ROOT="${INVENTREE_DATA_DIR}/media"
ENV INVENTREE_BACKUP_DIR="${INVENTREE_DATA_DIR}/backup"
@@ -43,6 +48,8 @@ ENV INVENTREE_BACKGROUND_WORKERS="4"
ENV INVENTREE_WEB_ADDR=0.0.0.0
ENV INVENTREE_WEB_PORT=8000
ENV VIRTUAL_ENV=/usr/local
LABEL org.label-schema.schema-version="1.0" \
org.label-schema.build-date=${DATE} \
org.label-schema.vendor="inventree" \
@@ -51,44 +58,60 @@ LABEL org.label-schema.schema-version="1.0" \
org.label-schema.vcs-url="https://github.com/inventree/InvenTree.git" \
org.label-schema.vcs-ref=${commit_tag}
# RUN apt-get upgrade && apt-get update
RUN apt-get update
# Install required system packages
RUN apt-get install -y --no-install-recommends \
git gcc g++ gettext gnupg libffi-dev libssl-dev \
# Weasyprint requirements : https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#debian-11
poppler-utils libpango-1.0-0 libpangoft2-1.0-0 \
# Install required system level packages
RUN apk add --no-cache \
git gettext py-cryptography \
# Image format support
libjpeg-dev webp libwebp-dev \
# SQLite support
sqlite3 \
# PostgreSQL support
libpq-dev postgresql-client \
# MySQL / MariaDB support
default-libmysqlclient-dev mariadb-client && \
apt-get autoclean && apt-get autoremove
libjpeg libwebp zlib \
# Weasyprint requirements : https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#alpine-3-12
py3-pip py3-pillow py3-cffi py3-brotli pango poppler-utils openldap \
# Core database packages
postgresql13-client && \
# fonts
apk --update --upgrade --no-cache add fontconfig ttf-freefont font-noto terminus-font && fc-cache -f
# Update pip
RUN pip install --upgrade pip
EXPOSE 8000
# For ARMv7 architecture, add the pinwheels repo (for cryptography library)
RUN mkdir -p ${INVENTREE_HOME}
WORKDIR ${INVENTREE_HOME}
COPY ./docker/requirements.txt base_requirements.txt
COPY ./requirements.txt ./
COPY ./docker/install_build_packages.sh .
RUN chmod +x install_build_packages.sh
# For ARMv7 architecture, add the piwheels repo (for cryptography library)
# Otherwise, we have to build from source, which is difficult
# Ref: https://github.com/inventree/InvenTree/pull/4598
RUN \
if [ `dpkg --print-architecture` = "armhf" ]; then \
printf "[global]\nextra-index-url=https://www.piwheels.org/simple\n" > /etc/pip.conf ; \
fi
RUN if [ `apk --print-arch` = "armv7" ]; then \
printf "[global]\nextra-index-url=https://www.piwheels.org/simple\n" > /etc/pip.conf ; \
fi
# Install required base-level python packages
COPY ./docker/requirements.txt base_requirements.txt
RUN pip install --disable-pip-version-check -U -r base_requirements.txt
COPY tasks.py docker/gunicorn.conf.py docker/init.sh ./
RUN chmod +x init.sh
ENTRYPOINT ["/bin/ash", "./init.sh"]
FROM inventree_base as prebuild
ENV PATH=/root/.local/bin:$PATH
RUN ./install_build_packages.sh --no-cache --virtual .build-deps && \
pip install --user uv --no-cache-dir && pip install -r base_requirements.txt -r requirements.txt --no-cache && \
apk --purge del .build-deps
# Frontend builder image:
FROM prebuild as frontend
RUN apk add --no-cache --update nodejs npm && npm install -g yarn
RUN yarn config set network-timeout 600000 -g
COPY InvenTree ${INVENTREE_HOME}/InvenTree
COPY src ${INVENTREE_HOME}/src
COPY tasks.py ${INVENTREE_HOME}/tasks.py
RUN cd ${INVENTREE_HOME}/InvenTree && inv frontend-compile
# InvenTree production image:
# - Copies required files from local directory
# - Installs required python packages from requirements.txt
# - Starts a gunicorn webserver
FROM inventree_base as production
ENV INVENTREE_DEBUG=False
@@ -97,28 +120,13 @@ ENV INVENTREE_DEBUG=False
ENV INVENTREE_COMMIT_HASH="${commit_hash}"
ENV INVENTREE_COMMIT_DATE="${commit_date}"
# use dependencies and compiled wheels from the prebuild image
ENV PATH=/root/.local/bin:$PATH
COPY --from=prebuild /root/.local /root/.local
# Copy source code
COPY InvenTree ${INVENTREE_HOME}/InvenTree
# Copy other key files
COPY requirements.txt ${INVENTREE_HOME}/requirements.txt
COPY tasks.py ${INVENTREE_HOME}/tasks.py
COPY docker/gunicorn.conf.py ${INVENTREE_HOME}/gunicorn.conf.py
COPY docker/init.sh ${INVENTREE_MNG_DIR}/init.sh
# Need to be running from within this directory
WORKDIR ${INVENTREE_MNG_DIR}
# Drop to the inventree user for the production image
#RUN adduser inventree
#RUN chown -R inventree:inventree ${INVENTREE_HOME}
#USER inventree
# Install InvenTree packages
RUN pip3 install --user --disable-pip-version-check -r ${INVENTREE_HOME}/requirements.txt
# Server init entrypoint
ENTRYPOINT ["/bin/bash", "./init.sh"]
COPY InvenTree ./InvenTree
COPY --from=frontend ${INVENTREE_HOME}/InvenTree/web/static/web ./InvenTree/web/static/web
# Launch the production server
# TODO: Work out why environment variables cannot be interpolated in this command
@@ -127,6 +135,19 @@ CMD gunicorn -c ./gunicorn.conf.py InvenTree.wsgi -b 0.0.0.0:8000 --chdir ./Inve
FROM inventree_base as dev
# Vite server (for local frontend development)
EXPOSE 5173
# Install packages required for building python packages
RUN ./install_build_packages.sh
RUN pip install uv --no-cache-dir && pip install -r base_requirements.txt --no-cache
# Install nodejs / npm / yarn
RUN apk add --no-cache --update nodejs npm && npm install -g yarn
RUN yarn config set network-timeout 600000 -g
# The development image requires the source code to be mounted to /home/inventree/
# So from here, we don't actually "do" anything, apart from some file management
@@ -139,7 +160,7 @@ ENV INVENTREE_PY_ENV="${INVENTREE_DATA_DIR}/env"
WORKDIR ${INVENTREE_HOME}
# Entrypoint ensures that we are running in the python virtual environment
ENTRYPOINT ["/bin/bash", "./docker/init.sh"]
ENTRYPOINT ["/bin/ash", "./docker/init.sh"]
# Launch the development server
CMD ["invoke", "server", "-a", "${INVENTREE_WEB_ADDR}:${INVENTREE_WEB_PORT}"]

View File

@@ -1,17 +1,61 @@
"""Admin classes"""
"""Admin classes."""
from django.contrib import admin
from django.db.models.fields import CharField
from django.http.request import HttpRequest
from djmoney.contrib.exchange.admin import RateAdmin
from djmoney.contrib.exchange.models import Rate
from import_export.exceptions import ImportExportError
from import_export.resources import ModelResource
class InvenTreeResource(ModelResource):
"""Custom subclass of the ModelResource class provided by django-import-export"
"""Custom subclass of the ModelResource class provided by django-import-export".
Ensures that exported data are escaped to prevent malicious formula injection.
Ref: https://owasp.org/www-community/attacks/CSV_Injection
"""
MAX_IMPORT_ROWS = 1000
MAX_IMPORT_COLS = 100
# List of fields which should be converted to empty strings if they are null
CONVERT_NULL_FIELDS = []
def import_data_inner(
self,
dataset,
dry_run,
raise_errors,
using_transactions,
collect_failed_rows,
rollback_on_validation_errors=None,
**kwargs,
):
"""Override the default import_data_inner function to provide better error handling."""
if len(dataset) > self.MAX_IMPORT_ROWS:
raise ImportExportError(
f'Dataset contains too many rows (max {self.MAX_IMPORT_ROWS})'
)
if len(dataset.headers) > self.MAX_IMPORT_COLS:
raise ImportExportError(
f'Dataset contains too many columns (max {self.MAX_IMPORT_COLS})'
)
return super().import_data_inner(
dataset,
dry_run,
raise_errors,
using_transactions,
collect_failed_rows,
rollback_on_validation_errors=rollback_on_validation_errors,
**kwargs,
)
def export_resource(self, obj):
"""Custom function to override default row export behaviour.
"""Custom function to override default row export behavior.
Specifically, strip illegal leading characters to prevent formula injection
"""
@@ -31,3 +75,39 @@ class InvenTreeResource(ModelResource):
row[idx] = val
return row
def get_fields(self, **kwargs):
"""Return fields, with some common exclusions."""
fields = super().get_fields(**kwargs)
fields_to_exclude = ['metadata', 'lft', 'rght', 'tree_id', 'level']
return [f for f in fields if f.column_name not in fields_to_exclude]
def before_import_row(self, row, row_number=None, **kwargs):
"""Run custom code before importing each row.
- Convert any null fields to empty strings, for fields which do not support null values
"""
# We can automatically determine which fields might need such a conversion
for field in self.Meta.model._meta.fields:
if isinstance(field, CharField):
if field.blank and not field.null:
if field.name not in self.CONVERT_NULL_FIELDS:
self.CONVERT_NULL_FIELDS.append(field.name)
for field in self.CONVERT_NULL_FIELDS:
if field in row and row[field] is None:
row[field] = ''
class CustomRateAdmin(RateAdmin):
"""Admin interface for the Rate class."""
def has_add_permission(self, request: HttpRequest) -> bool:
"""Disable the 'add' permission for Rate objects."""
return False
admin.site.unregister(Rate)
admin.site.register(Rate, CustomRateAdmin)

View File

@@ -1,30 +1,130 @@
"""Main JSON interface views."""
import sys
from django.conf import settings
from django.db import transaction
from django.http import JsonResponse
from django.utils.translation import gettext_lazy as _
from django_q.models import OrmQ
from rest_framework import permissions
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework import permissions, serializers
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
from rest_framework.serializers import ValidationError
from rest_framework.views import APIView
import InvenTree.version
import users.models
from InvenTree.filters import SEARCH_ORDER_FILTER
from InvenTree.mixins import ListCreateAPI
from InvenTree.permissions import RolePermission
from part.templatetags.inventree_extras import plugins_info
from InvenTree.templatetags.inventree_extras import plugins_info
from part.models import Part
from plugin.serializers import MetadataSerializer
from users.models import ApiToken
from .mixins import RetrieveUpdateAPI
from .status import is_worker_running
from .version import (inventreeApiVersion, inventreeInstanceName,
inventreeVersion)
from .email import is_email_configured
from .mixins import ListAPI, RetrieveUpdateAPI
from .status import check_system_health, is_worker_running
from .version import inventreeApiText
from .views import AjaxView
class VersionViewSerializer(serializers.Serializer):
"""Serializer for a single version."""
class VersionSerializer(serializers.Serializer):
"""Serializer for server version."""
server = serializers.CharField()
api = serializers.IntegerField()
commit_hash = serializers.CharField()
commit_date = serializers.CharField()
commit_branch = serializers.CharField()
python = serializers.CharField()
django = serializers.CharField()
class LinkSerializer(serializers.Serializer):
"""Serializer for all possible links."""
doc = serializers.URLField()
code = serializers.URLField()
credit = serializers.URLField()
app = serializers.URLField()
bug = serializers.URLField()
dev = serializers.BooleanField()
up_to_date = serializers.BooleanField()
version = VersionSerializer()
links = LinkSerializer()
class VersionView(APIView):
"""Simple JSON endpoint for InvenTree version information."""
permission_classes = [permissions.IsAdminUser]
@extend_schema(responses={200: OpenApiResponse(response=VersionViewSerializer)})
def get(self, request, *args, **kwargs):
"""Return information about the InvenTree server."""
return JsonResponse({
'dev': InvenTree.version.isInvenTreeDevelopmentVersion(),
'up_to_date': InvenTree.version.isInvenTreeUpToDate(),
'version': {
'server': InvenTree.version.inventreeVersion(),
'api': InvenTree.version.inventreeApiVersion(),
'commit_hash': InvenTree.version.inventreeCommitHash(),
'commit_date': InvenTree.version.inventreeCommitDate(),
'commit_branch': InvenTree.version.inventreeBranch(),
'python': InvenTree.version.inventreePythonVersion(),
'django': InvenTree.version.inventreeDjangoVersion(),
},
'links': {
'doc': InvenTree.version.inventreeDocUrl(),
'code': InvenTree.version.inventreeGithubUrl(),
'credit': InvenTree.version.inventreeCreditsUrl(),
'app': InvenTree.version.inventreeAppUrl(),
'bug': f'{InvenTree.version.inventreeGithubUrl()}issues',
},
})
class VersionSerializer(serializers.Serializer):
"""Serializer for a single version."""
version = serializers.CharField()
date = serializers.CharField()
gh = serializers.CharField()
text = serializers.CharField()
latest = serializers.BooleanField()
class Meta:
"""Meta class for VersionSerializer."""
fields = ['version', 'date', 'gh', 'text', 'latest']
class VersionApiSerializer(serializers.Serializer):
"""Serializer for the version api endpoint."""
VersionSerializer(many=True)
class VersionTextView(ListAPI):
"""Simple JSON endpoint for InvenTree version text."""
serializer_class = VersionSerializer
permission_classes = [permissions.IsAdminUser]
@extend_schema(responses={200: OpenApiResponse(response=VersionApiSerializer)})
def list(self, request, *args, **kwargs):
"""Return information about the InvenTree server."""
return JsonResponse(inventreeApiText())
class InfoView(AjaxView):
"""Simple JSON endpoint for InvenTree information.
@@ -34,39 +134,96 @@ class InfoView(AjaxView):
permission_classes = [permissions.AllowAny]
def worker_pending_tasks(self):
"""Return the current number of outstanding background tasks"""
"""Return the current number of outstanding background tasks."""
return OrmQ.objects.count()
def get(self, request, *args, **kwargs):
"""Serve current server information."""
is_staff = request.user.is_staff
if not is_staff and request.user.is_anonymous:
# Might be Token auth - check if so
is_staff = self.check_auth_header(request)
data = {
'server': 'InvenTree',
'version': inventreeVersion(),
'instance': inventreeInstanceName(),
'apiVersion': inventreeApiVersion(),
'version': InvenTree.version.inventreeVersion(),
'instance': InvenTree.version.inventreeInstanceName(),
'apiVersion': InvenTree.version.inventreeApiVersion(),
'worker_running': is_worker_running(),
'worker_pending_tasks': self.worker_pending_tasks(),
'plugins_enabled': settings.PLUGINS_ENABLED,
'plugins_install_disabled': settings.PLUGINS_INSTALL_DISABLED,
'active_plugins': plugins_info(),
'email_configured': is_email_configured(),
'debug_mode': settings.DEBUG,
'docker_mode': settings.DOCKER,
'default_locale': settings.LANGUAGE_CODE,
# Following fields are only available to staff users
'system_health': check_system_health() if is_staff else None,
'database': InvenTree.version.inventreeDatabase() if is_staff else None,
'platform': InvenTree.version.inventreePlatform() if is_staff else None,
'installer': InvenTree.version.inventreeInstaller() if is_staff else None,
'target': InvenTree.version.inventreeTarget() if is_staff else None,
}
return JsonResponse(data)
def check_auth_header(self, request):
"""Check if user is authenticated via a token in the header."""
from InvenTree.middleware import get_token_from_request
if token := get_token_from_request(request):
# Does the provided token match a valid user?
try:
token = ApiToken.objects.get(key=token)
# Check if the token is active and the user is a staff member
if token.active and token.user and token.user.is_staff:
return True
except ApiToken.DoesNotExist:
pass
return False
class NotFoundView(AjaxView):
"""Simple JSON view when accessing an invalid API view."""
permission_classes = [permissions.AllowAny]
def get(self, request, *args, **kwargs):
"""Proces an `not found` event on the API."""
data = {
'details': _('API endpoint not found'),
'url': request.build_absolute_uri(),
}
def not_found(self, request):
"""Return a 404 error."""
return JsonResponse(
{
'detail': _('API endpoint not found'),
'url': request.build_absolute_uri(),
},
status=404,
)
return JsonResponse(data, status=404)
def options(self, request, *args, **kwargs):
"""Return 404."""
return self.not_found(request)
def get(self, request, *args, **kwargs):
"""Return 404."""
return self.not_found(request)
def post(self, request, *args, **kwargs):
"""Return 404."""
return self.not_found(request)
def patch(self, request, *args, **kwargs):
"""Return 404."""
return self.not_found(request)
def put(self, request, *args, **kwargs):
"""Return 404."""
return self.not_found(request)
def delete(self, request, *args, **kwargs):
"""Return 404."""
return self.not_found(request)
class BulkDeleteMixin:
@@ -81,7 +238,7 @@ class BulkDeleteMixin:
"""
def filter_delete_queryset(self, queryset, request):
"""Provide custom filtering for the queryset *before* it is deleted"""
"""Provide custom filtering for the queryset *before* it is deleted."""
return queryset
def delete(self, request, *args, **kwargs):
@@ -109,24 +266,25 @@ class BulkDeleteMixin:
if not items and not filters:
raise ValidationError({
"non_field_errors": ["List of items or filters must be provided for bulk deletion"],
'non_field_errors': [
'List of items or filters must be provided for bulk deletion'
]
})
if items and type(items) is not list:
raise ValidationError({
"items": ["'items' must be supplied as a list object"]
'items': ["'items' must be supplied as a list object"]
})
if filters and type(filters) is not dict:
raise ValidationError({
"filters": ["'filters' must be supplied as a dict object"]
'filters': ["'filters' must be supplied as a dict object"]
})
# Keep track of how many items we deleted
n_deleted = 0
with transaction.atomic():
# Start with *all* models and perform basic filtering
queryset = model.objects.all()
queryset = self.filter_delete_queryset(queryset, request)
@@ -142,16 +300,12 @@ class BulkDeleteMixin:
n_deleted = queryset.count()
queryset.delete()
return Response(
{
'success': f"Deleted {n_deleted} items",
},
status=204
)
return Response({'success': f'Deleted {n_deleted} items'}, status=204)
class ListCreateDestroyAPIView(BulkDeleteMixin, ListCreateAPI):
"""Custom API endpoint which provides BulkDelete functionality in addition to List and Create"""
"""Custom API endpoint which provides BulkDelete functionality in addition to List and Create."""
...
@@ -183,26 +337,23 @@ class APIDownloadMixin:
if export_format and export_format in ['csv', 'tsv', 'xls', 'xlsx']:
queryset = self.filter_queryset(self.get_queryset())
return self.download_queryset(queryset, export_format)
else:
# Default to the parent class implementation
return super().get(request, *args, **kwargs)
# Default to the parent class implementation
return super().get(request, *args, **kwargs)
def download_queryset(self, queryset, export_format):
"""This function must be implemented to provide a downloadFile request."""
raise NotImplementedError("download_queryset method not implemented!")
raise NotImplementedError('download_queryset method not implemented!')
class AttachmentMixin:
"""Mixin for creating attachment objects, and ensuring the user information is saved correctly."""
permission_classes = [
permissions.IsAuthenticated,
RolePermission,
]
permission_classes = [permissions.IsAuthenticated, RolePermission]
filter_backends = SEARCH_ORDER_FILTER
search_fields = ['attachment', 'comment', 'link']
def perform_create(self, serializer):
"""Save the user information when a file is uploaded."""
attachment = serializer.save()
@@ -210,8 +361,18 @@ class AttachmentMixin:
attachment.save()
class APISearchView(APIView):
"""A general-purpose 'search' API endpoint
class APISearchViewSerializer(serializers.Serializer):
"""Serializer for the APISearchView."""
search = serializers.CharField()
search_regex = serializers.BooleanField(default=False, required=False)
search_whole = serializers.BooleanField(default=False, required=False)
limit = serializers.IntegerField(default=1, required=False)
offset = serializers.IntegerField(default=0, required=False)
class APISearchView(GenericAPIView):
"""A general-purpose 'search' API endpoint.
Returns hits against a number of different models simultaneously,
to consolidate multiple API requests into a single query.
@@ -219,13 +380,11 @@ class APISearchView(APIView):
Is much more efficient and simplifies code!
"""
permission_classes = [
permissions.IsAuthenticated,
]
permission_classes = [permissions.IsAuthenticated]
serializer_class = APISearchViewSerializer
def get_result_types(self):
"""Construct a list of search types we can return"""
"""Construct a list of search types we can return."""
import build.api
import company.api
import order.api
@@ -247,8 +406,7 @@ class APISearchView(APIView):
}
def post(self, request, *args, **kwargs):
"""Perform search query against available models"""
"""Perform search query against available models."""
data = request.data
results = {}
@@ -262,10 +420,12 @@ class APISearchView(APIView):
'offset': 0,
}
if 'search' not in data:
raise ValidationError({'search': 'Search term must be provided'})
for key, cls in self.get_result_types().items():
# Only return results which are specifically requested
if key in data:
params = data[key]
for k, v in pass_through_params.items():
@@ -292,83 +452,49 @@ class APISearchView(APIView):
table = f'{app_label}_{model_name}'
try:
if users.models.RuleSet.check_table_permission(request.user, table, 'view'):
if users.models.RuleSet.check_table_permission(
request.user, table, 'view'
):
results[key] = view.list(request, *args, **kwargs).data
else:
results[key] = {
'error': _('User does not have permission to view this model')
'error': _(
'User does not have permission to view this model'
)
}
except Exception as exc:
results[key] = {
'error': str(exc)
}
results[key] = {'error': str(exc)}
return Response(results)
class StatusView(APIView):
"""Generic API endpoint for discovering information on 'status codes' for a particular model.
This class should be implemented as a subclass for each type of status.
For example, the API endpoint /stock/status/ will have information about
all available 'StockStatus' codes
"""
permission_classes = [
permissions.IsAuthenticated,
]
# Override status_class for implementing subclass
MODEL_REF = 'statusmodel'
def get_status_model(self, *args, **kwargs):
"""Return the StatusCode moedl based on extra parameters passed to the view"""
status_model = self.kwargs.get(self.MODEL_REF, None)
if status_model is None:
raise ValidationError(f"StatusView view called without '{self.MODEL_REF}' parameter")
return status_model
def get(self, request, *args, **kwargs):
"""Perform a GET request to learn information about status codes"""
status_class = self.get_status_model()
if not status_class:
raise NotImplementedError("status_class not defined for this endpoint")
data = {
'class': status_class.__name__,
'values': status_class.dict(),
}
return Response(data)
class MetadataView(RetrieveUpdateAPI):
"""Generic API endpoint for reading and editing metadata for a model"""
"""Generic API endpoint for reading and editing metadata for a model."""
MODEL_REF = 'model'
def get_model_type(self):
"""Return the model type associated with this API instance"""
"""Return the model type associated with this API instance."""
model = self.kwargs.get(self.MODEL_REF, None)
if model is None:
raise ValidationError(f"MetadataView called without '{self.MODEL_REF}' parameter")
raise ValidationError(
f"MetadataView called without '{self.MODEL_REF}' parameter"
)
return model
def get_permission_model(self):
"""Return the 'permission' model associated with this view"""
"""Return the 'permission' model associated with this view."""
return self.get_model_type()
def get_queryset(self):
"""Return the queryset for this endpoint"""
"""Return the queryset for this endpoint."""
return self.get_model_type().objects.all()
def get_serializer(self, *args, **kwargs):
"""Return MetadataSerializer instance"""
"""Return MetadataSerializer instance."""
# Detect if we are currently generating the OpenAPI schema
if 'spectacular' in sys.argv:
return MetadataSerializer(Part, *args, **kwargs)
return MetadataSerializer(self.get_model_type(), *args, **kwargs)

View File

@@ -1,11 +1,268 @@
"""InvenTree API version information."""
# InvenTree API version
INVENTREE_API_VERSION = 107
INVENTREE_API_VERSION = 180
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
"""
Increment this API version number whenever there is a significant change to the API that any clients need to know about
INVENTREE_API_TEXT = """
v180 - 2024-3-02 : https://github.com/inventree/InvenTree/pull/6463
- Tweaks to API documentation to allow automatic documentation generation
v179 - 2024-03-01 : https://github.com/inventree/InvenTree/pull/6605
- Adds "subcategories" count to PartCategory serializer
- Adds "sublocations" count to StockLocation serializer
- Adds "image" field to PartBrief serializer
- Adds "image" field to CompanyBrief serializer
v178 - 2024-02-29 : https://github.com/inventree/InvenTree/pull/6604
- Adds "external_stock" field to the Part API endpoint
- Adds "external_stock" field to the BomItem API endpoint
- Adds "external_stock" field to the BuildLine API endpoint
- Stock quantites represented in the BuildLine API endpoint are now filtered by Build.source_location
v177 - 2024-02-27 : https://github.com/inventree/InvenTree/pull/6581
- Adds "subcategoies" count to PartCategoryTree serializer
- Adds "sublocations" count to StockLocationTree serializer
v176 - 2024-02-26 : https://github.com/inventree/InvenTree/pull/6535
- Adds the field "plugins_install_disabled" to the Server info API endpoint
v175 - 2024-02-21 : https://github.com/inventree/InvenTree/pull/6538
- Adds "parts" count to PartParameterTemplate serializer
v174 - 2024-02-21 : https://github.com/inventree/InvenTree/pull/6536
- Expose PartCategory filters to the API documentation
- Expose StockLocation filters to the API documentation
v173 - 2024-02-20 : https://github.com/inventree/InvenTree/pull/6483
- Adds "merge_items" to the PurchaseOrderLine create API endpoint
- Adds "auto_pricing" to the PurchaseOrderLine create/update API endpoint
v172 - 2024-02-20 : https://github.com/inventree/InvenTree/pull/6526
- Adds "enabled" field to the PartTestTemplate API endpoint
- Adds "enabled" filter to the PartTestTemplate list
- Adds "enabled" filter to the StockItemTestResult list
v171 - 2024-02-19 : https://github.com/inventree/InvenTree/pull/6516
- Adds "key" as a filterable parameter to PartTestTemplate list endpoint
v170 -> 2024-02-19 : https://github.com/inventree/InvenTree/pull/6514
- Adds "has_results" filter to the PartTestTemplate list endpoint
v169 -> 2024-02-14 : https://github.com/inventree/InvenTree/pull/6430
- Adds 'key' field to PartTestTemplate API endpoint
- Adds annotated 'results' field to PartTestTemplate API endpoint
- Adds 'template' field to StockItemTestResult API endpoint
v168 -> 2024-02-14 : https://github.com/inventree/InvenTree/pull/4824
- Adds machine CRUD API endpoints
- Adds machine settings API endpoints
- Adds machine restart API endpoint
- Adds machine types/drivers list API endpoints
- Adds machine registry status API endpoint
- Adds 'required' field to the global Settings API
- Discover sub-sub classes of the StatusCode API
v167 -> 2024-02-07: https://github.com/inventree/InvenTree/pull/6440
- Fixes for OpenAPI schema generation
v166 -> 2024-02-04 : https://github.com/inventree/InvenTree/pull/6400
- Adds package_name to plugin API
- Adds mechanism for uninstalling plugins via the API
v165 -> 2024-01-28 : https://github.com/inventree/InvenTree/pull/6040
- Adds supplier_part.name, part.creation_user, part.required_for_sales_order
v164 -> 2024-01-24 : https://github.com/inventree/InvenTree/pull/6343
- Adds "building" quantity to BuildLine API serializer
v163 -> 2024-01-22 : https://github.com/inventree/InvenTree/pull/6314
- Extends API endpoint to expose auth configuration information for signin pages
v162 -> 2024-01-14 : https://github.com/inventree/InvenTree/pull/6230
- Adds API endpoints to provide information on background tasks
v161 -> 2024-01-13 : https://github.com/inventree/InvenTree/pull/6222
- Adds API endpoint for system error information
v160 -> 2023-12-11 : https://github.com/inventree/InvenTree/pull/6072
- Adds API endpoint for allocating stock items against a sales order via barcode scan
v159 -> 2023-12-08 : https://github.com/inventree/InvenTree/pull/6056
- Adds API endpoint for reloading plugin registry
v158 -> 2023-11-21 : https://github.com/inventree/InvenTree/pull/5953
- Adds API endpoint for listing all settings of a particular plugin
- Adds API endpoint for registry status (errors)
v157 -> 2023-12-02 : https://github.com/inventree/InvenTree/pull/6021
- Add write-only "existing_image" field to Part API serializer
v156 -> 2023-11-26 : https://github.com/inventree/InvenTree/pull/5982
- Add POST endpoint for report and label creation
v155 -> 2023-11-24 : https://github.com/inventree/InvenTree/pull/5979
- Add "creation_date" field to Part instance serializer
v154 -> 2023-11-21 : https://github.com/inventree/InvenTree/pull/5944
- Adds "responsible" field to the ProjectCode table
v153 -> 2023-11-21 : https://github.com/inventree/InvenTree/pull/5956
- Adds override_min and override_max fields to part pricing API
v152 -> 2023-11-20 : https://github.com/inventree/InvenTree/pull/5949
- Adds barcode support for manufacturerpart model
- Adds API endpoint for adding parts to purchase order using barcode scan
v151 -> 2023-11-13 : https://github.com/inventree/InvenTree/pull/5906
- Allow user list API to be filtered by user active status
- Allow owner list API to be filtered by user active status
v150 -> 2023-11-07: https://github.com/inventree/InvenTree/pull/5875
- Extended user API endpoints to enable ordering
- Extended user API endpoints to enable user role changes
- Added endpoint to create a new user
v149 -> 2023-11-07 : https://github.com/inventree/InvenTree/pull/5876
- Add 'building' quantity to BomItem serializer
- Add extra ordering options for the BomItem list API
v148 -> 2023-11-06 : https://github.com/inventree/InvenTree/pull/5872
- Allow "quantity" to be specified when installing an item into another item
v147 -> 2023-11-04: https://github.com/inventree/InvenTree/pull/5860
- Adds "completed_lines" field to SalesOrder API endpoint
- Adds "completed_lines" field to PurchaseOrder API endpoint
v146 -> 2023-11-02: https://github.com/inventree/InvenTree/pull/5822
- Extended SSO Provider endpoint to contain if a provider is configured
- Adds API endpoints for Email Address model
v145 -> 2023-10-30: https://github.com/inventree/InvenTree/pull/5786
- Allow printing labels via POST including printing options in the body
v144 -> 2023-10-23: https://github.com/inventree/InvenTree/pull/5811
- Adds version information API endpoint
v143 -> 2023-10-29: https://github.com/inventree/InvenTree/pull/5810
- Extends the status endpoint to include information about system status and health
v142 -> 2023-10-20: https://github.com/inventree/InvenTree/pull/5759
- Adds generic API endpoints for looking up status models
v141 -> 2023-10-23 : https://github.com/inventree/InvenTree/pull/5774
- Changed 'part.responsible' from User to Owner
v140 -> 2023-10-20 : https://github.com/inventree/InvenTree/pull/5664
- Expand API token functionality
- Multiple API tokens can be generated per user
v139 -> 2023-10-11 : https://github.com/inventree/InvenTree/pull/5509
- Add new BarcodePOReceive endpoint to receive line items by scanning supplier barcodes
v138 -> 2023-10-11 : https://github.com/inventree/InvenTree/pull/5679
- Settings keys are no longer case sensitive
- Include settings units in API serializer
v137 -> 2023-10-04 : https://github.com/inventree/InvenTree/pull/5588
- Adds StockLocationType API endpoints
- Adds custom_icon, location_type to StockLocation endpoint
v136 -> 2023-09-23 : https://github.com/inventree/InvenTree/pull/5595
- Adds structural to StockLocation and PartCategory tree endpoints
v135 -> 2023-09-19 : https://github.com/inventree/InvenTree/pull/5569
- Adds location path detail to StockLocation and StockItem API endpoints
- Adds category path detail to PartCategory and Part API endpoints
v134 -> 2023-09-11 : https://github.com/inventree/InvenTree/pull/5525
- Allow "Attachment" list endpoints to be searched by attachment, link and comment fields
v133 -> 2023-09-08 : https://github.com/inventree/InvenTree/pull/5518
- Add extra optional fields which can be used for StockAdjustment endpoints
v132 -> 2023-09-07 : https://github.com/inventree/InvenTree/pull/5515
- Add 'issued_by' filter to BuildOrder API list endpoint
v131 -> 2023-08-09 : https://github.com/inventree/InvenTree/pull/5415
- Annotate 'available_variant_stock' to the SalesOrderLine serializer
v130 -> 2023-07-14 : https://github.com/inventree/InvenTree/pull/5251
- Refactor label printing interface
v129 -> 2023-07-06 : https://github.com/inventree/InvenTree/pull/5189
- Changes 'serial_lte' and 'serial_gte' stock filters to point to 'serial_int' field
v128 -> 2023-07-06 : https://github.com/inventree/InvenTree/pull/5186
- Adds 'available' filter for BuildLine API endpoint
v127 -> 2023-06-24 : https://github.com/inventree/InvenTree/pull/5094
- Enhancements for the PartParameter API endpoints
v126 -> 2023-06-19 : https://github.com/inventree/InvenTree/pull/5075
- Adds API endpoint for setting the "category" for multiple parts simultaneously
v125 -> 2023-06-17 : https://github.com/inventree/InvenTree/pull/5064
- Adds API endpoint for setting the "status" field for multiple stock items simultaneously
v124 -> 2023-06-17 : https://github.com/inventree/InvenTree/pull/5057
- Add "created_before" and "created_after" filters to the Part API
v123 -> 2023-06-15 : https://github.com/inventree/InvenTree/pull/5019
- Add Metadata to: Plugin Config
v122 -> 2023-06-14 : https://github.com/inventree/InvenTree/pull/5034
- Adds new BuildLineLabel label type
v121 -> 2023-06-14 : https://github.com/inventree/InvenTree/pull/4808
- Adds "ProjectCode" link to Build model
v120 -> 2023-06-07 : https://github.com/inventree/InvenTree/pull/4855
- Major overhaul of the build order API
- Adds new BuildLine model
v119 -> 2023-06-01 : https://github.com/inventree/InvenTree/pull/4898
- Add Metadata to: Part test templates, Part parameters, Part category parameter templates, BOM item substitute, Related Parts, Stock item test result
v118 -> 2023-06-01 : https://github.com/inventree/InvenTree/pull/4935
- Adds extra fields for the PartParameterTemplate model
v117 -> 2023-05-22 : https://github.com/inventree/InvenTree/pull/4854
- Part.units model now supports physical units (e.g. "kg", "m", "mm", etc)
- Replaces SupplierPart "pack_size" field with "pack_quantity"
- New field supports physical units, and allows for conversion between compatible units
v116 -> 2023-05-18 : https://github.com/inventree/InvenTree/pull/4823
- Updates to part parameter implementation, to use physical units
v115 -> 2023-05-18 : https://github.com/inventree/InvenTree/pull/4846
- Adds ability to partially scrap a build output
v114 -> 2023-05-16 : https://github.com/inventree/InvenTree/pull/4825
- Adds "delivery_date" to shipments
v113 -> 2023-05-13 : https://github.com/inventree/InvenTree/pull/4800
- Adds API endpoints for scrapping a build output
v112 -> 2023-05-13: https://github.com/inventree/InvenTree/pull/4741
- Adds flag use_pack_size to the stock addition API, which allows adding packs
v111 -> 2023-05-02 : https://github.com/inventree/InvenTree/pull/4367
- Adds tags to the Part serializer
- Adds tags to the SupplierPart serializer
- Adds tags to the ManufacturerPart serializer
- Adds tags to the StockItem serializer
- Adds tags to the StockLocation serializer
v110 -> 2023-04-26 : https://github.com/inventree/InvenTree/pull/4698
- Adds 'order_currency' field for PurchaseOrder / SalesOrder endpoints
v109 -> 2023-04-19 : https://github.com/inventree/InvenTree/pull/4636
- Adds API endpoints for the "ProjectCode" model
v108 -> 2023-04-17 : https://github.com/inventree/InvenTree/pull/4615
- Adds functionality to upload images for rendering in markdown notes
v107 -> 2023-04-04 : https://github.com/inventree/InvenTree/pull/4575
- Adds barcode support for PurchaseOrder model
@@ -69,7 +326,7 @@ v90 -> 2023-01-25 : https://github.com/inventree/InvenTree/pull/4186/files
v89 -> 2023-01-25 : https://github.com/inventree/InvenTree/pull/4214
- Adds updated field to SupplierPart API
- Adds API date orddering for supplier part list
- Adds API date ordering for supplier part list
v88 -> 2023-01-17: https://github.com/inventree/InvenTree/pull/4225
- Adds 'priority' field to Build model and api endpoints
@@ -167,7 +424,7 @@ v64 -> 2022-07-08 : https://github.com/inventree/InvenTree/pull/3310
- Allow BOM List API endpoint to be filtered by "on_order" parameter
v63 -> 2022-07-06 : https://github.com/inventree/InvenTree/pull/3301
- Allow BOM List API endpoint to be filtered by "available_stock" paramater
- Allow BOM List API endpoint to be filtered by "available_stock" parameter
v62 -> 2022-07-05 : https://github.com/inventree/InvenTree/pull/3296
- Allows search on BOM List API endpoint

View File

@@ -1,4 +1,4 @@
"""AppConfig for inventree app."""
"""AppConfig for InvenTree app."""
import logging
from importlib import import_module
@@ -9,17 +9,19 @@ from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.exceptions import AppRegistryNotReady
from django.db import transaction
from django.db.utils import IntegrityError
from django.db.utils import IntegrityError, OperationalError
import InvenTree.conversion
import InvenTree.ready
import InvenTree.tasks
from InvenTree.config import get_setting
from InvenTree.ready import canAppAccessDatabase, isInTestMode
logger = logging.getLogger("inventree")
logger = logging.getLogger('inventree')
class InvenTreeConfig(AppConfig):
"""AppConfig for inventree app."""
name = 'InvenTree'
def ready(self):
@@ -29,25 +31,43 @@ class InvenTreeConfig(AppConfig):
- Checking if migrations should be run
- Cleaning up tasks
- Starting regular tasks
- Updateing exchange rates
- Collecting notification mehods
- Updating exchange rates
- Collecting notification methods
- Collecting state transition methods
- Adding users set in the current environment
"""
if canAppAccessDatabase() or settings.TESTING_ENV:
InvenTree.tasks.check_for_migrations(worker=False)
# skip loading if plugin registry is not loaded or we run in a background thread
if (
not InvenTree.ready.isPluginRegistryLoaded()
or not InvenTree.ready.isInMainThread()
):
return
# Skip if running migrations
if InvenTree.ready.isRunningMigrations():
return
if InvenTree.ready.canAppAccessDatabase() or settings.TESTING_ENV:
self.remove_obsolete_tasks()
self.collect_tasks()
self.start_background_tasks()
if not isInTestMode(): # pragma: no cover
if not InvenTree.ready.isInTestMode(): # pragma: no cover
self.update_exchange_rates()
# Let the background worker check for migrations
InvenTree.tasks.offload_task(InvenTree.tasks.check_for_migrations)
self.update_site_url()
self.collect_notification_methods()
self.collect_state_transition_methods()
if canAppAccessDatabase() or settings.TESTING_ENV:
# Ensure the unit registry is loaded
InvenTree.conversion.get_unit_registry()
if InvenTree.ready.canAppAccessDatabase() or settings.TESTING_ENV:
self.add_user_on_startup()
self.add_user_from_file()
def remove_obsolete_tasks(self):
"""Delete any obsolete scheduled tasks in the database."""
@@ -62,36 +82,81 @@ class InvenTreeConfig(AppConfig):
return
# Remove any existing obsolete tasks
Schedule.objects.filter(func__in=obsolete).delete()
try:
Schedule.objects.filter(func__in=obsolete).delete()
except Exception:
logger.exception('Failed to remove obsolete tasks - database not ready')
def start_background_tasks(self):
"""Start all background tests for InvenTree."""
logger.info('Starting background tasks...')
logger.info("Starting background tasks...")
from django_q.models import Schedule
# List of existing scheduled tasks (in the database)
existing_tasks = {}
for existing_task in Schedule.objects.all():
existing_tasks[existing_task.func] = existing_task
tasks_to_create = []
tasks_to_update = []
# List of collected tasks found with the @scheduled_task decorator
tasks = InvenTree.tasks.tasks.task_list
for task in tasks:
ref_name = f'{task.func.__module__}.{task.func.__name__}'
InvenTree.tasks.schedule_task(
ref_name,
schedule_type=task.interval,
minutes=task.minutes,
)
# Put at least one task onto the backround worker stack,
# which will be processed as soon as the worker comes online
InvenTree.tasks.offload_task(
InvenTree.tasks.heartbeat,
force_async=True,
)
if ref_name in existing_tasks.keys():
# This task already exists - update the details if required
existing_task = existing_tasks[ref_name]
logger.info(f"Started {len(tasks)} scheduled background tasks...")
if (
existing_task.schedule_type != task.interval
or existing_task.minutes != task.minutes
):
existing_task.schedule_type = task.interval
existing_task.minutes = task.minutes
tasks_to_update.append(existing_task)
else:
# This task does *not* already exist - create it
tasks_to_create.append(
Schedule(
name=ref_name,
func=ref_name,
schedule_type=task.interval,
minutes=task.minutes,
)
)
if len(tasks_to_create) > 0:
Schedule.objects.bulk_create(tasks_to_create)
logger.info('Created %s new scheduled tasks', len(tasks_to_create))
if len(tasks_to_update) > 0:
Schedule.objects.bulk_update(tasks_to_update, ['schedule_type', 'minutes'])
logger.info('Updated %s existing scheduled tasks', len(tasks_to_update))
self.add_heartbeat()
logger.info('Started %s scheduled background tasks...', len(tasks))
def add_heartbeat(self):
"""Ensure there is at least one background task in the queue."""
import django_q.models
try:
if django_q.models.OrmQ.objects.count() == 0:
InvenTree.tasks.offload_task(
InvenTree.tasks.heartbeat, force_async=True
)
except Exception:
pass
def collect_tasks(self):
"""Collect all background tasks."""
for app_name, app in apps.app_configs.items():
if app_name == 'InvenTree':
continue
@@ -100,7 +165,7 @@ class InvenTreeConfig(AppConfig):
try:
import_module(f'{app.module.__package__}.tasks')
except Exception as e: # pragma: no cover
logger.error(f"Error loading tasks for {app_name}: {e}")
logger.exception('Error loading tasks for %s: %s', app_name, e)
def update_exchange_rates(self): # pragma: no cover
"""Update exchange rates each time the server is started.
@@ -122,22 +187,29 @@ class InvenTreeConfig(AppConfig):
update = False
try:
backend = ExchangeBackend.objects.get(name='InvenTreeExchange')
backend = ExchangeBackend.objects.filter(name='InvenTreeExchange')
last_update = backend.last_update
if backend.exists():
backend = backend.first()
if last_update is None:
# Never been updated
logger.info("Exchange backend has never been updated")
update = True
last_update = backend.last_update
# Backend currency has changed?
if base_currency != backend.base_currency:
logger.info(f"Base currency changed from {backend.base_currency} to {base_currency}")
update = True
if last_update is None:
# Never been updated
logger.info('Exchange backend has never been updated')
update = True
except (ExchangeBackend.DoesNotExist):
logger.info("Exchange backend not found - updating")
# Backend currency has changed?
if base_currency != backend.base_currency:
logger.info(
'Base currency changed from %s to %s',
backend.base_currency,
base_currency,
)
update = True
except ExchangeBackend.DoesNotExist:
logger.info('Exchange backend not found - updating')
update = True
except Exception:
@@ -147,8 +219,50 @@ class InvenTreeConfig(AppConfig):
if update:
try:
update_exchange_rates()
except OperationalError:
logger.warning('Could not update exchange rates - database not ready')
except Exception as e:
logger.error(f"Error updating exchange rates: {e}")
logger.exception('Error updating exchange rates: %s (%s)', e, type(e))
def update_site_url(self):
"""Update the site URL setting.
- If a fixed SITE_URL is specified (via configuration), it should override the INVENTREE_BASE_URL setting
- If multi-site support is enabled, update the site URL for the current site
"""
import common.models
if not InvenTree.ready.canAppAccessDatabase():
return
if InvenTree.ready.isImportingData() or InvenTree.ready.isRunningMigrations():
return
if settings.SITE_URL:
try:
if (
common.models.InvenTreeSetting.get_setting('INVENTREE_BASE_URL')
!= settings.SITE_URL
):
common.models.InvenTreeSetting.set_setting(
'INVENTREE_BASE_URL', settings.SITE_URL
)
logger.info('Updated INVENTREE_SITE_URL to %s', settings.SITE_URL)
except Exception:
pass
# If multi-site support is enabled, update the site URL for the current site
try:
from django.contrib.sites.models import Site
site = Site.objects.get_current()
site.domain = settings.SITE_URL
site.save()
logger.info('Updated current site URL to %s', settings.SITE_URL)
except Exception:
pass
def add_user_on_startup(self):
"""Add a user on startup."""
@@ -160,6 +274,9 @@ class InvenTreeConfig(AppConfig):
add_user = get_setting('INVENTREE_ADMIN_USER', 'admin_user')
add_email = get_setting('INVENTREE_ADMIN_EMAIL', 'admin_email')
add_password = get_setting('INVENTREE_ADMIN_PASSWORD', 'admin_password')
add_password_file = get_setting(
'INVENTREE_ADMIN_PASSWORD_FILE', 'admin_password_file', None
)
# check if all values are present
set_variables = 0
@@ -175,27 +292,77 @@ class InvenTreeConfig(AppConfig):
# not all needed variables set
if set_variables < 3:
logger.warn('Not all required settings for adding a user on startup are present:\nINVENTREE_ADMIN_USER, INVENTREE_ADMIN_EMAIL, INVENTREE_ADMIN_PASSWORD')
settings.USER_ADDED = True
# if a password file is present, do not warn - will be handled later
if add_password_file:
return
logger.warning(
'Not all required settings for adding a user on startup are present:\nINVENTREE_ADMIN_USER, INVENTREE_ADMIN_EMAIL, INVENTREE_ADMIN_PASSWORD'
)
return
# good to go -> create user
self._create_admin_user(add_user, add_email, add_password)
# do not try again
settings.USER_ADDED = True
def _create_admin_user(self, add_user, add_email, add_password):
user = get_user_model()
try:
with transaction.atomic():
if user.objects.filter(username=add_user).exists():
logger.info(f"User {add_user} already exists - skipping creation")
logger.info('User %s already exists - skipping creation', add_user)
else:
new_user = user.objects.create_superuser(add_user, add_email, add_password)
logger.info(f'User {str(new_user)} was created!')
except IntegrityError as _e:
logger.warning(f'The user "{add_user}" could not be created due to the following error:\n{str(_e)}')
new_user = user.objects.create_superuser(
add_user, add_email, add_password
)
logger.info('User %s was created!', str(new_user))
except IntegrityError:
logger.warning('The user "%s" could not be created', add_user)
def add_user_from_file(self):
"""Add the superuser from a file."""
# stop if checks were already created
if hasattr(settings, 'USER_ADDED_FILE') and settings.USER_ADDED_FILE:
return
# get values
add_password_file = get_setting(
'INVENTREE_ADMIN_PASSWORD_FILE', 'admin_password_file', None
)
# no variable set -> do not try anything
if not add_password_file:
settings.USER_ADDED_FILE = True
return
# check if file exists
add_password_file = Path(str(add_password_file))
if not add_password_file.exists():
logger.warning('The file "%s" does not exist', add_password_file)
settings.USER_ADDED_FILE = True
return
# good to go -> create user
self._create_admin_user(
get_setting('INVENTREE_ADMIN_USER', 'admin_user', 'admin'),
get_setting('INVENTREE_ADMIN_EMAIL', 'admin_email', ''),
add_password_file.read_text(encoding='utf-8'),
)
# do not try again
settings.USER_ADDED = True
settings.USER_ADDED_FILE = True
def collect_notification_methods(self):
"""Collect all notification methods."""
from common.notifications import storage
storage.collect()
def collect_state_transition_methods(self):
"""Collect all state transition methods."""
from generic.states import storage
storage.collect()

View File

@@ -0,0 +1,85 @@
"""Custom backend implementations."""
import datetime
import logging
import time
from django.db.utils import IntegrityError, OperationalError, ProgrammingError
from maintenance_mode.backends import AbstractStateBackend
import common.models
logger = logging.getLogger('inventree')
class InvenTreeMaintenanceModeBackend(AbstractStateBackend):
"""Custom backend for managing state of maintenance mode.
Stores a timestamp in the database to determine when maintenance mode will elapse.
"""
SETTING_KEY = '_MAINTENANCE_MODE'
def get_value(self) -> bool:
"""Get the current state of the maintenance mode.
Returns:
bool: True if maintenance mode is active, False otherwise.
"""
try:
setting = common.models.InvenTreeSetting.objects.get(key=self.SETTING_KEY)
value = str(setting.value).strip()
except common.models.InvenTreeSetting.DoesNotExist:
# Database is accessible, but setting is not available - assume False
return False
except (IntegrityError, OperationalError, ProgrammingError):
# Database is inaccessible - assume we are not in maintenance mode
logger.debug('Failed to read maintenance mode state - assuming True')
return True
# Extract timestamp from string
try:
# If the timestamp is in the past, we are now *out* of maintenance mode
timestamp = datetime.datetime.fromisoformat(value)
return timestamp > datetime.datetime.now()
except ValueError:
# If the value is not a valid timestamp, assume maintenance mode is not active
return False
def set_value(self, value: bool, retries: int = 5, minutes: int = 5):
"""Set the state of the maintenance mode.
Instead of simply writing "true" or "false" to the setting,
we write a timestamp to the setting, which is used to determine
when maintenance mode will elapse.
This ensures that we will always *exit* maintenance mode after a certain time period.
"""
logger.debug('Setting maintenance mode state: %s', value)
if value:
# Save as isoformat
timestamp = datetime.datetime.now() + datetime.timedelta(minutes=minutes)
timestamp = timestamp.isoformat()
else:
# Blank timestamp means maintenance mode is not active
timestamp = ''
while retries > 0:
try:
common.models.InvenTreeSetting.set_setting(self.SETTING_KEY, timestamp)
# Read the value back to confirm
if self.get_value() == value:
break
except (IntegrityError, OperationalError, ProgrammingError):
# In the database is locked, then
logger.debug(
'Failed to set maintenance mode state (%s retries left)', retries
)
time.sleep(0.1)
retries -= 1
if retries == 0:
logger.warning('Failed to set maintenance mode state')

View File

@@ -1,12 +1,12 @@
"""Pull rendered copies of the templated.
Only used for testing the js files! - This file is omited from coverage.
Only used for testing the js files! - This file is omitted from coverage.
"""
import os # pragma: no cover
import pathlib # pragma: no cover
from InvenTree.helpers import InvenTreeTestCase # pragma: no cover
from InvenTree.unit_test import InvenTreeTestCase # pragma: no cover
class RenderJavascriptFiles(InvenTreeTestCase): # pragma: no cover
@@ -17,29 +17,21 @@ class RenderJavascriptFiles(InvenTreeTestCase): # pragma: no cover
"""
def download_file(self, filename, prefix):
"""Function to `download`(copy) a file to a temporay firectory."""
"""Function to `download`(copy) a file to a temporary firectory."""
url = os.path.join(prefix, filename)
response = self.client.get(url)
here = os.path.abspath(os.path.dirname(__file__))
output_dir = os.path.join(
here,
'..',
'..',
'js_tmp',
)
output_dir = os.path.join(here, '..', '..', 'js_tmp')
output_dir = os.path.abspath(output_dir)
if not os.path.exists(output_dir):
os.mkdir(output_dir)
output_file = os.path.join(
output_dir,
filename,
)
output_file = os.path.join(output_dir, filename)
with open(output_file, 'wb') as output:
output.write(response.content)
@@ -48,12 +40,7 @@ class RenderJavascriptFiles(InvenTreeTestCase): # pragma: no cover
"""Download files in directory."""
here = os.path.abspath(os.path.dirname(__file__))
js_template_dir = os.path.join(
here,
'..',
'templates',
'js',
)
js_template_dir = os.path.join(here, '..', 'templates', 'js')
directory = os.path.join(js_template_dir, subdir)
@@ -76,9 +63,9 @@ class RenderJavascriptFiles(InvenTreeTestCase): # pragma: no cover
"""Look for all javascript files."""
n = 0
print("Rendering javascript files...")
print('Rendering javascript files...')
n += self.download_files('translated', '/js/i18n')
n += self.download_files('dynamic', '/js/dynamic')
print(f"Rendered {n} javascript files.")
print(f'Rendered {n} javascript files.')

View File

@@ -7,8 +7,12 @@ import os
import random
import shutil
import string
import warnings
from pathlib import Path
from django.core.files.base import ContentFile
from django.core.files.storage import Storage
logger = logging.getLogger('inventree')
CONFIG_DATA = None
CONFIG_LOOKUPS = {}
@@ -23,7 +27,6 @@ def to_list(value, delimiter=','):
However, the same setting may be specified via an environment variable,
using a comma delimited string!
"""
if type(value) in [list, tuple]:
return value
@@ -45,18 +48,22 @@ def to_dict(value):
if value is None:
return {}
if type(value) == dict:
if isinstance(value, dict):
return value
try:
return json.loads(value)
except Exception as error:
logger.error(f"Failed to parse value '{value}' as JSON with error {error}. Ensure value is a valid JSON string.")
logger.exception(
"Failed to parse value '%s' as JSON with error %s. Ensure value is a valid JSON string.",
value,
error,
)
return {}
def is_true(x):
"""Shortcut function to determine if a value "looks" like a boolean"""
"""Shortcut function to determine if a value "looks" like a boolean."""
return str(x).strip().lower() in ['1', 'y', 'yes', 't', 'true', 'on']
@@ -65,11 +72,15 @@ def get_base_dir() -> Path:
return Path(__file__).parent.parent.resolve()
def ensure_dir(path: Path) -> None:
def ensure_dir(path: Path, storage=None) -> None:
"""Ensure that a directory exists.
If it does not exist, create it.
"""
if storage and isinstance(storage, Storage):
if not storage.exists(str(path)):
storage.save(str(path / '.empty'), ContentFile(''))
return
if not path.exists():
path.mkdir(parents=True, exist_ok=True)
@@ -91,12 +102,14 @@ def get_config_file(create=True) -> Path:
cfg_filename = base_dir.joinpath('config.yaml').resolve()
if not cfg_filename.exists() and create:
print("InvenTree configuration file 'config.yaml' not found - creating default file")
print(
"InvenTree configuration file 'config.yaml' not found - creating default file"
)
ensure_dir(cfg_filename.parent)
cfg_template = base_dir.joinpath("config_template.yaml")
cfg_template = base_dir.joinpath('config_template.yaml')
shutil.copyfile(cfg_template, cfg_filename)
print(f"Created config file {cfg_filename}")
print(f'Created config file {cfg_filename}')
return cfg_filename
@@ -128,6 +141,42 @@ def load_config_data(set_cache: bool = False) -> map:
return data
def do_typecast(value, type, var_name=None):
"""Attempt to typecast a value.
Arguments:
value: Value to typecast
type: Function to use for typecasting the value e.g. int, float, str, list, dict
var_name: Name that should be logged e.g. 'INVENTREE_STATIC_ROOT'. Set if logging is required.
Returns:
Typecasted value or original value if typecasting failed.
"""
# Force 'list' of strings
if type is list:
value = to_list(value)
# Valid JSON string is required
elif type is dict:
value = to_dict(value)
elif type is not None:
# Try to typecast the value
try:
val = type(value)
return val
except Exception as error:
if var_name:
logger.exception(
"Failed to typecast '%s' with value '%s' to type '%s' with error %s",
var_name,
value,
type,
error,
)
return value
def get_setting(env_var=None, config_key=None, default_value=None, typecast=None):
"""Helper function for retrieving a configuration setting value.
@@ -139,42 +188,26 @@ def get_setting(env_var=None, config_key=None, default_value=None, typecast=None
env_var: Name of the environment variable e.g. 'INVENTREE_STATIC_ROOT'
config_key: Key to lookup in the configuration file
default_value: Value to return if first two options are not provided
typecast: Function to use for typecasting the value
typecast: Function to use for typecasting the value e.g. int, float, str, list, dict
"""
def try_typecasting(value, source: str):
"""Attempt to typecast the value"""
# Force 'list' of strings
if typecast is list:
value = to_list(value)
# Valid JSON string is required
elif typecast is dict:
value = to_dict(value)
elif typecast is not None:
# Try to typecast the value
try:
val = typecast(value)
set_metadata(source)
return val
except Exception as error:
logger.error(f"Failed to typecast '{env_var}' with value '{value}' to type '{typecast}' with error {error}")
set_metadata(source)
return value
def set_metadata(source: str):
"""Set lookup metadata for the setting."""
key = env_var or config_key
CONFIG_LOOKUPS[key] = {'env_var': env_var, 'config_key': config_key, 'source': source, 'accessed': datetime.datetime.now()}
CONFIG_LOOKUPS[key] = {
'env_var': env_var,
'config_key': config_key,
'source': source,
'accessed': datetime.datetime.now(),
}
# First, try to load from the environment variables
if env_var is not None:
val = os.getenv(env_var, None)
if val is not None:
return try_typecasting(val, 'env')
set_metadata('env')
return do_typecast(val, typecast, var_name=env_var)
# Next, try to load from configuration file
if config_key is not None:
@@ -184,7 +217,6 @@ def get_setting(env_var=None, config_key=None, default_value=None, typecast=None
# Hack to allow 'path traversal' in configuration file
for key in config_key.strip().split('.'):
if type(cfg_data) is not dict or key not in cfg_data:
result = None
break
@@ -193,21 +225,21 @@ def get_setting(env_var=None, config_key=None, default_value=None, typecast=None
cfg_data = cfg_data[key]
if result is not None:
return try_typecasting(result, 'yaml')
set_metadata('yaml')
return do_typecast(result, typecast, var_name=env_var)
# Finally, return the default value
return try_typecasting(default_value, 'default')
set_metadata('default')
return do_typecast(default_value, typecast, var_name=env_var)
def get_boolean_setting(env_var=None, config_key=None, default_value=False):
"""Helper function for retreiving a boolean configuration setting"""
"""Helper function for retrieving a boolean configuration setting."""
return is_true(get_setting(env_var, config_key, default_value))
def get_media_dir(create=True):
"""Return the absolute path for the 'media' directory (where uploaded files are stored)"""
"""Return the absolute path for the 'media' directory (where uploaded files are stored)."""
md = get_setting('INVENTREE_MEDIA_ROOT', 'media_root')
if not md:
@@ -222,8 +254,7 @@ def get_media_dir(create=True):
def get_static_dir(create=True):
"""Return the absolute path for the 'static' directory (where static files are stored)"""
"""Return the absolute path for the 'static' directory (where static files are stored)."""
sd = get_setting('INVENTREE_STATIC_ROOT', 'static_root')
if not sd:
@@ -238,8 +269,7 @@ def get_static_dir(create=True):
def get_backup_dir(create=True):
"""Return the absolute path for the backup directory"""
"""Return the absolute path for the backup directory."""
bd = get_setting('INVENTREE_BACKUP_DIR', 'backup_dir')
if not bd:
@@ -258,7 +288,6 @@ def get_plugin_file():
Note: It will be created if it does not already exist!
"""
# Check if the plugin.txt file (specifying required plugins) is specified
plugin_file = get_setting('INVENTREE_PLUGIN_FILE', 'plugin_file')
@@ -271,16 +300,25 @@ def get_plugin_file():
plugin_file = Path(plugin_file)
if not plugin_file.exists():
logger.warning("Plugin configuration file does not exist - creating default file")
logger.info(f"Creating plugin file at '{plugin_file}'")
logger.warning(
'Plugin configuration file does not exist - creating default file'
)
logger.info("Creating plugin file at '%s'", plugin_file)
ensure_dir(plugin_file.parent)
# If opening the file fails (no write permission, for example), then this will throw an error
plugin_file.write_text("# InvenTree Plugins (uses PIP framework to install)\n\n")
plugin_file.write_text(
'# InvenTree Plugins (uses PIP framework to install)\n\n'
)
return plugin_file
def get_plugin_dir():
"""Returns the path of the custom plugins directory."""
return get_setting('INVENTREE_PLUGIN_DIR', 'plugin_dir')
def get_secret_key():
"""Return the secret key value which will be used by django.
@@ -291,10 +329,9 @@ def get_secret_key():
C) Look for default key file "secret_key.txt"
D) Create "secret_key.txt" if it does not exist
"""
# Look for environment variable
if secret_key := get_setting('INVENTREE_SECRET_KEY', 'secret_key'):
logger.info("SECRET_KEY loaded by INVENTREE_SECRET_KEY") # pragma: no cover
logger.info('SECRET_KEY loaded by INVENTREE_SECRET_KEY') # pragma: no cover
return secret_key
# Look for secret key file
@@ -302,10 +339,10 @@ def get_secret_key():
secret_key_file = Path(secret_key_file).resolve()
else:
# Default location for secret key file
secret_key_file = get_base_dir().joinpath("secret_key.txt").resolve()
secret_key_file = get_base_dir().joinpath('secret_key.txt').resolve()
if not secret_key_file.exists():
logger.info(f"Generating random key file at '{secret_key_file}'")
logger.info("Generating random key file at '%s'", secret_key_file)
ensure_dir(secret_key_file.parent)
# Create a random key file
@@ -313,14 +350,16 @@ def get_secret_key():
key = ''.join([random.choice(options) for i in range(100)])
secret_key_file.write_text(key)
logger.info(f"Loading SECRET_KEY from '{secret_key_file}'")
logger.debug("Loading SECRET_KEY from '%s'", secret_key_file)
key_data = secret_key_file.read_text().strip()
return key_data
def get_custom_file(env_ref: str, conf_ref: str, log_ref: str, lookup_media: bool = False):
def get_custom_file(
env_ref: str, conf_ref: str, log_ref: str, lookup_media: bool = False
):
"""Returns the checked path to a custom file.
Set lookup_media to True to also search in the media folder.
@@ -336,12 +375,79 @@ def get_custom_file(env_ref: str, conf_ref: str, log_ref: str, lookup_media: boo
static_storage = StaticFilesStorage()
if static_storage.exists(value):
logger.info(f"Loading {log_ref} from static directory: {value}")
logger.info('Loading %s from %s directory: %s', log_ref, 'static', value)
elif lookup_media and default_storage.exists(value):
logger.info(f"Loading {log_ref} from media directory: {value}")
logger.info('Loading %s from %s directory: %s', log_ref, 'media', value)
else:
add_dir_str = ' or media' if lookup_media else ''
logger.warning(f"The {log_ref} file '{value}' could not be found in the static{add_dir_str} directories")
logger.warning(
"The %s file '%s' could not be found in the static %s directories",
log_ref,
value,
add_dir_str,
)
value = False
return value
def get_frontend_settings(debug=True):
"""Return a dictionary of settings for the frontend interface.
Note that the new config settings use the 'FRONTEND' key,
whereas the legacy key was 'PUI' (platform UI) which is now deprecated
"""
# Legacy settings
pui_settings = get_setting(
'INVENTREE_PUI_SETTINGS', 'pui_settings', {}, typecast=dict
)
if len(pui_settings) > 0:
warnings.warn(
"The 'INVENTREE_PUI_SETTINGS' key is deprecated. Please use 'INVENTREE_FRONTEND_SETTINGS' instead",
DeprecationWarning,
stacklevel=2,
)
# New settings
frontend_settings = get_setting(
'INVENTREE_FRONTEND_SETTINGS', 'frontend_settings', {}, typecast=dict
)
# Merge settings
settings = {**pui_settings, **frontend_settings}
# Set the base URL
if 'base_url' not in settings:
base_url = get_setting('INVENTREE_PUI_URL_BASE', 'pui_url_base', '')
if base_url:
warnings.warn(
"The 'INVENTREE_PUI_URL_BASE' key is deprecated. Please use 'INVENTREE_FRONTEND_URL_BASE' instead",
DeprecationWarning,
stacklevel=2,
)
else:
base_url = get_setting(
'INVENTREE_FRONTEND_URL_BASE', 'frontend_url_base', 'platform'
)
settings['base_url'] = base_url
# Set the server list
settings['server_list'] = settings.get('server_list', [])
# Set the debug flag
settings['debug'] = debug
if 'environment' not in settings:
settings['environment'] = 'development' if debug else 'production'
if debug and 'show_server_selector' not in settings:
# In debug mode, show server selector by default
settings['show_server_selector'] = True
elif len(settings['server_list']) == 0:
# If no servers are specified, show server selector
settings['show_server_selector'] = True
return settings

View File

@@ -2,11 +2,10 @@
"""Provides extra global data to all templates."""
import InvenTree.email
import InvenTree.status
from InvenTree.status_codes import (BuildStatus, PurchaseOrderStatus,
ReturnOrderLineStatus, ReturnOrderStatus,
SalesOrderStatus, StockHistoryCode,
StockStatus)
from generic.states import StatusCode
from InvenTree.helpers import inheritors
from users.models import RuleSet, check_user_role
@@ -28,13 +27,11 @@ def health_status(request):
status = {
'django_q_running': InvenTree.status.is_worker_running(),
'email_configured': InvenTree.status.is_email_configured(),
'email_configured': InvenTree.email.is_email_configured(),
}
# The following keys are required to denote system health
health_keys = [
'django_q_running',
]
health_keys = ['django_q_running']
all_healthy = True
@@ -56,17 +53,7 @@ def status_codes(request):
return {}
request._inventree_status_codes = True
return {
# Expose the StatusCode classes to the templates
'ReturnOrderStatus': ReturnOrderStatus,
'ReturnOrderLineStatus': ReturnOrderLineStatus,
'SalesOrderStatus': SalesOrderStatus,
'PurchaseOrderStatus': PurchaseOrderStatus,
'BuildStatus': BuildStatus,
'StockStatus': StockStatus,
'StockHistoryCode': StockHistoryCode,
}
return {cls.__name__: cls.template_context() for cls in inheritors(StatusCode)}
def user_roles(request):
@@ -83,11 +70,9 @@ def user_roles(request):
"""
user = request.user
roles = {
}
for role in RuleSet.RULESET_MODELS.keys():
roles = {}
for role in RuleSet.get_ruleset_models().keys():
permissions = {}
for perm in ['view', 'add', 'change', 'delete']:

View File

@@ -0,0 +1,246 @@
"""Helper functions for converting between units."""
import logging
import re
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
import pint
_unit_registry = None
logger = logging.getLogger('inventree')
def get_unit_registry():
"""Return a custom instance of the Pint UnitRegistry."""
global _unit_registry
# Cache the unit registry for speedier access
if _unit_registry is None:
return reload_unit_registry()
return _unit_registry
def reload_unit_registry():
"""Reload the unit registry from the database.
This function is called at startup, and whenever the database is updated.
"""
import time
t_start = time.time()
global _unit_registry
_unit_registry = None
reg = pint.UnitRegistry(autoconvert_offset_to_baseunit=True)
# Aliases for temperature units
reg.define('@alias degC = celsius = Celsius')
reg.define('@alias degF = fahrenheit = Fahrenheit')
reg.define('@alias degK = kelvin = Kelvin')
# Define some "standard" additional units
reg.define('piece = 1')
reg.define('each = 1 = ea')
reg.define('dozen = 12 = dz')
reg.define('hundred = 100')
reg.define('thousand = 1000')
# Allow for custom units to be defined in the database
try:
from common.models import CustomUnit
for cu in CustomUnit.objects.all():
try:
reg.define(cu.fmt_string())
except Exception as e:
logger.exception(
'Failed to load custom unit: %s - %s', cu.fmt_string(), e
)
# Once custom units are loaded, save registry
_unit_registry = reg
except Exception:
# Database is not ready, or CustomUnit model is not available
pass
dt = time.time() - t_start
logger.debug('Loaded unit registry in %.3f s', dt)
return reg
def from_engineering_notation(value):
"""Convert a provided value to 'natural' representation from 'engineering' notation.
Ref: https://en.wikipedia.org/wiki/Engineering_notation
In "engineering notation", the unit (or SI prefix) is often combined with the value,
and replaces the decimal point.
Examples:
- 1K2 -> 1.2K
- 3n05 -> 3.05n
- 8R6 -> 8.6R
And, we should also take into account any provided trailing strings:
- 1K2 ohm -> 1.2K ohm
- 10n005F -> 10.005nF
"""
value = str(value).strip()
pattern = f'(\d+)([a-zA-Z]+)(\d+)(.*)'
if match := re.match(pattern, value):
left, prefix, right, suffix = match.groups()
return f'{left}.{right}{prefix}{suffix}'
return value
def convert_value(value, unit):
"""Attempt to convert a value to a specified unit.
Arguments:
value: The value to convert
unit: The target unit to convert to
Returns:
The converted value (ideally a pint.Quantity value)
Raises:
Exception if the value cannot be converted to the specified unit
"""
ureg = get_unit_registry()
# Convert the provided value to a pint.Quantity object
value = ureg.Quantity(value)
# Convert to the specified unit
if unit:
if is_dimensionless(value):
magnitude = value.to_base_units().magnitude
value = ureg.Quantity(magnitude, unit)
else:
value = value.to(unit)
return value
def convert_physical_value(value: str, unit: str = None, strip_units=True):
"""Validate that the provided value is a valid physical quantity.
Arguments:
value: Value to validate (str)
unit: Optional unit to convert to, and validate against
strip_units: If True, strip units from the returned value, and return only the dimension
Raises:
ValidationError: If the value is invalid or cannot be converted to the specified unit
Returns:
The converted quantity, in the specified units
"""
ureg = get_unit_registry()
# Check that the provided unit is available in the unit registry
if unit:
try:
valid = unit in ureg
except Exception as exc:
valid = False
if not valid:
raise ValidationError(_(f'Invalid unit provided ({unit})'))
original = str(value).strip()
# Ensure that the value is a string
value = str(value).strip() if value else ''
unit = str(unit).strip() if unit else ''
# Error on blank values
if not value:
raise ValidationError(_('No value provided'))
# Construct a list of values to "attempt" to convert
attempts = [value]
# Attempt to convert from engineering notation
eng = from_engineering_notation(value)
attempts.append(eng)
# Append the unit, if provided
# These are the "final" attempts to convert the value, and *must* appear after previous attempts
if unit:
attempts.append(f'{value}{unit}')
attempts.append(f'{eng}{unit}')
value = None
# Run through the available "attempts", take the first successful result
for attempt in attempts:
try:
value = convert_value(attempt, unit)
break
except Exception as exc:
value = None
pass
if value is None:
if unit:
raise ValidationError(_(f'Could not convert {original} to {unit}'))
else:
raise ValidationError(_('Invalid quantity supplied'))
# Calculate the "magnitude" of the value, as a float
# If the value is specified strangely (e.g. as a fraction or a dozen), this can cause issues
# So, we ensure that it is converted to a floating point value
# If we wish to return a "raw" value, some trickery is required
try:
if unit:
magnitude = ureg.Quantity(value.to(ureg.Unit(unit))).magnitude
else:
magnitude = ureg.Quantity(value.to_base_units()).magnitude
magnitude = float(ureg.Quantity(magnitude).to_base_units().magnitude)
except Exception as exc:
raise ValidationError(_(f'Invalid quantity supplied ({exc})'))
if strip_units:
return magnitude
elif unit or value.units:
return ureg.Quantity(magnitude, unit or value.units)
return ureg.Quantity(magnitude)
def is_dimensionless(value):
"""Determine if the provided value is 'dimensionless'.
A dimensionless value might look like:
0.1
1/2 dozen
three thousand
1.2 dozen
(etc)
"""
ureg = get_unit_registry()
# Ensure the provided value is in the right format
value = ureg.Quantity(value)
if value.units == ureg.dimensionless:
return True
if value.to_base_units().units == ureg.dimensionless:
return True
# At this point, the value is not dimensionless
return False

View File

@@ -0,0 +1,89 @@
"""Code for managing email functionality in InvenTree."""
import logging
from django.conf import settings
from django.core import mail as django_mail
import InvenTree.ready
import InvenTree.tasks
logger = logging.getLogger('inventree')
def is_email_configured():
"""Check if email backend is configured.
NOTE: This does not check if the configuration is valid!
"""
configured = True
testing = settings.TESTING
if InvenTree.ready.isInTestMode():
return False
if InvenTree.ready.isImportingData():
return False
if not settings.EMAIL_HOST:
configured = False
# Display warning unless in test mode
if not testing: # pragma: no cover
logger.debug('EMAIL_HOST is not configured')
# Display warning unless in test mode
if not settings.EMAIL_HOST_USER and not testing: # pragma: no cover
logger.debug('EMAIL_HOST_USER is not configured')
# Display warning unless in test mode
if not settings.EMAIL_HOST_PASSWORD and testing: # pragma: no cover
logger.debug('EMAIL_HOST_PASSWORD is not configured')
# Email sender must be configured
if not settings.DEFAULT_FROM_EMAIL:
configured = False
if not testing: # pragma: no cover
logger.debug('DEFAULT_FROM_EMAIL is not configured')
return configured
def send_email(subject, body, recipients, from_email=None, html_message=None):
"""Send an email with the specified subject and body, to the specified recipients list."""
if isinstance(recipients, str):
recipients = [recipients]
import InvenTree.ready
import InvenTree.status
if InvenTree.ready.isImportingData():
# If we are importing data, don't send emails
return
if not InvenTree.email.is_email_configured() and not settings.TESTING:
# Email is not configured / enabled
return
# If a *from_email* is not specified, ensure that the default is set
if not from_email:
from_email = settings.DEFAULT_FROM_EMAIL
# If we still don't have a valid from_email, then we can't send emails
if not from_email:
if settings.TESTING:
from_email = 'from@test.com'
else:
logger.error('send_email failed: DEFAULT_FROM_EMAIL not specified')
return
InvenTree.tasks.offload_task(
django_mail.send_mail,
subject,
body,
from_email,
recipients,
fail_silently=False,
html_message=html_message,
)

View File

@@ -18,37 +18,58 @@ from rest_framework import serializers
from rest_framework.exceptions import ValidationError as DRFValidationError
from rest_framework.response import Response
import InvenTree.sentry
logger = logging.getLogger('inventree')
def log_error(path):
def log_error(path, error_name=None, error_info=None, error_data=None):
"""Log an error to the database.
- Uses python exception handling to extract error details
Arguments:
path: The 'path' (most likely a URL) associated with this error (optional)
"""
kwargs:
error_name: The name of the error (optional, overrides 'kind')
error_info: The error information (optional, overrides 'info')
error_data: The error data (optional, overrides 'data')
"""
kind, info, data = sys.exc_info()
# Check if the eror is on the ignore list
# Check if the error is on the ignore list
if kind in settings.IGNORED_ERRORS:
return
if error_name:
kind = error_name
else:
kind = getattr(kind, '__name__', 'Unknown Error')
if error_info:
info = error_info
if error_data:
data = error_data
else:
try:
data = '\n'.join(traceback.format_exception(kind, info, data))
except AttributeError:
data = 'No traceback information available'
# Log error to stderr
logger.error(info)
# Ensure the error information does not exceed field size limits
path = path[:200]
kind = kind[:128]
try:
Error.objects.create(
kind=kind.__name__,
info=info,
data='\n'.join(traceback.format_exception(kind, info, data)),
path=path,
)
except (OperationalError, IntegrityError):
Error.objects.create(kind=kind, info=info or '', data=data or '', path=path)
except Exception:
# Not much we can do if logging the error throws a db exception
pass
logger.exception('Failed to log exception to database')
def exception_handler(exc, context):
@@ -61,18 +82,12 @@ def exception_handler(exc, context):
"""
response = None
if settings.SENTRY_ENABLED and settings.SENTRY_DSN and not settings.DEBUG:
# Report this exception to sentry.io
from sentry_sdk import capture_exception
# The following types of errors are ignored, they are "expected"
do_not_report = [
DjangoValidationError,
DRFValidationError,
]
if not any([isinstance(exc, err) for err in do_not_report]):
capture_exception(exc)
# Pass exception to sentry.io handler
try:
InvenTree.sentry.report_exception(exc)
except Exception:
# If sentry.io fails, we don't want to crash the server!
pass
# Catch any django validation error, and re-throw a DRF validation error
if isinstance(exc, DjangoValidationError):
@@ -91,7 +106,7 @@ def exception_handler(exc, context):
# If in DEBUG mode, provide error information in the response
error_detail = str(exc)
else:
error_detail = _("Error details can be found in the admin panel")
error_detail = _('Error details can be found in the admin panel')
response_data = {
'error': type(exc).__name__,

View File

@@ -1,74 +1,116 @@
"""Exchangerate backend to use `exchangerate.host` to get rates."""
"""Custom exchange backend which hooks into the InvenTree plugin system to fetch exchange rates from an external API."""
import ssl
from urllib.error import URLError
from urllib.request import urlopen
import logging
from django.db.utils import OperationalError
from django.db.transaction import atomic
import certifi
from djmoney.contrib.exchange.backends.base import SimpleExchangeBackend
from djmoney.contrib.exchange.models import ExchangeBackend, Rate
from common.settings import currency_code_default, currency_codes
logger = logging.getLogger('inventree')
class InvenTreeExchange(SimpleExchangeBackend):
"""Backend for automatically updating currency exchange rates.
Uses the `exchangerate.host` service API
Uses the plugin system to actually fetch the rates from an external API.
"""
name = "InvenTreeExchange"
name = 'InvenTreeExchange'
def __init__(self):
"""Set API url."""
self.url = "https://api.exchangerate.host/latest"
super().__init__()
def get_params(self):
"""Placeholder to set API key. Currently not required by `exchangerate.host`."""
# No API key is required
return {
}
def get_response(self, **kwargs):
"""Custom code to get response from server.
Note: Adds a 5-second timeout
"""
url = self.get_url(**kwargs)
try:
context = ssl.create_default_context(cafile=certifi.where())
response = urlopen(url, timeout=5, context=context)
return response.read()
except Exception:
# Something has gone wrong, but we can just try again next time
# Raise a TypeError so the outer function can handle this
raise TypeError
def update_rates(self, base_currency=None):
def get_rates(self, **kwargs) -> None:
"""Set the requested currency codes and get rates."""
# Set default - see B008
from common.models import InvenTreeSetting
from plugin import registry
base_currency = kwargs.get('base_currency', currency_code_default())
symbols = kwargs.get('symbols', currency_codes())
# Find the selected exchange rate plugin
slug = InvenTreeSetting.get_setting('CURRENCY_UPDATE_PLUGIN', '', create=False)
if slug:
plugin = registry.get_plugin(slug)
else:
plugin = None
if not plugin:
# Find the first active currency exchange plugin
plugins = registry.with_mixin('currencyexchange', active=True)
if len(plugins) > 0:
plugin = plugins[0]
if not plugin:
logger.warning(
'No active currency exchange plugins found - skipping update'
)
return {}
logger.info("Running exchange rate update using plugin '%s'", plugin.name)
# Plugin found - run the update task
try:
rates = plugin.update_exchange_rates(base_currency, symbols)
except Exception as exc:
logger.exception('Exchange rate update failed: %s', exc)
return {}
if not rates:
logger.warning(
'Exchange rate update failed - no data returned from plugin %s', slug
)
return {}
# Update exchange rates based on returned data
if type(rates) is not dict:
logger.warning(
'Invalid exchange rate data returned from plugin %s (type %s)',
slug,
type(rates),
)
return {}
# Ensure base currency is provided
rates[base_currency] = 1.00
return rates
@atomic
def update_rates(self, base_currency=None, **kwargs):
"""Call to update all exchange rates."""
backend, _ = ExchangeBackend.objects.update_or_create(
name=self.name, defaults={'base_currency': base_currency}
)
if base_currency is None:
base_currency = currency_code_default()
symbols = ','.join(currency_codes())
symbols = currency_codes()
try:
super().update_rates(base=base_currency, symbols=symbols)
# catch connection errors
except URLError:
print('Encountered connection error while updating')
except TypeError:
print('Exchange returned invalid response')
except OperationalError as e:
if 'SerializationFailure' in e.__cause__.__class__.__name__:
print('Serialization Failure while updating exchange rates')
# We are just going to swallow this exception because the
# exchange rates will be updated later by the scheduled task
else:
# Other operational errors probably are still show stoppers
# so reraise them so that the log contains the stacktrace
raise
logger.info(
'Updating exchange rates for %s (%s currencies)',
base_currency,
len(symbols),
)
# Fetch new rates from the backend
# If the backend fails, the existing rates will not be updated
rates = self.get_rates(base_currency=base_currency, symbols=symbols)
if rates:
# Clear out existing rates
backend.clear_rates()
Rate.objects.bulk_create([
Rate(currency=currency, value=amount, backend=backend)
for currency, amount in rates.items()
])
else:
logger.info(
'No exchange rates returned from backend - currencies not updated'
)
logger.info('Updated exchange rates for %s', base_currency)

View File

@@ -4,13 +4,14 @@ import sys
from decimal import Decimal
from django import forms
from django.db import models as models
from django.db import models
from django.utils.translation import gettext_lazy as _
from djmoney.forms.fields import MoneyField
from djmoney.models.fields import MoneyField as ModelMoneyField
from djmoney.models.validators import MinMoneyValidator
from rest_framework.fields import URLField as RestURLField
from rest_framework.fields import empty
import InvenTree.helpers
@@ -18,10 +19,10 @@ from .validators import AllowedURLValidator, allowable_url_schemes
class InvenTreeRestURLField(RestURLField):
"""Custom field for DRF with custom scheme vaildators."""
"""Custom field for DRF with custom scheme validators."""
def __init__(self, **kwargs):
"""Update schemes."""
# Enforce 'max length' parameter in form validation
if 'max_length' not in kwargs:
kwargs['max_length'] = 200
@@ -29,6 +30,21 @@ class InvenTreeRestURLField(RestURLField):
super().__init__(**kwargs)
self.validators[-1].schemes = allowable_url_schemes()
def run_validation(self, data=empty):
"""Override default validation behaviour for this field type."""
import common.models
strict_urls = common.models.InvenTreeSetting.get_setting(
'INVENTREE_STRICT_URLS', True, cache=False
)
if not strict_urls and data is not empty:
if '://' not in data:
# Validate as if there were a schema provided
data = 'http://' + data
return super().run_validation(data=data)
class InvenTreeURLField(models.URLField):
"""Custom URL field which has custom scheme validators."""
@@ -36,20 +52,29 @@ class InvenTreeURLField(models.URLField):
default_validators = [AllowedURLValidator()]
def __init__(self, **kwargs):
"""Initialization method for InvenTreeURLField"""
"""Initialization method for InvenTreeURLField."""
# Max length for InvenTreeURLField is set to 200
kwargs['max_length'] = 200
super().__init__(**kwargs)
def money_kwargs():
def money_kwargs(**kwargs):
"""Returns the database settings for MoneyFields."""
from common.settings import currency_code_default, currency_code_mappings
kwargs = {}
kwargs['currency_choices'] = currency_code_mappings()
kwargs['default_currency'] = currency_code_default()
# Default values (if not specified)
if 'max_digits' not in kwargs:
kwargs['max_digits'] = 19
if 'decimal_places' not in kwargs:
kwargs['decimal_places'] = 6
if 'currency_choices' not in kwargs:
kwargs['currency_choices'] = currency_code_mappings()
if 'default_currency' not in kwargs:
kwargs['default_currency'] = currency_code_default()
return kwargs
@@ -63,16 +88,8 @@ class InvenTreeModelMoneyField(ModelMoneyField):
# remove currency information for a clean migration
kwargs['default_currency'] = ''
kwargs['currency_choices'] = []
else:
# set defaults
kwargs.update(money_kwargs())
# Default values (if not specified)
if 'max_digits' not in kwargs:
kwargs['max_digits'] = 19
if 'decimal_places' not in kwargs:
kwargs['decimal_places'] = 6
kwargs = money_kwargs(**kwargs)
# Set a minimum value validator
validators = kwargs.get('validators', [])
@@ -81,11 +98,8 @@ class InvenTreeModelMoneyField(ModelMoneyField):
# If no validators are provided, add some "standard" ones
if len(validators) == 0:
if not allow_negative:
validators.append(
MinMoneyValidator(0),
)
validators.append(MinMoneyValidator(0))
kwargs['validators'] = validators
@@ -111,13 +125,10 @@ class InvenTreeModelMoneyField(ModelMoneyField):
class InvenTreeMoneyField(MoneyField):
"""Custom MoneyField for clean migrations while using dynamic currency settings."""
def __init__(self, *args, **kwargs):
"""Override initial values with the real info from database."""
kwargs.update(money_kwargs())
kwargs['max_digits'] = 19
kwargs['decimal_places'] = 6
kwargs = money_kwargs(**kwargs)
super().__init__(*args, **kwargs)
@@ -131,11 +142,7 @@ class DatePickerFormField(forms.DateField):
required = kwargs.get('required', False)
initial = kwargs.get('initial', None)
widget = forms.DateInput(
attrs={
'type': 'date',
}
)
widget = forms.DateInput(attrs={'type': 'date'})
forms.DateField.__init__(
self,
@@ -143,13 +150,12 @@ class DatePickerFormField(forms.DateField):
initial=initial,
help_text=help_text,
widget=widget,
label=label
label=label,
)
def round_decimal(value, places, normalize=False):
"""Round value to the specified number of places."""
if type(value) in [Decimal, float]:
value = round(value, places)
@@ -186,20 +192,19 @@ class RoundingDecimalField(models.DecimalField):
def formfield(self, **kwargs):
"""Return a Field instance for this field."""
kwargs['form_class'] = RoundingDecimalFormField
return super().formfield(**kwargs)
class InvenTreeNotesField(models.TextField):
"""Custom implementation of a 'notes' field"""
"""Custom implementation of a 'notes' field."""
# Maximum character limit for the various 'notes' fields
NOTES_MAX_LENGTH = 50000
def __init__(self, **kwargs):
"""Configure default initial values for this field"""
"""Configure default initial values for this field."""
kwargs['max_length'] = self.NOTES_MAX_LENGTH
kwargs['verbose_name'] = _('Notes')
kwargs['blank'] = True

View File

@@ -1,22 +1,43 @@
"""General filters for InvenTree."""
from datetime import datetime
from django.conf import settings
from django.utils import timezone
from django.utils.timezone import make_aware
from django_filters import rest_framework as rest_filters
from rest_framework import filters
from InvenTree.helpers import str2bool
import InvenTree.helpers
class InvenTreeDateFilter(rest_filters.DateFilter):
"""Custom DateFilter class which handles timezones correctly."""
def filter(self, qs, value):
"""Override the filter method to handle timezones correctly."""
if settings.USE_TZ:
if value is not None:
tz = timezone.get_current_timezone()
value = datetime(value.year, value.month, value.day)
value = make_aware(value, tz, True)
return super().filter(qs, value)
class InvenTreeSearchFilter(filters.SearchFilter):
"""Custom search filter which allows adjusting of search terms dynamically"""
"""Custom search filter which allows adjusting of search terms dynamically."""
def get_search_fields(self, view, request):
"""Return a set of search fields for the request, adjusted based on request params.
The following query params are available to 'augment' the search (in decreasing order of priority)
- search_regex: If True, search is perfomed on 'regex' comparison
- search_regex: If True, search is performed on 'regex' comparison
"""
regex = str2bool(request.query_params.get('search_regex', False))
regex = InvenTree.helpers.str2bool(
request.query_params.get('search_regex', False)
)
search_fields = super().get_search_fields(view, request)
@@ -36,8 +57,9 @@ class InvenTreeSearchFilter(filters.SearchFilter):
Depending on the request parameters, we may "augment" these somewhat
"""
whole = str2bool(request.query_params.get('search_whole', False))
whole = InvenTree.helpers.str2bool(
request.query_params.get('search_whole', False)
)
terms = []
@@ -53,7 +75,7 @@ class InvenTreeSearchFilter(filters.SearchFilter):
if whole:
# Wrap the search term to enable word-boundary matching
term = r"\y" + term + r"\y"
term = r'\y' + term + r'\y'
terms.append(term)
@@ -91,7 +113,6 @@ class InvenTreeOrderingFilter(filters.OrderingFilter):
ordering = []
for field in ordering_initial:
reverse = field.startswith('-')
if reverse:
@@ -145,7 +166,4 @@ SEARCH_ORDER_FILTER_ALIAS = [
InvenTreeOrderingFilter,
]
ORDER_FILTER = [
rest_filters.DjangoFilterBackend,
filters.OrderingFilter,
]
ORDER_FILTER = [rest_filters.DjangoFilterBackend, filters.OrderingFilter]

View File

@@ -1,17 +1,22 @@
"""Custom string formatting functions and helpers"""
"""Custom string formatting functions and helpers."""
import re
import string
from django.conf import settings
from django.utils import translation
from django.utils.translation import gettext_lazy as _
from babel import Locale
from babel.numbers import parse_pattern
from djmoney.money import Money
def parse_format_string(fmt_string: str) -> dict:
"""Extract formatting information from the provided format string.
Returns a dict object which contains structured information about the format groups
"""
groups = string.Formatter().parse(fmt_string)
info = {}
@@ -31,16 +36,13 @@ def parse_format_string(fmt_string: str) -> dict:
else:
seen_groups.add(name)
info[group[1]] = {
'format': group[1],
'prefix': group[0],
}
info[group[1]] = {'format': group[1], 'prefix': group[0]}
return info
def construct_format_regex(fmt_string: str) -> str:
r"""Construct a regular expression based on a provided format string
r"""Construct a regular expression based on a provided format string.
This function turns a python format string into a regular expression,
which can be used for two purposes:
@@ -62,8 +64,7 @@ def construct_format_regex(fmt_string: str) -> str:
Raises:
ValueError: Format string is invalid
"""
pattern = "^"
pattern = '^'
for group in string.Formatter().parse(fmt_string):
prefix = group[0] # Prefix (literal text appearing before this group)
@@ -71,9 +72,23 @@ def construct_format_regex(fmt_string: str) -> str:
format = group[2] # Format specifier e.g :04d
rep = [
'+', '-', '.',
'{', '}', '(', ')',
'^', '$', '~', '!', '@', ':', ';', '|', '\'', '"',
'+',
'-',
'.',
'{',
'}',
'(',
')',
'^',
'$',
'~',
'!',
'@',
':',
';',
'|',
"'",
'"',
]
# Escape any special regex characters
@@ -90,8 +105,7 @@ def construct_format_regex(fmt_string: str) -> str:
# Add a named capture group for the format entry
if name:
# Check if integer values are requried
# Check if integer values are required
if format.endswith('d'):
chr = '\d'
else:
@@ -101,9 +115,9 @@ def construct_format_regex(fmt_string: str) -> str:
# TODO: Introspect required width
w = '+'
pattern += f"(?P<{name}>{chr}{w})"
pattern += f'(?P<{name}>{chr}{w})'
pattern += "$"
pattern += '$'
return pattern
@@ -121,7 +135,6 @@ def validate_string(value: str, fmt_string: str) -> str:
Raises:
ValueError: The provided format string is invalid
"""
pattern = construct_format_regex(fmt_string)
result = re.match(pattern, value)
@@ -130,7 +143,7 @@ def validate_string(value: str, fmt_string: str) -> str:
def extract_named_group(name: str, value: str, fmt_string: str) -> str:
"""Extract a named value from the provided string, given the provided format string
"""Extract a named value from the provided string, given the provided format string.
Args:
name: Name of group to extract e.g. 'ref'
@@ -145,7 +158,6 @@ def extract_named_group(name: str, value: str, fmt_string: str) -> str:
NameError: named value does not exist in the format string
IndexError: named value could not be found in the provided entry
"""
info = parse_format_string(fmt_string)
if name not in info.keys():
@@ -159,8 +171,49 @@ def extract_named_group(name: str, value: str, fmt_string: str) -> str:
result = re.match(pattern, value)
if not result:
raise ValueError(_("Provided value does not match required pattern: ") + fmt_string)
raise ValueError(
_('Provided value does not match required pattern: ') + fmt_string
)
# And return the value we are interested in
# Note: This will raise an IndexError if the named group was not matched
return result.group(name)
def format_money(
money: Money,
decimal_places: int = None,
format: str = None,
include_symbol: bool = True,
) -> str:
"""Format money object according to the currently set local.
Args:
money (Money): The money object to format
decimal_places (int): Number of decimal places to use
format (str): Format pattern according LDML / the babel format pattern syntax (https://babel.pocoo.org/en/latest/numbers.html)
Returns:
str: The formatted string
Raises:
ValueError: format string is incorrectly specified
"""
language = None and translation.get_language() or settings.LANGUAGE_CODE
locale = Locale.parse(translation.to_locale(language))
if format:
pattern = parse_pattern(format)
else:
pattern = locale.currency_formats['standard']
if decimal_places is not None:
pattern.frac_prec = (decimal_places, decimal_places)
result = pattern.apply(
money.amount,
locale,
currency=money.currency.code if include_symbol else '',
currency_digits=decimal_places is None,
decimal_quantization=decimal_places is not None,
)
return result

View File

@@ -6,22 +6,24 @@ from urllib.parse import urlencode
from django import forms
from django.conf import settings
from django.contrib.auth.models import Group, User
from django.contrib.sites.models import Site
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from allauth.account.adapter import DefaultAccountAdapter
from allauth.account.forms import SignupForm, set_form_field_order
from allauth.exceptions import ImmediateHttpResponse
from allauth.account.forms import LoginForm, SignupForm, set_form_field_order
from allauth.core.exceptions import ImmediateHttpResponse
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from allauth_2fa.adapter import OTPAdapter
from allauth_2fa.utils import user_has_valid_totp_device
from crispy_forms.bootstrap import (AppendedText, PrependedAppendedText,
PrependedText)
from crispy_forms.bootstrap import AppendedText, PrependedAppendedText, PrependedText
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Field, Layout
from dj_rest_auth.registration.serializers import RegisterSerializer
from rest_framework import serializers
import InvenTree.helpers_model
import InvenTree.sso
from common.models import InvenTreeSetting
from InvenTree.exceptions import log_error
@@ -65,10 +67,10 @@ class HelperForm(forms.ModelForm):
# Look for font-awesome icons
if prefix and prefix.startswith('fa-'):
prefix = r"<i class='fas {fa}'/>".format(fa=prefix)
prefix = f"<i class='fas {prefix}'/>"
if suffix and suffix.startswith('fa-'):
suffix = r"<i class='fas {fa}'/>".format(fa=suffix)
suffix = f"<i class='fas {suffix}'/>"
if prefix and suffix:
layouts.append(
@@ -77,31 +79,19 @@ class HelperForm(forms.ModelForm):
field,
prepended_text=prefix,
appended_text=suffix,
placeholder=placeholder
placeholder=placeholder,
)
)
)
elif prefix:
layouts.append(
Field(
PrependedText(
field,
prefix,
placeholder=placeholder
)
)
Field(PrependedText(field, prefix, placeholder=placeholder))
)
elif suffix:
layouts.append(
Field(
AppendedText(
field,
suffix,
placeholder=placeholder
)
)
Field(AppendedText(field, suffix, placeholder=placeholder))
)
else:
@@ -117,10 +107,7 @@ class EditUserForm(HelperForm):
"""Metaclass options."""
model = User
fields = [
'first_name',
'last_name',
]
fields = ['first_name', 'last_name']
class SetPasswordForm(HelperForm):
@@ -130,11 +117,7 @@ class SetPasswordForm(HelperForm):
"""Metaclass options."""
model = User
fields = [
'enter_password',
'confirm_password',
'old_password',
]
fields = ['enter_password', 'confirm_password', 'old_password']
enter_password = forms.CharField(
max_length=100,
@@ -143,7 +126,7 @@ class SetPasswordForm(HelperForm):
initial='',
widget=forms.PasswordInput(attrs={'autocomplete': 'off'}),
label=_('Enter password'),
help_text=_('Enter new password')
help_text=_('Enter new password'),
)
confirm_password = forms.CharField(
@@ -153,17 +136,37 @@ class SetPasswordForm(HelperForm):
initial='',
widget=forms.PasswordInput(attrs={'autocomplete': 'off'}),
label=_('Confirm password'),
help_text=_('Confirm new password')
help_text=_('Confirm new password'),
)
old_password = forms.CharField(
label=_("Old password"),
label=_('Old password'),
strip=False,
widget=forms.PasswordInput(attrs={'autocomplete': 'current-password', 'autofocus': True}),
required=False,
widget=forms.PasswordInput(
attrs={'autocomplete': 'current-password', 'autofocus': True}
),
)
# override allauth
class CustomLoginForm(LoginForm):
"""Custom login form to override default allauth behaviour."""
def login(self, request, redirect_url=None):
"""Perform login action.
First check that:
- A valid user has been supplied
"""
if not self.user:
# No user supplied - redirect to the login page
return HttpResponseRedirect(reverse('account_login'))
# Now perform default login action
return super().login(request, redirect_url)
class CustomSignupForm(SignupForm):
"""Override to use dynamic settings."""
@@ -175,70 +178,93 @@ class CustomSignupForm(SignupForm):
# check for two mail fields
if InvenTreeSetting.get_setting('LOGIN_SIGNUP_MAIL_TWICE'):
self.fields["email2"] = forms.EmailField(
label=_("Email (again)"),
self.fields['email2'] = forms.EmailField(
label=_('Email (again)'),
widget=forms.TextInput(
attrs={
"type": "email",
"placeholder": _("Email address confirmation"),
'type': 'email',
'placeholder': _('Email address confirmation'),
}
),
)
# check for two password fields
if not InvenTreeSetting.get_setting('LOGIN_SIGNUP_PWD_TWICE'):
self.fields.pop("password2")
self.fields.pop('password2')
# reorder fields
set_form_field_order(self, ["username", "email", "email2", "password1", "password2", ])
set_form_field_order(
self, ['username', 'email', 'email2', 'password1', 'password2']
)
def clean(self):
"""Make sure the supllied emails match if enabled in settings."""
"""Make sure the supplied emails match if enabled in settings."""
cleaned_data = super().clean()
# check for two mail fields
if InvenTreeSetting.get_setting('LOGIN_SIGNUP_MAIL_TWICE'):
email = cleaned_data.get("email")
email2 = cleaned_data.get("email2")
email = cleaned_data.get('email')
email2 = cleaned_data.get('email2')
if (email and email2) and email != email2:
self.add_error("email2", _("You must type the same email each time."))
self.add_error('email2', _('You must type the same email each time.'))
return cleaned_data
def registration_enabled():
"""Determine whether user registration is enabled."""
if (
InvenTreeSetting.get_setting('LOGIN_ENABLE_REG')
or InvenTree.sso.registration_enabled()
):
if settings.EMAIL_HOST:
return True
else:
logger.error(
'Registration cannot be enabled, because EMAIL_HOST is not configured.'
)
return False
class RegistratonMixin:
"""Mixin to check if registration should be enabled."""
def is_open_for_signup(self, request, *args, **kwargs):
"""Check if signup is enabled in settings.
Configure the class variable `REGISTRATION_SETTING` to set which setting should be used, defualt: `LOGIN_ENABLE_REG`.
Configure the class variable `REGISTRATION_SETTING` to set which setting should be used, default: `LOGIN_ENABLE_REG`.
"""
if settings.EMAIL_HOST and (InvenTreeSetting.get_setting('LOGIN_ENABLE_REG') or InvenTreeSetting.get_setting('LOGIN_ENABLE_SSO_REG')):
if registration_enabled():
return super().is_open_for_signup(request, *args, **kwargs)
return False
def clean_email(self, email):
"""Check if the mail is valid to the pattern in LOGIN_SIGNUP_MAIL_RESTRICTION (if enabled in settings)."""
mail_restriction = InvenTreeSetting.get_setting('LOGIN_SIGNUP_MAIL_RESTRICTION', None)
mail_restriction = InvenTreeSetting.get_setting(
'LOGIN_SIGNUP_MAIL_RESTRICTION', None
)
if not mail_restriction:
return super().clean_email(email)
split_email = email.split('@')
if len(split_email) != 2:
logger.error(f'The user {email} has an invalid email address')
raise forms.ValidationError(_('The provided primary email address is not valid.'))
logger.error('The user %s has an invalid email address', email)
raise forms.ValidationError(
_('The provided primary email address is not valid.')
)
mailoptions = mail_restriction.split(',')
for option in mailoptions:
if not option.startswith('@'):
log_error('LOGIN_SIGNUP_MAIL_RESTRICTION is not configured correctly')
raise forms.ValidationError(_('The provided primary email address is not valid.'))
raise forms.ValidationError(
_('The provided primary email address is not valid.')
)
else:
if split_email[1] == option[1:]:
return super().clean_email(email)
logger.info(f'The provided email domain for {email} is not approved')
logger.info('The provided email domain for %s is not approved', email)
raise forms.ValidationError(_('The provided email domain is not approved.'))
def save_user(self, request, user, form, commit=True):
@@ -253,7 +279,10 @@ class RegistratonMixin:
group = Group.objects.get(id=start_group)
user.groups.add(group)
except Group.DoesNotExist:
logger.error('The setting `SIGNUP_GROUP` contains an non existant group', start_group)
logger.exception(
'The setting `SIGNUP_GROUP` contains an non existent group',
start_group,
)
user.save()
return user
@@ -263,11 +292,14 @@ class CustomUrlMixin:
def get_email_confirmation_url(self, request, emailconfirmation):
"""Custom email confirmation (activation) url."""
url = reverse("account_confirm_email", args=[emailconfirmation.key])
return Site.objects.get_current().domain + url
url = reverse('account_confirm_email', args=[emailconfirmation.key])
return InvenTree.helpers_model.construct_absolute_url(url)
class CustomAccountAdapter(CustomUrlMixin, RegistratonMixin, OTPAdapter, DefaultAccountAdapter):
class CustomAccountAdapter(
CustomUrlMixin, RegistratonMixin, OTPAdapter, DefaultAccountAdapter
):
"""Override of adapter to use dynamic settings."""
def send_mail(self, template_prefix, email, context):
@@ -276,7 +308,7 @@ class CustomAccountAdapter(CustomUrlMixin, RegistratonMixin, OTPAdapter, Default
try:
result = super().send_mail(template_prefix, email, context)
except Exception:
# An exception ocurred while attempting to send email
# An exception occurred while attempting to send email
# Log it (for admin users) and return silently
log_error('account email')
result = False
@@ -285,8 +317,18 @@ class CustomAccountAdapter(CustomUrlMixin, RegistratonMixin, OTPAdapter, Default
return False
def get_email_confirmation_url(self, request, emailconfirmation):
"""Construct the email confirmation url."""
from InvenTree.helpers_model import construct_absolute_url
class CustomSocialAccountAdapter(CustomUrlMixin, RegistratonMixin, DefaultSocialAccountAdapter):
url = super().get_email_confirmation_url(request, emailconfirmation)
url = construct_absolute_url(url)
return url
class CustomSocialAccountAdapter(
CustomUrlMixin, RegistratonMixin, DefaultSocialAccountAdapter
):
"""Override of adapter to use dynamic settings."""
def is_auto_signup_allowed(self, request, sociallogin):
@@ -313,9 +355,41 @@ class CustomSocialAccountAdapter(CustomUrlMixin, RegistratonMixin, DefaultSocial
if request.GET:
redirect_url += '?' + urlencode(request.GET)
raise ImmediateHttpResponse(
response=HttpResponseRedirect(redirect_url)
)
raise ImmediateHttpResponse(response=HttpResponseRedirect(redirect_url))
# Otherwise defer to the original allauth adapter.
return super().login(request, user)
def authentication_error(
self, request, provider_id, error=None, exception=None, extra_context=None
):
"""Callback method for authentication errors."""
if not error:
error = request.GET.get('error', None)
if not exception:
exception = request.GET.get('error_description', None)
path = request.path or 'sso'
# Log the error to the database
log_error(path, error_name=error, error_data=exception)
logger.error("SSO error for provider '%s' - check admin error log", provider_id)
# override dj-rest-auth
class CustomRegisterSerializer(RegisterSerializer):
"""Override of serializer to use dynamic settings."""
email = serializers.EmailField()
def __init__(self, instance=None, data=..., **kwargs):
"""Check settings to influence which fields are needed."""
kwargs['email_required'] = InvenTreeSetting.get_setting('LOGIN_MAIL_REQUIRED')
super().__init__(instance, data, **kwargs)
def save(self, request):
"""Override to check if registration is open."""
if registration_enabled():
return super().save(request)
raise forms.ValidationError(_('Registration is disabled.'))

View File

@@ -8,54 +8,91 @@ import os
import os.path
import re
from decimal import Decimal, InvalidOperation
from pathlib import Path
from typing import TypeVar
from wsgiref.util import FileWrapper
from django.conf import settings
from django.contrib.auth.models import Permission
from django.contrib.staticfiles.storage import StaticFilesStorage
from django.core.exceptions import FieldError, ValidationError
from django.core.files.storage import default_storage
from django.core.validators import URLValidator
from django.http import StreamingHttpResponse
from django.test import TestCase
from django.utils.translation import gettext_lazy as _
import moneyed.localization
import regex
import requests
from bleach import clean
from djmoney.contrib.exchange.models import convert_money
from djmoney.money import Money
from PIL import Image
import InvenTree.version
from common.models import InvenTreeSetting
from common.notifications import (InvenTreeNotificationBodies,
NotificationBody, trigger_notification)
from common.settings import currency_code_default
from .api_tester import ExchangeRateMixin, UserMixin
from .settings import MEDIA_URL, STATIC_URL
logger = logging.getLogger('inventree')
def getSetting(key, backup_value=None):
"""Shortcut for reading a setting value from the database."""
return InvenTreeSetting.get_setting(key, backup_value=backup_value)
def extract_int(reference, clip=0x7FFFFFFF, allow_negative=False):
"""Extract an integer out of reference."""
# Default value if we cannot convert to an integer
ref_int = 0
reference = str(reference).strip()
# Ignore empty string
if len(reference) == 0:
return 0
# Look at the start of the string - can it be "integerized"?
result = re.match(r'^(\d+)', reference)
if result and len(result.groups()) == 1:
ref = result.groups()[0]
try:
ref_int = int(ref)
except Exception:
ref_int = 0
else:
# Look at the "end" of the string
result = re.search(r'(\d+)$', reference)
if result and len(result.groups()) == 1:
ref = result.groups()[0]
try:
ref_int = int(ref)
except Exception:
ref_int = 0
# Ensure that the returned values are within the range that can be stored in an IntegerField
# Note: This will result in large values being "clipped"
if clip is not None:
if ref_int > clip:
ref_int = clip
elif ref_int < -clip:
ref_int = -clip
if not allow_negative and ref_int < 0:
ref_int = abs(ref_int)
return ref_int
def generateTestKey(test_name):
def generateTestKey(test_name: str) -> str:
"""Generate a test 'key' for a given test name. This must not have illegal chars as it will be used for dict lookup in a template.
Tests must be named such that they will have unique keys.
"""
if test_name is None:
test_name = ''
key = test_name.strip().lower()
key = key.replace(" ", "")
key = key.replace(' ', '')
# Remove any characters that cannot be used to represent a variable
key = re.sub(r'[^a-zA-Z0-9]', '', key)
key = re.sub(r'[^a-zA-Z0-9_]', '', key)
# If the key starts with a digit, prefix with an underscore
if key[0].isdigit():
key = '_' + key
return key
@@ -67,13 +104,12 @@ def constructPathString(path, max_chars=250):
path: A list of strings e.g. ['path', 'to', 'location']
max_chars: Maximum number of characters
"""
pathstring = '/'.join(path)
# Replace middle elements to limit the pathstring
if len(pathstring) > max_chars:
n = int(max_chars / 2 - 2)
pathstring = pathstring[:n] + "..." + pathstring[-n:]
pathstring = pathstring[:n] + '...' + pathstring[-n:]
return pathstring
@@ -88,130 +124,6 @@ def getStaticUrl(filename):
return os.path.join(STATIC_URL, str(filename))
def construct_absolute_url(*arg):
"""Construct (or attempt to construct) an absolute URL from a relative URL.
This is useful when (for example) sending an email to a user with a link
to something in the InvenTree web framework.
This requires the BASE_URL configuration option to be set!
"""
base = str(InvenTreeSetting.get_setting('INVENTREE_BASE_URL'))
url = '/'.join(arg)
if not base:
return url
# Strip trailing slash from base url
if base.endswith('/'):
base = base[:-1]
if url.startswith('/'):
url = url[1:]
url = f"{base}/{url}"
return url
def download_image_from_url(remote_url, timeout=2.5):
"""Download an image file from a remote URL.
This is a potentially dangerous operation, so we must perform some checks:
- The remote URL is available
- The Content-Length is provided, and is not too large
- The file is a valid image file
Arguments:
remote_url: The remote URL to retrieve image
max_size: Maximum allowed image size (default = 1MB)
timeout: Connection timeout in seconds (default = 5)
Returns:
An in-memory PIL image file, if the download was successful
Raises:
requests.exceptions.ConnectionError: Connection could not be established
requests.exceptions.Timeout: Connection timed out
requests.exceptions.HTTPError: Server responded with invalid response code
ValueError: Server responded with invalid 'Content-Length' value
TypeError: Response is not a valid image
"""
# Check that the provided URL at least looks valid
validator = URLValidator()
validator(remote_url)
# Calculate maximum allowable image size (in bytes)
max_size = int(InvenTreeSetting.get_setting('INVENTREE_DOWNLOAD_IMAGE_MAX_SIZE')) * 1024 * 1024
# Add user specified user-agent to request (if specified)
user_agent = InvenTreeSetting.get_setting('INVENTREE_DOWNLOAD_FROM_URL_USER_AGENT')
if user_agent:
headers = {"User-Agent": user_agent}
else:
headers = None
try:
response = requests.get(
remote_url,
timeout=timeout,
allow_redirects=True,
stream=True,
headers=headers,
)
# Throw an error if anything goes wrong
response.raise_for_status()
except requests.exceptions.ConnectionError as exc:
raise Exception(_("Connection error") + f": {str(exc)}")
except requests.exceptions.Timeout as exc:
raise exc
except requests.exceptions.HTTPError:
raise requests.exceptions.HTTPError(_("Server responded with invalid status code") + f": {response.status_code}")
except Exception as exc:
raise Exception(_("Exception occurred") + f": {str(exc)}")
if response.status_code != 200:
raise Exception(_("Server responded with invalid status code") + f": {response.status_code}")
try:
content_length = int(response.headers.get('Content-Length', 0))
except ValueError:
raise ValueError(_("Server responded with invalid Content-Length value"))
if content_length > max_size:
raise ValueError(_("Image size is too large"))
# Download the file, ensuring we do not exceed the reported size
fo = io.BytesIO()
dl_size = 0
chunk_size = 64 * 1024
for chunk in response.iter_content(chunk_size=chunk_size):
dl_size += len(chunk)
if dl_size > max_size:
raise ValueError(_("Image download exceeded maximum size"))
fo.write(chunk)
if dl_size == 0:
raise ValueError(_("Remote server returned empty response"))
# Now, attempt to convert the downloaded data to a valid image file
# img.verify() will throw an exception if the image is not valid
try:
img = Image.open(fo).convert()
img.verify()
except Exception:
raise TypeError(_("Supplied URL is not a valid image file"))
return img
def TestIfImage(img):
"""Test if an image file is indeed an image."""
try:
@@ -223,20 +135,18 @@ def TestIfImage(img):
def getBlankImage():
"""Return the qualified path for the 'blank image' placeholder."""
return getStaticUrl("img/blank_image.png")
return getStaticUrl('img/blank_image.png')
def getBlankThumbnail():
"""Return the qualified path for the 'blank image' thumbnail placeholder."""
return getStaticUrl("img/blank_image.thumbnail.png")
return getStaticUrl('img/blank_image.thumbnail.png')
def getLogoImage(as_file=False, custom=True):
"""Return the InvenTree logo image, or a custom logo if available."""
"""Return the path to the logo-file."""
if custom and settings.CUSTOM_LOGO:
static_storage = StaticFilesStorage()
if static_storage.exists(settings.CUSTOM_LOGO):
@@ -248,30 +158,26 @@ def getLogoImage(as_file=False, custom=True):
if storage is not None:
if as_file:
return f"file://{storage.path(settings.CUSTOM_LOGO)}"
else:
return storage.url(settings.CUSTOM_LOGO)
return f'file://{storage.path(settings.CUSTOM_LOGO)}'
return storage.url(settings.CUSTOM_LOGO)
# If we have got to this point, return the default logo
if as_file:
path = settings.STATIC_ROOT.joinpath('img/inventree.png')
return f"file://{path}"
else:
return getStaticUrl('img/inventree.png')
return f'file://{path}'
return getStaticUrl('img/inventree.png')
def getSplashScren(custom=True):
"""Return the InvenTree splash screen, or a custom splash if available"""
def getSplashScreen(custom=True):
"""Return the InvenTree splash screen, or a custom splash if available."""
static_storage = StaticFilesStorage()
if custom and settings.CUSTOM_SPLASH:
if static_storage.exists(settings.CUSTOM_SPLASH):
return static_storage.url(settings.CUSTOM_SPLASH)
# No custom splash screen
return static_storage.url("img/inventree_splash.jpg")
return static_storage.url('img/inventree_splash.jpg')
def TestIfImageURL(url):
@@ -280,10 +186,15 @@ def TestIfImageURL(url):
Simply tests the extension against a set of allowed values
"""
return os.path.splitext(os.path.basename(url))[-1].lower() in [
'.jpg', '.jpeg', '.j2k',
'.png', '.bmp',
'.tif', '.tiff',
'.webp', '.gif',
'.jpg',
'.jpeg',
'.j2k',
'.png',
'.bmp',
'.tif',
'.tiff',
'.webp',
'.gif',
]
@@ -298,13 +209,12 @@ def str2bool(text, test=True):
True if the text looks like the selected boolean value
"""
if test:
return str(text).lower() in ['1', 'y', 'yes', 't', 'true', 'ok', 'on', ]
else:
return str(text).lower() in ['0', 'n', 'no', 'none', 'f', 'false', 'off', ]
return str(text).lower() in ['1', 'y', 'yes', 't', 'true', 'ok', 'on']
return str(text).lower() in ['0', 'n', 'no', 'none', 'f', 'false', 'off']
def str2int(text, default=None):
"""Convert a string to int if possible
"""Convert a string to int if possible.
Args:
text: Int like string
@@ -325,8 +235,7 @@ def is_bool(text):
return True
elif str2bool(text, False):
return True
else:
return False
return False
def isNull(text):
@@ -338,7 +247,15 @@ def isNull(text):
Returns:
True if the text looks like a null value
"""
return str(text).strip().lower() in ['top', 'null', 'none', 'empty', 'false', '-1', '']
return str(text).strip().lower() in [
'top',
'null',
'none',
'empty',
'false',
'-1',
'',
]
def normalize(d):
@@ -370,7 +287,7 @@ def increment(value):
# Provide a default value if provided with a null input
return '1'
pattern = r"(.*?)(\d+)?$"
pattern = r'(.*?)(\d+)?$'
result = re.search(pattern, value)
@@ -429,7 +346,7 @@ def decimal2string(d):
if '.' not in s:
return s
return s.rstrip("0").rstrip(".")
return s.rstrip('0').rstrip('.')
def decimal2money(d, currency=None):
@@ -470,15 +387,13 @@ def MakeBarcode(cls_name, object_pk: int, object_data=None, **kwargs):
"""Generate a string for a barcode. Adds some global InvenTree parameters.
Args:
object_type: string describing the object type e.g. 'StockItem'
object_id: ID (Primary Key) of the object in the database
object_url: url for JSON API detail view of the object
data: Python dict object containing extra datawhich will be rendered to string (must only contain stringable values)
cls_name: string describing the object type e.g. 'StockItem'
object_pk (int): ID (Primary Key) of the object in the database
object_data: Python dict object containing extra data which will be rendered to string (must only contain stringable values)
Returns:
json string of the supplied data plus some other data
"""
if object_data is None:
object_data = {}
@@ -497,22 +412,17 @@ def MakeBarcode(cls_name, object_pk: int, object_data=None, **kwargs):
object_data['id'] = object_pk
data[cls_name] = object_data
return json.dumps(data, sort_keys=True)
return str(json.dumps(data, sort_keys=True))
def GetExportFormats():
"""Return a list of allowable file formats for exporting data."""
return [
'csv',
'tsv',
'xls',
'xlsx',
'json',
'yaml',
]
return ['csv', 'tsv', 'xls', 'xlsx', 'json', 'yaml']
def DownloadFile(data, filename, content_type='application/text', inline=False) -> StreamingHttpResponse:
def DownloadFile(
data, filename, content_type='application/text', inline=False
) -> StreamingHttpResponse:
"""Create a dynamic file for the user to download.
Args:
@@ -527,20 +437,22 @@ def DownloadFile(data, filename, content_type='application/text', inline=False)
filename = WrapWithQuotes(filename)
length = len(data)
if type(data) == str:
if isinstance(data, str):
wrapper = FileWrapper(io.StringIO(data))
else:
wrapper = FileWrapper(io.BytesIO(data))
response = StreamingHttpResponse(wrapper, content_type=content_type)
if type(data) == str:
if isinstance(data, str):
length = len(bytes(data, response.charset))
response['Content-Length'] = length
disposition = "inline" if inline else "attachment"
response['Content-Disposition'] = f'{disposition}; filename={filename}'
if inline:
disposition = f'inline; filename={filename}'
else:
disposition = f'attachment; filename={filename}'
response['Content-Disposition'] = disposition
return response
@@ -555,7 +467,6 @@ def increment_serial_number(serial: str):
Returns:
incremented value, or None if incrementing could not be performed.
"""
from plugin.registry import registry
# Ensure we start with a string value
@@ -581,7 +492,7 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value=
- Individual serials are separated by comma: 1, 2, 3, 6,22
- Sequential ranges with provided limits are separated by hyphens: 1-5, 20 - 40
- The "next" available serial number can be specified with the tilde (~) character
- Serial numbers can be supplied as <start>+ for getting all expecteded numbers starting from <start>
- Serial numbers can be supplied as <start>+ for getting all expected numbers starting from <start>
- Serial numbers can be supplied as <start>+<length> for getting <length> numbers starting from <start>
Actual generation of sequential serials is passed to the 'validation' plugin mixin,
@@ -592,14 +503,13 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value=
expected_quantity: The number of (unique) serial numbers we expect
starting_value: Provide a starting value for the sequence (or None)
"""
if starting_value is None:
starting_value = increment_serial_number(None)
try:
expected_quantity = int(expected_quantity)
except ValueError:
raise ValidationError([_("Invalid quantity provided")])
raise ValidationError([_('Invalid quantity provided')])
if input_string:
input_string = str(input_string).strip()
@@ -607,7 +517,7 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value=
input_string = ''
if len(input_string) == 0:
raise ValidationError([_("Empty serial number string")])
raise ValidationError([_('Empty serial number string')])
next_value = increment_serial_number(starting_value)
@@ -617,27 +527,26 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value=
next_value = increment_serial_number(next_value)
# Split input string by whitespace or comma (,) characters
groups = re.split(r"[\s,]+", input_string)
groups = re.split(r'[\s,]+', input_string)
serials = []
errors = []
def add_error(error: str):
"""Helper function for adding an error message"""
"""Helper function for adding an error message."""
if error not in errors:
errors.append(error)
def add_serial(serial):
"""Helper function to check for duplicated values"""
"""Helper function to check for duplicated values."""
serial = serial.strip()
# Ignore blank / emtpy serials
# Ignore blank / empty serials
if len(serial) == 0:
return
if serial in serials:
add_error(_("Duplicate serial") + f": {serial}")
add_error(_('Duplicate serial') + f': {serial}')
else:
serials.append(serial)
@@ -652,7 +561,6 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value=
return serials
for group in groups:
# Calculate the "remaining" quantity of serial numbers
remaining = expected_quantity - len(serials)
@@ -670,7 +578,7 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value=
if a == b:
# Invalid group
add_error(_("Invalid group range: {g}").format(g=group))
add_error(_(f'Invalid group range: {group}'))
continue
group_items = []
@@ -699,13 +607,21 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value=
break
if len(group_items) > remaining:
add_error(_("Group range {g} exceeds allowed quantity ({q})".format(g=group, q=expected_quantity)))
elif len(group_items) > 0 and group_items[0] == a and group_items[-1] == b:
add_error(
_(
f'Group range {group} exceeds allowed quantity ({expected_quantity})'
)
)
elif (
len(group_items) > 0
and group_items[0] == a
and group_items[-1] == b
):
# In this case, the range extraction looks like it has worked
for item in group_items:
add_serial(item)
else:
add_error(_("Invalid group range: {g}").format(g=group))
add_error(_(f'Invalid group range: {group}'))
else:
# In the case of a different number of hyphens, simply add the entire group
@@ -723,20 +639,24 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value=
sequence_count = max(0, expected_quantity - len(serials))
if len(items) > 2 or len(items) == 0:
add_error(_("Invalid group sequence: {g}").format(g=group))
add_error(_(f'Invalid group sequence: {group}'))
continue
elif len(items) == 2:
try:
if items[1]:
sequence_count = int(items[1]) + 1
except ValueError:
add_error(_("Invalid group sequence: {g}").format(g=group))
add_error(_(f'Invalid group sequence: {group}'))
continue
value = items[0]
# Keep incrementing up to the specified quantity
while value is not None and value not in sequence_items and counter < sequence_count:
while (
value is not None
and value not in sequence_items
and counter < sequence_count
):
sequence_items.append(value)
value = increment_serial_number(value)
counter += 1
@@ -745,7 +665,7 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value=
for item in sequence_items:
add_serial(item)
else:
add_error(_("Invalid group sequence: {g}").format(g=group))
add_error(_(f'Invalid group sequence: {group}'))
else:
# At this point, we assume that the 'group' is just a single serial value
@@ -755,10 +675,14 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value=
raise ValidationError(errors)
if len(serials) == 0:
raise ValidationError([_("No serial numbers found")])
raise ValidationError([_('No serial numbers found')])
if len(errors) == 0 and len(serials) != expected_quantity:
raise ValidationError([_("Number of unique serial numbers ({s}) must match quantity ({q})").format(s=len(serials), q=expected_quantity)])
raise ValidationError([
_(
f'Number of unique serial numbers ({len(serials)}) must match quantity ({expected_quantity})'
)
])
return serials
@@ -795,9 +719,7 @@ def validateFilterString(value, model=None):
pair = group.split('=')
if len(pair) != 2:
raise ValidationError(
"Invalid group: {g}".format(g=group)
)
raise ValidationError(f'Invalid group: {group}')
k, v = pair
@@ -805,9 +727,7 @@ def validateFilterString(value, model=None):
v = v.strip()
if not k or not v:
raise ValidationError(
"Invalid group: {g}".format(g=group)
)
raise ValidationError(f'Invalid group: {group}')
results[k] = v
@@ -816,82 +736,11 @@ def validateFilterString(value, model=None):
try:
model.objects.filter(**results)
except FieldError as e:
raise ValidationError(
str(e),
)
raise ValidationError(str(e))
return results
def addUserPermission(user, permission):
"""Shortcut function for adding a certain permission to a user."""
perm = Permission.objects.get(codename=permission)
user.user_permissions.add(perm)
def addUserPermissions(user, permissions):
"""Shortcut function for adding multiple permissions to a user."""
for permission in permissions:
addUserPermission(user, permission)
def getMigrationFileNames(app):
"""Return a list of all migration filenames for provided app."""
local_dir = Path(__file__).parent
files = local_dir.joinpath('..', app, 'migrations').iterdir()
# Regex pattern for migration files
regex = re.compile(r"^[\d]+_.*\.py$")
migration_files = []
for f in files:
if regex.match(f.name):
migration_files.append(f.name)
return migration_files
def getOldestMigrationFile(app, exclude_extension=True, ignore_initial=True):
"""Return the filename associated with the oldest migration."""
oldest_num = -1
oldest_file = None
for f in getMigrationFileNames(app):
if ignore_initial and f.startswith('0001_initial'):
continue
num = int(f.split('_')[0])
if oldest_file is None or num < oldest_num:
oldest_num = num
oldest_file = f
if exclude_extension:
oldest_file = oldest_file.replace('.py', '')
return oldest_file
def getNewestMigrationFile(app, exclude_extension=True):
"""Return the filename associated with the newest migration."""
newest_file = None
newest_num = -1
for f in getMigrationFileNames(app):
num = int(f.split('_')[0])
if newest_file is None or num > newest_num:
newest_num = num
newest_file = f
if exclude_extension:
newest_file = newest_file.replace('.py', '')
return newest_file
def clean_decimal(number):
"""Clean-up decimal value."""
# Check if empty
@@ -925,7 +774,11 @@ def clean_decimal(number):
# Number cannot be converted to Decimal (eg. a string containing letters)
return Decimal(0)
return clean_number.quantize(Decimal(1)) if clean_number == clean_number.to_integral() else clean_number.normalize()
return (
clean_number.quantize(Decimal(1))
if clean_number == clean_number.to_integral()
else clean_number.normalize()
)
def strip_html_tags(value: str, raise_error=True, field_name=None):
@@ -933,57 +786,45 @@ def strip_html_tags(value: str, raise_error=True, field_name=None):
If raise_error is True, a ValidationError will be thrown if HTML tags are detected
"""
cleaned = clean(
value,
strip=True,
tags=[],
attributes=[],
)
cleaned = clean(value, strip=True, tags=[], attributes=[])
# Add escaped characters back in
replacements = {
'&gt;': '>',
'&lt;': '<',
'&amp;': '&',
}
replacements = {'&gt;': '>', '&lt;': '<', '&amp;': '&'}
for o, r in replacements.items():
cleaned = cleaned.replace(o, r)
# If the length changed, it means that HTML tags were removed!
if len(cleaned) != len(value) and raise_error:
field = field_name or 'non_field_errors'
raise ValidationError({
field: [_("Remove HTML tags from this value")]
})
raise ValidationError({field: [_('Remove HTML tags from this value')]})
return cleaned
def remove_non_printable_characters(value: str, remove_newline=True, remove_ascii=True, remove_unicode=True):
"""Remove non-printable / control characters from the provided string"""
def remove_non_printable_characters(
value: str, remove_newline=True, remove_ascii=True, remove_unicode=True
):
"""Remove non-printable / control characters from the provided string."""
cleaned = value
if remove_ascii:
# Remove ASCII control characters
# Note that we do not sub out 0x0A (\n) here, it is done separately below
cleaned = regex.sub(u'[\x00-\x09]+', '', cleaned)
cleaned = regex.sub(u'[\x0b-\x1F\x7F]+', '', cleaned)
cleaned = regex.sub('[\x00-\x09]+', '', cleaned)
cleaned = regex.sub('[\x0b-\x1f\x7f]+', '', cleaned)
if remove_newline:
cleaned = regex.sub(u'[\x0a]+', '', cleaned)
cleaned = regex.sub('[\x0a]+', '', cleaned)
if remove_unicode:
# Remove Unicode control characters
if remove_newline:
cleaned = regex.sub(u'[^\P{C}]+', '', cleaned)
cleaned = regex.sub('[^\P{C}]+', '', cleaned)
else:
# Use 'negative-lookahead' to exclude newline character
cleaned = regex.sub(u'(?![\x0A])[^\P{C}]+', '', cleaned)
cleaned = regex.sub('(?![\x0a])[^\P{C}]+', '', cleaned)
return cleaned
@@ -996,7 +837,6 @@ def hash_barcode(barcode_data):
We first remove any non-printable characters from the barcode data,
as some browsers have issues scanning characters in.
"""
barcode_data = str(barcode_data).strip()
barcode_data = remove_non_printable_characters(barcode_data)
@@ -1005,7 +845,14 @@ def hash_barcode(barcode_data):
return str(hash.hexdigest())
def get_objectreference(obj, type_ref: str = 'content_type', object_ref: str = 'object_id'):
def hash_file(filename: str):
"""Return the MD5 hash of a file."""
return hashlib.md5(open(filename, 'rb').read()).hexdigest()
def get_objectreference(
obj, type_ref: str = 'content_type', object_ref: str = 'object_id'
):
"""Lookup method for the GenericForeignKey fields.
Attributes:
@@ -1022,7 +869,6 @@ def get_objectreference(obj, type_ref: str = 'content_type', object_ref: str = '
The method name must always be the name of the field prefixed by 'get_'
"""
model_cls = getattr(obj, type_ref)
obj_id = getattr(obj, object_ref)
@@ -1044,17 +890,17 @@ def get_objectreference(obj, type_ref: str = 'content_type', object_ref: str = '
ret = {}
if url_fnc:
ret['link'] = url_fnc()
return {
'name': str(item),
'model': str(model_cls._meta.verbose_name),
**ret
}
return {'name': str(item), 'model': str(model_cls._meta.verbose_name), **ret}
def inheritors(cls):
Inheritors_T = TypeVar('Inheritors_T')
def inheritors(cls: type[Inheritors_T]) -> set[type[Inheritors_T]]:
"""Return all classes that are subclasses from the supplied cls."""
subcls = set()
work = [cls]
while work:
parent = work.pop()
for child in parent.__subclasses__():
@@ -1064,100 +910,13 @@ def inheritors(cls):
return subcls
class InvenTreeTestCase(ExchangeRateMixin, UserMixin, TestCase):
"""Testcase with user setup buildin."""
pass
def is_ajax(request):
"""Check if the current request is an AJAX request."""
return request.headers.get('x-requested-with') == 'XMLHttpRequest'
def notify_responsible(instance, sender, content: NotificationBody = InvenTreeNotificationBodies.NewOrder, exclude=None):
"""Notify all responsible parties of a change in an instance.
Parses the supplied content with the provided instance and sender and sends a notification to all responsible users,
excluding the optional excluded list.
Args:
instance: The newly created instance
sender: Sender model reference
content (NotificationBody, optional): _description_. Defaults to InvenTreeNotificationBodies.NewOrder.
exclude (User, optional): User instance that should be excluded. Defaults to None.
"""
if instance.responsible is not None:
# Setup context for notification parsing
content_context = {
'instance': str(instance),
'verbose_name': sender._meta.verbose_name,
'app_label': sender._meta.app_label,
'model_name': sender._meta.model_name,
}
# Setup notification context
context = {
'instance': instance,
'name': content.name.format(**content_context),
'message': content.message.format(**content_context),
'link': InvenTree.helpers.construct_absolute_url(instance.get_absolute_url()),
'template': {
'html': content.template.format(**content_context),
'subject': content.name.format(**content_context),
}
}
# Create notification
trigger_notification(
instance,
content.slug.format(**content_context),
targets=[instance.responsible],
target_exclude=[exclude],
context=context,
)
def render_currency(money, decimal_places=None, currency=None, include_symbol=True, min_decimal_places=None):
"""Render a currency / Money object to a formatted string (e.g. for reports)
Arguments:
money: The Money instance to be rendered
decimal_places: The number of decimal places to render to. If unspecified, uses the PRICING_DECIMAL_PLACES setting.
currency: Optionally convert to the specified currency
include_symbol: Render with the appropriate currency symbol
min_decimal_places: The minimum number of decimal places to render to. If unspecified, uses the PRICING_DECIMAL_PLACES_MIN setting.
"""
if money in [None, '']:
return '-'
if type(money) is not Money:
return '-'
if currency is not None:
# Attempt to convert to the provided currency
# If cannot be done, leave the original
try:
money = convert_money(money, currency)
except Exception:
pass
if decimal_places is None:
decimal_places = InvenTreeSetting.get_setting('PRICING_DECIMAL_PLACES', 6)
if min_decimal_places is None:
min_decimal_places = InvenTreeSetting.get_setting('PRICING_DECIMAL_PLACES_MIN', 0)
value = Decimal(str(money.amount)).normalize()
value = str(value)
if '.' in value:
decimals = len(value.split('.')[-1])
decimals = max(decimals, min_decimal_places)
decimals = min(decimals, decimal_places)
decimal_places = decimals
else:
decimal_places = max(decimal_places, 2)
return moneyed.localization.format_money(
money,
decimal_places=decimal_places,
include_symbol=include_symbol,
)
def pui_url(subpath: str) -> str:
"""Return the URL for a PUI subpath."""
if not subpath.startswith('/'):
subpath = '/' + subpath
return f'/{settings.FRONTEND_URL_BASE}{subpath}'

View File

@@ -0,0 +1,106 @@
"""Provides helper mixins that are used throughout the InvenTree project."""
import inspect
from pathlib import Path
from django.conf import settings
from plugin import registry as plg_registry
class ClassValidationMixin:
"""Mixin to validate class attributes and overrides.
Class attributes:
required_attributes: List of class attributes that need to be defined
required_overrides: List of functions that need override, a nested list mean either one of them needs an override
Example:
```py
class Parent(ClassValidationMixin):
NAME: str
def test(self):
pass
required_attributes = ["NAME"]
required_overrides = [test]
class MyClass(Parent):
pass
myClass = MyClass()
myClass.validate() # raises NotImplementedError
```
"""
required_attributes = []
required_overrides = []
@classmethod
def validate(cls):
"""Validate the class against the required attributes/overrides."""
def attribute_missing(key):
"""Check if attribute is missing."""
return not hasattr(cls, key) or getattr(cls, key) == ''
def override_missing(base_implementation):
"""Check if override is missing."""
if isinstance(base_implementation, list):
return all(override_missing(x) for x in base_implementation)
return base_implementation == getattr(
cls, base_implementation.__name__, None
)
missing_attributes = list(filter(attribute_missing, cls.required_attributes))
missing_overrides = list(filter(override_missing, cls.required_overrides))
errors = []
if len(missing_attributes) > 0:
errors.append(
f"did not provide the following attributes: {', '.join(missing_attributes)}"
)
if len(missing_overrides) > 0:
missing_overrides_list = []
for base_implementation in missing_overrides:
if isinstance(base_implementation, list):
missing_overrides_list.append(
'one of '
+ ' or '.join(attr.__name__ for attr in base_implementation)
)
else:
missing_overrides_list.append(base_implementation.__name__)
errors.append(
f"did not override the required attributes: {', '.join(missing_overrides_list)}"
)
if len(errors) > 0:
raise NotImplementedError(f"'{cls}' " + ' and '.join(errors))
class ClassProviderMixin:
"""Mixin to get metadata about a class itself, e.g. the plugin that provided that class."""
@classmethod
def get_provider_file(cls):
"""File that contains the Class definition."""
return inspect.getfile(cls)
@classmethod
def get_provider_plugin(cls):
"""Plugin that contains the Class definition, otherwise None."""
for plg in plg_registry.plugins.values():
if plg.package_path == cls.__module__:
return plg
@classmethod
def get_is_builtin(cls):
"""Is this Class build in the Inventree source code?"""
try:
Path(cls.get_provider_file()).relative_to(settings.BASE_DIR)
return True
except ValueError:
# Path(...).relative_to throws an ValueError if its not relative to the InvenTree source base dir
return False

View File

@@ -0,0 +1,364 @@
"""Provides helper functions used throughout the InvenTree project that access the database."""
import io
import logging
from decimal import Decimal
from urllib.parse import urljoin
from django.conf import settings
from django.core.validators import URLValidator
from django.db.utils import OperationalError, ProgrammingError
from django.utils.translation import gettext_lazy as _
import requests
from djmoney.contrib.exchange.models import convert_money
from djmoney.money import Money
from PIL import Image
import common.models
import InvenTree
import InvenTree.helpers_model
import InvenTree.version
from common.notifications import (
InvenTreeNotificationBodies,
NotificationBody,
trigger_notification,
)
from InvenTree.format import format_money
logger = logging.getLogger('inventree')
def getSetting(key, backup_value=None):
"""Shortcut for reading a setting value from the database."""
return common.models.InvenTreeSetting.get_setting(key, backup_value=backup_value)
def get_base_url(request=None):
"""Return the base URL for the InvenTree server.
The base URL is determined in the following order of decreasing priority:
1. If a request object is provided, use the request URL
2. Multi-site is enabled, and the current site has a valid URL
3. If settings.SITE_URL is set (e.g. in the Django settings), use that
4. If the InvenTree setting INVENTREE_BASE_URL is set, use that
"""
# Check if a request is provided
if request:
return request.build_absolute_uri('/')
# Check if multi-site is enabled
try:
from django.contrib.sites.models import Site
return Site.objects.get_current().domain
except (ImportError, RuntimeError):
pass
# Check if a global site URL is provided
if site_url := getattr(settings, 'SITE_URL', None):
return site_url
# Check if a global InvenTree setting is provided
try:
if site_url := common.models.InvenTreeSetting.get_setting(
'INVENTREE_BASE_URL', create=False, cache=False
):
return site_url
except (ProgrammingError, OperationalError):
pass
# No base URL available
return ''
def construct_absolute_url(*arg, base_url=None, request=None):
"""Construct (or attempt to construct) an absolute URL from a relative URL.
Args:
*arg: The relative URL to construct
base_url: The base URL to use for the construction (if not provided, will attempt to determine from settings)
request: The request object to use for the construction (optional)
"""
relative_url = '/'.join(arg)
if not base_url:
base_url = get_base_url(request=request)
return urljoin(base_url, relative_url)
def download_image_from_url(remote_url, timeout=2.5):
"""Download an image file from a remote URL.
This is a potentially dangerous operation, so we must perform some checks:
- The remote URL is available
- The Content-Length is provided, and is not too large
- The file is a valid image file
Arguments:
remote_url: The remote URL to retrieve image
max_size: Maximum allowed image size (default = 1MB)
timeout: Connection timeout in seconds (default = 5)
Returns:
An in-memory PIL image file, if the download was successful
Raises:
requests.exceptions.ConnectionError: Connection could not be established
requests.exceptions.Timeout: Connection timed out
requests.exceptions.HTTPError: Server responded with invalid response code
ValueError: Server responded with invalid 'Content-Length' value
TypeError: Response is not a valid image
"""
# Check that the provided URL at least looks valid
validator = URLValidator()
validator(remote_url)
# Calculate maximum allowable image size (in bytes)
max_size = (
int(
common.models.InvenTreeSetting.get_setting(
'INVENTREE_DOWNLOAD_IMAGE_MAX_SIZE'
)
)
* 1024
* 1024
)
# Add user specified user-agent to request (if specified)
user_agent = common.models.InvenTreeSetting.get_setting(
'INVENTREE_DOWNLOAD_FROM_URL_USER_AGENT'
)
if user_agent:
headers = {'User-Agent': user_agent}
else:
headers = None
try:
response = requests.get(
remote_url,
timeout=timeout,
allow_redirects=True,
stream=True,
headers=headers,
)
# Throw an error if anything goes wrong
response.raise_for_status()
except requests.exceptions.ConnectionError as exc:
raise Exception(_('Connection error') + f': {str(exc)}')
except requests.exceptions.Timeout as exc:
raise exc
except requests.exceptions.HTTPError:
raise requests.exceptions.HTTPError(
_('Server responded with invalid status code') + f': {response.status_code}'
)
except Exception as exc:
raise Exception(_('Exception occurred') + f': {str(exc)}')
if response.status_code != 200:
raise Exception(
_('Server responded with invalid status code') + f': {response.status_code}'
)
try:
content_length = int(response.headers.get('Content-Length', 0))
except ValueError:
raise ValueError(_('Server responded with invalid Content-Length value'))
if content_length > max_size:
raise ValueError(_('Image size is too large'))
# Download the file, ensuring we do not exceed the reported size
file = io.BytesIO()
dl_size = 0
chunk_size = 64 * 1024
for chunk in response.iter_content(chunk_size=chunk_size):
dl_size += len(chunk)
if dl_size > max_size:
raise ValueError(_('Image download exceeded maximum size'))
file.write(chunk)
if dl_size == 0:
raise ValueError(_('Remote server returned empty response'))
# Now, attempt to convert the downloaded data to a valid image file
# img.verify() will throw an exception if the image is not valid
try:
img = Image.open(file).convert()
img.verify()
except Exception:
raise TypeError(_('Supplied URL is not a valid image file'))
return img
def render_currency(
money,
decimal_places=None,
currency=None,
min_decimal_places=None,
max_decimal_places=None,
include_symbol=True,
):
"""Render a currency / Money object to a formatted string (e.g. for reports).
Arguments:
money: The Money instance to be rendered
decimal_places: The number of decimal places to render to. If unspecified, uses the PRICING_DECIMAL_PLACES setting.
currency: Optionally convert to the specified currency
min_decimal_places: The minimum number of decimal places to render to. If unspecified, uses the PRICING_DECIMAL_PLACES_MIN setting.
max_decimal_places: The maximum number of decimal places to render to. If unspecified, uses the PRICING_DECIMAL_PLACES setting.
include_symbol: If True, include the currency symbol in the output
"""
if money in [None, '']:
return '-'
if type(money) is not Money:
return '-'
if currency is not None:
# Attempt to convert to the provided currency
# If cannot be done, leave the original
try:
money = convert_money(money, currency)
except Exception:
pass
if decimal_places is None:
decimal_places = common.models.InvenTreeSetting.get_setting(
'PRICING_DECIMAL_PLACES', 6
)
if min_decimal_places is None:
min_decimal_places = common.models.InvenTreeSetting.get_setting(
'PRICING_DECIMAL_PLACES_MIN', 0
)
if max_decimal_places is None:
max_decimal_places = common.models.InvenTreeSetting.get_setting(
'PRICING_DECIMAL_PLACES', 6
)
value = Decimal(str(money.amount)).normalize()
value = str(value)
if '.' in value:
decimals = len(value.split('.')[-1])
decimals = max(decimals, min_decimal_places)
decimals = min(decimals, decimal_places)
decimal_places = decimals
else:
decimal_places = max(decimal_places, 2)
decimal_places = max(decimal_places, max_decimal_places)
return format_money(
money, decimal_places=decimal_places, include_symbol=include_symbol
)
def getModelsWithMixin(mixin_class) -> list:
"""Return a list of models that inherit from the given mixin class.
Args:
mixin_class: The mixin class to search for
Returns:
List of models that inherit from the given mixin class
"""
from django.contrib.contenttypes.models import ContentType
try:
db_models = [
x.model_class() for x in ContentType.objects.all() if x is not None
]
except (OperationalError, ProgrammingError):
# Database is likely not yet ready
db_models = []
return [x for x in db_models if x is not None and issubclass(x, mixin_class)]
def notify_responsible(
instance,
sender,
content: NotificationBody = InvenTreeNotificationBodies.NewOrder,
exclude=None,
):
"""Notify all responsible parties of a change in an instance.
Parses the supplied content with the provided instance and sender and sends a notification to all responsible users,
excluding the optional excluded list.
Args:
instance: The newly created instance
sender: Sender model reference
content (NotificationBody, optional): _description_. Defaults to InvenTreeNotificationBodies.NewOrder.
exclude (User, optional): User instance that should be excluded. Defaults to None.
"""
import InvenTree.ready
if InvenTree.ready.isImportingData() or InvenTree.ready.isRunningMigrations():
return
notify_users(
[instance.responsible], instance, sender, content=content, exclude=exclude
)
def notify_users(
users,
instance,
sender,
content: NotificationBody = InvenTreeNotificationBodies.NewOrder,
exclude=None,
):
"""Notify all passed users or groups.
Parses the supplied content with the provided instance and sender and sends a notification to all users,
excluding the optional excluded list.
Args:
users: List of users or groups to notify
instance: The newly created instance
sender: Sender model reference
content (NotificationBody, optional): _description_. Defaults to InvenTreeNotificationBodies.NewOrder.
exclude (User, optional): User instance that should be excluded. Defaults to None.
"""
# Setup context for notification parsing
content_context = {
'instance': str(instance),
'verbose_name': sender._meta.verbose_name,
'app_label': sender._meta.app_label,
'model_name': sender._meta.model_name,
}
# Setup notification context
context = {
'instance': instance,
'name': content.name.format(**content_context),
'message': content.message.format(**content_context),
'link': InvenTree.helpers_model.construct_absolute_url(
instance.get_absolute_url()
),
'template': {'subject': content.name.format(**content_context)},
}
if content.template:
context['template']['html'] = content.template.format(**content_context)
# Create notification
trigger_notification(
instance,
content.slug.format(**content_context),
targets=users,
target_exclude=[exclude],
context=context,
)

View File

@@ -0,0 +1,48 @@
"""Support translation locales for InvenTree.
If a new language translation is supported, it must be added here
After adding a new language, run the following command:
python manage.py makemessages -l <language_code> -e html,js,py --no-wrap
where <language_code> is the code for the new language
Additionally, update the following files with the new locale code:
- /src/frontend/.linguirc file
- /src/frontend/src/context/LanguageContext.tsx
"""
from django.utils.translation import gettext_lazy as _
LOCALES = [
('bg', _('Bulgarian')),
('cs', _('Czech')),
('da', _('Danish')),
('de', _('German')),
('el', _('Greek')),
('en', _('English')),
('es', _('Spanish')),
('es-mx', _('Spanish (Mexican)')),
('fa', _('Farsi / Persian')),
('fi', _('Finnish')),
('fr', _('French')),
('he', _('Hebrew')),
('hi', _('Hindi')),
('hu', _('Hungarian')),
('it', _('Italian')),
('ja', _('Japanese')),
('ko', _('Korean')),
('nl', _('Dutch')),
('no', _('Norwegian')),
('pl', _('Polish')),
('pt', _('Portuguese')),
('pt-br', _('Portuguese (Brazilian)')),
('ru', _('Russian')),
('sk', _('Slovak')),
('sl', _('Slovenian')),
('sr', _('Serbian')),
('sv', _('Swedish')),
('th', _('Thai')),
('tr', _('Turkish')),
('vi', _('Vietnamese')),
('zh-hans', _('Chinese (Simplified)')),
('zh-hant', _('Chinese (Traditional)')),
]

View File

@@ -0,0 +1,75 @@
"""Functions for magic login."""
from django.conf import settings
from django.contrib.auth.models import User
from django.core.mail import send_mail
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
import sesame.utils
from rest_framework import serializers
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
import InvenTree.version
def send_simple_login_email(user, link):
"""Send an email with the login link to this user."""
site_name = InvenTree.version.inventreeInstanceName()
context = {'username': user.username, 'site_name': site_name, 'link': link}
email_plaintext_message = render_to_string(
'InvenTree/user_simple_login.txt', context
)
send_mail(
_(f'[{site_name}] Log in to the app'),
email_plaintext_message,
settings.DEFAULT_FROM_EMAIL,
[user.email],
)
class GetSimpleLoginSerializer(serializers.Serializer):
"""Serializer for the simple login view."""
email = serializers.CharField(label=_('Email'))
class GetSimpleLoginView(GenericAPIView):
"""View to send a simple login link."""
permission_classes = ()
serializer_class = GetSimpleLoginSerializer
def post(self, request, *args, **kwargs):
"""Get the token for the current user or fail."""
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
self.email_submitted(email=serializer.data['email'])
return Response({'status': 'ok'})
def email_submitted(self, email):
"""Notify user about link."""
user = self.get_user(email)
if user is None:
print('user not found:', email)
return
link = self.create_link(user)
send_simple_login_email(user, link)
def get_user(self, email):
"""Find the user with this email address."""
try:
return User.objects.get(email=email)
except User.DoesNotExist:
return None
def create_link(self, user):
"""Create a login link for this user."""
link = reverse('sesame-login')
link = self.request.build_absolute_uri(link)
link += sesame.utils.get_query_string(user)
return link

View File

@@ -0,0 +1,19 @@
"""Check if there are any pending database migrations, and run them."""
import logging
from django.core.management.base import BaseCommand
from InvenTree.tasks import check_for_migrations
logger = logging.getLogger('inventree')
class Command(BaseCommand):
"""Check if there are any pending database migrations, and run them."""
def handle(self, *args, **kwargs):
"""Check for any pending database migrations."""
logger.info('Checking for pending database migrations')
check_for_migrations(force=True, reload_registry=False)
logger.info('Database migrations complete')

View File

@@ -12,7 +12,7 @@ class Command(BaseCommand):
def handle(self, *args, **kwargs):
"""Cleanup old (undefined) settings in the database."""
logger.info("Collecting settings")
logger.info('Collecting settings')
from common.models import InvenTreeSetting, InvenTreeUserSetting
# general settings
@@ -23,7 +23,7 @@ class Command(BaseCommand):
for setting in db_settings:
if setting.key not in model_settings:
setting.delete()
logger.info(f"deleted setting '{setting.key}'")
logger.info("deleted setting '%s'", setting.key)
# user settings
db_settings = InvenTreeUserSetting.objects.all()
@@ -33,6 +33,6 @@ class Command(BaseCommand):
for setting in db_settings:
if setting.key not in model_settings:
setting.delete()
logger.info(f"deleted user setting '{setting.key}'")
logger.info("deleted user setting '%s'", setting.key)
logger.info("checked all settings")
logger.info('checked all settings')

View File

@@ -12,9 +12,7 @@ from django.utils.translation import override as lang_over
def render_file(file_name, source, target, locales, ctx):
"""Renders a file into all provided locales."""
for locale in locales:
# Enforce lower-case for locale names
locale = locale.lower()
locale = locale.replace('_', '-')
@@ -23,8 +21,8 @@ def render_file(file_name, source, target, locales, ctx):
with open(target_file, 'w') as localised_file:
with lang_over(locale):
renderd = render_to_string(os.path.join(source, file_name), ctx)
localised_file.write(renderd)
rendered = render_to_string(os.path.join(source, file_name), ctx)
localised_file.write(rendered)
class Command(BaseCommand):
@@ -51,15 +49,18 @@ class Command(BaseCommand):
# render!
request = HttpRequest()
ctx = {}
processors = tuple(import_string(path) for path in settings.STATFILES_I18_PROCESSORS)
processors = tuple(
import_string(path) for path in settings.STATFILES_I18_PROCESSORS
)
for processor in processors:
ctx.update(processor(request))
for file in os.listdir(SOURCE_DIR, ):
for file in os.listdir(SOURCE_DIR):
path = os.path.join(SOURCE_DIR, file)
if os.path.exists(path) and os.path.isfile(path):
print(f"render {file}")
render_file(file, SOURCE_DIR, TARGET_DIR, locales, ctx)
else:
raise NotImplementedError('Using multi-level directories is not implemented at this point') # TODO multilevel dir if needed
print(f"rendered all files in {SOURCE_DIR}")
raise NotImplementedError(
'Using multi-level directories is not implemented at this point'
) # TODO multilevel dir if needed
print(f'Rendered all files in {SOURCE_DIR}')

View File

@@ -3,55 +3,73 @@
- This is crucial after importing any fixtures, etc
"""
import logging
from django.core.management.base import BaseCommand
from maintenance_mode.core import maintenance_mode_on, set_maintenance_mode
logger = logging.getLogger('inventree')
class Command(BaseCommand):
"""Rebuild all database models which leverage the MPTT structure."""
def handle(self, *args, **kwargs):
"""Rebuild all database models which leverage the MPTT structure."""
with maintenance_mode_on():
self.rebuild_models()
set_maintenance_mode(False)
def rebuild_models(self):
"""Rebuild all MPTT models in the database."""
# Part model
try:
print("Rebuilding Part objects")
logger.info('Rebuilding Part objects')
from part.models import Part
Part.objects.rebuild()
except Exception:
print("Error rebuilding Part objects")
logger.info('Error rebuilding Part objects')
# Part category
try:
print("Rebuilding PartCategory objects")
logger.info('Rebuilding PartCategory objects')
from part.models import PartCategory
PartCategory.objects.rebuild()
except Exception:
print("Error rebuilding PartCategory objects")
logger.info('Error rebuilding PartCategory objects')
# StockItem model
try:
print("Rebuilding StockItem objects")
logger.info('Rebuilding StockItem objects')
from stock.models import StockItem
StockItem.objects.rebuild()
except Exception:
print("Error rebuilding StockItem objects")
logger.info('Error rebuilding StockItem objects')
# StockLocation model
try:
print("Rebuilding StockLocation objects")
logger.info('Rebuilding StockLocation objects')
from stock.models import StockLocation
StockLocation.objects.rebuild()
except Exception:
print("Error rebuilding StockLocation objects")
logger.info('Error rebuilding StockLocation objects')
# Build model
try:
print("Rebuilding Build objects")
logger.info('Rebuilding Build objects')
from build.models import Build
Build.objects.rebuild()
except Exception:
print("Error rebuilding Build objects")
logger.info('Error rebuilding Build objects')

View File

@@ -4,6 +4,7 @@
"""
import logging
import os
from django.core.management.base import BaseCommand
from django.db.utils import OperationalError, ProgrammingError
@@ -26,31 +27,43 @@ class Command(BaseCommand):
img = model.image
logger.info(f"Generating thumbnail image for '{img}'")
# Check for image paths
img_paths = []
for x in [model.image, model.image.thumbnail, model.image.preview]:
if x and x.path:
img_paths.append(x.path)
if len(img_paths) > 0:
if all((os.path.exists(path) for path in img_paths)):
# All images exist - skip further work
return
logger.info("Generating thumbnail image for '%s'", img)
try:
model.image.render_variations(replace=False)
except FileNotFoundError:
logger.warning(f"Warning: Image file '{img}' is missing")
logger.warning("Warning: Image file '%s' is missing", img)
except UnidentifiedImageError:
logger.warning(f"Warning: Image file '{img}' is not a valid image")
logger.warning("Warning: Image file '%s' is not a valid image", img)
def handle(self, *args, **kwargs):
"""Rebuild all thumbnail images."""
logger.info("Rebuilding Part thumbnails")
logger.info('Rebuilding Part thumbnails')
for part in Part.objects.exclude(image=None):
try:
self.rebuild_thumbnail(part)
except (OperationalError, ProgrammingError):
logger.error("ERROR: Database read error.")
logger.exception('ERROR: Database read error.')
break
logger.info("Rebuilding Company thumbnails")
logger.info('Rebuilding Company thumbnails')
for company in Company.objects.exclude(image=None):
try:
self.rebuild_thumbnail(company)
except (OperationalError, ProgrammingError):
logger.error("ERROR: abase read error.")
logger.exception('ERROR: abase read error.')
break

View File

@@ -18,7 +18,12 @@ class Command(BaseCommand):
if not mail:
raise KeyError('A mail is required')
user = get_user_model()
mfa_user = [*set(user.objects.filter(email=mail) | user.objects.filter(emailaddress__email=mail))]
mfa_user = [
*set(
user.objects.filter(email=mail)
| user.objects.filter(emailaddress__email=mail)
)
]
if len(mfa_user) == 0:
print('No user with this mail associated')

View File

@@ -0,0 +1,19 @@
"""Check if there are any pending database migrations, and run them."""
import logging
from django.core.management.base import BaseCommand
from InvenTree.tasks import check_for_migrations
logger = logging.getLogger('inventree')
class Command(BaseCommand):
"""Check if there are any pending database migrations, and run them."""
def handle(self, *args, **kwargs):
"""Check for any pending database migrations."""
logger.info('Checking for pending database migrations')
check_for_migrations(force=True, reload_registry=False)
logger.info('Database migrations complete')

View File

@@ -12,13 +12,12 @@ class Command(BaseCommand):
def handle(self, *args, **kwargs):
"""Wait till the database is ready."""
self.stdout.write("Waiting for database...")
self.stdout.write('Waiting for database...')
connected = False
while not connected:
time.sleep(5)
time.sleep(2)
try:
connection.ensure_connection()
@@ -26,12 +25,12 @@ class Command(BaseCommand):
connected = True
except OperationalError as e:
self.stdout.write(f"Could not connect to database: {e}")
self.stdout.write(f'Could not connect to database: {e}')
except ImproperlyConfigured as e:
self.stdout.write(f"Improperly configured: {e}")
self.stdout.write(f'Improperly configured: {e}')
else:
if not connection.is_usable():
self.stdout.write("Database configuration is not usable")
self.stdout.write('Database configuration is not usable')
if connected:
self.stdout.write("Database connection sucessful!")
self.stdout.write('Database connection successful!')

View File

@@ -7,9 +7,11 @@ from rest_framework.fields import empty
from rest_framework.metadata import SimpleMetadata
from rest_framework.utils import model_meta
import common.models
import InvenTree.permissions
import users.models
from InvenTree.helpers import str2bool
from InvenTree.serializers import DependentField
logger = logging.getLogger('inventree')
@@ -28,7 +30,7 @@ class InvenTreeMetadata(SimpleMetadata):
"""
def determine_metadata(self, request, view):
"""Overwrite the metadata to adapt to hte request user."""
"""Overwrite the metadata to adapt to the request user."""
self.request = request
self.view = view
@@ -36,7 +38,7 @@ class InvenTreeMetadata(SimpleMetadata):
"""
Custom context information to pass through to the OPTIONS endpoint,
if the "context=True" is supplied to the OPTIONS requst
if the "context=True" is supplied to the OPTIONS request
Serializer class can supply context data by defining a get_context_data() method (no arguments)
"""
@@ -44,8 +46,9 @@ class InvenTreeMetadata(SimpleMetadata):
context = {}
if str2bool(request.query_params.get('context', False)):
if hasattr(self, 'serializer') and hasattr(self.serializer, 'get_context_data'):
if hasattr(self, 'serializer') and hasattr(
self.serializer, 'get_context_data'
):
context = self.serializer.get_context_data()
metadata['context'] = context
@@ -67,7 +70,7 @@ class InvenTreeMetadata(SimpleMetadata):
metadata['model'] = tbl_label
table = f"{app_label}_{tbl_label}"
table = f'{app_label}_{tbl_label}'
actions = metadata.get('actions', None)
@@ -84,9 +87,12 @@ class InvenTreeMetadata(SimpleMetadata):
'DELETE': 'delete',
}
# let the view define a custom rolemap
if hasattr(view, 'rolemap'):
rolemap.update(view.rolemap)
# Remove any HTTP methods that the user does not have permission for
for method, permission in rolemap.items():
result = check(user, table, permission)
if method in actions and not result:
@@ -118,10 +124,7 @@ class InvenTreeMetadata(SimpleMetadata):
model_class = None
# Attributes to copy extra attributes from the model to the field (if they don't exist)
extra_attributes = [
'help_text',
'max_length',
]
extra_attributes = ['help_text', 'max_length']
try:
model_class = serializer.Meta.model
@@ -137,11 +140,8 @@ class InvenTreeMetadata(SimpleMetadata):
# Iterate through simple fields
for name, field in model_fields.fields.items():
if name in serializer_info.keys():
if field.has_default():
default = field.default
if callable(default):
@@ -157,13 +157,11 @@ class InvenTreeMetadata(SimpleMetadata):
for attr in extra_attributes:
if attr not in serializer_info[name]:
if hasattr(field, attr):
serializer_info[name][attr] = getattr(field, attr)
# Iterate through relations
for name, relation in model_fields.relations.items():
if name not in serializer_info.keys():
# Skip relation not defined in serializer
continue
@@ -174,11 +172,17 @@ class InvenTreeMetadata(SimpleMetadata):
# Extract and provide the "limit_choices_to" filters
# This is used to automatically filter AJAX requests
serializer_info[name]['filters'] = relation.model_field.get_limit_choices_to()
serializer_info[name]['filters'] = (
relation.model_field.get_limit_choices_to()
)
for attr in extra_attributes:
if attr not in serializer_info[name] and hasattr(relation.model_field, attr):
serializer_info[name][attr] = getattr(relation.model_field, attr)
if attr not in serializer_info[name] and hasattr(
relation.model_field, attr
):
serializer_info[name][attr] = getattr(
relation.model_field, attr
)
if name in model_default_values:
serializer_info[name]['default'] = model_default_values[name]
@@ -205,7 +209,10 @@ class InvenTreeMetadata(SimpleMetadata):
pk = kwargs[field]
break
if pk is not None:
if issubclass(model_class, common.models.BaseInvenTreeSetting):
instance = model_class.get_setting_object(**kwargs, create=False)
elif pk is not None:
try:
instance = model_class.objects.get(pk=pk)
except (ValueError, model_class.DoesNotExist):
@@ -215,11 +222,9 @@ class InvenTreeMetadata(SimpleMetadata):
"""If there is an instance associated with this API View, introspect that instance to find any specific API info."""
if hasattr(instance, 'api_instance_filters'):
instance_filters = instance.api_instance_filters()
for field_name, field_filters in instance_filters.items():
if field_name not in serializer_info.keys():
# The field might be missing, but is added later on
# This function seems to get called multiple times?
@@ -238,6 +243,10 @@ class InvenTreeMetadata(SimpleMetadata):
We take the regular DRF metadata and add our own unique flavor
"""
# Try to add the child property to the dependent field to be used by the super call
if self.label_lookup[field] == 'dependent field':
field.get_child(raise_exception=True)
# Run super method first
field_info = super().get_field_info(field)
@@ -247,17 +256,20 @@ class InvenTreeMetadata(SimpleMetadata):
# Force non-nullable fields to read as "required"
# (even if there is a default value!)
if not field.allow_null and not (hasattr(field, 'allow_blank') and field.allow_blank):
if not field.allow_null and not (
hasattr(field, 'allow_blank') and field.allow_blank
):
field_info['required'] = True
# Introspect writable related fields
if field_info['type'] == 'field' and not field_info['read_only']:
# If the field is a PrimaryKeyRelatedField, we can extract the model from the queryset
if isinstance(field, serializers.PrimaryKeyRelatedField):
model = field.queryset.model
else:
logger.debug("Could not extract model for:", field_info.get('label'), '->', field)
logger.debug(
'Could not extract model for:', field_info.get('label'), '->', field
)
model = None
if model:
@@ -271,4 +283,11 @@ class InvenTreeMetadata(SimpleMetadata):
else:
field_info['api_url'] = model.get_api_url()
# Add more metadata about dependent fields
if field_info['type'] == 'dependent field':
field_info['depends_on'] = field.depends_on
return field_info
InvenTreeMetadata.label_lookup[DependentField] = 'dependent field'

View File

@@ -7,17 +7,32 @@ from django.conf import settings
from django.contrib.auth.middleware import PersistentRemoteUserMiddleware
from django.http import HttpResponse
from django.shortcuts import redirect
from django.urls import Resolver404, include, re_path, resolve, reverse_lazy
from django.urls import Resolver404, include, path, resolve, reverse_lazy
from allauth_2fa.middleware import (AllauthTwoFactorMiddleware,
BaseRequire2FAMiddleware)
from allauth_2fa.middleware import AllauthTwoFactorMiddleware, BaseRequire2FAMiddleware
from error_report.middleware import ExceptionProcessor
from rest_framework.authtoken.models import Token
from common.models import InvenTreeSetting
from InvenTree.urls import frontendpatterns
from users.models import ApiToken
logger = logging.getLogger("inventree")
logger = logging.getLogger('inventree')
def get_token_from_request(request):
"""Extract token information from a request object."""
auth_keys = ['Authorization', 'authorization']
token_keys = ['token', 'bearer']
for k in auth_keys:
if auth_header := request.headers.get(k, None):
auth_header = auth_header.strip().lower().split()
if len(auth_header) > 1:
if auth_header[0].strip().lower().replace(':', '') in token_keys:
token = auth_header[1]
return token
return None
class AuthRequiredMiddleware(object):
@@ -27,6 +42,22 @@ class AuthRequiredMiddleware(object):
"""Save response object."""
self.get_response = get_response
def check_token(self, request) -> bool:
"""Check if the user is authenticated via token."""
if token := get_token_from_request(request):
# Does the provided token match a valid user?
try:
token = ApiToken.objects.get(key=token)
if token.active and token.user:
# Provide the user information to the request
request.user = token.user
return True
except ApiToken.DoesNotExist:
logger.warning('Access denied for unknown token %s', token)
return False
def __call__(self, request):
"""Check if user needs to be authenticated and is.
@@ -65,22 +96,15 @@ class AuthRequiredMiddleware(object):
elif request.path_info.startswith('/accounts/'):
authorized = True
elif 'Authorization' in request.headers.keys() or 'authorization' in request.headers.keys():
auth = request.headers.get('Authorization', request.headers.get('authorization')).strip()
elif (
request.path_info.startswith(f'/{settings.FRONTEND_URL_BASE}/')
or request.path_info.startswith('/assets/')
or request.path_info == f'/{settings.FRONTEND_URL_BASE}'
):
authorized = True
if auth.lower().startswith('token') and len(auth.split()) == 2:
token_key = auth.split()[1]
# Does the provided token match a valid user?
try:
token = Token.objects.get(key=token_key)
# Provide the user information to the request
request.user = token.user
authorized = True
except Token.DoesNotExist:
logger.warning(f"Access denied for unknown token {token_key}")
elif self.check_token(request):
authorized = True
# No authorization was found for the request
if not authorized:
@@ -95,34 +119,34 @@ class AuthRequiredMiddleware(object):
]
# Do not redirect requests to any of these paths
paths_ignore = [
'/api/',
'/js/',
'/media/',
'/static/',
]
paths_ignore = ['/api/', '/js/', '/media/', '/static/']
if path not in urls and not any([path.startswith(p) for p in paths_ignore]):
if path not in urls and not any(
path.startswith(p) for p in paths_ignore
):
# Save the 'next' parameter to pass through to the login view
return redirect(f'{reverse_lazy("account_login")}?next={request.path}')
else:
# Return a 401 (Unauthorized) response code for this request
return HttpResponse('Unauthorized', status=401)
return redirect(
f'{reverse_lazy("account_login")}?next={request.path}'
)
# Return a 401 (Unauthorized) response code for this request
return HttpResponse('Unauthorized', status=401)
response = self.get_response(request)
return response
url_matcher = re_path('', include(frontendpatterns))
url_matcher = path('', include(frontendpatterns))
class Check2FAMiddleware(BaseRequire2FAMiddleware):
"""Check if user is required to have MFA enabled."""
def require_2fa(self, request):
"""Use setting to check if MFA should be enforced for frontend page."""
from common.models import InvenTreeSetting
try:
if url_matcher.resolve(request.path[1:]):
return InvenTreeSetting.get_setting('LOGIN_ENFORCE_MFA')
@@ -133,6 +157,7 @@ class Check2FAMiddleware(BaseRequire2FAMiddleware):
class CustomAllauthTwoFactorMiddleware(AllauthTwoFactorMiddleware):
"""This function ensures only frontend code triggers the MFA auth cycle."""
def process_request(self, request):
"""Check if requested url is forntend and enforce MFA check."""
try:
@@ -144,6 +169,7 @@ class CustomAllauthTwoFactorMiddleware(AllauthTwoFactorMiddleware):
class InvenTreeRemoteUserMiddleware(PersistentRemoteUserMiddleware):
"""Middleware to check if HTTP-header based auth is enabled and to set it up."""
header = settings.REMOTE_LOGIN_HEADER
def process_request(self, request):
@@ -158,11 +184,38 @@ class InvenTreeExceptionProcessor(ExceptionProcessor):
"""Custom exception processor that respects blocked errors."""
def process_exception(self, request, exception):
"""Check if kind is ignored before procesing."""
"""Check if kind is ignored before processing."""
kind, info, data = sys.exc_info()
# Check if the eror is on the ignore list
# Check if the error is on the ignore list
if kind in settings.IGNORED_ERRORS:
return
return super().process_exception(request, exception)
import traceback
from django.views.debug import ExceptionReporter
from error_report.models import Error
from error_report.settings import ERROR_DETAIL_SETTINGS
# Error reporting is disabled
if not ERROR_DETAIL_SETTINGS.get('ERROR_DETAIL_ENABLE', True):
return
path = request.build_absolute_uri()
# Truncate the path to a reasonable length
# Otherwise we get a database error,
# because the path field is limited to 200 characters
if len(path) > 200:
path = path[:195] + '...'
error = Error.objects.create(
kind=kind.__name__,
html=ExceptionReporter(request, kind, info, data).get_traceback_html(),
path=path,
info=info,
data='\n'.join(traceback.format_exception(kind, info, data)),
)
error.save()

View File

@@ -9,7 +9,7 @@ from InvenTree.fields import InvenTreeNotesField
from InvenTree.helpers import remove_non_printable_characters, strip_html_tags
class CleanMixin():
class CleanMixin:
"""Model mixin class which cleans inputs using the Mozilla bleach tools."""
# Define a list of field names which will *not* be cleaned
@@ -21,13 +21,17 @@ class CleanMixin():
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
return Response(
serializer.data, status=status.HTTP_201_CREATED, headers=headers
)
def update(self, request, *args, **kwargs):
"""Override to clean data before processing it."""
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=self.clean_data(request.data), partial=partial)
serializer = self.get_serializer(
instance, data=self.clean_data(request.data), partial=partial
)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
@@ -49,7 +53,6 @@ class CleanMixin():
Ref: https://github.com/mozilla/bleach/issues/192
"""
cleaned = strip_html_tags(data, field_name=field)
# By default, newline characters are removed
@@ -61,9 +64,7 @@ class CleanMixin():
field = model._meta.get_field(field)
# The following field types allow newline characters
allow_newline = [
InvenTreeNotesField,
]
allow_newline = [InvenTreeNotesField]
for field_type in allow_newline:
if issubclass(type(field), field_type):
@@ -75,7 +76,9 @@ class CleanMixin():
except FieldDoesNotExist:
pass
cleaned = remove_non_printable_characters(cleaned, remove_newline=remove_newline)
cleaned = remove_non_printable_characters(
cleaned, remove_newline=remove_newline
)
return cleaned
@@ -88,16 +91,14 @@ class CleanMixin():
`ugly`. Prevents XSS on the server-level.
Args:
data (dict): Data that should be sanatized.
data (dict): Data that should be Sanitized.
Returns:
dict: Provided data sanatized; still in the same order.
dict: Provided data Sanitized; still in the same order.
"""
clean_data = {}
for k, v in data.items():
if k in self.SAFE_FIELDS:
ret = v
elif isinstance(v, str):
@@ -125,17 +126,20 @@ class CreateAPI(CleanMixin, generics.CreateAPIView):
class RetrieveAPI(generics.RetrieveAPIView):
"""View for retreive API."""
"""View for retrieve API."""
pass
class RetrieveUpdateAPI(CleanMixin, generics.RetrieveUpdateAPIView):
"""View for retrieve and update API."""
pass
class CustomDestroyModelMixin:
"""This mixin was created pass the kwargs from the API to the models."""
def destroy(self, request, *args, **kwargs):
"""Custom destroy method to pass kwargs."""
instance = self.get_object()
@@ -147,11 +151,14 @@ class CustomDestroyModelMixin:
instance.delete(**kwargs)
class CustomRetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
CustomDestroyModelMixin,
generics.GenericAPIView):
class CustomRetrieveUpdateDestroyAPIView(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
CustomDestroyModelMixin,
generics.GenericAPIView,
):
"""This APIView was created pass the kwargs from the API to the models."""
def get(self, request, *args, **kwargs):
"""Custom get method to pass kwargs."""
return self.retrieve(request, *args, **kwargs)

File diff suppressed because it is too large Load Diff

View File

@@ -8,8 +8,7 @@ import users.models
def get_model_for_view(view, raise_error=True):
"""Attempt to introspect the 'model' type for an API view"""
"""Attempt to introspect the 'model' type for an API view."""
if hasattr(view, 'get_permission_model'):
return view.get_permission_model()
@@ -19,7 +18,7 @@ def get_model_for_view(view, raise_error=True):
if hasattr(view, 'get_serializer_class'):
return view.get_serializr_class().Meta.model
raise AttributeError(f"Serializer class not specified for {view.__class__}")
raise AttributeError(f'Serializer class not specified for {view.__class__}')
class RolePermission(permissions.BasePermission):
@@ -62,10 +61,18 @@ class RolePermission(permissions.BasePermission):
'DELETE': 'delete',
}
# let the view define a custom rolemap
if hasattr(view, 'rolemap'):
rolemap.update(view.rolemap)
permission = rolemap[request.method]
# The required role may be defined for the view class
if role := getattr(view, 'role_required', None):
# If the role is specified as "role.permission", split it
if '.' in role:
role, permission = role.split('.')
return users.models.check_user_role(user, role, permission)
try:
@@ -75,7 +82,7 @@ class RolePermission(permissions.BasePermission):
app_label = model._meta.app_label
model_name = model._meta.model_name
table = f"{app_label}_{model_name}"
table = f'{app_label}_{model_name}'
except AttributeError:
# We will assume that if the serializer class does *not* have a Meta,
# then we don't need a permission
@@ -92,9 +99,23 @@ class IsSuperuser(permissions.IsAdminUser):
return bool(request.user and request.user.is_superuser)
class IsStaffOrReadOnly(permissions.IsAdminUser):
"""Allows read-only access to any user, but write access is restricted to staff users."""
def has_permission(self, request, view):
"""Check if the user is a superuser."""
return bool(
request.user
and request.user.is_staff
or request.method in permissions.SAFE_METHODS
)
def auth_exempt(view_func):
"""Mark a view function as being exempt from auth requirements."""
def wrapped_view(*args, **kwargs):
return view_func(*args, **kwargs)
wrapped_view.auth_exempt = True
return wraps(view_func)(wrapped_view)

View File

@@ -1,5 +1,6 @@
"""Functions to check if certain parts of InvenTree are ready."""
import os
import sys
@@ -9,36 +10,110 @@ def isInTestMode():
def isImportingData():
"""Returns True if the database is currently importing data, e.g. 'loaddata' command is performed."""
return 'loaddata' in sys.argv
"""Returns True if the database is currently importing (or exporting) data, e.g. 'loaddata' command is performed."""
return any((x in sys.argv for x in ['flush', 'loaddata', 'dumpdata']))
def canAppAccessDatabase(allow_test: bool = False, allow_plugins: bool = False, allow_shell: bool = False):
def isRunningMigrations():
"""Return True if the database is currently running migrations."""
return any(
(
x in sys.argv
for x in ['migrate', 'makemigrations', 'showmigrations', 'runmigrations']
)
)
def isRebuildingData():
"""Return true if any of the rebuilding commands are being executed."""
return any(
(
x in sys.argv
for x in ['prerender', 'rebuild_models', 'rebuild_thumbnails', 'rebuild']
)
)
def isRunningBackup():
"""Return true if any of the backup commands are being executed."""
return any(
(
x in sys.argv
for x in [
'backup',
'restore',
'dbbackup',
'dbresotore',
'mediabackup',
'mediarestore',
]
)
)
def isInWorkerThread():
"""Returns True if the current thread is a background worker thread."""
return 'qcluster' in sys.argv
def isInServerThread():
"""Returns True if the current thread is a server thread."""
if isInWorkerThread():
return False
if 'runserver' in sys.argv:
return True
if 'gunicorn' in sys.argv[0]:
return True
return False
def isInMainThread():
"""Django runserver starts two processes, one for the actual dev server and the other to reload the application.
- The RUN_MAIN env is set in that case. However if --noreload is applied, this variable
is not set because there are no different threads.
"""
if 'runserver' in sys.argv and '--noreload' not in sys.argv:
return os.environ.get('RUN_MAIN', None) == 'true'
return not isInWorkerThread()
def canAppAccessDatabase(
allow_test: bool = False, allow_plugins: bool = False, allow_shell: bool = False
):
"""Returns True if the apps.py file can access database records.
There are some circumstances where we don't want the ready function in apps.py
to touch the database
"""
# Prevent database access if we are running backups
if isRunningBackup():
return False
# Prevent database access if we are importing data
if isImportingData():
return False
# Prevent database access if we are rebuilding data
if isRebuildingData():
return False
# Prevent database access if we are running migrations
if not allow_plugins and isRunningMigrations():
return False
# If any of the following management commands are being executed,
# prevent custom "on load" code from running!
excluded_commands = [
'flush',
'loaddata',
'dumpdata',
'check',
'createsuperuser',
'wait_for_db',
'prerender',
'rebuild_models',
'rebuild_thumbnails',
'makemessages',
'compilemessages',
'backup',
'dbbackup',
'mediabackup',
'restore',
'dbrestore',
'mediarestore',
]
if not allow_shell:
@@ -49,14 +124,26 @@ def canAppAccessDatabase(allow_test: bool = False, allow_plugins: bool = False,
excluded_commands.append('test')
if not allow_plugins:
excluded_commands.extend([
'makemigrations',
'migrate',
'collectstatic',
])
excluded_commands.extend(['collectstatic'])
for cmd in excluded_commands:
if cmd in sys.argv:
return False
return True
def isPluginRegistryLoaded():
"""Ensures that the plugin registry is already loaded.
The plugin registry reloads all apps onetime after starting if there are AppMixin plugins,
so that the discovered AppConfigs are added to Django. This triggers the ready function of
AppConfig to execute twice. Add this check to prevent from running two times.
Note: All apps using this check need to be registered after the plugins app in settings.py
Returns: 'False' if the registry has not fully loaded the plugins yet.
"""
from plugin import registry
return registry.plugins_loaded

View File

@@ -1,50 +1,197 @@
"""Functions to sanitize user input files."""
from bleach import clean
from bleach.css_sanitizer import CSSSanitizer
ALLOWED_ELEMENTS_SVG = [
'a', 'animate', 'animateColor', 'animateMotion',
'animateTransform', 'circle', 'defs', 'desc', 'ellipse', 'font-face',
'font-face-name', 'font-face-src', 'g', 'glyph', 'hkern',
'linearGradient', 'line', 'marker', 'metadata', 'missing-glyph',
'mpath', 'path', 'polygon', 'polyline', 'radialGradient', 'rect',
'set', 'stop', 'svg', 'switch', 'text', 'title', 'tspan', 'use'
'a',
'animate',
'animateColor',
'animateMotion',
'animateTransform',
'circle',
'defs',
'desc',
'ellipse',
'font-face',
'font-face-name',
'font-face-src',
'g',
'glyph',
'hkern',
'linearGradient',
'line',
'marker',
'metadata',
'missing-glyph',
'mpath',
'path',
'polygon',
'polyline',
'radialGradient',
'rect',
'set',
'stop',
'svg',
'switch',
'text',
'title',
'tspan',
'use',
]
ALLOWED_ATTRIBUTES_SVG = [
'accent-height', 'accumulate', 'additive', 'alphabetic',
'arabic-form', 'ascent', 'attributeName', 'attributeType',
'baseProfile', 'bbox', 'begin', 'by', 'calcMode', 'cap-height',
'class', 'color', 'color-rendering', 'content', 'cx', 'cy', 'd', 'dx',
'dy', 'descent', 'display', 'dur', 'end', 'fill', 'fill-opacity',
'fill-rule', 'font-family', 'font-size', 'font-stretch', 'font-style',
'font-variant', 'font-weight', 'from', 'fx', 'fy', 'g1', 'g2',
'glyph-name', 'gradientUnits', 'hanging', 'height', 'horiz-adv-x',
'horiz-origin-x', 'id', 'ideographic', 'k', 'keyPoints',
'keySplines', 'keyTimes', 'lang', 'marker-end', 'marker-mid',
'marker-start', 'markerHeight', 'markerUnits', 'markerWidth',
'mathematical', 'max', 'min', 'name', 'offset', 'opacity', 'orient',
'origin', 'overline-position', 'overline-thickness', 'panose-1',
'path', 'pathLength', 'points', 'preserveAspectRatio', 'r', 'refX',
'refY', 'repeatCount', 'repeatDur', 'requiredExtensions',
'requiredFeatures', 'restart', 'rotate', 'rx', 'ry', 'slope',
'stemh', 'stemv', 'stop-color', 'stop-opacity',
'strikethrough-position', 'strikethrough-thickness', 'stroke',
'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap',
'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity',
'stroke-width', 'systemLanguage', 'target', 'text-anchor', 'to',
'transform', 'type', 'u1', 'u2', 'underline-position',
'underline-thickness', 'unicode', 'unicode-range', 'units-per-em',
'values', 'version', 'viewBox', 'visibility', 'width', 'widths', 'x',
'x-height', 'x1', 'x2', 'xlink:actuate', 'xlink:arcrole',
'xlink:href', 'xlink:role', 'xlink:show', 'xlink:title',
'xlink:type', 'xml:base', 'xml:lang', 'xml:space', 'xmlns',
'xmlns:xlink', 'y', 'y1', 'y2', 'zoomAndPan', 'style'
'accent-height',
'accumulate',
'additive',
'alphabetic',
'arabic-form',
'ascent',
'attributeName',
'attributeType',
'baseProfile',
'bbox',
'begin',
'by',
'calcMode',
'cap-height',
'class',
'color',
'color-rendering',
'content',
'cx',
'cy',
'd',
'dx',
'dy',
'descent',
'display',
'dur',
'end',
'fill',
'fill-opacity',
'fill-rule',
'font-family',
'font-size',
'font-stretch',
'font-style',
'font-variant',
'font-weight',
'from',
'fx',
'fy',
'g1',
'g2',
'glyph-name',
'gradientUnits',
'hanging',
'height',
'horiz-adv-x',
'horiz-origin-x',
'id',
'ideographic',
'k',
'keyPoints',
'keySplines',
'keyTimes',
'lang',
'marker-end',
'marker-mid',
'marker-start',
'markerHeight',
'markerUnits',
'markerWidth',
'mathematical',
'max',
'min',
'name',
'offset',
'opacity',
'orient',
'origin',
'overline-position',
'overline-thickness',
'panose-1',
'path',
'pathLength',
'points',
'preserveAspectRatio',
'r',
'refX',
'refY',
'repeatCount',
'repeatDur',
'requiredExtensions',
'requiredFeatures',
'restart',
'rotate',
'rx',
'ry',
'slope',
'stemh',
'stemv',
'stop-color',
'stop-opacity',
'strikethrough-position',
'strikethrough-thickness',
'stroke',
'stroke-dasharray',
'stroke-dashoffset',
'stroke-linecap',
'stroke-linejoin',
'stroke-miterlimit',
'stroke-opacity',
'stroke-width',
'systemLanguage',
'target',
'text-anchor',
'to',
'transform',
'type',
'u1',
'u2',
'underline-position',
'underline-thickness',
'unicode',
'unicode-range',
'units-per-em',
'values',
'version',
'viewBox',
'visibility',
'width',
'widths',
'x',
'x-height',
'x1',
'x2',
'xlink:actuate',
'xlink:arcrole',
'xlink:href',
'xlink:role',
'xlink:show',
'xlink:title',
'xlink:type',
'xml:base',
'xml:lang',
'xml:space',
'xmlns',
'xmlns:xlink',
'y',
'y1',
'y2',
'zoomAndPan',
'style',
]
def sanitize_svg(file_data: str, strip: bool = True, elements: str = ALLOWED_ELEMENTS_SVG, attributes: str = ALLOWED_ATTRIBUTES_SVG) -> str:
"""Sanatize a SVG file.
def sanitize_svg(
file_data,
strip: bool = True,
elements: str = ALLOWED_ELEMENTS_SVG,
attributes: str = ALLOWED_ATTRIBUTES_SVG,
) -> str:
"""Sanitize a SVG file.
Args:
file_data (str): SVG as string.
@@ -55,6 +202,9 @@ def sanitize_svg(file_data: str, strip: bool = True, elements: str = ALLOWED_ELE
Returns:
str: Sanitzied SVG file.
"""
# Handle byte-encoded data
if isinstance(file_data, bytes):
file_data = file_data.decode('utf-8')
cleaned = clean(
file_data,
@@ -62,6 +212,7 @@ def sanitize_svg(file_data: str, strip: bool = True, elements: str = ALLOWED_ELE
attributes=attributes,
strip=strip,
strip_comments=strip,
css_sanitizer=CSSSanitizer()
css_sanitizer=CSSSanitizer(),
)
return cleaned

View File

@@ -0,0 +1,73 @@
"""Configuration for Sentry.io error reporting."""
import logging
from django.conf import settings
from django.core.exceptions import ValidationError
from django.http import Http404
import rest_framework.exceptions
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
import InvenTree.version
logger = logging.getLogger('inventree')
def default_sentry_dsn():
"""Return the default Sentry.io DSN for InvenTree."""
return 'https://3928ccdba1d34895abde28031fd00100@o378676.ingest.sentry.io/6494600'
def sentry_ignore_errors():
"""Return a list of error types to ignore.
These error types will *not* be reported to sentry.io.
"""
return [
Http404,
ValidationError,
rest_framework.exceptions.AuthenticationFailed,
rest_framework.exceptions.NotAuthenticated,
rest_framework.exceptions.PermissionDenied,
rest_framework.exceptions.ValidationError,
]
def init_sentry(dsn, sample_rate, tags):
"""Initialize sentry.io error reporting."""
logger.info('Initializing sentry.io integration')
sentry_sdk.init(
dsn=dsn,
integrations=[DjangoIntegration()],
traces_sample_rate=sample_rate,
send_default_pii=True,
ignore_errors=sentry_ignore_errors(),
release=InvenTree.version.INVENTREE_SW_VERSION,
environment='development'
if InvenTree.version.isInvenTreeDevelopmentVersion()
else 'production',
)
for key, val in tags.items():
sentry_sdk.set_tag(f'inventree_{key}', val)
sentry_sdk.set_tag('api', InvenTree.version.inventreeApiVersion())
sentry_sdk.set_tag('platform', InvenTree.version.inventreePlatform())
sentry_sdk.set_tag('git_branch', InvenTree.version.inventreeBranch())
sentry_sdk.set_tag('git_commit', InvenTree.version.inventreeCommitHash())
sentry_sdk.set_tag('git_date', InvenTree.version.inventreeCommitDate())
def report_exception(exc):
"""Report an exception to sentry.io."""
if settings.SENTRY_ENABLED and settings.SENTRY_DSN:
if not any(isinstance(exc, e) for e in sentry_ignore_errors()):
logger.info('Reporting exception to sentry.io: %s', exc)
try:
sentry_sdk.capture_exception(exc)
except Exception:
logger.warning('Failed to report exception to sentry.io')

View File

@@ -2,6 +2,7 @@
import os
from collections import OrderedDict
from copy import deepcopy
from decimal import Decimal
from django.conf import settings
@@ -15,15 +16,19 @@ from djmoney.contrib.django_rest_framework.fields import MoneyField
from djmoney.money import Money
from djmoney.utils import MONEY_CLASSES, get_currency_field_name
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.fields import empty
from rest_framework.serializers import DecimalField
from rest_framework.utils import model_meta
from taggit.serializers import TaggitSerializer
from common.models import InvenTreeSetting
import common.models as common_models
from common.settings import currency_code_default, currency_code_mappings
from InvenTree.fields import InvenTreeRestURLField, InvenTreeURLField
from InvenTree.helpers import download_image_from_url
class EmptySerializer(serializers.Serializer):
"""Empty serializer for use in testing."""
class InvenTreeMoneySerializer(MoneyField):
@@ -33,16 +38,15 @@ class InvenTreeMoneySerializer(MoneyField):
"""
def __init__(self, *args, **kwargs):
"""Overrite default values."""
kwargs["max_digits"] = kwargs.get("max_digits", 19)
self.decimal_places = kwargs["decimal_places"] = kwargs.get("decimal_places", 6)
kwargs["required"] = kwargs.get("required", False)
"""Override default values."""
kwargs['max_digits'] = kwargs.get('max_digits', 19)
self.decimal_places = kwargs['decimal_places'] = kwargs.get('decimal_places', 6)
kwargs['required'] = kwargs.get('required', False)
super().__init__(*args, **kwargs)
def get_value(self, data):
"""Test that the returned amount is a valid Decimal."""
amount = super(DecimalField, self).get_value(data)
# Convert an empty string to None
@@ -55,28 +59,41 @@ class InvenTreeMoneySerializer(MoneyField):
amount = Decimal(amount)
amount = round(amount, self.decimal_places)
except Exception:
raise ValidationError({
self.field_name: [_("Must be a valid number")],
})
raise ValidationError({self.field_name: [_('Must be a valid number')]})
currency = data.get(get_currency_field_name(self.field_name), self.default_currency)
currency = data.get(
get_currency_field_name(self.field_name), self.default_currency
)
if currency and amount is not None and not isinstance(amount, MONEY_CLASSES) and amount is not empty:
if (
currency
and amount is not None
and not isinstance(amount, MONEY_CLASSES)
and amount is not empty
):
return Money(amount, currency)
return amount
class InvenTreeCurrencySerializer(serializers.ChoiceField):
"""Custom serializers for selecting currency option"""
"""Custom serializers for selecting currency option."""
def __init__(self, *args, **kwargs):
"""Initialize the currency serializer"""
"""Initialize the currency serializer."""
choices = currency_code_mappings()
kwargs['choices'] = currency_code_mappings()
allow_blank = kwargs.get('allow_blank', False) or kwargs.get(
'allow_null', False
)
if allow_blank:
choices = [('', '---------')] + choices
kwargs['choices'] = choices
if 'default' not in kwargs and 'required' not in kwargs:
kwargs['default'] = currency_code_default
kwargs['default'] = '' if allow_blank else currency_code_default
if 'label' not in kwargs:
kwargs['label'] = _('Currency')
@@ -87,6 +104,104 @@ class InvenTreeCurrencySerializer(serializers.ChoiceField):
super().__init__(*args, **kwargs)
class DependentField(serializers.Field):
"""A dependent field can be used to dynamically return child fields based on the value of other fields."""
child = None
def __init__(self, *args, depends_on, field_serializer, **kwargs):
"""A dependent field can be used to dynamically return child fields based on the value of other fields.
Example:
This example adds two fields. If the client selects integer, an integer field will be shown, but if he
selects char, an char field will be shown. For any other value, nothing will be shown.
class TestSerializer(serializers.Serializer):
select_type = serializers.ChoiceField(choices=[
("integer", "Integer"),
("char", "Char"),
])
my_field = DependentField(depends_on=["select_type"], field_serializer="get_my_field")
def get_my_field(self, fields):
if fields["select_type"] == "integer":
return serializers.IntegerField()
if fields["select_type"] == "char":
return serializers.CharField()
"""
super().__init__(*args, **kwargs)
self.depends_on = depends_on
self.field_serializer = field_serializer
def get_child(self, raise_exception=False):
"""This method tries to extract the child based on the provided data in the request by the client."""
data = deepcopy(self.context['request'].data)
def visit_parent(node):
"""Recursively extract the data for the parent field/serializer in reverse."""
nonlocal data
if node.parent:
visit_parent(node.parent)
# only do for composite fields and stop right before the current field
if hasattr(node, 'child') and node is not self and isinstance(data, dict):
data = data.get(node.field_name, None)
visit_parent(self)
# ensure that data is a dictionary and that a parent exists
if not isinstance(data, dict) or self.parent is None:
return
# check if the request data contains the dependent fields, otherwise skip getting the child
for f in self.depends_on:
if data.get(f, None) is None:
if (
self.parent
and (v := getattr(self.parent.fields[f], 'default', None))
is not None
):
data[f] = v
else:
return
# partially validate the data for options requests that set raise_exception while calling .get_child(...)
if raise_exception:
validation_data = {k: v for k, v in data.items() if k in self.depends_on}
serializer = self.parent.__class__(
context=self.context, data=validation_data, partial=True
)
serializer.is_valid(raise_exception=raise_exception)
# try to get the field serializer
field_serializer = getattr(self.parent, self.field_serializer)
child = field_serializer(data)
if not child:
return
self.child = child
self.child.bind(field_name='', parent=self)
def to_internal_value(self, data):
"""This method tries to convert the data to an internal representation based on the defined to_internal_value method on the child."""
self.get_child()
if self.child:
return self.child.to_internal_value(data)
return None
def to_representation(self, value):
"""This method tries to convert the data to representation based on the defined to_representation method on the child."""
self.get_child()
if self.child:
return self.child.to_representation(value)
return None
class InvenTreeModelSerializer(serializers.ModelSerializer):
"""Inherits the standard Django ModelSerializer class, but also ensures that the underlying model class data are checked on validation."""
@@ -101,7 +216,6 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
"""Custom __init__ routine to ensure that *default* values (as specified in the ORM) are used by the DRF serializers, *if* the values are not provided by the user."""
# If instance is None, we are creating a new instance
if instance is None and data is not empty:
if data is None:
data = OrderedDict()
else:
@@ -116,7 +230,6 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
fields = model_meta.get_field_info(ModelClass)
for field_name, field in fields.fields.items():
"""
Update the field IF (and ONLY IF):
@@ -124,7 +237,6 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
- The field does not already have a value set
"""
if field.has_default() and field_name not in data:
value = field.default
# Account for callable functions
@@ -152,9 +264,7 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
fields = model_meta.get_field_info(ModelClass)
for field_name, field in fields.fields.items():
if field.has_default() and field_name not in initials:
value = field.default
# Account for callable functions
@@ -188,8 +298,7 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
return self.instance
def create(self, validated_data):
"""Custom create method which supports field adjustment"""
"""Custom create method which supports field adjustment."""
initial_data = validated_data.copy()
# Remove any fields which do not exist on the model
@@ -213,7 +322,6 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
In addition to running validators on the serializer fields,
this class ensures that the underlying model is also validated.
"""
# Run any native validation checks first (may raise a ValidationError)
data = super().run_validation(data)
@@ -244,7 +352,6 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
try:
instance.full_clean()
except (ValidationError, DjangoValidationError) as exc:
data = exc.message_dict
# Change '__all__' key (django style) to 'non_field_errors' (DRF style)
@@ -257,20 +364,119 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
return data
class InvenTreeTaggitSerializer(TaggitSerializer):
"""Updated from https://github.com/glemmaPaul/django-taggit-serializer."""
def update(self, instance, validated_data):
"""Overridden update method to re-add the tagmanager."""
to_be_tagged, validated_data = self._pop_tags(validated_data)
tag_object = super().update(instance, validated_data)
for key in to_be_tagged.keys():
# re-add the tagmanager
new_tagobject = tag_object.__class__.objects.get(id=tag_object.id)
setattr(tag_object, key, getattr(new_tagobject, key))
return self._save_tags(tag_object, to_be_tagged)
class InvenTreeTagModelSerializer(InvenTreeTaggitSerializer, InvenTreeModelSerializer):
"""Combination of InvenTreeTaggitSerializer and InvenTreeModelSerializer."""
pass
class UserSerializer(InvenTreeModelSerializer):
"""Serializer for a User."""
class Meta:
"""Metaclass defines serializer fields."""
model = User
fields = [
'pk',
'username',
'first_name',
'last_name',
'email'
fields = ['pk', 'username', 'first_name', 'last_name', 'email']
read_only_fields = ['username']
class ExendedUserSerializer(UserSerializer):
"""Serializer for a User with a bit more info."""
from users.serializers import GroupSerializer
groups = GroupSerializer(read_only=True, many=True)
class Meta(UserSerializer.Meta):
"""Metaclass defines serializer fields."""
fields = UserSerializer.Meta.fields + [
'groups',
'is_staff',
'is_superuser',
'is_active',
]
read_only_fields = UserSerializer.Meta.read_only_fields + ['groups']
def validate(self, attrs):
"""Expanded validation for changing user role."""
# Check if is_staff or is_superuser is in attrs
role_change = 'is_staff' in attrs or 'is_superuser' in attrs
request_user = self.context['request'].user
if role_change:
if request_user.is_superuser:
# Superusers can change any role
pass
elif request_user.is_staff and 'is_superuser' not in attrs:
# Staff can change any role except is_superuser
pass
else:
raise PermissionDenied(
_('You do not have permission to change this user role.')
)
return super().validate(attrs)
class UserCreateSerializer(ExendedUserSerializer):
"""Serializer for creating a new User."""
def validate(self, attrs):
"""Expanded valiadation for auth."""
# Check that the user trying to create a new user is a superuser
if not self.context['request'].user.is_superuser:
raise serializers.ValidationError(_('Only superusers can create new users'))
# Generate a random password
password = User.objects.make_random_password(length=14)
attrs.update({'password': password})
return super().validate(attrs)
def create(self, validated_data):
"""Send an e email to the user after creation."""
from InvenTree.helpers_model import get_base_url
base_url = get_base_url()
instance = super().create(validated_data)
# Make sure the user cannot login until they have set a password
instance.set_unusable_password()
message = (
_('Your account has been created.')
+ '\n\n'
+ _('Please use the password reset function to login')
)
if base_url:
message += f'\n\nURL: {base_url}'
# Send the user an onboarding email (from current site)
instance.email_user(subject=_('Welcome to InvenTree'), message=message)
return instance
class InvenTreeAttachmentSerializerField(serializers.FileField):
"""Override the DRF native FileField serializer, to remove the leading server path.
@@ -305,7 +511,7 @@ class InvenTreeAttachmentSerializer(InvenTreeModelSerializer):
@staticmethod
def attachment_fields(extra_fields=None):
"""Default set of fields for an attachment serializer"""
"""Default set of fields for an attachment serializer."""
fields = [
'pk',
'attachment',
@@ -324,17 +530,11 @@ class InvenTreeAttachmentSerializer(InvenTreeModelSerializer):
user_detail = UserSerializer(source='user', read_only=True, many=False)
attachment = InvenTreeAttachmentSerializerField(
required=False,
allow_null=False,
)
attachment = InvenTreeAttachmentSerializerField(required=False, allow_null=False)
# The 'filename' field must be present in the serializer
filename = serializers.CharField(
label=_('Filename'),
required=False,
source='basename',
allow_blank=False,
label=_('Filename'), required=False, source='basename', allow_blank=False
)
upload_date = serializers.DateField(read_only=True)
@@ -368,7 +568,7 @@ class InvenTreeDecimalField(serializers.FloatField):
try:
return Decimal(str(data))
except Exception:
raise serializers.ValidationError(_("Invalid value"))
raise serializers.ValidationError(_('Invalid value'))
class DataFileUploadSerializer(serializers.Serializer):
@@ -385,13 +585,11 @@ class DataFileUploadSerializer(serializers.Serializer):
class Meta:
"""Metaclass options."""
fields = [
'data_file',
]
fields = ['data_file']
data_file = serializers.FileField(
label=_("Data File"),
help_text=_("Select data file for upload"),
label=_('Data File'),
help_text=_('Select data file for upload'),
required=True,
allow_empty_file=False,
)
@@ -405,20 +603,16 @@ class DataFileUploadSerializer(serializers.Serializer):
# Remove the leading . from the extension
ext = ext[1:]
accepted_file_types = [
'xls', 'xlsx',
'csv', 'tsv',
'xml',
]
accepted_file_types = ['xls', 'xlsx', 'csv', 'tsv', 'xml']
if ext not in accepted_file_types:
raise serializers.ValidationError(_("Unsupported file type"))
raise serializers.ValidationError(_('Unsupported file type'))
# Impose a 50MB limit on uploaded BOM files
max_upload_file_size = 50 * 1024 * 1024
if data_file.size > max_upload_file_size:
raise serializers.ValidationError(_("File is too large"))
raise serializers.ValidationError(_('File is too large'))
# Read file data into memory (bytes object)
try:
@@ -439,10 +633,10 @@ class DataFileUploadSerializer(serializers.Serializer):
raise serializers.ValidationError(str(e))
if len(self.dataset.headers) == 0:
raise serializers.ValidationError(_("No columns found in file"))
raise serializers.ValidationError(_('No columns found in file'))
if len(self.dataset) == 0:
raise serializers.ValidationError(_("No data rows found in file"))
raise serializers.ValidationError(_('No data rows found in file'))
return data_file
@@ -492,7 +686,7 @@ class DataFileUploadSerializer(serializers.Serializer):
pass
# Extract a list of valid model field names
model_field_names = [key for key in model_fields.keys()]
model_field_names = list(model_fields.keys())
# Provide a dict of available columns from the dataset
file_columns = {}
@@ -536,24 +730,14 @@ class DataFileExtractSerializer(serializers.Serializer):
class Meta:
"""Metaclass options."""
fields = [
'columns',
'rows',
]
fields = ['columns', 'rows']
# Mapping of columns
columns = serializers.ListField(
child=serializers.CharField(
allow_blank=True,
),
)
columns = serializers.ListField(child=serializers.CharField(allow_blank=True))
rows = serializers.ListField(
child=serializers.ListField(
child=serializers.CharField(
allow_blank=True,
allow_null=True,
),
child=serializers.CharField(allow_blank=True, allow_null=True)
)
)
@@ -565,10 +749,10 @@ class DataFileExtractSerializer(serializers.Serializer):
self.rows = data.get('rows', [])
if len(self.rows) == 0:
raise serializers.ValidationError(_("No data rows provided"))
raise serializers.ValidationError(_('No data rows provided'))
if len(self.columns) == 0:
raise serializers.ValidationError(_("No data columns supplied"))
raise serializers.ValidationError(_('No data columns supplied'))
self.validate_extracted_columns()
@@ -591,16 +775,9 @@ class DataFileExtractSerializer(serializers.Serializer):
processed_row = self.process_row(self.row_to_dict(row))
if processed_row:
rows.append({
"original": row,
"data": processed_row,
})
rows.append({'original': row, 'data': processed_row})
return {
'fields': model_fields,
'columns': self.columns,
'rows': rows,
}
return {'fields': model_fields, 'columns': self.columns, 'rows': rows}
def process_row(self, row):
"""Process a 'row' of data, which is a mapped column:value dict.
@@ -614,12 +791,9 @@ class DataFileExtractSerializer(serializers.Serializer):
def row_to_dict(self, row):
"""Convert a "row" to a named data dict."""
row_dict = {
'errors': {},
}
row_dict = {'errors': {}}
for idx, value in enumerate(row):
if idx < len(self.columns):
col = self.columns[idx]
@@ -639,16 +813,16 @@ class DataFileExtractSerializer(serializers.Serializer):
cols_seen = set()
for name, field in model_fields.items():
required = field.get('required', False)
# Check for missing required columns
if required:
if name not in self.columns:
raise serializers.ValidationError(_(f"Missing required column: '{name}'"))
raise serializers.ValidationError(
_(f"Missing required column: '{name}'")
)
for col in self.columns:
if not col:
continue
@@ -670,18 +844,15 @@ class RemoteImageMixin(metaclass=serializers.SerializerMetaclass):
"""
def skip_create_fields(self):
"""Ensure the 'remote_image' field is skipped when creating a new instance"""
return [
'remote_image',
]
"""Ensure the 'remote_image' field is skipped when creating a new instance."""
return ['remote_image']
remote_image = serializers.URLField(
required=False,
allow_blank=False,
write_only=True,
label=_("URL"),
help_text=_("URL of remote image file"),
label=_('Remote Image'),
help_text=_('URL of remote image file'),
)
def validate_remote_image(self, url):
@@ -690,12 +861,17 @@ class RemoteImageMixin(metaclass=serializers.SerializerMetaclass):
- Attempt to download the image and store it against this object instance
- Catches and re-throws any errors
"""
from InvenTree.helpers_model import download_image_from_url
if not url:
return
if not InvenTreeSetting.get_setting('INVENTREE_DOWNLOAD_FROM_URL'):
raise ValidationError(_("Downloading images from remote URL is not enabled"))
if not common_models.InvenTreeSetting.get_setting(
'INVENTREE_DOWNLOAD_FROM_URL'
):
raise ValidationError(
_('Downloading images from remote URL is not enabled')
)
try:
self.remote_image_file = download_image_from_url(url)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,269 @@
"""API endpoints for social authentication with allauth."""
import logging
from importlib import import_module
from django.urls import NoReverseMatch, include, path, reverse
from allauth.account.models import EmailAddress
from allauth.socialaccount import providers
from allauth.socialaccount.providers.oauth2.views import OAuth2Adapter, OAuth2LoginView
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework import serializers
from rest_framework.exceptions import NotFound
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
import InvenTree.sso
from common.models import InvenTreeSetting
from InvenTree.mixins import CreateAPI, ListAPI, ListCreateAPI
from InvenTree.serializers import EmptySerializer, InvenTreeModelSerializer
logger = logging.getLogger('inventree')
class GenericOAuth2ApiLoginView(OAuth2LoginView):
"""Api view to login a user with a social account."""
def dispatch(self, request, *args, **kwargs):
"""Dispatch the regular login view directly."""
return self.login(request, *args, **kwargs)
class GenericOAuth2ApiConnectView(GenericOAuth2ApiLoginView):
"""Api view to connect a social account to the current user."""
def dispatch(self, request, *args, **kwargs):
"""Dispatch the connect request directly."""
# Override the request method be in connection mode
request.GET = request.GET.copy()
request.GET['process'] = 'connect'
# Resume the dispatch
return super().dispatch(request, *args, **kwargs)
def handle_oauth2(adapter: OAuth2Adapter):
"""Define urls for oauth2 endpoints."""
return [
path(
'login/',
GenericOAuth2ApiLoginView.adapter_view(adapter),
name=f'{provider.id}_api_login',
),
path(
'connect/',
GenericOAuth2ApiConnectView.adapter_view(adapter),
name=f'{provider.id}_api_connect',
),
]
legacy = {
'twitter': 'twitter_oauth2',
'bitbucket': 'bitbucket_oauth2',
'linkedin': 'linkedin_oauth2',
'vimeo': 'vimeo_oauth2',
'openid': 'openid_connect',
} # legacy connectors
# Collect urls for all loaded providers
social_auth_urlpatterns = []
provider_urlpatterns = []
for name, provider in providers.registry.provider_map.items():
try:
prov_mod = import_module(provider.get_package() + '.views')
except ImportError:
logger.exception('Could not import authentication provider %s', name)
continue
# Try to extract the adapter class
adapters = [
cls
for cls in prov_mod.__dict__.values()
if isinstance(cls, type)
and not cls == OAuth2Adapter
and issubclass(cls, OAuth2Adapter)
]
# Get urls
urls = []
if len(adapters) == 1:
urls = handle_oauth2(adapter=adapters[0])
else:
if provider.id in legacy:
logger.warning(
'`%s` is not supported on platform UI. Use `%s` instead.',
provider.id,
legacy[provider.id],
)
continue
else:
logger.error(
'Found handler that is not yet ready for platform UI: `%s`. Open an feature request on GitHub if you need it implemented.',
provider.id,
)
continue
provider_urlpatterns += [path(f'{provider.id}/', include(urls))]
social_auth_urlpatterns += provider_urlpatterns
class SocialProviderListResponseSerializer(serializers.Serializer):
"""Serializer for the SocialProviderListView."""
class SocialProvider(serializers.Serializer):
"""Serializer for the SocialProviderListResponseSerializer."""
id = serializers.CharField()
name = serializers.CharField()
configured = serializers.BooleanField()
login = serializers.URLField()
connect = serializers.URLField()
display_name = serializers.CharField()
sso_enabled = serializers.BooleanField()
sso_registration = serializers.BooleanField()
mfa_required = serializers.BooleanField()
providers = SocialProvider(many=True)
registration_enabled = serializers.BooleanField()
password_forgotten_enabled = serializers.BooleanField()
class SocialProviderListView(ListAPI):
"""List of available social providers."""
permission_classes = (AllowAny,)
serializer_class = EmptySerializer
@extend_schema(
responses={200: OpenApiResponse(response=SocialProviderListResponseSerializer)}
)
def get(self, request, *args, **kwargs):
"""Get the list of providers."""
provider_list = []
for provider in providers.registry.provider_map.values():
provider_data = {
'id': provider.id,
'name': provider.name,
'configured': False,
}
try:
provider_data['login'] = request.build_absolute_uri(
reverse(f'{provider.id}_api_login')
)
except NoReverseMatch:
provider_data['login'] = None
try:
provider_data['connect'] = request.build_absolute_uri(
reverse(f'{provider.id}_api_connect')
)
except NoReverseMatch:
provider_data['connect'] = None
provider_data['configured'] = InvenTree.sso.check_provider(provider)
provider_data['display_name'] = InvenTree.sso.provider_display_name(
provider
)
provider_list.append(provider_data)
data = {
'sso_enabled': InvenTree.sso.login_enabled(),
'sso_registration': InvenTree.sso.registration_enabled(),
'mfa_required': InvenTreeSetting.get_setting('LOGIN_ENFORCE_MFA'),
'providers': provider_list,
'registration_enabled': InvenTreeSetting.get_setting('LOGIN_ENABLE_REG'),
'password_forgotten_enabled': InvenTreeSetting.get_setting(
'LOGIN_ENABLE_PWD_FORGOT'
),
}
return Response(data)
class EmailAddressSerializer(InvenTreeModelSerializer):
"""Serializer for the EmailAddress model."""
class Meta:
"""Meta options for EmailAddressSerializer."""
model = EmailAddress
fields = '__all__'
class EmptyEmailAddressSerializer(InvenTreeModelSerializer):
"""Empty Serializer for the EmailAddress model."""
class Meta:
"""Meta options for EmailAddressSerializer."""
model = EmailAddress
fields = []
class EmailListView(ListCreateAPI):
"""List of registered email addresses for current users."""
permission_classes = (IsAuthenticated,)
serializer_class = EmailAddressSerializer
def get_queryset(self):
"""Only return data for current user."""
return EmailAddress.objects.filter(user=self.request.user)
class EmailActionMixin(CreateAPI):
"""Mixin to modify email addresses for current users."""
serializer_class = EmptyEmailAddressSerializer
permission_classes = (IsAuthenticated,)
def get_queryset(self):
"""Filter queryset for current user."""
return EmailAddress.objects.filter(
user=self.request.user, pk=self.kwargs['pk']
).first()
@extend_schema(responses={200: OpenApiResponse(response=EmailAddressSerializer)})
def post(self, request, *args, **kwargs):
"""Filter item, run action and return data."""
email = self.get_queryset()
if not email:
raise NotFound
self.special_action(email, request, *args, **kwargs)
return Response(EmailAddressSerializer(email).data)
class EmailVerifyView(EmailActionMixin):
"""Re-verify an email for a currently logged in user."""
def special_action(self, email, request, *args, **kwargs):
"""Send confirmation."""
if email.verified:
return
email.send_confirmation(request)
class EmailPrimaryView(EmailActionMixin):
"""Make an email for a currently logged in user primary."""
def special_action(self, email, *args, **kwargs):
"""Mark email as primary."""
if email.primary:
return
email.set_as_primary()
class EmailRemoveView(EmailActionMixin):
"""Remove an email for a currently logged in user."""
def special_action(self, email, *args, **kwargs):
"""Delete email."""
email.delete()

View File

@@ -0,0 +1,77 @@
"""Helper functions for Single Sign On functionality."""
import logging
from common.models import InvenTreeSetting
from InvenTree.helpers import str2bool
logger = logging.getLogger('inventree')
def get_provider_app(provider):
"""Return the SocialApp object for the given provider."""
from allauth.socialaccount.models import SocialApp
try:
apps = SocialApp.objects.filter(provider__iexact=provider.id)
except SocialApp.DoesNotExist:
logger.warning("SSO SocialApp not found for provider '%s'", provider.id)
return None
if apps.count() > 1:
logger.warning("Multiple SocialApps found for provider '%s'", provider.id)
if apps.count() == 0:
logger.warning("SSO SocialApp not found for provider '%s'", provider.id)
return apps.first()
def check_provider(provider, raise_error=False):
"""Check if the given provider is correctly configured.
To be correctly configured, the following must be true:
- Provider must either have a registered SocialApp
- Must have at least one site enabled
"""
import allauth.app_settings
# First, check that the provider is enabled
app = get_provider_app(provider)
if not app:
return False
if allauth.app_settings.SITES_ENABLED:
# At least one matching site must be specified
if not app.sites.exists():
logger.error('SocialApp %s has no sites configured', app)
return False
# At this point, we assume that the provider is correctly configured
return True
def provider_display_name(provider):
"""Return the 'display name' for the given provider."""
if app := get_provider_app(provider):
return app.name
# Fallback value if app not found
return provider.name
def login_enabled() -> bool:
"""Return True if SSO login is enabled."""
return str2bool(InvenTreeSetting.get_setting('LOGIN_ENABLE_SSO'))
def registration_enabled() -> bool:
"""Return True if SSO registration is enabled."""
return str2bool(InvenTreeSetting.get_setting('LOGIN_ENABLE_SSO_REG'))
def auto_registration_enabled() -> bool:
"""Return True if SSO auto-registration is enabled."""
return str2bool(InvenTreeSetting.get_setting('LOGIN_SIGNUP_SSO_AUTO'))

View File

@@ -105,6 +105,10 @@ main {
font-size: 110%;
}
.bg-qr-code {
background-color: #FFF !important;
}
.qr-code {
max-width: 400px;
max-height: 400px;
@@ -219,8 +223,7 @@ main {
}
.sub-table {
margin-left: 45px;
margin-right: 45px;
margin-left: 60px;
}
.detail-icon .glyphicon {
@@ -266,10 +269,6 @@ main {
}
/* Styles for table buttons and filtering */
.button-toolbar .btn {
margin-left: 1px;
margin-right: 1px;
}
.filter-list {
display: inline-block;
@@ -301,16 +300,14 @@ main {
.filter-tag {
display: inline-block;
*display: inline;
zoom: 1;
padding-top: 3px;
padding-left: 3px;
padding-right: 3px;
border: 1px solid #aaa;
border-radius: 3px;
background: #eee;
margin: 1px;
margin-left: 5px;
margin-right: 5px;
margin: 5px;
padding: 5px;
padding-top: 1px;
padding-bottom: 1px;
color: var(--bs-body-color);
border: 1px solid var(--border-color);
border-radius: 10px;
background: var(--secondary-color);
white-space: nowrap;
}
@@ -321,7 +318,6 @@ main {
.filter-input {
display: inline-block;
*display: inline;
zoom: 1;
}
.filter-tag:hover {
@@ -1094,4 +1090,14 @@ a {
.sso-provider-link a {
width: 100%;
text-align: left;
}
}
.flex-cell {
display: flex;
align-items: center;
justify-content: space-between;
}
.large-treeview-icon {
font-size: 1em;
}

View File

@@ -4,20 +4,20 @@
import logging
from datetime import timedelta
from django.conf import settings
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django_q.models import Success
from django_q.monitor import Stat
from django_q.status import Stat
import InvenTree.email
import InvenTree.ready
logger = logging.getLogger("inventree")
logger = logging.getLogger('inventree')
def is_worker_running(**kwargs):
"""Return True if the background worker process is oprational."""
"""Return True if the background worker process is operational."""
clusters = Stat.get_all()
if len(clusters) > 0:
@@ -33,43 +33,17 @@ def is_worker_running(**kwargs):
now = timezone.now()
past = now - timedelta(minutes=10)
results = Success.objects.filter(
started__gte=past
)
results = Success.objects.filter(started__gte=past)
# If any results are returned, then the background worker is running!
return results.exists()
try:
result = results.exists()
except Exception:
# We may throw an exception if the database is not ready,
# or if the django_q table is not yet created (i.e. in CI testing)
result = False
def is_email_configured():
"""Check if email backend is configured.
NOTE: This does not check if the configuration is valid!
"""
configured = True
if InvenTree.ready.isInTestMode():
return False
if InvenTree.ready.isImportingData():
return False
if not settings.EMAIL_HOST:
configured = False
# Display warning unless in test mode
if not settings.TESTING: # pragma: no cover
logger.debug("EMAIL_HOST is not configured")
# Display warning unless in test mode
if not settings.TESTING: # pragma: no cover
logger.debug("EMAIL_HOST_USER is not configured")
# Display warning unless in test mode
if not settings.TESTING: # pragma: no cover
logger.debug("EMAIL_HOST_PASSWORD is not configured")
return configured
return result
def check_system_health(**kwargs):
@@ -89,13 +63,13 @@ def check_system_health(**kwargs):
if not is_worker_running(**kwargs): # pragma: no cover
result = False
logger.warning(_("Background worker check failed"))
logger.warning(_('Background worker check failed'))
if not is_email_configured(): # pragma: no cover
if not InvenTree.email.is_email_configured(): # pragma: no cover
result = False
logger.warning(_("Email backend not configured"))
logger.warning(_('Email backend not configured'))
if not result: # pragma: no cover
logger.warning(_("InvenTree system health checks failed"))
logger.warning(_('InvenTree system health checks failed'))
return result

View File

@@ -2,443 +2,196 @@
from django.utils.translation import gettext_lazy as _
class StatusCode:
"""Base class for representing a set of StatusCodes.
This is used to map a set of integer values to text.
"""
colors = {}
@classmethod
def render(cls, key, large=False):
"""Render the value as a HTML label."""
# If the key cannot be found, pass it back
if key not in cls.options.keys():
return key
value = cls.options.get(key, key)
color = cls.colors.get(key, 'secondary')
span_class = f'badge rounded-pill bg-{color}'
return "<span class='{cl}'>{value}</span>".format(
cl=span_class,
value=value
)
@classmethod
def list(cls):
"""Return the StatusCode options as a list of mapped key / value items."""
return list(cls.dict().values())
@classmethod
def text(cls, key):
"""Text for supplied status code."""
return cls.options.get(key, None)
@classmethod
def items(cls):
"""All status code items."""
return cls.options.items()
@classmethod
def keys(cls):
"""All status code keys."""
return cls.options.keys()
@classmethod
def labels(cls):
"""All status code labels."""
return cls.options.values()
@classmethod
def names(cls):
"""Return a map of all 'names' of status codes in this class
Will return a dict object, with the attribute name indexed to the integer value.
e.g.
{
'PENDING': 10,
'IN_PROGRESS': 20,
}
"""
keys = cls.keys()
status_names = {}
for d in dir(cls):
if d.startswith('_'):
continue
if d != d.upper():
continue
value = getattr(cls, d, None)
if value is None:
continue
if callable(value):
continue
if type(value) != int:
continue
if value not in keys:
continue
status_names[d] = value
return status_names
@classmethod
def dict(cls):
"""Return a dict representation containing all required information"""
values = {}
for name, value, in cls.names().items():
entry = {
'key': value,
'name': name,
'label': cls.label(value),
}
if hasattr(cls, 'colors'):
if color := cls.colors.get(value, None):
entry['color'] = color
values[name] = entry
return values
@classmethod
def label(cls, value):
"""Return the status code label associated with the provided value."""
return cls.options.get(value, value)
@classmethod
def value(cls, label):
"""Return the value associated with the provided label."""
for k in cls.options.keys():
if cls.options[k].lower() == label.lower():
return k
raise ValueError("Label not found")
from generic.states import StatusCode
class PurchaseOrderStatus(StatusCode):
"""Defines a set of status codes for a PurchaseOrder."""
# Order status codes
PENDING = 10 # Order is pending (not yet placed)
PLACED = 20 # Order has been placed with supplier
COMPLETE = 30 # Order has been completed
CANCELLED = 40 # Order was cancelled
LOST = 50 # Order was lost
RETURNED = 60 # Order was returned
PENDING = 10, _('Pending'), 'secondary' # Order is pending (not yet placed)
PLACED = 20, _('Placed'), 'primary' # Order has been placed with supplier
COMPLETE = 30, _('Complete'), 'success' # Order has been completed
CANCELLED = 40, _('Cancelled'), 'danger' # Order was cancelled
LOST = 50, _('Lost'), 'warning' # Order was lost
RETURNED = 60, _('Returned'), 'warning' # Order was returned
options = {
PENDING: _("Pending"),
PLACED: _("Placed"),
COMPLETE: _("Complete"),
CANCELLED: _("Cancelled"),
LOST: _("Lost"),
RETURNED: _("Returned"),
}
colors = {
PENDING: 'secondary',
PLACED: 'primary',
COMPLETE: 'success',
CANCELLED: 'danger',
LOST: 'warning',
RETURNED: 'warning',
}
class PurchaseOrderStatusGroups:
"""Groups for PurchaseOrderStatus codes."""
# Open orders
OPEN = [
PENDING,
PLACED,
]
OPEN = [PurchaseOrderStatus.PENDING.value, PurchaseOrderStatus.PLACED.value]
# Failed orders
FAILED = [
CANCELLED,
LOST,
RETURNED
PurchaseOrderStatus.CANCELLED.value,
PurchaseOrderStatus.LOST.value,
PurchaseOrderStatus.RETURNED.value,
]
class SalesOrderStatus(StatusCode):
"""Defines a set of status codes for a SalesOrder."""
PENDING = 10 # Order is pending
IN_PROGRESS = 15 # Order has been issued, and is in progress
SHIPPED = 20 # Order has been shipped to customer
CANCELLED = 40 # Order has been cancelled
LOST = 50 # Order was lost
RETURNED = 60 # Order was returned
PENDING = 10, _('Pending'), 'secondary' # Order is pending
IN_PROGRESS = (
15,
_('In Progress'),
'primary',
) # Order has been issued, and is in progress
SHIPPED = 20, _('Shipped'), 'success' # Order has been shipped to customer
CANCELLED = 40, _('Cancelled'), 'danger' # Order has been cancelled
LOST = 50, _('Lost'), 'warning' # Order was lost
RETURNED = 60, _('Returned'), 'warning' # Order was returned
options = {
PENDING: _("Pending"),
IN_PROGRESS: _("In Progress"),
SHIPPED: _("Shipped"),
CANCELLED: _("Cancelled"),
LOST: _("Lost"),
RETURNED: _("Returned"),
}
colors = {
PENDING: 'secondary',
IN_PROGRESS: 'primary',
SHIPPED: 'success',
CANCELLED: 'danger',
LOST: 'warning',
RETURNED: 'warning',
}
class SalesOrderStatusGroups:
"""Groups for SalesOrderStatus codes."""
# Open orders
OPEN = [
PENDING,
IN_PROGRESS,
]
OPEN = [SalesOrderStatus.PENDING.value, SalesOrderStatus.IN_PROGRESS.value]
# Completed orders
COMPLETE = [
SHIPPED,
]
COMPLETE = [SalesOrderStatus.SHIPPED.value]
class StockStatus(StatusCode):
"""Status codes for Stock."""
OK = 10 # Item is OK
ATTENTION = 50 # Item requires attention
DAMAGED = 55 # Item is damaged
DESTROYED = 60 # Item is destroyed
REJECTED = 65 # Item is rejected
LOST = 70 # Item has been lost
QUARANTINED = 75 # Item has been quarantined and is unavailable
RETURNED = 85 # Item has been returned from a customer
OK = 10, _('OK'), 'success' # Item is OK
ATTENTION = 50, _('Attention needed'), 'warning' # Item requires attention
DAMAGED = 55, _('Damaged'), 'warning' # Item is damaged
DESTROYED = 60, _('Destroyed'), 'danger' # Item is destroyed
REJECTED = 65, _('Rejected'), 'danger' # Item is rejected
LOST = 70, _('Lost'), 'dark' # Item has been lost
QUARANTINED = (
75,
_('Quarantined'),
'info',
) # Item has been quarantined and is unavailable
RETURNED = 85, _('Returned'), 'warning' # Item has been returned from a customer
options = {
OK: _("OK"),
ATTENTION: _("Attention needed"),
DAMAGED: _("Damaged"),
DESTROYED: _("Destroyed"),
LOST: _("Lost"),
REJECTED: _("Rejected"),
QUARANTINED: _("Quarantined"),
RETURNED: _("Returned"),
}
colors = {
OK: 'success',
ATTENTION: 'warning',
DAMAGED: 'danger',
DESTROYED: 'danger',
LOST: 'dark',
REJECTED: 'danger',
QUARANTINED: 'info'
}
class StockStatusGroups:
"""Groups for StockStatus codes."""
# The following codes correspond to parts that are 'available' or 'in stock'
AVAILABLE_CODES = [
OK,
ATTENTION,
DAMAGED,
RETURNED,
StockStatus.OK.value,
StockStatus.ATTENTION.value,
StockStatus.DAMAGED.value,
StockStatus.RETURNED.value,
]
class StockHistoryCode(StatusCode):
"""Status codes for StockHistory."""
LEGACY = 0
LEGACY = 0, _('Legacy stock tracking entry')
CREATED = 1
CREATED = 1, _('Stock item created')
# Manual editing operations
EDITED = 5
ASSIGNED_SERIAL = 6
EDITED = 5, _('Edited stock item')
ASSIGNED_SERIAL = 6, _('Assigned serial number')
# Manual stock operations
STOCK_COUNT = 10
STOCK_ADD = 11
STOCK_REMOVE = 12
STOCK_COUNT = 10, _('Stock counted')
STOCK_ADD = 11, _('Stock manually added')
STOCK_REMOVE = 12, _('Stock manually removed')
# Location operations
STOCK_MOVE = 20
STOCK_MOVE = 20, _('Location changed')
STOCK_UPDATE = 25, _('Stock updated')
# Installation operations
INSTALLED_INTO_ASSEMBLY = 30
REMOVED_FROM_ASSEMBLY = 31
INSTALLED_INTO_ASSEMBLY = 30, _('Installed into assembly')
REMOVED_FROM_ASSEMBLY = 31, _('Removed from assembly')
INSTALLED_CHILD_ITEM = 35
REMOVED_CHILD_ITEM = 36
INSTALLED_CHILD_ITEM = 35, _('Installed component item')
REMOVED_CHILD_ITEM = 36, _('Removed component item')
# Stock splitting operations
SPLIT_FROM_PARENT = 40
SPLIT_CHILD_ITEM = 42
SPLIT_FROM_PARENT = 40, _('Split from parent item')
SPLIT_CHILD_ITEM = 42, _('Split child item')
# Stock merging operations
MERGED_STOCK_ITEMS = 45
MERGED_STOCK_ITEMS = 45, _('Merged stock items')
# Convert stock item to variant
CONVERTED_TO_VARIANT = 48
CONVERTED_TO_VARIANT = 48, _('Converted to variant')
# Build order codes
BUILD_OUTPUT_CREATED = 50
BUILD_OUTPUT_COMPLETED = 55
BUILD_CONSUMED = 57
BUILD_OUTPUT_CREATED = 50, _('Build order output created')
BUILD_OUTPUT_COMPLETED = 55, _('Build order output completed')
BUILD_OUTPUT_REJECTED = 56, _('Build order output rejected')
BUILD_CONSUMED = 57, _('Consumed by build order')
# Sales order codes
SHIPPED_AGAINST_SALES_ORDER = 60
SHIPPED_AGAINST_SALES_ORDER = 60, _('Shipped against Sales Order')
# Purchase order codes
RECEIVED_AGAINST_PURCHASE_ORDER = 70
RECEIVED_AGAINST_PURCHASE_ORDER = 70, _('Received against Purchase Order')
# Return order codes
RETURNED_AGAINST_RETURN_ORDER = 80
RETURNED_AGAINST_RETURN_ORDER = 80, _('Returned against Return Order')
# Customer actions
SENT_TO_CUSTOMER = 100
RETURNED_FROM_CUSTOMER = 105
options = {
LEGACY: _('Legacy stock tracking entry'),
CREATED: _('Stock item created'),
EDITED: _('Edited stock item'),
ASSIGNED_SERIAL: _('Assigned serial number'),
STOCK_COUNT: _('Stock counted'),
STOCK_ADD: _('Stock manually added'),
STOCK_REMOVE: _('Stock manually removed'),
STOCK_MOVE: _('Location changed'),
INSTALLED_INTO_ASSEMBLY: _('Installed into assembly'),
REMOVED_FROM_ASSEMBLY: _('Removed from assembly'),
INSTALLED_CHILD_ITEM: _('Installed component item'),
REMOVED_CHILD_ITEM: _('Removed component item'),
SPLIT_FROM_PARENT: _('Split from parent item'),
SPLIT_CHILD_ITEM: _('Split child item'),
MERGED_STOCK_ITEMS: _('Merged stock items'),
CONVERTED_TO_VARIANT: _('Converted to variant'),
SENT_TO_CUSTOMER: _('Sent to customer'),
RETURNED_FROM_CUSTOMER: _('Returned from customer'),
BUILD_OUTPUT_CREATED: _('Build order output created'),
BUILD_OUTPUT_COMPLETED: _('Build order output completed'),
BUILD_CONSUMED: _('Consumed by build order'),
SHIPPED_AGAINST_SALES_ORDER: _("Shipped against Sales Order"),
RECEIVED_AGAINST_PURCHASE_ORDER: _('Received against Purchase Order'),
RETURNED_AGAINST_RETURN_ORDER: _('Returned against Return Order'),
}
SENT_TO_CUSTOMER = 100, _('Sent to customer')
RETURNED_FROM_CUSTOMER = 105, _('Returned from customer')
class BuildStatus(StatusCode):
"""Build status codes."""
PENDING = 10 # Build is pending / active
PRODUCTION = 20 # BuildOrder is in production
CANCELLED = 30 # Build was cancelled
COMPLETE = 40 # Build is complete
PENDING = 10, _('Pending'), 'secondary' # Build is pending / active
PRODUCTION = 20, _('Production'), 'primary' # BuildOrder is in production
CANCELLED = 30, _('Cancelled'), 'danger' # Build was cancelled
COMPLETE = 40, _('Complete'), 'success' # Build is complete
options = {
PENDING: _("Pending"),
PRODUCTION: _("Production"),
CANCELLED: _("Cancelled"),
COMPLETE: _("Complete"),
}
colors = {
PENDING: 'secondary',
PRODUCTION: 'primary',
COMPLETE: 'success',
CANCELLED: 'danger',
}
class BuildStatusGroups:
"""Groups for BuildStatus codes."""
ACTIVE_CODES = [
PENDING,
PRODUCTION,
]
ACTIVE_CODES = [BuildStatus.PENDING.value, BuildStatus.PRODUCTION.value]
class ReturnOrderStatus(StatusCode):
"""Defines a set of status codes for a ReturnOrder"""
"""Defines a set of status codes for a ReturnOrder."""
# Order is pending, waiting for receipt of items
PENDING = 10
PENDING = 10, _('Pending'), 'secondary'
# Items have been received, and are being inspected
IN_PROGRESS = 20
IN_PROGRESS = 20, _('In Progress'), 'primary'
COMPLETE = 30
CANCELLED = 40
COMPLETE = 30, _('Complete'), 'success'
CANCELLED = 40, _('Cancelled'), 'danger'
OPEN = [
PENDING,
IN_PROGRESS,
]
options = {
PENDING: _("Pending"),
IN_PROGRESS: _("In Progress"),
COMPLETE: _("Complete"),
CANCELLED: _("Cancelled"),
}
class ReturnOrderStatusGroups:
"""Groups for ReturnOrderStatus codes."""
colors = {
PENDING: 'secondary',
IN_PROGRESS: 'primary',
COMPLETE: 'success',
CANCELLED: 'danger',
}
OPEN = [ReturnOrderStatus.PENDING.value, ReturnOrderStatus.IN_PROGRESS.value]
class ReturnOrderLineStatus(StatusCode):
"""Defines a set of status codes for a ReturnOrderLineItem"""
"""Defines a set of status codes for a ReturnOrderLineItem."""
PENDING = 10
PENDING = 10, _('Pending'), 'secondary'
# Item is to be returned to customer, no other action
RETURN = 20
RETURN = 20, _('Return'), 'success'
# Item is to be repaired, and returned to customer
REPAIR = 30
REPAIR = 30, _('Repair'), 'primary'
# Item is to be replaced (new item shipped)
REPLACE = 40
REPLACE = 40, _('Replace'), 'warning'
# Item is to be refunded (cannot be repaired)
REFUND = 50
REFUND = 50, _('Refund'), 'info'
# Item is rejected
REJECT = 60
options = {
PENDING: _('Pending'),
RETURN: _('Return'),
REPAIR: _('Repair'),
REFUND: _('Refund'),
REPLACE: _('Replace'),
REJECT: _('Reject')
}
colors = {
PENDING: 'secondary',
RETURN: 'success',
REPAIR: 'primary',
REFUND: 'info',
REPLACE: 'warning',
REJECT: 'danger',
}
REJECT = 60, _('Reject'), 'danger'

View File

@@ -9,25 +9,29 @@ import time
import warnings
from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Callable, List
from typing import Callable
from django.conf import settings
from django.core import mail as django_mail
from django.core.exceptions import AppRegistryNotReady
from django.core.management import call_command
from django.db import DEFAULT_DB_ALIAS, connections
from django.db.migrations.executor import MigrationExecutor
from django.db.utils import (NotSupportedError, OperationalError,
ProgrammingError)
from django.db.utils import NotSupportedError, OperationalError, ProgrammingError
from django.utils import timezone
import requests
from maintenance_mode.core import (get_maintenance_mode, maintenance_mode_on,
set_maintenance_mode)
from maintenance_mode.core import (
get_maintenance_mode,
maintenance_mode_on,
set_maintenance_mode,
)
from InvenTree.config import get_setting
from plugin import registry
logger = logging.getLogger("inventree")
from .version import isInvenTreeUpToDate
logger = logging.getLogger('inventree')
def schedule_task(taskname, **kwargs):
@@ -42,24 +46,20 @@ def schedule_task(taskname, **kwargs):
try:
from django_q.models import Schedule
except AppRegistryNotReady: # pragma: no cover
logger.info("Could not start background tasks - App registry not ready")
logger.info('Could not start background tasks - App registry not ready')
return
try:
# If this task is already scheduled, don't schedule it again
# Instead, update the scheduling parameters
if Schedule.objects.filter(func=taskname).exists():
logger.debug(f"Scheduled task '{taskname}' already exists - updating!")
logger.debug("Scheduled task '%s' already exists - updating!", taskname)
Schedule.objects.filter(func=taskname).update(**kwargs)
else:
logger.info(f"Creating scheduled task '{taskname}'")
logger.info("Creating scheduled task '%s'", taskname)
Schedule.objects.create(
name=taskname,
func=taskname,
**kwargs
)
Schedule.objects.create(name=taskname, func=taskname, **kwargs)
except (OperationalError, ProgrammingError): # pragma: no cover
# Required if the DB is not ready yet
pass
@@ -71,15 +71,15 @@ def raise_warning(msg):
# If testing is running raise a warning that can be asserted
if settings.TESTING:
warnings.warn(msg)
warnings.warn(msg, stacklevel=2)
def check_daily_holdoff(task_name: str, n_days: int = 1) -> bool:
"""Check if a periodic task should be run, based on the provided setting name.
Arguments:
task_name: The name of the task being run, e.g. 'dummy_task'
setting_name: The name of the global setting, e.g. 'INVENTREE_DUMMY_TASK_INTERVAL'
task_name (str): The name of the task being run, e.g. 'dummy_task'
n_days (int): The number of days between task runs (default = 1)
Returns:
bool: If the task should be run *now*, or wait another day
@@ -90,15 +90,18 @@ def check_daily_holdoff(task_name: str, n_days: int = 1) -> bool:
Note that this function creates some *hidden* global settings (designated with the _ prefix),
which are used to keep a running track of when the particular task was was last run.
"""
from common.models import InvenTreeSetting
from InvenTree.ready import isInTestMode
if n_days <= 0:
logger.info(f"Specified interval for task '{task_name}' < 1 - task will not run")
logger.info(
"Specified interval for task '%s' < 1 - task will not run", task_name
)
return False
# Sleep a random number of seconds to prevent worker conflict
time.sleep(random.randint(1, 5))
if not isInTestMode():
time.sleep(random.randint(1, 5))
attempt_key = f'_{task_name}_ATTEMPT'
success_key = f'_{task_name}_SUCCESS'
@@ -116,7 +119,9 @@ def check_daily_holdoff(task_name: str, n_days: int = 1) -> bool:
threshold = datetime.now() - timedelta(days=n_days)
if last_success > threshold:
logger.info(f"Last successful run for '{task_name}' was too recent - skipping task")
logger.info(
"Last successful run for '%s' was too recent - skipping task", task_name
)
return False
# Check for any information we have about this task
@@ -133,7 +138,9 @@ def check_daily_holdoff(task_name: str, n_days: int = 1) -> bool:
threshold = datetime.now() - timedelta(hours=12)
if last_attempt > threshold:
logger.info(f"Last attempt for '{task_name}' was too recent - skipping task")
logger.info(
"Last attempt for '%s' was too recent - skipping task", task_name
)
return False
# Record this attempt
@@ -144,28 +151,34 @@ def check_daily_holdoff(task_name: str, n_days: int = 1) -> bool:
def record_task_attempt(task_name: str):
"""Record that a multi-day task has been attempted *now*"""
"""Record that a multi-day task has been attempted *now*."""
from common.models import InvenTreeSetting
logger.info(f"Logging task attempt for '{task_name}'")
logger.info("Logging task attempt for '%s'", task_name)
InvenTreeSetting.set_setting(f'_{task_name}_ATTEMPT', datetime.now().isoformat(), None)
InvenTreeSetting.set_setting(
f'_{task_name}_ATTEMPT', datetime.now().isoformat(), None
)
def record_task_success(task_name: str):
"""Record that a multi-day task was successful *now*"""
"""Record that a multi-day task was successful *now*."""
from common.models import InvenTreeSetting
InvenTreeSetting.set_setting(f'_{task_name}_SUCCESS', datetime.now().isoformat(), None)
InvenTreeSetting.set_setting(
f'_{task_name}_SUCCESS', datetime.now().isoformat(), None
)
def offload_task(taskname, *args, force_async=False, force_sync=False, **kwargs):
def offload_task(
taskname, *args, force_async=False, force_sync=False, **kwargs
) -> bool:
"""Create an AsyncTask if workers are running. This is different to a 'scheduled' task, in that it only runs once!
If workers are not running or force_sync flag
is set then the task is ran synchronously.
If workers are not running or force_sync flag, is set then the task is ran synchronously.
Returns:
bool: True if the task was offloaded (or ran), False otherwise
"""
try:
import importlib
@@ -174,20 +187,34 @@ def offload_task(taskname, *args, force_async=False, force_sync=False, **kwargs)
from InvenTree.status import is_worker_running
except AppRegistryNotReady: # pragma: no cover
logger.warning(f"Could not offload task '{taskname}' - app registry not ready")
return
logger.warning("Could not offload task '%s' - app registry not ready", taskname)
if force_async:
# Cannot async the task, so return False
return False
else:
force_sync = True
except (OperationalError, ProgrammingError): # pragma: no cover
raise_warning(f"Could not offload task '{taskname}' - database not ready")
if force_async:
# Cannot async the task, so return False
return False
else:
force_sync = True
if force_async or (is_worker_running() and not force_sync):
# Running as asynchronous task
try:
task = AsyncTask(taskname, *args, **kwargs)
task.run()
except ImportError:
raise_warning(f"WARNING: '{taskname}' not started - Function not found")
raise_warning(f"WARNING: '{taskname}' not offloaded - Function not found")
return False
except Exception as exc:
raise_warning(f"WARNING: '{taskname}' not offloaded due to {str(exc)}")
return False
else:
if callable(taskname):
# function was passed - use that
_func = taskname
@@ -197,15 +224,19 @@ def offload_task(taskname, *args, force_async=False, force_sync=False, **kwargs)
app, mod, func = taskname.split('.')
app_mod = app + '.' + mod
except ValueError:
raise_warning(f"WARNING: '{taskname}' not started - Malformed function path")
return
raise_warning(
f"WARNING: '{taskname}' not started - Malformed function path"
)
return False
# Import module from app
try:
_mod = importlib.import_module(app_mod)
except ModuleNotFoundError:
raise_warning(f"WARNING: '{taskname}' not started - No module named '{app_mod}'")
return
raise_warning(
f"WARNING: '{taskname}' not started - No module named '{app_mod}'"
)
return False
# Retrieve function
try:
@@ -218,11 +249,20 @@ def offload_task(taskname, *args, force_async=False, force_sync=False, **kwargs)
if not _func:
_func = eval(func) # pragma: no cover
except NameError:
raise_warning(f"WARNING: '{taskname}' not started - No function named '{func}'")
return
raise_warning(
f"WARNING: '{taskname}' not started - No function named '{func}'"
)
return False
# Workers are not running: run it as synchronous task
_func(*args, **kwargs)
try:
_func(*args, **kwargs)
except Exception as exc:
raise_warning(f"WARNING: '{taskname}' not started due to {str(exc)}")
return False
# Finally, task either completed successfully or was offloaded
return True
@dataclass()
@@ -238,19 +278,20 @@ class ScheduledTask:
interval: str
minutes: int = None
MINUTES = "I"
HOURLY = "H"
DAILY = "D"
WEEKLY = "W"
MONTHLY = "M"
QUARTERLY = "Q"
YEARLY = "Y"
MINUTES = 'I'
HOURLY = 'H'
DAILY = 'D'
WEEKLY = 'W'
MONTHLY = 'M'
QUARTERLY = 'Q'
YEARLY = 'Y'
TYPE = [MINUTES, HOURLY, DAILY, WEEKLY, MONTHLY, QUARTERLY, YEARLY]
class TaskRegister:
"""Registery for periodicall tasks."""
task_list: List[ScheduledTask] = []
"""Registry for periodic tasks."""
task_list: list[ScheduledTask] = []
def register(self, task, schedule, minutes: int = None):
"""Register a task with the que."""
@@ -265,8 +306,9 @@ def scheduled_task(interval: str, minutes: int = None, tasklist: TaskRegister =
Example:
```python
@register(ScheduledTask.DAILY)
def my_custom_funciton():
@scheduled_task(ScheduledTask.DAILY)
def my_custom_function():
# Perform a custom function once per day
...
```
@@ -294,6 +336,7 @@ def scheduled_task(interval: str, minutes: int = None, tasklist: TaskRegister =
_tasks.register(admin_class, interval, minutes=minutes)
return admin_class
return _task_wrapper
@@ -304,9 +347,9 @@ def heartbeat():
(There is probably a less "hacky" way of achieving this)?
"""
try:
from django_q.models import Success
from django_q.models import OrmQ, Success
except AppRegistryNotReady: # pragma: no cover
logger.info("Could not perform heartbeat task - App registry not ready")
logger.info('Could not perform heartbeat task - App registry not ready')
return
threshold = timezone.now() - timedelta(minutes=30)
@@ -314,16 +357,20 @@ def heartbeat():
# Delete heartbeat results more than half an hour old,
# otherwise they just create extra noise
heartbeats = Success.objects.filter(
func='InvenTree.tasks.heartbeat',
started__lte=threshold
func='InvenTree.tasks.heartbeat', started__lte=threshold
)
heartbeats.delete()
# Clear out any other pending heartbeat tasks
for task in OrmQ.objects.all():
if task.func() == 'InvenTree.tasks.heartbeat':
task.delete()
@scheduled_task(ScheduledTask.DAILY)
def delete_successful_tasks():
"""Delete successful task logs which are older than a specified period"""
"""Delete successful task logs which are older than a specified period."""
try:
from django_q.models import Success
@@ -333,22 +380,21 @@ def delete_successful_tasks():
threshold = timezone.now() - timedelta(days=days)
# Delete successful tasks
results = Success.objects.filter(
started__lte=threshold
)
results = Success.objects.filter(started__lte=threshold)
if results.count() > 0:
logger.info(f"Deleting {results.count()} successful task records")
logger.info('Deleting %s successful task records', results.count())
results.delete()
except AppRegistryNotReady: # pragma: no cover
logger.info("Could not perform 'delete_successful_tasks' - App registry not ready")
logger.info(
"Could not perform 'delete_successful_tasks' - App registry not ready"
)
@scheduled_task(ScheduledTask.DAILY)
def delete_failed_tasks():
"""Delete failed task logs which are older than a specified period"""
"""Delete failed task logs which are older than a specified period."""
try:
from django_q.models import Failure
@@ -358,12 +404,10 @@ def delete_failed_tasks():
threshold = timezone.now() - timedelta(days=days)
# Delete failed tasks
results = Failure.objects.filter(
started__lte=threshold
)
results = Failure.objects.filter(started__lte=threshold)
if results.count() > 0:
logger.info(f"Deleting {results.count()} failed task records")
logger.info('Deleting %s failed task records', results.count())
results.delete()
except AppRegistryNotReady: # pragma: no cover
@@ -381,48 +425,48 @@ def delete_old_error_logs():
days = InvenTreeSetting.get_setting('INVENTREE_DELETE_ERRORS_DAYS', 30)
threshold = timezone.now() - timedelta(days=days)
errors = Error.objects.filter(
when__lte=threshold,
)
errors = Error.objects.filter(when__lte=threshold)
if errors.count() > 0:
logger.info(f"Deleting {errors.count()} old error logs")
logger.info('Deleting %s old error logs', errors.count())
errors.delete()
except AppRegistryNotReady: # pragma: no cover
# Apps not yet loaded
logger.info("Could not perform 'delete_old_error_logs' - App registry not ready")
logger.info(
"Could not perform 'delete_old_error_logs' - App registry not ready"
)
@scheduled_task(ScheduledTask.DAILY)
def delete_old_notifications():
"""Delete old notification logs"""
"""Delete old notification logs."""
try:
from common.models import (InvenTreeSetting, NotificationEntry,
NotificationMessage)
from common.models import (
InvenTreeSetting,
NotificationEntry,
NotificationMessage,
)
days = InvenTreeSetting.get_setting('INVENTREE_DELETE_NOTIFICATIONS_DAYS', 30)
threshold = timezone.now() - timedelta(days=days)
items = NotificationEntry.objects.filter(
updated__lte=threshold
)
items = NotificationEntry.objects.filter(updated__lte=threshold)
if items.count() > 0:
logger.info(f"Deleted {items.count()} old notification entries")
logger.info('Deleted %s old notification entries', items.count())
items.delete()
items = NotificationMessage.objects.filter(
creation__lte=threshold
)
items = NotificationMessage.objects.filter(creation__lte=threshold)
if items.count() > 0:
logger.info(f"Deleted {items.count()} old notification messages")
logger.info('Deleted %s old notification messages', items.count())
items.delete()
except AppRegistryNotReady:
logger.info("Could not perform 'delete_old_notifications' - App registry not ready")
logger.info(
"Could not perform 'delete_old_notifications' - App registry not ready"
)
@scheduled_task(ScheduledTask.DAILY)
@@ -430,18 +474,23 @@ def check_for_updates():
"""Check if there is an update for InvenTree."""
try:
import common.models
from common.notifications import trigger_superuser_notification
except AppRegistryNotReady: # pragma: no cover
# Apps not yet loaded!
logger.info("Could not perform 'check_for_updates' - App registry not ready")
return
interval = int(common.models.InvenTreeSetting.get_setting('INVENTREE_UPDATE_CHECK_INTERVAL', 7, cache=False))
interval = int(
common.models.InvenTreeSetting.get_setting(
'INVENTREE_UPDATE_CHECK_INTERVAL', 7, cache=False
)
)
# Check if we should check for updates *today*
if not check_daily_holdoff('check_for_updates', interval):
return
logger.info("Checking for InvenTree software updates")
logger.info('Checking for InvenTree software updates')
headers = {}
@@ -450,15 +499,17 @@ def check_for_updates():
token = os.getenv('GITHUB_TOKEN', None)
if token:
headers['Authorization'] = f"Bearer {token}"
headers['Authorization'] = f'Bearer {token}'
response = requests.get(
'https://api.github.com/repos/inventree/inventree/releases/latest',
headers=headers
headers=headers,
)
if response.status_code != 200:
raise ValueError(f'Unexpected status code from GitHub API: {response.status_code}') # pragma: no cover
raise ValueError(
f'Unexpected status code from GitHub API: {response.status_code}'
) # pragma: no cover
data = json.loads(response.text)
@@ -467,10 +518,10 @@ def check_for_updates():
if not tag:
raise ValueError("'tag_name' missing from GitHub response") # pragma: no cover
match = re.match(r"^.*(\d+)\.(\d+)\.(\d+).*$", tag)
match = re.match(r'^.*(\d+)\.(\d+)\.(\d+).*$', tag)
if len(match.groups()) != 3: # pragma: no cover
logger.warning(f"Version '{tag}' did not match expected pattern")
logger.warning("Version '%s' did not match expected pattern", tag)
return
latest_version = [int(x) for x in match.groups()]
@@ -478,168 +529,185 @@ def check_for_updates():
if len(latest_version) != 3:
raise ValueError(f"Version '{tag}' is not correct format") # pragma: no cover
logger.info(f"Latest InvenTree version: '{tag}'")
logger.info("Latest InvenTree version: '%s'", tag)
# Save the version to the database
common.models.InvenTreeSetting.set_setting(
'_INVENTREE_LATEST_VERSION',
tag,
None
)
common.models.InvenTreeSetting.set_setting('_INVENTREE_LATEST_VERSION', tag, None)
# Record that this task was successful
record_task_success('check_for_updates')
# Send notification if there is a new version
if not isInvenTreeUpToDate():
logger.warning('InvenTree is not up-to-date, sending notification')
plg = registry.get_plugin('InvenTreeCoreNotificationsPlugin')
if not plg:
logger.warning('Cannot send notification - plugin not found')
return
plg = plg.plugin_config()
if not plg:
logger.warning('Cannot send notification - plugin config not found')
return
# Send notification
trigger_superuser_notification(
plg, f'An update for InvenTree to version {tag} is available'
)
@scheduled_task(ScheduledTask.DAILY)
def update_exchange_rates():
"""Update currency exchange rates."""
try:
from djmoney.contrib.exchange.models import ExchangeBackend, Rate
def update_exchange_rates(force: bool = False):
"""Update currency exchange rates.
Arguments:
force: If True, force the update to run regardless of the last update time
"""
try:
from djmoney.contrib.exchange.models import Rate
from common.models import InvenTreeSetting
from common.settings import currency_code_default, currency_codes
from InvenTree.exchange import InvenTreeExchange
except AppRegistryNotReady: # pragma: no cover
# Apps not yet loaded!
logger.info("Could not perform 'update_exchange_rates' - App registry not ready")
logger.info(
"Could not perform 'update_exchange_rates' - App registry not ready"
)
return
except Exception: # pragma: no cover
# Other error?
except Exception as exc: # pragma: no cover
logger.info("Could not perform 'update_exchange_rates' - %s", exc)
return
# Test to see if the database is ready yet
try:
backend = ExchangeBackend.objects.get(name='InvenTreeExchange')
except ExchangeBackend.DoesNotExist:
pass
except Exception: # pragma: no cover
# Some other error
logger.warning("update_exchange_rates: Database not ready")
return
if not force:
interval = int(
InvenTreeSetting.get_setting('CURRENCY_UPDATE_INTERVAL', 1, cache=False)
)
if not check_daily_holdoff('update_exchange_rates', interval):
logger.info('Skipping exchange rate update (interval not reached)')
return
backend = InvenTreeExchange()
logger.info(f"Updating exchange rates from {backend.url}")
base = currency_code_default()
logger.info(f"Using base currency '{base}'")
logger.info("Updating exchange rates using base currency '%s'", base)
try:
backend.update_rates(base_currency=base)
# Remove any exchange rates which are not in the provided currencies
Rate.objects.filter(backend="InvenTreeExchange").exclude(currency__in=currency_codes()).delete()
Rate.objects.filter(backend='InvenTreeExchange').exclude(
currency__in=currency_codes()
).delete()
# Record successful task execution
record_task_success('update_exchange_rates')
except (AppRegistryNotReady, OperationalError, ProgrammingError):
logger.warning('Could not update exchange rates - database not ready')
except Exception as e: # pragma: no cover
logger.error(f"Error updating exchange rates: {e}")
logger.exception('Error updating exchange rates: %s', str(type(e)))
@scheduled_task(ScheduledTask.DAILY)
def run_backup():
"""Run the backup command."""
from common.models import InvenTreeSetting
if not InvenTreeSetting.get_setting('INVENTREE_BACKUP_ENABLE', False, cache=False):
# Backups are not enabled - exit early
return
interval = int(InvenTreeSetting.get_setting('INVENTREE_BACKUP_DAYS', 1, cache=False))
interval = int(
InvenTreeSetting.get_setting('INVENTREE_BACKUP_DAYS', 1, cache=False)
)
# Check if should run this task *today*
if not check_daily_holdoff('run_backup', interval):
return
logger.info("Performing automated database backup task")
logger.info('Performing automated database backup task')
call_command("dbbackup", noinput=True, clean=True, compress=True, interactive=False)
call_command("mediabackup", noinput=True, clean=True, compress=True, interactive=False)
call_command('dbbackup', noinput=True, clean=True, compress=True, interactive=False)
call_command(
'mediabackup', noinput=True, clean=True, compress=True, interactive=False
)
# Record that this task was successful
record_task_success('run_backup')
def send_email(subject, body, recipients, from_email=None, html_message=None):
"""Send an email with the specified subject and body, to the specified recipients list."""
if type(recipients) == str:
recipients = [recipients]
offload_task(
django_mail.send_mail,
subject,
body,
from_email,
recipients,
fail_silently=False,
html_message=html_message
)
@scheduled_task(ScheduledTask.DAILY)
def check_for_migrations(worker: bool = True):
"""Checks if migrations are needed.
If the setting auto_update is enabled we will start updateing.
"""
# Test if auto-updates are enabled
if not get_setting('INVENTREE_AUTO_UPDATE', 'auto_update'):
return
from plugin import registry
plan = get_migration_plan()
# Check if there are any open migrations
if not plan:
logger.info('There are no open migrations')
return
logger.info('There are open migrations')
# Log open migrations
for migration in plan:
logger.info(migration[0])
# Set the application to maintenance mode - no access from now on.
logger.info('Going into maintenance')
set_maintenance_mode(True)
logger.info('Mainentance mode is on now')
# Check if we are worker - go kill all other workers then.
# Only the frontend workers run updates.
if worker:
logger.info('Current process is a worker - shutting down cluster')
# Ok now we are ready to go ahead!
# To be sure we are in maintenance this is wrapped
with maintenance_mode_on():
logger.info('Starting migrations')
print('Starting migrations')
try:
call_command('migrate', interactive=False)
except NotSupportedError as e: # pragma: no cover
if settings.DATABASES['default']['ENGINE'] != 'django.db.backends.sqlite3':
raise e
logger.error(f'Error during migrations: {e}')
print('Migrations done')
logger.info('Ran migrations')
# Make sure we are out of maintenance again
logger.info('Checking InvenTree left maintenance mode')
if get_maintenance_mode():
logger.warning('Mainentance was still on - releasing now')
set_maintenance_mode(False)
logger.info('Released out of maintenance')
# We should be current now - triggering full reload to make sure all models
# are loaded fully in their new state.
registry.reload_plugins(full_reload=True, force_reload=True)
def get_migration_plan():
"""Returns a list of migrations which are needed to be run."""
executor = MigrationExecutor(connections[DEFAULT_DB_ALIAS])
plan = executor.migration_plan(executor.loader.graph.leaf_nodes())
return plan
@scheduled_task(ScheduledTask.DAILY)
def check_for_migrations(force: bool = False, reload_registry: bool = True):
"""Checks if migrations are needed.
If the setting auto_update is enabled we will start updating.
"""
from common.models import InvenTreeSetting
from plugin import registry
def set_pending_migrations(n: int):
"""Helper function to inform the user about pending migrations."""
logger.info('There are %s pending migrations', n)
InvenTreeSetting.set_setting('_PENDING_MIGRATIONS', n, None)
logger.info('Checking for pending database migrations')
if reload_registry:
# Force plugin registry reload
registry.check_reload()
plan = get_migration_plan()
n = len(plan)
# Check if there are any open migrations
if not plan:
set_pending_migrations(0)
return
set_pending_migrations(n)
# Test if auto-updates are enabled
if not force and not get_setting('INVENTREE_AUTO_UPDATE', 'auto_update'):
logger.info('Auto-update is disabled - skipping migrations')
return
# Log open migrations
for migration in plan:
logger.info('- %s', str(migration[0]))
# Set the application to maintenance mode - no access from now on.
set_maintenance_mode(True)
# To be sure we are in maintenance this is wrapped
with maintenance_mode_on():
logger.info('Starting migration process...')
try:
call_command('migrate', interactive=False)
except NotSupportedError as e: # pragma: no cover
if settings.DATABASES['default']['ENGINE'] != 'django.db.backends.sqlite3':
raise e
logger.exception('Error during migrations: %s', e)
else:
set_pending_migrations(0)
logger.info('Completed %s migrations', n)
# Make sure we are out of maintenance mode
if get_maintenance_mode():
logger.warning('Maintenance mode was not disabled - forcing it now')
set_maintenance_mode(False)
logger.info('Manually released maintenance mode')
if reload_registry:
# We should be current now - triggering full reload to make sure all models
# are loaded fully in their new state.
registry.reload_plugins(full_reload=True, force_reload=True, collect=True)

View File

@@ -0,0 +1,35 @@
"""Custom template loader for InvenTree."""
import os
from django.conf import settings
from django.template.loaders.base import Loader as BaseLoader
from django.template.loaders.cached import Loader as CachedLoader
class InvenTreeTemplateLoader(CachedLoader):
"""Custom template loader which bypasses cache for PDF export."""
def get_template(self, template_name, skip=None):
"""Return a template object for the given template name.
Any custom report or label templates will be forced to reload (without cache).
This ensures that generated PDF reports / labels are always up-to-date.
"""
# List of template patterns to skip cache for
skip_cache_dirs = [
os.path.abspath(os.path.join(settings.MEDIA_ROOT, 'report')),
os.path.abspath(os.path.join(settings.MEDIA_ROOT, 'label')),
'snippets/',
]
# Initially load the template using the cached loader
template = CachedLoader.get_template(self, template_name, skip)
template_path = str(template.name)
# If the template matches any of the skip patterns, reload it without cache
if any(template_path.startswith(d) for d in skip_cache_dirs):
template = BaseLoader.get_template(self, template_name, skip)
return template

View File

@@ -0,0 +1,8 @@
Hello {{username}},
You requested that we send you a link to log in to our app:
{{link}}
Regards,
{{site_name}}

View File

@@ -0,0 +1 @@
"""Custom InvenTree template tags for HTML template rendering."""

View File

@@ -0,0 +1,163 @@
"""This module provides custom translation tags specifically for use with javascript code.
Translated strings are escaped, such that they can be used as string literals in a javascript file.
"""
import django.templatetags.i18n
from django import template
from django.template import TemplateSyntaxError
from django.templatetags.i18n import TranslateNode
import bleach
import InvenTree.translation
register = template.Library()
@register.simple_tag()
def translation_stats(lang_code):
"""Return the translation percentage for the given language code."""
if lang_code is None:
return None
return InvenTree.translation.get_translation_percent(lang_code)
class CustomTranslateNode(TranslateNode):
"""Custom translation node class, which sanitizes the translated strings for javascript use."""
def __init__(self, filter_expression, noop, asvar, message_context, escape=False):
"""Custom constructor for TranslateNode class.
- Adds an 'escape' argument, which is passed to the render function
"""
super().__init__(filter_expression, noop, asvar, message_context)
self.escape = escape
def render(self, context):
"""Custom render function overrides / extends default behaviour."""
result = super().render(context)
result = bleach.clean(result)
# Remove any escape sequences
for seq in ['\a', '\b', '\f', '\n', '\r', '\t', '\v']:
result = result.replace(seq, '')
# Remove other disallowed characters
for c in ['\\', '`', ';', '|', '&']:
result = result.replace(c, '')
# Escape any quotes contained in the string, if the request is for a javascript file
request = context.get('request', None)
template = getattr(context, 'template_name', None)
request = context.get('request', None)
escape = self.escape
if template and str(template).endswith('.js'):
escape = True
if request and str(request.path).endswith('.js'):
escape = True
if escape:
result = result.replace("'", r'\'')
result = result.replace('"', r'\"')
# Return the 'clean' resulting string
return result
@register.tag('translate')
@register.tag('trans')
def do_translate(parser, token):
"""Custom translation function.
- Lifted from https://github.com/django/django/blob/main/django/templatetags/i18n.py.
- The only difference is that we pass this to our custom rendering node class
"""
bits = token.split_contents()
if len(bits) < 2:
raise TemplateSyntaxError("'%s' takes at least one argument" % bits[0])
message_string = parser.compile_filter(bits[1])
remaining = bits[2:]
escape = False
noop = False
asvar = None
message_context = None
seen = set()
invalid_context = {'as', 'noop'}
while remaining:
option = remaining.pop(0)
if option in seen:
raise TemplateSyntaxError(
"The '%s' option was specified more than once." % option
)
elif option == 'noop':
noop = True
elif option == 'context':
try:
value = remaining.pop(0)
except IndexError:
raise TemplateSyntaxError(
"No argument provided to the '%s' tag for the context option."
% bits[0]
)
if value in invalid_context:
raise TemplateSyntaxError(
"Invalid argument '%s' provided to the '%s' tag for the context "
'option' % (value, bits[0])
)
message_context = parser.compile_filter(value)
elif option == 'as':
try:
value = remaining.pop(0)
except IndexError:
raise TemplateSyntaxError(
"No argument provided to the '%s' tag for the as option." % bits[0]
)
asvar = value
elif option == 'escape':
escape = True
else:
raise TemplateSyntaxError(
"Unknown argument for '%s' tag: '%s'. The only options "
"available are 'noop', 'context' \"xxx\", and 'as VAR'."
% (bits[0], option)
)
seen.add(option)
return CustomTranslateNode(
message_string, noop, asvar, message_context, escape=escape
)
# Re-register tags which we have not explicitly overridden
register.tag('blocktrans', django.templatetags.i18n.do_block_translate)
register.tag('blocktranslate', django.templatetags.i18n.do_block_translate)
register.tag('language', django.templatetags.i18n.language)
register.tag(
'get_available_languages', django.templatetags.i18n.do_get_available_languages
)
register.tag('get_language_info', django.templatetags.i18n.do_get_language_info)
register.tag(
'get_language_info_list', django.templatetags.i18n.do_get_language_info_list
)
register.tag('get_current_language', django.templatetags.i18n.do_get_current_language)
register.tag(
'get_current_language_bidi', django.templatetags.i18n.do_get_current_language_bidi
)
register.filter('language_name', django.templatetags.i18n.language_name)
register.filter(
'language_name_translated', django.templatetags.i18n.language_name_translated
)
register.filter('language_name_local', django.templatetags.i18n.language_name_local)
register.filter('language_bidi', django.templatetags.i18n.language_bidi)

View File

@@ -2,23 +2,24 @@
import logging
import os
import sys
from datetime import date, datetime
from django import template
from django.conf import settings as djangosettings
from django.templatetags.static import StaticNode
from django.urls import reverse
from django.urls import NoReverseMatch, reverse
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
import common.models
import InvenTree.helpers
from common.models import ColorTheme, InvenTreeSetting, InvenTreeUserSetting
import InvenTree.helpers_model
import plugin.models
from common.settings import currency_code_default
from InvenTree import settings, version
from plugin import registry
from plugin.models import NotificationUserSetting, PluginSetting
from plugin.plugin import InvenTreePlugin
register = template.Library()
@@ -53,8 +54,7 @@ def render_date(context, date_object):
if date_object is None:
return None
if type(date_object) == str:
if isinstance(date_object, str):
date_object = date_object.strip()
# Check for empty string
@@ -65,29 +65,25 @@ def render_date(context, date_object):
try:
date_object = date.fromisoformat(date_object)
except ValueError:
logger.warning(f"Tried to convert invalid date string: {date_object}")
logger.warning('Tried to convert invalid date string: %s', date_object)
return None
# We may have already pre-cached the date format by calling this already!
user_date_format = context.get('user_date_format', None)
if user_date_format is None:
user = context.get('user', None)
if user and user.is_authenticated:
# User is specified - look for their date display preference
user_date_format = InvenTreeUserSetting.get_setting('DATE_DISPLAY_FORMAT', user=user)
user_date_format = common.models.InvenTreeUserSetting.get_setting(
'DATE_DISPLAY_FORMAT', user=user
)
else:
user_date_format = 'YYYY-MM-DD'
# Convert the format string to Pythonic equivalent
replacements = [
('YYYY', '%Y'),
('MMM', '%b'),
('MM', '%m'),
('DD', '%d'),
]
replacements = [('YYYY', '%Y'), ('MMM', '%b'), ('MM', '%m'), ('DD', '%d')]
for o, n in replacements:
user_date_format = user_date_format.replace(o, n)
@@ -102,9 +98,8 @@ def render_date(context, date_object):
@register.simple_tag
def render_currency(money, **kwargs):
"""Render a currency / Money object"""
return InvenTree.helpers.render_currency(money, **kwargs)
"""Render a currency / Money object."""
return InvenTree.helpers_model.render_currency(money, **kwargs)
@register.simple_tag()
@@ -127,7 +122,7 @@ def to_list(*args):
@register.simple_tag()
def part_allocation_count(build, part, *args, **kwargs):
"""Return the total number of <part> allocated to <build>"""
"""Return the total number of <part> allocated to <build>."""
return InvenTree.helpers.decimal2string(build.getAllocatedQuantity(part))
@@ -140,7 +135,7 @@ def inventree_in_debug_mode(*args, **kwargs):
@register.simple_tag()
def inventree_show_about(user, *args, **kwargs):
"""Return True if the about modal should be shown."""
if InvenTreeSetting.get_setting('INVENTREE_RESTRICT_ABOUT'):
if common.models.InvenTreeSetting.get_setting('INVENTREE_RESTRICT_ABOUT'):
# Return False if the user is not a superuser, or no user information is provided
if not user or not user.is_superuser:
return False
@@ -160,6 +155,12 @@ def plugins_enabled(*args, **kwargs):
return djangosettings.PLUGINS_ENABLED
@register.simple_tag()
def plugins_install_disabled(*args, **kwargs):
"""Return True if plugin install is disabled for the server instance."""
return djangosettings.PLUGINS_INSTALL_DISABLED
@register.simple_tag()
def plugins_info(*args, **kwargs):
"""Return information about activated plugins."""
@@ -171,24 +172,15 @@ def plugins_info(*args, **kwargs):
plug_list = [plg for plg in registry.plugins.values() if plg.plugin_config().active]
# Format list
return [
{
'name': plg.name,
'slug': plg.slug,
'version': plg.version
} for plg in plug_list
{'name': plg.name, 'slug': plg.slug, 'version': plg.version}
for plg in plug_list
]
@register.simple_tag()
def inventree_db_engine(*args, **kwargs):
"""Return the InvenTree database backend e.g. 'postgresql'."""
db = djangosettings.DATABASES['default']
engine = db.get('ENGINE', _('Unknown database'))
engine = engine.replace('django.db.backends.', '')
return engine
return version.inventreeDatabase() or _('Unknown database')
@register.simple_tag()
@@ -199,7 +191,7 @@ def inventree_instance_name(*args, **kwargs):
@register.simple_tag()
def inventree_title(*args, **kwargs):
"""Return the title for the current instance - respecting the settings"""
"""Return the title for the current instance - respecting the settings."""
return version.inventreeInstanceTitle()
@@ -209,55 +201,50 @@ def inventree_logo(**kwargs):
Returns a path to an image file, which can be rendered in the web interface
"""
return InvenTree.helpers.getLogoImage(**kwargs)
@register.simple_tag()
def inventree_splash(**kwargs):
"""Return the URL for the InvenTree splash screen, *or* a custom screen if the user has provided one."""
return InvenTree.helpers.getSplashScren(**kwargs)
return InvenTree.helpers.getSplashScreen(**kwargs)
@register.simple_tag()
def inventree_base_url(*args, **kwargs):
"""Return the INVENTREE_BASE_URL setting."""
return InvenTreeSetting.get_setting('INVENTREE_BASE_URL')
"""Return the base URL of the InvenTree server."""
return InvenTree.helpers_model.get_base_url()
@register.simple_tag()
def python_version(*args, **kwargs):
"""Return the current python version."""
return sys.version.split(' ')[0]
return version.inventreePythonVersion()
@register.simple_tag()
def inventree_version(shortstring=False, *args, **kwargs):
"""Return InvenTree version string."""
if shortstring:
return _("{title} v{version}".format(
title=version.inventreeInstanceTitle(),
version=version.inventreeVersion()
))
return f'{version.inventreeInstanceTitle()} v{version.inventreeVersion()}'
return version.inventreeVersion()
@register.simple_tag()
def inventree_is_development(*args, **kwargs):
"""Returns True if this is a development version of InvenTree"""
"""Returns True if this is a development version of InvenTree."""
return version.isInvenTreeDevelopmentVersion()
@register.simple_tag()
def inventree_is_release(*args, **kwargs):
"""Returns True if this is a release version of InvenTree"""
"""Returns True if this is a release version of InvenTree."""
return not version.isInvenTreeDevelopmentVersion()
@register.simple_tag()
def inventree_docs_version(*args, **kwargs):
"""Returns the InvenTree documentation version"""
"""Returns the InvenTree documentation version."""
return version.inventreeDocsVersion()
@@ -285,24 +272,52 @@ def inventree_commit_date(*args, **kwargs):
return version.inventreeCommitDate()
@register.simple_tag()
def inventree_installer(*args, **kwargs):
"""Return InvenTree package installer string."""
return version.inventreeInstaller()
@register.simple_tag()
def inventree_branch(*args, **kwargs):
"""Return InvenTree git branch string."""
return version.inventreeBranch()
@register.simple_tag()
def inventree_target(*args, **kwargs):
"""Return InvenTree target string."""
return version.inventreeTarget()
@register.simple_tag()
def inventree_platform(*args, **kwargs):
"""Return InvenTree platform string."""
return version.inventreePlatform()
@register.simple_tag()
def inventree_github_url(*args, **kwargs):
"""Return URL for InvenTree github site."""
return "https://github.com/InvenTree/InvenTree/"
return version.inventreeGithubUrl()
@register.simple_tag()
def inventree_docs_url(*args, **kwargs):
"""Return URL for InvenTree documenation site."""
tag = version.inventreeDocsVersion()
"""Return URL for InvenTree documentation site."""
return version.inventreeDocUrl()
return f"https://docs.inventree.org/en/{tag}"
@register.simple_tag()
def inventree_app_url(*args, **kwargs):
"""Return URL for InvenTree app site."""
return version.inventreeAppUrl()
@register.simple_tag()
def inventree_credits_url(*args, **kwargs):
"""Return URL for InvenTree credits site."""
return "https://docs.inventree.org/en/latest/credits/"
return version.inventreeCreditsUrl()
@register.simple_tag()
@@ -313,58 +328,70 @@ def default_currency(*args, **kwargs):
@register.simple_tag()
def setting_object(key, *args, **kwargs):
"""Return a setting object speciifed by the given key.
"""Return a setting object specified by the given key.
(Or return None if the setting does not exist)
if a user-setting was requested return that
"""
cache = kwargs.get('cache', True)
if 'plugin' in kwargs:
# Note, 'plugin' is an instance of an InvenTreePlugin class
plugin = kwargs['plugin']
plg = kwargs['plugin']
if issubclass(plg.__class__, InvenTreePlugin):
try:
plg = plg.plugin_config()
except plugin.models.PluginConfig.DoesNotExist:
return None
return PluginSetting.get_setting_object(key, plugin=plugin, cache=cache)
return plugin.models.PluginSetting.get_setting_object(
key, plugin=plg, cache=cache
)
elif 'method' in kwargs:
return NotificationUserSetting.get_setting_object(key, user=kwargs['user'], method=kwargs['method'], cache=cache)
return plugin.models.NotificationUserSetting.get_setting_object(
key, user=kwargs['user'], method=kwargs['method'], cache=cache
)
elif 'user' in kwargs:
return InvenTreeUserSetting.get_setting_object(key, user=kwargs['user'], cache=cache)
return common.models.InvenTreeUserSetting.get_setting_object(
key, user=kwargs['user'], cache=cache
)
else:
return InvenTreeSetting.get_setting_object(key, cache=cache)
return common.models.InvenTreeSetting.get_setting_object(key, cache=cache)
@register.simple_tag()
def settings_value(key, *args, **kwargs):
"""Return a settings value specified by the given key."""
if 'user' in kwargs:
if not kwargs['user'] or (kwargs['user'] and kwargs['user'].is_authenticated is False):
return InvenTreeUserSetting.get_setting(key)
return InvenTreeUserSetting.get_setting(key, user=kwargs['user'])
if not kwargs['user'] or (
kwargs['user'] and kwargs['user'].is_authenticated is False
):
return common.models.InvenTreeUserSetting.get_setting(key)
return common.models.InvenTreeUserSetting.get_setting(key, user=kwargs['user'])
return InvenTreeSetting.get_setting(key)
return common.models.InvenTreeSetting.get_setting(key)
@register.simple_tag()
def user_settings(user, *args, **kwargs):
"""Return all USER settings as a key:value dict."""
return InvenTreeUserSetting.allValues(user=user)
return common.models.InvenTreeUserSetting.allValues(user=user)
@register.simple_tag()
def global_settings(*args, **kwargs):
"""Return all GLOBAL InvenTree settings as a key:value dict."""
return InvenTreeSetting.allValues()
return common.models.InvenTreeSetting.allValues()
@register.simple_tag()
def visible_global_settings(*args, **kwargs):
"""Return any global settings which are not marked as 'hidden'."""
return InvenTreeSetting.allValues(exclude_hidden=True)
return common.models.InvenTreeSetting.allValues(exclude_hidden=True)
@register.simple_tag()
@@ -400,7 +427,7 @@ def progress_bar(val, max_val, *args, **kwargs):
style_tags.append(f'max-width: {max_width};')
html = f"""
<div id='{item_id}' class='progress' style='{" ".join(style_tags)}'>
<div id='{item_id}' class='progress' style='{' '.join(style_tags)}'>
<div class='progress-bar {style}' role='progressbar' aria-valuemin='0' aria-valuemax='100' style='width:{percent}%'></div>
<div class='progress-value'>{val} / {max_val}</div>
</div>
@@ -411,7 +438,7 @@ def progress_bar(val, max_val, *args, **kwargs):
@register.simple_tag()
def get_color_theme_css(username):
"""Return the cutsom theme .css file for the selected user"""
"""Return the custom theme .css file for the selected user."""
user_theme_name = get_user_color_theme(username)
# Build path to CSS sheet
inventree_css_sheet = os.path.join('css', 'color-themes', user_theme_name + '.css')
@@ -425,6 +452,8 @@ def get_color_theme_css(username):
@register.simple_tag()
def get_user_color_theme(username):
"""Get current user color theme."""
from common.models import ColorTheme
try:
user_theme = ColorTheme.objects.filter(user=username).get()
user_theme_name = user_theme.name
@@ -441,11 +470,10 @@ def get_available_themes(*args, **kwargs):
"""Return the available theme choices."""
themes = []
from common.models import ColorTheme
for key, name in ColorTheme.get_color_themes_choices():
themes.append({
'key': key,
'name': name
})
themes.append({'key': key, 'name': name})
return themes
@@ -462,20 +490,16 @@ def primitive_to_javascript(primitive):
elif type(primitive) in [int, float]:
return primitive
else:
# Wrap with quotes
return format_html("'{}'", primitive) # noqa: P103
# Wrap with quotes
return format_html("'{}'", primitive) # noqa: P103
@register.simple_tag()
def js_bool(val):
"""Return a javascript boolean value (true or false)"""
"""Return a javascript boolean value (true or false)."""
if val:
return 'true'
else:
return 'false'
return 'false'
@register.filter
@@ -488,17 +512,6 @@ def keyvalue(dict, key):
return dict.get(key)
@register.simple_tag()
def call_method(obj, method_name, *args):
"""Enables calling model methods / functions from templates with arguments.
Usage:
{% call_method model_object 'fnc_name' argument1 %}
"""
method = getattr(obj, method_name)
return method(*args)
@register.simple_tag()
def authorized_owners(group):
"""Return authorized owners."""
@@ -520,8 +533,11 @@ def authorized_owners(group):
@register.simple_tag()
def object_link(url_name, pk, ref):
"""Return highlighted link to object."""
ref_url = reverse(url_name, kwargs={'pk': pk})
return mark_safe(f'<b><a href="{ref_url}">{ref}</a></b>')
try:
ref_url = reverse(url_name, kwargs={'pk': pk})
return mark_safe(f'<b><a href="{ref_url}">{ref}</a></b>')
except NoReverseMatch:
return None
@register.simple_tag()
@@ -551,7 +567,6 @@ class I18nStaticNode(StaticNode):
self.original = self.path.var
if hasattr(context, 'request'):
# Convert the "requested" language code to a standard format
language_code = context.request.LANGUAGE_CODE.lower().strip()
language_code = language_code.replace('_', '-')
@@ -560,16 +575,11 @@ class I18nStaticNode(StaticNode):
# - First, try the original requested code, e.g. 'pt-br'
# - Next, try a simpler version of the code e.g. 'pt'
# - Finally, fall back to english
options = [
language_code,
language_code.split('-')[0],
'en',
]
options = [language_code, language_code.split('-')[0], 'en']
for lng in options:
lng_file = os.path.join(
djangosettings.STATIC_ROOT,
self.original.format(lng=lng)
djangosettings.STATIC_ROOT, self.original.format(lng=lng)
)
if os.path.exists(lng_file):
@@ -586,14 +596,14 @@ if settings.DEBUG:
@register.simple_tag()
def i18n_static(url_name):
"""Simple tag to enable {% url %} functionality instead of {% static %}"""
"""Simple tag to enable {% url %} functionality instead of {% static %}."""
return reverse(url_name)
else: # pragma: no cover
@register.tag('i18n_static')
def do_i18n_static(parser, token):
"""Overrides normal static, adds language - lookup for prerenderd files #1485
"""Overrides normal static, adds language - lookup for prerenderd files #1485.
Usage (like static):
{% i18n_static path [as varname] %}
@@ -601,8 +611,55 @@ else: # pragma: no cover
bits = token.split_contents()
loc_name = settings.STATICFILES_I18_PREFIX
# change path to called ressource
# change path to called resource
bits[1] = f"'{loc_name}/{{lng}}.{bits[1][1:-1]}'"
token.contents = ' '.join(bits)
return I18nStaticNode.handle_token(parser, token)
@register.simple_tag()
def admin_index(user):
"""Return a URL for the admin interface."""
if not djangosettings.INVENTREE_ADMIN_ENABLED:
return ''
if not user.is_staff:
return ''
return reverse('admin:index')
@register.simple_tag()
def admin_url(user, table, pk):
"""Generate a link to the admin site for the given model instance.
- If the admin site is disabled, an empty URL is returned
- If the user is not a staff user, an empty URL is returned
- If the user does not have the correct permission, an empty URL is returned
"""
app, model = table.strip().split('.')
from django.urls import reverse
if not djangosettings.INVENTREE_ADMIN_ENABLED:
return ''
if not user.is_staff:
return ''
# Check the user has the correct permission
perm_string = f'{app}.change_{model}'
if not user.has_perm(perm_string):
return ''
# Fallback URL
url = reverse(f'admin:{app}_{model}_changelist')
if pk:
try:
url = reverse(f'admin:{app}_{model}_change', args=(pk,))
except NoReverseMatch:
pass
return url

View File

@@ -0,0 +1,31 @@
"""This module provides template tags pertaining to SSO functionality."""
from django import template
import InvenTree.sso
register = template.Library()
@register.simple_tag()
def sso_login_enabled():
"""Return True if single-sign-on is enabled."""
return InvenTree.sso.login_enabled()
@register.simple_tag()
def sso_reg_enabled():
"""Return True if single-sign-on is enabled for self-registration."""
return InvenTree.sso.registration_enabled()
@register.simple_tag()
def sso_auto_enabled():
"""Return True if single-sign-on is enabled for auto-registration."""
return InvenTree.sso.auto_registration_enabled()
@register.simple_tag()
def sso_check_provider(provider):
"""Return True if the given provider is correctly configured."""
return InvenTree.sso.check_provider(provider)

View File

@@ -6,8 +6,7 @@ from django.urls import reverse
from rest_framework import status
from InvenTree.api_tester import InvenTreeAPITestCase
from InvenTree.helpers import InvenTreeTestCase
from InvenTree.unit_test import InvenTreeAPITestCase, InvenTreeTestCase
from users.models import RuleSet, update_group_roles
@@ -18,6 +17,7 @@ class HTMLAPITests(InvenTreeTestCase):
which raised an AssertionError when using the HTML API interface,
while the regular JSON interface continued to work as expected.
"""
roles = 'all'
def test_part_api(self):
@@ -25,7 +25,7 @@ class HTMLAPITests(InvenTreeTestCase):
url = reverse('api-part-list')
# Check JSON response
response = self.client.get(url, HTTP_ACCEPT='application/json')
response = self.client.get(url, headers={'accept': 'application/json'})
self.assertEqual(response.status_code, 200)
def test_build_api(self):
@@ -33,7 +33,7 @@ class HTMLAPITests(InvenTreeTestCase):
url = reverse('api-build-list')
# Check JSON response
response = self.client.get(url, HTTP_ACCEPT='application/json')
response = self.client.get(url, headers={'accept': 'application/json'})
self.assertEqual(response.status_code, 200)
def test_stock_api(self):
@@ -41,7 +41,7 @@ class HTMLAPITests(InvenTreeTestCase):
url = reverse('api-stock-list')
# Check JSON response
response = self.client.get(url, HTTP_ACCEPT='application/json')
response = self.client.get(url, headers={'accept': 'application/json'})
self.assertEqual(response.status_code, 200)
def test_company_list(self):
@@ -49,7 +49,7 @@ class HTMLAPITests(InvenTreeTestCase):
url = reverse('api-company-list')
# Check JSON response
response = self.client.get(url, HTTP_ACCEPT='application/json')
response = self.client.get(url, headers={'accept': 'application/json'})
self.assertEqual(response.status_code, 200)
def test_not_found(self):
@@ -61,12 +61,7 @@ class HTMLAPITests(InvenTreeTestCase):
class APITests(InvenTreeAPITestCase):
"""Tests for the InvenTree API."""
fixtures = [
'location',
'category',
'part',
'stock'
]
fixtures = ['location', 'category', 'part', 'stock']
token = None
auto_login = False
@@ -74,11 +69,11 @@ class APITests(InvenTreeAPITestCase):
"""Helper function to use basic auth."""
# Use basic authentication
authstring = bytes("{u}:{p}".format(u=self.username, p=self.password), "ascii")
authstring = bytes('{u}:{p}'.format(u=self.username, p=self.password), 'ascii')
# Use "basic" auth by default
auth = b64encode(authstring).decode("ascii")
self.client.credentials(HTTP_AUTHORIZATION="Basic {auth}".format(auth=auth))
auth = b64encode(authstring).decode('ascii')
self.client.credentials(HTTP_AUTHORIZATION='Basic {auth}'.format(auth=auth))
def tokenAuth(self):
"""Helper function to use token auth."""
@@ -262,49 +257,35 @@ class APITests(InvenTreeAPITestCase):
class BulkDeleteTests(InvenTreeAPITestCase):
"""Unit tests for the BulkDelete endpoints"""
"""Unit tests for the BulkDelete endpoints."""
superuser = True
def test_errors(self):
"""Test that the correct errors are thrown"""
"""Test that the correct errors are thrown."""
url = reverse('api-stock-test-result-list')
# DELETE without any of the required fields
response = self.delete(
url,
{},
expected_code=400
)
response = self.delete(url, {}, expected_code=400)
self.assertIn('List of items or filters must be provided for bulk deletion', str(response.data))
self.assertIn(
'List of items or filters must be provided for bulk deletion',
str(response.data),
)
# DELETE with invalid 'items'
response = self.delete(
url,
{
'items': {"hello": "world"},
},
expected_code=400,
)
response = self.delete(url, {'items': {'hello': 'world'}}, expected_code=400)
self.assertIn("'items' must be supplied as a list object", str(response.data))
# DELETE with invalid 'filters'
response = self.delete(
url,
{
'filters': [1, 2, 3],
},
expected_code=400,
)
response = self.delete(url, {'filters': [1, 2, 3]}, expected_code=400)
self.assertIn("'filters' must be supplied as a dict object", str(response.data))
class SearchTests(InvenTreeAPITestCase):
"""Unit tests for global search endpoint"""
"""Unit tests for global search endpoint."""
fixtures = [
'category',
@@ -317,18 +298,20 @@ class SearchTests(InvenTreeAPITestCase):
'sales_order',
]
def test_results(self):
"""Test individual result types"""
def test_empty(self):
"""Test empty request."""
data = ['', None, {}]
for d in data:
response = self.post(reverse('api-search'), d, expected_code=400)
self.assertIn('Search term must be provided', str(response.data))
def test_results(self):
"""Test individual result types."""
response = self.post(
reverse('api-search'),
{
'search': 'chair',
'limit': 3,
'part': {},
'build': {},
},
expected_code=200
{'search': 'chair', 'limit': 3, 'part': {}, 'build': {}},
expected_code=200,
)
# No build results
@@ -345,12 +328,7 @@ class SearchTests(InvenTreeAPITestCase):
# Search for orders
response = self.post(
reverse('api-search'),
{
'search': '01',
'limit': 2,
'purchaseorder': {},
'salesorder': {},
},
{'search': '01', 'limit': 2, 'purchaseorder': {}, 'salesorder': {}},
expected_code=200,
)
@@ -361,8 +339,7 @@ class SearchTests(InvenTreeAPITestCase):
self.assertNotIn('build', response.data)
def test_permissions(self):
"""Test that users with insufficient permissions are handled correctly"""
"""Test that users with insufficient permissions are handled correctly."""
# First, remove all roles
for ruleset in self.group.rule_sets.all():
ruleset.can_view = False
@@ -384,33 +361,25 @@ class SearchTests(InvenTreeAPITestCase):
'salesorder',
]
query = {
'search': 'c',
'limit': 3,
}
query = {'search': 'c', 'limit': 3}
for mdl in models:
query[mdl] = {}
response = self.post(
reverse('api-search'),
query,
expected_code=200
)
response = self.post(reverse('api-search'), query, expected_code=200)
# Check for 'permission denied' error
for mdl in models:
self.assertEqual(response.data[mdl]['error'], 'User does not have permission to view this model')
self.assertEqual(
response.data[mdl]['error'],
'User does not have permission to view this model',
)
# Assign view roles for some parts
self.assignRole('build.view')
self.assignRole('part.view')
response = self.post(
reverse('api-search'),
query,
expected_code=200
)
response = self.post(reverse('api-search'), query, expected_code=200)
# Check for expected results, based on permissions
# We expect results to be returned for the following model types
@@ -430,4 +399,6 @@ class SearchTests(InvenTreeAPITestCase):
self.assertIn('count', result)
else:
self.assertIn('error', result)
self.assertEqual(result['error'], 'User does not have permission to view this model')
self.assertEqual(
result['error'], 'User does not have permission to view this model'
)

View File

@@ -0,0 +1,45 @@
"""Tests for api_version."""
from django.urls import reverse
from InvenTree.api_version import INVENTREE_API_VERSION
from InvenTree.unit_test import InvenTreeAPITestCase
from InvenTree.version import inventreeApiText, parse_version_text
class ApiVersionTests(InvenTreeAPITestCase):
"""Tests for api_version functions and APIs."""
def test_api(self):
"""Test that the API text is correct."""
url = reverse('api-version-text')
response = self.client.get(url, format='json')
data = response.json()
self.assertEqual(len(data), 10)
response = self.client.get(reverse('api-version'), format='json').json()
self.assertIn('version', response)
self.assertIn('dev', response)
self.assertIn('up_to_date', response)
def test_inventree_api_text(self):
"""Test that the inventreeApiText function works expected."""
# Normal run
resp = inventreeApiText()
self.assertEqual(len(resp), 10)
# More responses
resp = inventreeApiText(20)
self.assertEqual(len(resp), 20)
# Specific version
resp = inventreeApiText(start_version=5)
self.assertEqual(list(resp)[0], 'v5')
def test_parse_version_text(self):
"""Test that api version text is correctly parsed."""
resp = parse_version_text()
# Check that all texts are parsed
self.assertEqual(len(resp), INVENTREE_API_VERSION - 1)

View File

@@ -2,20 +2,25 @@
from django.conf import settings
from django.http import Http404
from django.test import tag
from django.urls import reverse
from error_report.models import Error
from InvenTree.exceptions import log_error
from InvenTree.helpers import InvenTreeTestCase
from InvenTree.unit_test import InvenTreeTestCase
# TODO change test to not rely on CUI
@tag('cui')
class MiddlewareTests(InvenTreeTestCase):
"""Test for middleware functions."""
def check_path(self, url, code=200, **kwargs):
"""Helper function to run a request."""
response = self.client.get(url, HTTP_ACCEPT='application/json', **kwargs)
response = self.client.get(
url, headers={'accept': 'application/json'}, **kwargs
)
self.assertEqual(response.status_code, code)
return response
@@ -28,13 +33,13 @@ class MiddlewareTests(InvenTreeTestCase):
self.client.logout()
# check that static files go through
# TODO @matmair reenable this check
# TODO @matmair re-enable this check
# self.check_path('/static/css/inventree.css', 302)
# check that account things go through
self.check_path(reverse('account_login'))
# logout goes diretly to login
# logout goes directly to login
self.check_path(reverse('account_logout'))
# check that frontend code is redirected to login
@@ -66,6 +71,7 @@ class MiddlewareTests(InvenTreeTestCase):
def test_error_exceptions(self):
"""Test that ignored errors are not logged."""
def check(excpected_nbr=0):
# Check that errors are empty
errors = Error.objects.all()

View File

@@ -67,16 +67,24 @@ class InvenTreeTaskTests(TestCase):
# Error runs
# Malformed taskname
with self.assertWarnsMessage(UserWarning, "WARNING: 'InvenTree' not started - Malformed function path"):
with self.assertWarnsMessage(
UserWarning, "WARNING: 'InvenTree' not started - Malformed function path"
):
InvenTree.tasks.offload_task('InvenTree')
# Non exsistent app
with self.assertWarnsMessage(UserWarning, "WARNING: 'InvenTreeABC.test_tasks.doesnotmatter' not started - No module named 'InvenTreeABC.test_tasks'"):
# Non existent app
with self.assertWarnsMessage(
UserWarning,
"WARNING: 'InvenTreeABC.test_tasks.doesnotmatter' not started - No module named 'InvenTreeABC.test_tasks'",
):
InvenTree.tasks.offload_task('InvenTreeABC.test_tasks.doesnotmatter')
# Non exsistent function
with self.assertWarnsMessage(UserWarning, "WARNING: 'InvenTree.test_tasks.doesnotexsist' not started - No function named 'doesnotexsist'"):
InvenTree.tasks.offload_task('InvenTree.test_tasks.doesnotexsist')
# Non existent function
with self.assertWarnsMessage(
UserWarning,
"WARNING: 'InvenTree.test_tasks.doesnotexist' not started - No function named 'doesnotexist'",
):
InvenTree.tasks.offload_task('InvenTree.test_tasks.doesnotexist')
def test_task_hearbeat(self):
"""Test the task heartbeat."""
@@ -86,7 +94,9 @@ class InvenTreeTaskTests(TestCase):
"""Test the task delete_successful_tasks."""
from django_q.models import Success
Success.objects.create(name='abc', func='abc', stopped=threshold, started=threshold_low)
Success.objects.create(
name='abc', func='abc', stopped=threshold, started=threshold_low
)
InvenTree.tasks.offload_task(InvenTree.tasks.delete_successful_tasks)
results = Success.objects.filter(started__lte=threshold)
self.assertEqual(len(results), 0)
@@ -99,14 +109,14 @@ class InvenTreeTaskTests(TestCase):
error_obj.save()
# Check that it is not empty
errors = Error.objects.filter(when__lte=threshold,)
errors = Error.objects.filter(when__lte=threshold)
self.assertNotEqual(len(errors), 0)
# Run action
InvenTree.tasks.offload_task(InvenTree.tasks.delete_old_error_logs)
# Check that it is empty again
errors = Error.objects.filter(when__lte=threshold,)
errors = Error.objects.filter(when__lte=threshold)
self.assertEqual(len(errors), 0)
def test_task_check_for_updates(self):
@@ -146,7 +156,9 @@ class InvenTreeTaskTests(TestCase):
# Cleanup
try:
migration_name = InvenTree.tasks.get_migration_plan()[0][0].name + '.py'
migration_path = settings.BASE_DIR / 'InvenTree' / 'migrations' / migration_name
migration_path = (
settings.BASE_DIR / 'InvenTree' / 'migrations' / migration_name
)
migration_path.unlink()
except IndexError: # pragma: no cover
pass

View File

@@ -4,28 +4,29 @@ import os
import re
from pathlib import Path
from django.test import TestCase
from django.test import TestCase, tag
from django.urls import reverse
@tag('cui')
class URLTest(TestCase):
"""Test all files for broken url tags."""
# Need fixture data in the database
fixtures = [
'settings',
'build',
'company',
'manufacturer_part',
'price_breaks',
'supplier_part',
'order',
'sales_order',
'bom',
'category',
'params',
'part_pricebreaks',
'part',
'bom',
'build',
'test_templates',
'location',
'stock_tests',
@@ -44,7 +45,7 @@ class URLTest(TestCase):
('part', 'templates'),
('report', 'templates'),
('stock', 'templates'),
('templates', ),
('templates',),
]
template_files = []
@@ -53,11 +54,9 @@ class URLTest(TestCase):
tld = os.path.join(here, '..')
for directory in template_dirs:
template_dir = os.path.join(tld, *directory)
for path in Path(template_dir).rglob(suffix):
f = os.path.abspath(path)
if f not in template_files:
@@ -69,25 +68,18 @@ class URLTest(TestCase):
"""Search for all instances of {% url %} in supplied template file."""
urls = []
pattern = "{% url ['\"]([^'\"]+)['\"]([^%]*)%}"
pattern = '{% url [\'"]([^\'"]+)[\'"]([^%]*)%}'
with open(input_file, 'r') as f:
data = f.read()
results = re.findall(pattern, data)
for result in results:
if len(result) == 2:
urls.append([
result[0].strip(),
result[1].strip()
])
urls.append([result[0].strip(), result[1].strip()])
elif len(result) == 1: # pragma: no cover
urls.append([
result[0].strip(),
''
])
urls.append([result[0].strip(), ''])
return urls
@@ -100,16 +92,16 @@ class URLTest(TestCase):
pk = None
# TODO: Handle reverse lookup of admin URLs!
if url.startswith("admin:"):
if url.startswith('admin:'):
return
# TODO can this be more elegant?
if url.startswith("account_"):
if url.startswith('account_'):
return
if pk:
# We will assume that there is at least one item in the database
reverse(url, kwargs={"pk": 1})
reverse(url, kwargs={'pk': 1})
else:
reverse(url)
@@ -122,14 +114,14 @@ class URLTest(TestCase):
def test_html_templates(self):
"""Test all HTML templates for broken url tags."""
template_files = self.find_files("*.html")
template_files = self.find_files('*.html')
for f in template_files:
self.check_file(f)
def test_js_templates(self):
"""Test all JS templates for broken url tags."""
template_files = self.find_files("*.js")
template_files = self.find_files('*.js')
for f in template_files:
self.check_file(f)

View File

@@ -3,9 +3,10 @@
import os
from django.contrib.auth import get_user_model
from django.test import tag
from django.urls import reverse
from InvenTree.helpers import InvenTreeTestCase
from InvenTree.unit_test import InvenTreeTestCase
class ViewTests(InvenTreeTestCase):
@@ -23,18 +24,19 @@ class ViewTests(InvenTreeTestCase):
def test_index_redirect(self):
"""Top-level URL should redirect to "index" page."""
response = self.client.get("/")
response = self.client.get('/')
self.assertEqual(response.status_code, 302)
def get_index_page(self):
"""Retrieve the index page (used for subsequent unit tests)"""
response = self.client.get("/index/")
"""Retrieve the index page (used for subsequent unit tests)."""
response = self.client.get('/index/')
self.assertEqual(response.status_code, 200)
return str(response.content.decode())
@tag('cui')
def test_panels(self):
"""Test that the required 'panels' are present."""
content = self.get_index_page()
@@ -43,9 +45,9 @@ class ViewTests(InvenTreeTestCase):
# TODO: In future, run the javascript and ensure that the panels get created!
@tag('cui')
def test_settings_page(self):
"""Test that the 'settings' page loads correctly"""
"""Test that the 'settings' page loads correctly."""
# Settings page loads
url = reverse('settings')
@@ -61,35 +63,20 @@ class ViewTests(InvenTreeTestCase):
self.assertEqual(response.status_code, 200)
content = response.content.decode()
user_panels = [
'account',
'user-display',
'user-home',
'user-reports',
]
user_panels = ['account', 'user-display', 'user-home', 'user-reports']
staff_panels = [
'server',
'login',
'barcodes',
'pricing',
'parts',
'stock',
]
staff_panels = ['server', 'login', 'barcodes', 'pricing', 'parts', 'stock']
plugin_panels = [
'plugin',
]
plugin_panels = ['plugin']
# Default user has staff access, so all panels will be present
for panel in user_panels + staff_panels + plugin_panels:
self.assertIn(f"select-{panel}", content)
self.assertIn(f"panel-{panel}", content)
self.assertIn(f'select-{panel}', content)
self.assertIn(f'panel-{panel}', content)
# Now create a user who does not have staff access
pleb_user = get_user_model().objects.create_user(
username='pleb',
password='notstaff',
username='pleb', password='notstaff'
)
pleb_user.groups.add(self.group)
@@ -99,10 +86,7 @@ class ViewTests(InvenTreeTestCase):
self.client.logout()
result = self.client.login(
username='pleb',
password='notstaff',
)
result = self.client.login(username='pleb', password='notstaff')
self.assertTrue(result)
@@ -112,23 +96,26 @@ class ViewTests(InvenTreeTestCase):
# Normal user still has access to user-specific panels
for panel in user_panels:
self.assertIn(f"select-{panel}", content)
self.assertIn(f"panel-{panel}", content)
self.assertIn(f'select-{panel}', content)
self.assertIn(f'panel-{panel}', content)
# Normal user does NOT have access to global or plugin settings
for panel in staff_panels + plugin_panels:
self.assertNotIn(f"select-{panel}", content)
self.assertNotIn(f"panel-{panel}", content)
self.assertNotIn(f'select-{panel}', content)
self.assertNotIn(f'panel-{panel}', content)
# TODO: Replace this with a PUI test
@tag('cui')
def test_url_login(self):
"""Test logging in via arguments"""
"""Test logging in via arguments."""
# Log out
self.client.logout()
response = self.client.get("/index/")
response = self.client.get('/index/')
self.assertEqual(response.status_code, 302)
# Try login with url
response = self.client.get(f"/accounts/login/?next=/&login={self.username}&password={self.password}")
response = self.client.get(
f'/accounts/login/?next=/&login={self.username}&password={self.password}'
)
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, '/')

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,146 @@
"""OpenTelemetry setup functions."""
import base64
import logging
from typing import Optional
from opentelemetry import metrics, trace
from opentelemetry.instrumentation.django import DjangoInstrumentor
from opentelemetry.instrumentation.redis import RedisInstrumentor
from opentelemetry.instrumentation.requests import RequestsInstrumentor
from opentelemetry.sdk import _logs as logs
from opentelemetry.sdk import resources
from opentelemetry.sdk._logs import export as logs_export
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import (
ConsoleMetricExporter,
PeriodicExportingMetricReader,
)
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter
import InvenTree.ready
from InvenTree.version import inventreeVersion
# Logger configuration
logger = logging.getLogger('inventree')
def setup_tracing(
endpoint: str,
headers: dict,
resources_input: Optional[dict] = None,
console: bool = False,
auth: Optional[dict] = None,
is_http: bool = False,
append_http: bool = True,
):
"""Set up tracing for the application in the current context.
Args:
endpoint: The endpoint to send the traces to.
headers: The headers to send with the traces.
resources_input: The resources to send with the traces.
console: Whether to output the traces to the console.
"""
if InvenTree.ready.isImportingData() or InvenTree.ready.isRunningMigrations():
return
if resources_input is None:
resources_input = {}
if auth is None:
auth = {}
# Setup the auth headers
if 'basic' in auth:
basic_auth = auth['basic']
if 'username' in basic_auth and 'password' in basic_auth:
auth_raw = f'{basic_auth["username"]}:{basic_auth["password"]}'
auth_token = base64.b64encode(auth_raw.encode('utf-8')).decode('utf-8')
headers['Authorization'] = f'Basic {auth_token}'
else:
logger.warning('Basic auth is missing username or password')
# Clean up headers
headers = {k: v for k, v in headers.items() if v is not None}
# Initialize the OTLP Resource
resource = resources.Resource(
attributes={
resources.SERVICE_NAME: 'BACKEND',
resources.SERVICE_NAMESPACE: 'INVENTREE',
resources.SERVICE_VERSION: inventreeVersion(),
**resources_input,
}
)
# Import the OTLP exporters
if is_http:
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
from opentelemetry.exporter.otlp.proto.http.metric_exporter import (
OTLPMetricExporter,
)
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
OTLPSpanExporter,
)
else:
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import (
OTLPMetricExporter,
)
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
OTLPSpanExporter,
)
# Spans / Tracs
span_exporter = OTLPSpanExporter(
headers=headers,
endpoint=endpoint if not (is_http and append_http) else f'{endpoint}/v1/traces',
)
trace_processor = BatchSpanProcessor(span_exporter)
trace_provider = TracerProvider(resource=resource)
trace.set_tracer_provider(trace_provider)
trace_provider.add_span_processor(trace_processor)
# For debugging purposes, export the traces to the console
if console:
trace_provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))
# Metrics
metric_perodic_reader = PeriodicExportingMetricReader(
OTLPMetricExporter(
headers=headers,
endpoint=endpoint
if not (is_http and append_http)
else f'{endpoint}/v1/metrics',
)
)
metric_readers = [metric_perodic_reader]
# For debugging purposes, export the metrics to the console
if console:
console_metric_exporter = ConsoleMetricExporter()
console_metric_reader = PeriodicExportingMetricReader(console_metric_exporter)
metric_readers.append(console_metric_reader)
meter_provider = MeterProvider(resource=resource, metric_readers=metric_readers)
metrics.set_meter_provider(meter_provider)
# Logs
log_exporter = OTLPLogExporter(
headers=headers,
endpoint=endpoint if not (is_http and append_http) else f'{endpoint}/v1/logs',
)
log_provider = logs.LoggerProvider(resource=resource)
log_provider.add_log_record_processor(
logs_export.BatchLogRecordProcessor(log_exporter)
)
handler = logs.LoggingHandler(level=logging.INFO, logger_provider=log_provider)
logger = logging.getLogger('inventree')
logger.addHandler(handler)
def setup_instruments():
"""Run auto-insturmentation for OpenTelemetry tracing."""
DjangoInstrumentor().instrument()
RedisInstrumentor().instrument()
RequestsInstrumentor().instrument()

View File

@@ -0,0 +1,49 @@
"""Translation helper functions."""
import json
from django.conf import settings
# translation completion stats
_translation_stats = None
def reload_translation_stats():
"""Reload the translation stats from the compiled file."""
global _translation_stats
STATS_FILE = settings.BASE_DIR.joinpath('InvenTree/locale_stats.json').absolute()
try:
with open(STATS_FILE, 'r') as f:
_translation_stats = json.load(f)
except Exception:
_translation_stats = None
return
keys = _translation_stats.keys()
# Note that the names used in the stats file may not align 100%
for code, _lang in settings.LANGUAGES:
if code in keys:
# Direct match, move on
continue
code_lower = code.lower().replace('-', '_')
for k in keys:
if k.lower() == code_lower:
# Make a copy of the code which matches
_translation_stats[code] = _translation_stats[k]
break
def get_translation_percent(lang_code):
"""Return the translation percentage for the given language code."""
if _translation_stats is None:
reload_translation_stats()
if _translation_stats is None:
return 0
return _translation_stats.get(lang_code, 0)

View File

@@ -1,12 +1,18 @@
"""Helper functions for performing API unit tests."""
"""Helper functions for unit testing / CI."""
import csv
import io
import json
import re
from contextlib import contextmanager
from pathlib import Path
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.contrib.auth.models import Group, Permission
from django.db import connections
from django.http.response import StreamingHttpResponse
from django.test import TestCase
from django.test.utils import CaptureQueriesContext
from djmoney.contrib.exchange.models import ExchangeBackend, Rate
from rest_framework.test import APITestCase
@@ -15,6 +21,74 @@ from plugin import registry
from plugin.models import PluginConfig
def addUserPermission(user, permission):
"""Shortcut function for adding a certain permission to a user."""
perm = Permission.objects.get(codename=permission)
user.user_permissions.add(perm)
def addUserPermissions(user, permissions):
"""Shortcut function for adding multiple permissions to a user."""
for permission in permissions:
addUserPermission(user, permission)
def getMigrationFileNames(app):
"""Return a list of all migration filenames for provided app."""
local_dir = Path(__file__).parent
files = local_dir.joinpath('..', app, 'migrations').iterdir()
# Regex pattern for migration files
regex = re.compile(r'^[\d]+_.*\.py$')
migration_files = []
for f in files:
if regex.match(f.name):
migration_files.append(f.name)
return migration_files
def getOldestMigrationFile(app, exclude_extension=True, ignore_initial=True):
"""Return the filename associated with the oldest migration."""
oldest_num = -1
oldest_file = None
for f in getMigrationFileNames(app):
if ignore_initial and f.startswith('0001_initial'):
continue
num = int(f.split('_')[0])
if oldest_file is None or num < oldest_num:
oldest_num = num
oldest_file = f
if exclude_extension:
oldest_file = oldest_file.replace('.py', '')
return oldest_file
def getNewestMigrationFile(app, exclude_extension=True):
"""Return the filename associated with the newest migration."""
newest_file = None
newest_num = -1
for f in getMigrationFileNames(app):
num = int(f.split('_')[0])
if newest_file is None or num > newest_num:
newest_num = num
newest_file = f
if exclude_extension:
newest_file = newest_file.replace('.py', '')
return newest_file
class UserMixin:
"""Mixin to setup a user and login for tests.
@@ -35,14 +109,12 @@ class UserMixin:
@classmethod
def setUpTestData(cls):
"""Run setup for all tests in a given class"""
"""Run setup for all tests in a given class."""
super().setUpTestData()
# Create a user to log in with
cls.user = get_user_model().objects.create_user(
username=cls.username,
password=cls.password,
email=cls.email
username=cls.username, password=cls.password, email=cls.email
)
# Create a group for the user
@@ -67,8 +139,7 @@ class UserMixin:
cls.assignRole(role=role, group=cls.group)
def setUp(self):
"""Run setup for individual test methods"""
"""Run setup for individual test methods."""
if self.auto_login:
self.client.login(username=self.username, password=self.password)
@@ -81,7 +152,6 @@ class UserMixin:
assign_all: Set to True to assign *all* roles
group: The group to assign roles to (or leave None to use the group assigned to this class)
"""
if group is None:
group = cls.group
@@ -90,15 +160,15 @@ class UserMixin:
raise TypeError('assignRole: assign_all must be a boolean value')
if not role and not assign_all:
raise ValueError('assignRole: either role must be provided, or assign_all must be set')
raise ValueError(
'assignRole: either role must be provided, or assign_all must be set'
)
if not assign_all and role:
rule, perm = role.split('.')
for ruleset in group.rule_sets.all():
if assign_all or ruleset.name == rule:
if assign_all or perm == 'view':
ruleset.can_view = True
elif assign_all or perm == 'change':
@@ -128,53 +198,70 @@ class PluginMixin:
class ExchangeRateMixin:
"""Mixin class for generating exchange rate data"""
"""Mixin class for generating exchange rate data."""
def generate_exchange_rates(self):
"""Helper function which generates some exchange rates to work with"""
rates = {
'AUD': 1.5,
'CAD': 1.7,
'GBP': 0.9,
'USD': 1.0,
}
"""Helper function which generates some exchange rates to work with."""
rates = {'AUD': 1.5, 'CAD': 1.7, 'GBP': 0.9, 'USD': 1.0}
# Create a dummy backend
ExchangeBackend.objects.create(
name='InvenTreeExchange',
base_currency='USD',
)
ExchangeBackend.objects.create(name='InvenTreeExchange', base_currency='USD')
backend = ExchangeBackend.objects.get(name='InvenTreeExchange')
items = []
for currency, rate in rates.items():
items.append(
Rate(
currency=currency,
value=rate,
backend=backend,
)
)
items.append(Rate(currency=currency, value=rate, backend=backend))
Rate.objects.bulk_create(items)
class InvenTreeTestCase(ExchangeRateMixin, UserMixin, TestCase):
"""Testcase with user setup buildin."""
pass
class InvenTreeAPITestCase(ExchangeRateMixin, UserMixin, APITestCase):
"""Base class for running InvenTree API tests."""
def checkResponse(self, url, method, expected_code, response):
"""Debug output for an unexpected response"""
@contextmanager
def assertNumQueriesLessThan(
self, value, using='default', verbose=False, debug=False
):
"""Context manager to check that the number of queries is less than a certain value.
Example:
with self.assertNumQueriesLessThan(10):
# Do some stuff
Ref: https://stackoverflow.com/questions/1254170/django-is-there-a-way-to-count-sql-queries-from-an-unit-test/59089020#59089020
"""
with CaptureQueriesContext(connections[using]) as context:
yield # your test will be run here
if verbose:
msg = '\r\n%s' % json.dumps(context.captured_queries, indent=4)
else:
msg = None
n = len(context.captured_queries)
if debug:
print(f'Expected less than {value} queries, got {n} queries')
self.assertLess(n, value, msg=msg)
def checkResponse(self, url, method, expected_code, response):
"""Debug output for an unexpected response."""
# No expected code, return
if expected_code is None:
return
if expected_code != response.status_code:
print(f"Unexpected {method} response at '{url}': status_code = {response.status_code}")
print(
f"Unexpected {method} response at '{url}': status_code = {response.status_code}"
)
if hasattr(response, 'data'):
print('data:', response.data)
@@ -200,77 +287,78 @@ class InvenTreeAPITestCase(ExchangeRateMixin, UserMixin, APITestCase):
return actions
def get(self, url, data=None, expected_code=200, format='json'):
def get(self, url, data=None, expected_code=200, format='json', **kwargs):
"""Issue a GET request."""
# Set default - see B006
if data is None:
data = {}
response = self.client.get(url, data, format=format)
response = self.client.get(url, data, format=format, **kwargs)
self.checkResponse(url, 'GET', expected_code, response)
return response
def post(self, url, data=None, expected_code=None, format='json'):
def post(self, url, data=None, expected_code=None, format='json', **kwargs):
"""Issue a POST request."""
# Set default value - see B006
if data is None:
data = {}
response = self.client.post(url, data=data, format=format)
response = self.client.post(url, data=data, format=format, **kwargs)
self.checkResponse(url, 'POST', expected_code, response)
return response
def delete(self, url, data=None, expected_code=None, format='json'):
def delete(self, url, data=None, expected_code=None, format='json', **kwargs):
"""Issue a DELETE request."""
if data is None:
data = {}
response = self.client.delete(url, data=data, format=format)
response = self.client.delete(url, data=data, format=format, **kwargs)
self.checkResponse(url, 'DELETE', expected_code, response)
return response
def patch(self, url, data, expected_code=None, format='json'):
def patch(self, url, data, expected_code=None, format='json', **kwargs):
"""Issue a PATCH request."""
response = self.client.patch(url, data=data, format=format)
response = self.client.patch(url, data=data, format=format, **kwargs)
self.checkResponse(url, 'PATCH', expected_code, response)
return response
def put(self, url, data, expected_code=None, format='json'):
def put(self, url, data, expected_code=None, format='json', **kwargs):
"""Issue a PUT request."""
response = self.client.put(url, data=data, format=format)
response = self.client.put(url, data=data, format=format, **kwargs)
self.checkResponse(url, 'PUT', expected_code, response)
return response
def options(self, url, expected_code=None):
def options(self, url, expected_code=None, **kwargs):
"""Issue an OPTIONS request."""
response = self.client.options(url, format='json')
response = self.client.options(url, format='json', **kwargs)
self.checkResponse(url, 'OPTIONS', expected_code, response)
return response
def download_file(self, url, data, expected_code=None, expected_fn=None, decode=True):
def download_file(
self, url, data, expected_code=None, expected_fn=None, decode=True
):
"""Download a file from the server, and return an in-memory file."""
response = self.client.get(url, data=data, format='json')
if expected_code is not None:
self.assertEqual(response.status_code, expected_code)
self.checkResponse(url, 'DOWNLOAD_FILE', expected_code, response)
# Check that the response is of the correct type
if not isinstance(response, StreamingHttpResponse):
raise ValueError("Response is not a StreamingHttpResponse object as expected")
raise ValueError(
'Response is not a StreamingHttpResponse object as expected'
)
# Extract filename
disposition = response.headers['Content-Disposition']
@@ -284,27 +372,34 @@ class InvenTreeAPITestCase(ExchangeRateMixin, UserMixin, APITestCase):
if decode:
# Decode data and return as StringIO file object
fo = io.StringIO()
fo.name = fo
fo.write(response.getvalue().decode('UTF-8'))
file = io.StringIO()
file.name = file
file.write(response.getvalue().decode('UTF-8'))
else:
# Return a a BytesIO file object
fo = io.BytesIO()
fo.name = fn
fo.write(response.getvalue())
file = io.BytesIO()
file.name = fn
file.write(response.getvalue())
fo.seek(0)
file.seek(0)
return fo
return file
def process_csv(self, fo, delimiter=',', required_cols=None, excluded_cols=None, required_rows=None):
def process_csv(
self,
file_object,
delimiter=',',
required_cols=None,
excluded_cols=None,
required_rows=None,
):
"""Helper function to process and validate a downloaded csv file."""
# Check that the correct object type has been passed
self.assertTrue(isinstance(fo, io.StringIO))
self.assertTrue(isinstance(file_object, io.StringIO))
fo.seek(0)
file_object.seek(0)
reader = csv.reader(fo, delimiter=delimiter)
reader = csv.reader(file_object, delimiter=delimiter)
headers = []
rows = []

View File

@@ -7,193 +7,454 @@ from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import include, path, re_path
from django.views.decorators.csrf import csrf_exempt
from django.views.generic.base import RedirectView
from rest_framework.documentation import include_docs_urls
from dj_rest_auth.registration.views import (
ConfirmEmailView,
SocialAccountDisconnectView,
SocialAccountListView,
)
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView
from sesame.views import LoginView
from build.api import build_api_urls
import build.api
import common.api
import company.api
import label.api
import machine.api
import order.api
import part.api
import plugin.api
import report.api
import stock.api
import users.api
from build.urls import build_urls
from common.api import admin_api_urls, common_api_urls, settings_api_urls
from common.urls import common_urls
from company.api import company_api_urls
from company.urls import (company_urls, manufacturer_part_urls,
supplier_part_urls)
from label.api import label_api_urls
from order.api import order_api_urls
from company.urls import company_urls, manufacturer_part_urls, supplier_part_urls
from order.urls import order_urls
from part.api import bom_api_urls, part_api_urls
from part.urls import part_urls
from plugin.api import plugin_api_urls
from plugin.urls import get_plugin_urls
from report.api import report_api_urls
from stock.api import stock_api_urls
from stock.urls import stock_urls
from users.api import user_urls
from web.urls import api_urls as web_api_urls
from web.urls import urlpatterns as platform_urls
from .api import APISearchView, InfoView, NotFoundView
from .views import (AboutView, AppearanceSelectView, CustomConnectionsView,
CustomEmailView, CustomLoginView,
CustomPasswordResetFromKeyView,
CustomSessionDeleteOtherView, CustomSessionDeleteView,
CustomTwoFactorRemove, DatabaseStatsView, DynamicJsView,
EditUserView, IndexView, NotificationsView, SearchView,
SetPasswordView, SettingsView, auth_request)
from .api import APISearchView, InfoView, NotFoundView, VersionTextView, VersionView
from .magic_login import GetSimpleLoginView
from .social_auth_urls import (
EmailListView,
EmailPrimaryView,
EmailRemoveView,
EmailVerifyView,
SocialProviderListView,
social_auth_urlpatterns,
)
from .views import (
AboutView,
AppearanceSelectView,
CustomConnectionsView,
CustomEmailView,
CustomLoginView,
CustomPasswordResetFromKeyView,
CustomSessionDeleteOtherView,
CustomSessionDeleteView,
DatabaseStatsView,
DynamicJsView,
EditUserView,
IndexView,
NotificationsView,
SearchView,
SetPasswordView,
SettingsView,
auth_request,
)
admin.site.site_header = "InvenTree Admin"
admin.site.site_header = 'InvenTree Admin'
apipatterns = [
# Global search
path('search/', APISearchView.as_view(), name='api-search'),
re_path(r'^settings/', include(settings_api_urls)),
re_path(r'^part/', include(part_api_urls)),
re_path(r'^bom/', include(bom_api_urls)),
re_path(r'^company/', include(company_api_urls)),
re_path(r'^stock/', include(stock_api_urls)),
re_path(r'^build/', include(build_api_urls)),
re_path(r'^order/', include(order_api_urls)),
re_path(r'^label/', include(label_api_urls)),
re_path(r'^report/', include(report_api_urls)),
re_path(r'^user/', include(user_urls)),
re_path(r'^admin/', include(admin_api_urls)),
path('settings/', include(common.api.settings_api_urls)),
path('part/', include(part.api.part_api_urls)),
path('bom/', include(part.api.bom_api_urls)),
path('company/', include(company.api.company_api_urls)),
path('stock/', include(stock.api.stock_api_urls)),
path('build/', include(build.api.build_api_urls)),
path('order/', include(order.api.order_api_urls)),
path('label/', include(label.api.label_api_urls)),
path('report/', include(report.api.report_api_urls)),
path('machine/', include(machine.api.machine_api_urls)),
path('user/', include(users.api.user_urls)),
path('admin/', include(common.api.admin_api_urls)),
path('web/', include(web_api_urls)),
# Plugin endpoints
path('', include(plugin_api_urls)),
# Webhook endpoints
path('', include(common_api_urls)),
# InvenTree information endpoint
path('', InfoView.as_view(), name='api-inventree-info'),
path('', include(plugin.api.plugin_api_urls)),
# Common endpoints endpoint
path('', include(common.api.common_api_urls)),
# OpenAPI Schema
path(
'schema/',
SpectacularAPIView.as_view(custom_settings={'SCHEMA_PATH_PREFIX': '/api/'}),
name='schema',
),
# InvenTree information endpoints
path(
'version-text', VersionTextView.as_view(), name='api-version-text'
), # version text
path('version/', VersionView.as_view(), name='api-version'), # version info
path('', InfoView.as_view(), name='api-inventree-info'), # server info
# Auth API endpoints
path(
'auth/',
include([
re_path(
r'^registration/account-confirm-email/(?P<key>[-:\w]+)/$',
ConfirmEmailView.as_view(),
name='account_confirm_email',
),
path('registration/', include('dj_rest_auth.registration.urls')),
path(
'providers/', SocialProviderListView.as_view(), name='social_providers'
),
path(
'emails/',
include([
path(
'<int:pk>/',
include([
path(
'primary/',
EmailPrimaryView.as_view(),
name='email-primary',
),
path(
'verify/',
EmailVerifyView.as_view(),
name='email-verify',
),
path(
'remove/',
EmailRemoveView().as_view(),
name='email-remove',
),
]),
),
path('', EmailListView.as_view(), name='email-list'),
]),
),
path('social/', include(social_auth_urlpatterns)),
path(
'social/', SocialAccountListView.as_view(), name='social_account_list'
),
path(
'social/<int:pk>/disconnect/',
SocialAccountDisconnectView.as_view(),
name='social_account_disconnect',
),
path('logout/', users.api.Logout.as_view(), name='api-logout'),
path(
'login-redirect/',
users.api.LoginRedirect.as_view(),
name='api-login-redirect',
),
path('', include('dj_rest_auth.urls')),
]),
),
# Magic login URLs
path(
'email/generate/',
csrf_exempt(GetSimpleLoginView().as_view()),
name='sesame-generate',
),
path('email/login/', LoginView.as_view(), name='sesame-login'),
# Unknown endpoint
re_path(r'^.*$', NotFoundView.as_view(), name='api-404'),
]
settings_urls = [
re_path(r'^i18n/?', include('django.conf.urls.i18n')),
re_path(r'^appearance/?', AppearanceSelectView.as_view(), name='settings-appearance'),
path('i18n/', include('django.conf.urls.i18n')),
path('appearance/', AppearanceSelectView.as_view(), name='settings-appearance'),
# Catch any other urls
re_path(r'^.*$', SettingsView.as_view(template_name='InvenTree/settings/settings.html'), name='settings'),
path(
'',
SettingsView.as_view(template_name='InvenTree/settings/settings.html'),
name='settings',
),
]
notifications_urls = [
# Catch any other urls
re_path(r'^.*$', NotificationsView.as_view(), name='notifications'),
path('', NotificationsView.as_view(), name='notifications')
]
# These javascript files are served "dynamically" - i.e. rendered on demand
dynamic_javascript_urls = [
re_path(r'^calendar.js', DynamicJsView.as_view(template_name='js/dynamic/calendar.js'), name='calendar.js'),
re_path(r'^nav.js', DynamicJsView.as_view(template_name='js/dynamic/nav.js'), name='nav.js'),
re_path(r'^settings.js', DynamicJsView.as_view(template_name='js/dynamic/settings.js'), name='settings.js'),
path(
'calendar.js',
DynamicJsView.as_view(template_name='js/dynamic/calendar.js'),
name='calendar.js',
),
path(
'nav.js',
DynamicJsView.as_view(template_name='js/dynamic/nav.js'),
name='nav.js',
),
path(
'permissions.js',
DynamicJsView.as_view(template_name='js/dynamic/permissions.js'),
name='permissions.js',
),
path(
'settings.js',
DynamicJsView.as_view(template_name='js/dynamic/settings.js'),
name='settings.js',
),
]
# These javascript files are pased through the Django translation layer
# These javascript files are passed through the Django translation layer
translated_javascript_urls = [
re_path(r'^api.js', DynamicJsView.as_view(template_name='js/translated/api.js'), name='api.js'),
re_path(r'^attachment.js', DynamicJsView.as_view(template_name='js/translated/attachment.js'), name='attachment.js'),
re_path(r'^barcode.js', DynamicJsView.as_view(template_name='js/translated/barcode.js'), name='barcode.js'),
re_path(r'^bom.js', DynamicJsView.as_view(template_name='js/translated/bom.js'), name='bom.js'),
re_path(r'^build.js', DynamicJsView.as_view(template_name='js/translated/build.js'), name='build.js'),
re_path(r'^charts.js', DynamicJsView.as_view(template_name='js/translated/charts.js'), name='charts.js'),
re_path(r'^company.js', DynamicJsView.as_view(template_name='js/translated/company.js'), name='company.js'),
re_path(r'^filters.js', DynamicJsView.as_view(template_name='js/translated/filters.js'), name='filters.js'),
re_path(r'^forms.js', DynamicJsView.as_view(template_name='js/translated/forms.js'), name='forms.js'),
re_path(r'^helpers.js', DynamicJsView.as_view(template_name='js/translated/helpers.js'), name='helpers.js'),
re_path(r'^label.js', DynamicJsView.as_view(template_name='js/translated/label.js'), name='label.js'),
re_path(r'^model_renderers.js', DynamicJsView.as_view(template_name='js/translated/model_renderers.js'), name='model_renderers.js'),
re_path(r'^modals.js', DynamicJsView.as_view(template_name='js/translated/modals.js'), name='modals.js'),
re_path(r'^order.js', DynamicJsView.as_view(template_name='js/translated/order.js'), name='order.js'),
re_path(r'^part.js', DynamicJsView.as_view(template_name='js/translated/part.js'), name='part.js'),
re_path(r'^purchase_order.js', DynamicJsView.as_view(template_name='js/translated/purchase_order.js'), name='purchase_order.js'),
re_path(r'^return_order.js', DynamicJsView.as_view(template_name='js/translated/return_order.js'), name='return_order.js'),
re_path(r'^report.js', DynamicJsView.as_view(template_name='js/translated/report.js'), name='report.js'),
re_path(r'^sales_order.js', DynamicJsView.as_view(template_name='js/translated/sales_order.js'), name='sales_order.js'),
re_path(r'^search.js', DynamicJsView.as_view(template_name='js/translated/search.js'), name='search.js'),
re_path(r'^stock.js', DynamicJsView.as_view(template_name='js/translated/stock.js'), name='stock.js'),
re_path(r'^status_codes.js', DynamicJsView.as_view(template_name='js/translated/status_codes.js'), name='status_codes.js'),
re_path(r'^plugin.js', DynamicJsView.as_view(template_name='js/translated/plugin.js'), name='plugin.js'),
re_path(r'^pricing.js', DynamicJsView.as_view(template_name='js/translated/pricing.js'), name='pricing.js'),
re_path(r'^news.js', DynamicJsView.as_view(template_name='js/translated/news.js'), name='news.js'),
re_path(r'^tables.js', DynamicJsView.as_view(template_name='js/translated/tables.js'), name='tables.js'),
re_path(r'^table_filters.js', DynamicJsView.as_view(template_name='js/translated/table_filters.js'), name='table_filters.js'),
re_path(r'^notification.js', DynamicJsView.as_view(template_name='js/translated/notification.js'), name='notification.js'),
path(
'api.js',
DynamicJsView.as_view(template_name='js/translated/api.js'),
name='api.js',
),
path(
'attachment.js',
DynamicJsView.as_view(template_name='js/translated/attachment.js'),
name='attachment.js',
),
path(
'barcode.js',
DynamicJsView.as_view(template_name='js/translated/barcode.js'),
name='barcode.js',
),
path(
'bom.js',
DynamicJsView.as_view(template_name='js/translated/bom.js'),
name='bom.js',
),
path(
'build.js',
DynamicJsView.as_view(template_name='js/translated/build.js'),
name='build.js',
),
path(
'charts.js',
DynamicJsView.as_view(template_name='js/translated/charts.js'),
name='charts.js',
),
path(
'company.js',
DynamicJsView.as_view(template_name='js/translated/company.js'),
name='company.js',
),
path(
'filters.js',
DynamicJsView.as_view(template_name='js/translated/filters.js'),
name='filters.js',
),
path(
'forms.js',
DynamicJsView.as_view(template_name='js/translated/forms.js'),
name='forms.js',
),
path(
'helpers.js',
DynamicJsView.as_view(template_name='js/translated/helpers.js'),
name='helpers.js',
),
path(
'index.js',
DynamicJsView.as_view(template_name='js/translated/index.js'),
name='index.js',
),
path(
'label.js',
DynamicJsView.as_view(template_name='js/translated/label.js'),
name='label.js',
),
path(
'model_renderers.js',
DynamicJsView.as_view(template_name='js/translated/model_renderers.js'),
name='model_renderers.js',
),
path(
'modals.js',
DynamicJsView.as_view(template_name='js/translated/modals.js'),
name='modals.js',
),
path(
'order.js',
DynamicJsView.as_view(template_name='js/translated/order.js'),
name='order.js',
),
path(
'part.js',
DynamicJsView.as_view(template_name='js/translated/part.js'),
name='part.js',
),
path(
'purchase_order.js',
DynamicJsView.as_view(template_name='js/translated/purchase_order.js'),
name='purchase_order.js',
),
path(
'return_order.js',
DynamicJsView.as_view(template_name='js/translated/return_order.js'),
name='return_order.js',
),
path(
'report.js',
DynamicJsView.as_view(template_name='js/translated/report.js'),
name='report.js',
),
path(
'sales_order.js',
DynamicJsView.as_view(template_name='js/translated/sales_order.js'),
name='sales_order.js',
),
path(
'search.js',
DynamicJsView.as_view(template_name='js/translated/search.js'),
name='search.js',
),
path(
'stock.js',
DynamicJsView.as_view(template_name='js/translated/stock.js'),
name='stock.js',
),
path(
'status_codes.js',
DynamicJsView.as_view(template_name='js/translated/status_codes.js'),
name='status_codes.js',
),
path(
'plugin.js',
DynamicJsView.as_view(template_name='js/translated/plugin.js'),
name='plugin.js',
),
path(
'pricing.js',
DynamicJsView.as_view(template_name='js/translated/pricing.js'),
name='pricing.js',
),
path(
'news.js',
DynamicJsView.as_view(template_name='js/translated/news.js'),
name='news.js',
),
path(
'tables.js',
DynamicJsView.as_view(template_name='js/translated/tables.js'),
name='tables.js',
),
path(
'table_filters.js',
DynamicJsView.as_view(template_name='js/translated/table_filters.js'),
name='table_filters.js',
),
path(
'notification.js',
DynamicJsView.as_view(template_name='js/translated/notification.js'),
name='notification.js',
),
]
backendpatterns = [
# "Dynamic" javascript files which are rendered using InvenTree templating.
re_path(r'^js/dynamic/', include(dynamic_javascript_urls)),
re_path(r'^js/i18n/', include(translated_javascript_urls)),
re_path(r'^auth/', include('rest_framework.urls', namespace='rest_framework')),
re_path(r'^auth/?', auth_request),
re_path(r'^api/', include(apipatterns)),
re_path(r'^api-doc/', include_docs_urls(title='InvenTree API')),
path('auth/', include('rest_framework.urls', namespace='rest_framework')),
path('auth/', auth_request),
path('api/', include(apipatterns)),
path('api-doc/', SpectacularRedocView.as_view(url_name='schema'), name='api-doc'),
]
frontendpatterns = [
if settings.ENABLE_CLASSIC_FRONTEND:
# "Dynamic" javascript files which are rendered using InvenTree templating.
backendpatterns += [
re_path(r'^js/dynamic/', include(dynamic_javascript_urls)),
re_path(r'^js/i18n/', include(translated_javascript_urls)),
]
classic_frontendpatterns = [
# Apps
re_path(r'^build/', include(build_urls)),
re_path(r'^common/', include(common_urls)),
re_path(r'^company/', include(company_urls)),
re_path(r'^order/', include(order_urls)),
re_path(r'^manufacturer-part/', include(manufacturer_part_urls)),
re_path(r'^part/', include(part_urls)),
re_path(r'^stock/', include(stock_urls)),
re_path(r'^supplier-part/', include(supplier_part_urls)),
re_path(r'^edit-user/', EditUserView.as_view(), name='edit-user'),
re_path(r'^set-password/', SetPasswordView.as_view(), name='set-password'),
re_path(r'^index/', IndexView.as_view(), name='index'),
re_path(r'^notifications/', include(notifications_urls)),
re_path(r'^search/', SearchView.as_view(), name='search'),
re_path(r'^settings/', include(settings_urls)),
re_path(r'^about/', AboutView.as_view(), name='about'),
re_path(r'^stats/', DatabaseStatsView.as_view(), name='stats'),
# admin sites
re_path(f'^{settings.INVENTREE_ADMIN_URL}/error_log/', include('error_report.urls')),
re_path(f'^{settings.INVENTREE_ADMIN_URL}/', admin.site.urls, name='inventree-admin'),
path('build/', include(build_urls)),
path('common/', include(common_urls)),
path('company/', include(company_urls)),
path('order/', include(order_urls)),
path('manufacturer-part/', include(manufacturer_part_urls)),
path('part/', include(part_urls)),
path('stock/', include(stock_urls)),
path('supplier-part/', include(supplier_part_urls)),
path('edit-user/', EditUserView.as_view(), name='edit-user'),
path('set-password/', SetPasswordView.as_view(), name='set-password'),
path('index/', IndexView.as_view(), name='index'),
path('notifications/', include(notifications_urls)),
path('search/', SearchView.as_view(), name='search'),
path('settings/', include(settings_urls)),
path('about/', AboutView.as_view(), name='about'),
path('stats/', DatabaseStatsView.as_view(), name='stats'),
# DB user sessions
path('accounts/sessions/other/delete/', view=CustomSessionDeleteOtherView.as_view(), name='session_delete_other', ),
re_path(r'^accounts/sessions/(?P<pk>\w+)/delete/$', view=CustomSessionDeleteView.as_view(), name='session_delete', ),
path(
'accounts/sessions/other/delete/',
view=CustomSessionDeleteOtherView.as_view(),
name='session_delete_other',
),
re_path(
r'^accounts/sessions/(?P<pk>\w+)/delete/$',
view=CustomSessionDeleteView.as_view(),
name='session_delete',
),
# Single Sign On / allauth
# overrides of urlpatterns
re_path(r'^accounts/email/', CustomEmailView.as_view(), name='account_email'),
re_path(r'^accounts/social/connections/', CustomConnectionsView.as_view(), name='socialaccount_connections'),
re_path(r"^accounts/password/reset/key/(?P<uidb36>[0-9A-Za-z]+)-(?P<key>.+)/$", CustomPasswordResetFromKeyView.as_view(), name="account_reset_password_from_key"),
# Temporary fix for django-allauth-2fa # TODO remove
# See https://github.com/inventree/InvenTree/security/advisories/GHSA-8j76-mm54-52xq
re_path(r'^accounts/two_factor/remove/?$', CustomTwoFactorRemove.as_view(), name='two-factor-remove'),
path('accounts/email/', CustomEmailView.as_view(), name='account_email'),
path(
'accounts/social/connections/',
CustomConnectionsView.as_view(),
name='socialaccount_connections',
),
re_path(
r'^accounts/password/reset/key/(?P<uidb36>[0-9A-Za-z]+)-(?P<key>.+)/$',
CustomPasswordResetFromKeyView.as_view(),
name='account_reset_password_from_key',
),
# Override login page
re_path("accounts/login/", CustomLoginView.as_view(), name="account_login"),
re_path(r'^accounts/', include('allauth_2fa.urls')), # MFA support
re_path(r'^accounts/', include('allauth.urls')), # included urlpatterns
path('accounts/login/', CustomLoginView.as_view(), name='account_login'),
path('accounts/', include('allauth_2fa.urls')), # MFA support
path('accounts/', include('allauth.urls')), # included urlpatterns
]
urlpatterns = []
if settings.INVENTREE_ADMIN_ENABLED:
admin_url = (settings.INVENTREE_ADMIN_URL,)
urlpatterns += [
path(f'{admin_url}/error_log/', include('error_report.urls')),
path(f'{admin_url}/', admin.site.urls, name='inventree-admin'),
]
urlpatterns += backendpatterns
frontendpatterns = []
if settings.ENABLE_CLASSIC_FRONTEND:
frontendpatterns += classic_frontendpatterns
if settings.ENABLE_PLATFORM_FRONTEND:
frontendpatterns += platform_urls
if not settings.ENABLE_CLASSIC_FRONTEND:
# Add a redirect for login views
frontendpatterns += [
path(
'accounts/login/',
RedirectView.as_view(url=settings.FRONTEND_URL_BASE, permanent=False),
name='account_login',
)
]
urlpatterns += frontendpatterns
# Append custom plugin URLs (if plugin support is enabled)
if settings.PLUGINS_ENABLED:
frontendpatterns.append(get_plugin_urls())
urlpatterns = [
re_path('', include(frontendpatterns)),
re_path('', include(backendpatterns)),
]
urlpatterns.append(get_plugin_urls())
# Server running in "DEBUG" mode?
if settings.DEBUG:
@@ -203,12 +464,24 @@ if settings.DEBUG:
# Media file access
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# Debug toolbar access (only allowed in DEBUG mode)
if settings.DEBUG_TOOLBAR_ENABLED:
import debug_toolbar
urlpatterns = [
path('__debug__/', include(debug_toolbar.urls)),
] + urlpatterns
# Redirect for favicon.ico
urlpatterns.append(
path(
'favicon.ico',
RedirectView.as_view(url=f'{settings.STATIC_URL}img/favicon/favicon.ico'),
)
)
# Send any unknown URLs to the parts page
urlpatterns += [re_path(r'^.*$', RedirectView.as_view(url='/index/', permanent=False), name='index')]
urlpatterns += [
re_path(
r'^.*$',
RedirectView.as_view(
url='/index/'
if settings.ENABLE_CLASSIC_FRONTEND
else settings.FRONTEND_URL_BASE,
permanent=False,
),
name='index',
)
]

View File

@@ -8,9 +8,30 @@ from django.core import validators
from django.core.exceptions import FieldDoesNotExist, ValidationError
from django.utils.translation import gettext_lazy as _
import pint
from jinja2 import Template
from moneyed import CURRENCIES
import InvenTree.conversion
def validate_physical_units(unit):
"""Ensure that a given unit is a valid physical unit."""
unit = unit.strip()
# Ignore blank units
if not unit:
return
ureg = InvenTree.conversion.get_unit_registry()
try:
ureg(unit)
except AttributeError:
raise ValidationError(_('Invalid physical unit'))
except pint.errors.UndefinedUnitError:
raise ValidationError(_('Invalid physical unit'))
def validate_currency_code(code):
"""Check that a given code is a valid currency code."""
@@ -39,15 +60,29 @@ def allowable_url_schemes():
class AllowedURLValidator(validators.URLValidator):
"""Custom URL validator to allow for custom schemes."""
def __call__(self, value):
"""Validate the URL."""
import common.models
self.schemes = allowable_url_schemes()
# Determine if 'strict' URL validation is required (i.e. if the URL must have a schema prefix)
strict_urls = common.models.InvenTreeSetting.get_setting(
'INVENTREE_STRICT_URLS', True, cache=False
)
if not strict_urls:
# Allow URLs which do not have a provided schema
if '://' not in value:
# Validate as if it were http
value = 'http://' + value
super().__call__(value)
def validate_purchase_order_reference(value):
"""Validate the 'reference' field of a PurchaseOrder."""
from order.models import PurchaseOrder
# If we get to here, run the "default" validation routine
@@ -56,7 +91,6 @@ def validate_purchase_order_reference(value):
def validate_sales_order_reference(value):
"""Validate the 'reference' field of a SalesOrder."""
from order.models import SalesOrder
# If we get to here, run the "default" validation routine
@@ -84,7 +118,7 @@ def validate_overage(value):
i = Decimal(value)
if i < 0:
raise ValidationError(_("Overage value must not be negative"))
raise ValidationError(_('Overage value must not be negative'))
# Looks like a number
return True
@@ -100,17 +134,15 @@ def validate_overage(value):
f = float(v)
if f < 0:
raise ValidationError(_("Overage value must not be negative"))
raise ValidationError(_('Overage value must not be negative'))
elif f > 100:
raise ValidationError(_("Overage must not exceed 100%"))
raise ValidationError(_('Overage must not exceed 100%'))
return True
except ValueError:
pass
raise ValidationError(
_("Invalid value for overage")
)
raise ValidationError(_('Invalid value for overage'))
def validate_part_name_format(value):
@@ -118,7 +150,6 @@ def validate_part_name_format(value):
Make sure that each template container has a field of Part Model
"""
# Make sure that the field_name exists in Part model
from part.models import Part
@@ -147,8 +178,6 @@ def validate_part_name_format(value):
try:
Template(value).render({'part': p})
except Exception as exc:
raise ValidationError({
'value': str(exc)
})
raise ValidationError({'value': str(exc)})
return True

View File

@@ -4,29 +4,65 @@ Provides information on the current InvenTree version
"""
import os
import pathlib
import platform
import re
import subprocess
import sys
from datetime import datetime as dt
from datetime import timedelta as td
import django
from django.conf import settings
import common.models
from InvenTree.api_version import INVENTREE_API_VERSION
from dulwich.repo import NotGitRepository, Repo
from .api_version import INVENTREE_API_TEXT, INVENTREE_API_VERSION
# InvenTree software version
INVENTREE_SW_VERSION = "0.11.0"
INVENTREE_SW_VERSION = '0.14.0'
# Discover git
try:
main_repo = Repo(pathlib.Path(__file__).parent.parent.parent)
main_commit = main_repo[main_repo.head()]
except (NotGitRepository, FileNotFoundError):
main_commit = None
def checkMinPythonVersion():
"""Check that the Python version is at least 3.9."""
version = sys.version.split(' ')[0]
docs = 'https://docs.inventree.org/en/stable/start/intro/#python-requirements'
msg = f"""
InvenTree requires Python 3.9 or above - you are running version {version}.
- Refer to the InvenTree documentation for more information:
- {docs}
"""
if sys.version_info.major < 3:
raise RuntimeError(msg)
if sys.version_info.major == 3 and sys.version_info.minor < 9:
raise RuntimeError(msg)
print(f'Python version {version} - {sys.executable}')
def inventreeInstanceName():
"""Returns the InstanceName settings for the current database."""
return common.models.InvenTreeSetting.get_setting("INVENTREE_INSTANCE", "")
import common.models
return common.models.InvenTreeSetting.get_setting('INVENTREE_INSTANCE', '')
def inventreeInstanceTitle():
"""Returns the InstanceTitle for the current database."""
if common.models.InvenTreeSetting.get_setting("INVENTREE_INSTANCE_TITLE", False):
return common.models.InvenTreeSetting.get_setting("INVENTREE_INSTANCE", "")
else:
return 'InvenTree'
import common.models
if common.models.InvenTreeSetting.get_setting('INVENTREE_INSTANCE_TITLE', False):
return common.models.InvenTreeSetting.get_setting('INVENTREE_INSTANCE', '')
return 'InvenTree'
def inventreeVersion():
@@ -39,7 +75,7 @@ def inventreeVersionTuple(version=None):
if version is None:
version = INVENTREE_SW_VERSION
match = re.match(r"^.*(\d+)\.(\d+)\.(\d+).*$", str(version))
match = re.match(r'^.*(\d+)\.(\d+)\.(\d+).*$', str(version))
return [int(g) for g in match.groups()]
@@ -56,9 +92,29 @@ def inventreeDocsVersion():
Release -> "major.minor.sub" e.g. "0.5.2"
"""
if isInvenTreeDevelopmentVersion():
return "latest"
else:
return INVENTREE_SW_VERSION # pragma: no cover
return 'latest'
return INVENTREE_SW_VERSION # pragma: no cover
def inventreeDocUrl():
"""Return URL for InvenTree documentation site."""
tag = inventreeDocsVersion()
return f'https://docs.inventree.org/en/{tag}'
def inventreeAppUrl():
"""Return URL for InvenTree app site."""
return f'{inventreeDocUrl()}/app/app/'
def inventreeCreditsUrl():
"""Return URL for InvenTree credits site."""
return 'https://docs.inventree.org/en/latest/credits/'
def inventreeGithubUrl():
"""Return URL for InvenTree github site."""
return 'https://github.com/InvenTree/InvenTree/'
def isInvenTreeUpToDate():
@@ -66,7 +122,11 @@ def isInvenTreeUpToDate():
A background task periodically queries GitHub for latest version, and stores it to the database as "_INVENTREE_LATEST_VERSION"
"""
latest = common.models.InvenTreeSetting.get_setting('_INVENTREE_LATEST_VERSION', backup_value=None, create=False)
import common.models
latest = common.models.InvenTreeSetting.get_setting(
'_INVENTREE_LATEST_VERSION', backup_value=None, create=False
)
# No record for "latest" version - we must assume we are up to date!
if not latest:
@@ -84,11 +144,64 @@ def inventreeApiVersion():
return INVENTREE_API_VERSION
def parse_version_text():
"""Parse the version text to structured data."""
patched_data = INVENTREE_API_TEXT.split('\n\n')
# Remove first newline on latest version
patched_data[0] = patched_data[0].replace('\n', '', 1)
version_data = {}
for version in patched_data:
data = version.split('\n')
version_split = data[0].split(' -> ')
version_detail = (
version_split[1].split(':', 1) if len(version_split) > 1 else ['']
)
new_data = {
'version': version_split[0].strip(),
'date': version_detail[0].strip(),
'gh': version_detail[1].strip() if len(version_detail) > 1 else None,
'text': data[1:],
'latest': False,
}
version_data[new_data['version']] = new_data
return version_data
INVENTREE_API_TEXT_DATA = parse_version_text()
"""Pre-processed API version text."""
def inventreeApiText(versions: int = 10, start_version: int = 0):
"""Returns API version descriptors.
Args:
versions: Number of versions to return. Default: 10
start_version: first version to report. Defaults to return the latest {versions} versions.
"""
version_data = INVENTREE_API_TEXT_DATA
# Define the range of versions to return
if start_version == 0:
start_version = INVENTREE_API_VERSION - versions
return {
f'v{a}': version_data.get(f'v{a}', None)
for a in range(start_version, start_version + versions)
}
def inventreeDjangoVersion():
"""Returns the version of Django library."""
return django.get_version()
def inventreePythonVersion():
"""Returns the version of python."""
return sys.version.split(' ')[0]
def inventreeCommitHash():
"""Returns the git commit hash for the running codebase."""
# First look in the environment variables, i.e. if running in docker
@@ -97,10 +210,9 @@ def inventreeCommitHash():
if commit_hash:
return commit_hash
try:
return str(subprocess.check_output('git rev-parse --short HEAD'.split()), 'utf-8').strip()
except Exception: # pragma: no cover
if main_commit is None:
return None
return main_commit.sha().hexdigest()[0:7]
def inventreeCommitDate():
@@ -111,8 +223,63 @@ def inventreeCommitDate():
if commit_date:
return commit_date.split(' ')[0]
try:
d = str(subprocess.check_output('git show -s --format=%ci'.split()), 'utf-8').strip()
return d.split(' ')[0]
except Exception: # pragma: no cover
if main_commit is None:
return None
commit_dt = dt.fromtimestamp(main_commit.commit_time) + td(
seconds=main_commit.commit_timezone
)
return str(commit_dt.date())
def inventreeInstaller():
"""Returns the installer for the running codebase - if set."""
# First look in the environment variables, e.g. if running in docker
installer = os.environ.get('INVENTREE_PKG_INSTALLER', '')
if installer:
return installer
elif settings.DOCKER:
return 'DOC'
elif main_commit is not None:
return 'GIT'
return None
def inventreeBranch():
"""Returns the branch for the running codebase - if set."""
# First look in the environment variables, e.g. if running in docker
branch = os.environ.get('INVENTREE_PKG_BRANCH', '')
if branch:
return branch
if main_commit is None:
return None
try:
branch = main_repo.refs.follow(b'HEAD')[0][1].decode()
return branch.removeprefix('refs/heads/')
except IndexError:
return None # pragma: no cover
def inventreeTarget():
"""Returns the target platform for the running codebase - if set."""
# First look in the environment variables, e.g. if running in docker
return os.environ.get('INVENTREE_PKG_TARGET', None)
def inventreePlatform():
"""Returns the platform for the instance."""
return platform.platform(aliased=True)
def inventreeDatabase():
"""Return the InvenTree database backend e.g. 'postgresql'."""
db = settings.DATABASES['default']
return db.get('ENGINE', None).replace('django.db.backends.', '')

View File

@@ -4,12 +4,8 @@ In particular these views provide base functionality for rendering Django forms
as JSON objects and passing them to modal forms (using jQuery / bootstrap).
"""
import json
from django.conf import settings
from django.contrib.auth import password_validation
from django.contrib.auth.mixins import (LoginRequiredMixin,
PermissionRequiredMixin)
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.core.exceptions import ValidationError
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import redirect
@@ -23,21 +19,19 @@ from django.views.generic.base import RedirectView, TemplateView
from allauth.account.forms import AddEmailForm
from allauth.account.models import EmailAddress
from allauth.account.views import (EmailView, LoginView,
PasswordResetFromKeyView)
from allauth.account.views import EmailView, LoginView, PasswordResetFromKeyView
from allauth.socialaccount.forms import DisconnectForm
from allauth.socialaccount.views import ConnectionsView
from allauth_2fa.views import TwoFactorRemove
from djmoney.contrib.exchange.models import ExchangeBackend, Rate
from user_sessions.views import SessionDeleteOtherView, SessionDeleteView
from common.models import ColorTheme, InvenTreeSetting
from common.settings import currency_code_default, currency_codes
import common.models as common_models
import common.settings as common_settings
from part.models import PartCategory
from users.models import RuleSet, check_user_role
from .forms import EditUserForm, SetPasswordForm
from .helpers import remove_non_printable_characters, strip_html_tags
from .helpers import is_ajax, remove_non_printable_characters, strip_html_tags
def auth_request(request):
@@ -47,8 +41,7 @@ def auth_request(request):
"""
if request.user.is_authenticated:
return HttpResponse(status=200)
else:
return HttpResponse(status=403)
return HttpResponse(status=403)
class InvenTreeRoleMixin(PermissionRequiredMixin):
@@ -109,7 +102,6 @@ class InvenTreeRoleMixin(PermissionRequiredMixin):
return True
for required in roles_required:
(role, permission) = required.split('.')
if role not in RuleSet.RULESET_NAMES:
@@ -143,12 +135,14 @@ class InvenTreeRoleMixin(PermissionRequiredMixin):
app_label = model._meta.app_label
model_name = model._meta.model_name
table = f"{app_label}_{model_name}"
table = f'{app_label}_{model_name}'
permission = self.get_permission_class()
if not permission:
raise AttributeError(f"permission_class not defined for {type(self).__name__}")
raise AttributeError(
f'permission_class not defined for {type(self).__name__}'
)
# Check if the user has the required permission
return RuleSet.check_table_permission(user, table, permission)
@@ -187,7 +181,6 @@ class InvenTreeRoleMixin(PermissionRequiredMixin):
}
for view_class in permission_map.keys():
if issubclass(type(self), view_class):
return permission_map[view_class]
@@ -226,8 +219,7 @@ class AjaxMixin(InvenTreeRoleMixin):
"""
if method == 'POST':
return self.request.POST.get(name, None)
else:
return self.request.GET.get(name, None)
return self.request.GET.get(name, None)
def get_data(self):
"""Get extra context data (default implementation is empty dict).
@@ -264,7 +256,7 @@ class AjaxMixin(InvenTreeRoleMixin):
if not data:
data = {}
if not request.is_ajax():
if not is_ajax(request):
return HttpResponseRedirect('/')
if context is None:
@@ -288,9 +280,7 @@ class AjaxMixin(InvenTreeRoleMixin):
data['title'] = self.get_form_title()
data['html_form'] = render_to_string(
self.ajax_template_name,
context,
request=request
self.ajax_template_name, context, request=request
)
# Custom feedback`data
@@ -335,14 +325,16 @@ class AjaxUpdateView(AjaxMixin, UpdateView):
"""
super(UpdateView, self).get(request, *args, **kwargs)
return self.renderJsonResponse(request, self.get_form(), context=self.get_context_data())
return self.renderJsonResponse(
request, self.get_form(), context=self.get_context_data()
)
def save(self, object, form, **kwargs):
"""Method for updating the object in the database. Default implementation is very simple, but can be overridden if required.
Args:
object - The current object, to be updated
form - The validated form
object: The current object, to be updated
form: The validated form
Returns:
object instance for supplied form
@@ -357,7 +349,7 @@ class AjaxUpdateView(AjaxMixin, UpdateView):
- Updates model with POST field data
- Performs form and object validation
- If errors exist, re-render the form
- Otherwise, return sucess status
- Otherwise, return success status
"""
self.request = request
@@ -385,8 +377,7 @@ class AjaxUpdateView(AjaxMixin, UpdateView):
data[key] = value
if valid:
# Save the updated objec to the database
# Save the updated object to the database
self.save(self.object, form)
self.object = self.get_object()
@@ -405,8 +396,8 @@ class AjaxUpdateView(AjaxMixin, UpdateView):
class EditUserView(AjaxUpdateView):
"""View for editing user information."""
ajax_template_name = "modal_form.html"
ajax_form_title = _("Edit User Information")
ajax_template_name = 'modal_form.html'
ajax_form_title = _('Edit User Information')
form_class = EditUserForm
def get_object(self):
@@ -417,8 +408,8 @@ class EditUserView(AjaxUpdateView):
class SetPasswordView(AjaxUpdateView):
"""View for setting user password."""
ajax_template_name = "InvenTree/password.html"
ajax_form_title = _("Set Password")
ajax_template_name = 'InvenTree/password.html'
ajax_form_title = _('Set Password')
form_class = SetPasswordForm
def get_object(self):
@@ -447,8 +438,7 @@ class SetPasswordView(AjaxUpdateView):
if valid:
# Old password must be correct
if not user.check_password(old_password):
if user.has_usable_password() and not user.check_password(old_password):
form.add_error('old_password', _('Wrong password provided'))
valid = False
@@ -501,54 +491,52 @@ class SearchView(TemplateView):
class DynamicJsView(TemplateView):
"""View for returning javacsript files, which instead of being served dynamically, are passed through the django translation engine!"""
template_name = ""
template_name = ''
content_type = 'text/javascript'
class SettingsView(TemplateView):
"""View for configuring User settings."""
template_name = "InvenTree/settings/settings.html"
template_name = 'InvenTree/settings/settings.html'
def get_context_data(self, **kwargs):
"""Add data for template."""
ctx = super().get_context_data(**kwargs).copy()
ctx['settings'] = InvenTreeSetting.objects.all().order_by('key')
ctx['settings'] = common_models.InvenTreeSetting.objects.all().order_by('key')
ctx["base_currency"] = currency_code_default()
ctx["currencies"] = currency_codes
ctx['base_currency'] = common_settings.currency_code_default()
ctx['currencies'] = common_settings.currency_codes
ctx["rates"] = Rate.objects.filter(backend="InvenTreeExchange")
ctx['rates'] = Rate.objects.filter(backend='InvenTreeExchange')
ctx["categories"] = PartCategory.objects.all().order_by('tree_id', 'lft', 'name')
ctx['categories'] = PartCategory.objects.all().order_by(
'tree_id', 'lft', 'name'
)
# When were the rates last updated?
try:
backend = ExchangeBackend.objects.get(name='InvenTreeExchange')
ctx["rates_updated"] = backend.last_update
backend = ExchangeBackend.objects.filter(name='InvenTreeExchange')
if backend.exists():
backend = backend.first()
ctx['rates_updated'] = backend.last_update
except Exception:
ctx["rates_updated"] = None
# load locale stats
STAT_FILE = settings.BASE_DIR.joinpath('InvenTree/locale_stats.json').absolute()
try:
ctx["locale_stats"] = json.load(open(STAT_FILE, 'r'))
except Exception:
ctx["locale_stats"] = {}
ctx['rates_updated'] = None
# Forms and context for allauth
ctx['add_email_form'] = AddEmailForm
ctx["can_add_email"] = EmailAddress.objects.can_add_email(self.request.user)
ctx['can_add_email'] = EmailAddress.objects.can_add_email(self.request.user)
# Form and context for allauth social-accounts
ctx["request"] = self.request
ctx['request'] = self.request
ctx['social_form'] = DisconnectForm(request=self.request)
# user db sessions
ctx['session_key'] = self.request.session.session_key
ctx['session_list'] = self.request.user.session_set.filter(expire_date__gt=now()).order_by('-last_activity')
ctx['session_list'] = self.request.user.session_set.filter(
expire_date__gt=now()
).order_by('-last_activity')
return ctx
@@ -563,20 +551,23 @@ class AllauthOverrides(LoginRequiredMixin):
class CustomEmailView(AllauthOverrides, EmailView):
"""Override of allauths EmailView to always show the settings but leave the functions allow."""
success_url = reverse_lazy("settings")
success_url = reverse_lazy('settings')
class CustomConnectionsView(AllauthOverrides, ConnectionsView):
"""Override of allauths ConnectionsView to always show the settings but leave the functions allow."""
success_url = reverse_lazy("settings")
success_url = reverse_lazy('settings')
class CustomPasswordResetFromKeyView(PasswordResetFromKeyView):
"""Override of allauths PasswordResetFromKeyView to always show the settings but leave the functions allow."""
success_url = reverse_lazy("account_login")
success_url = reverse_lazy('account_login')
class UserSessionOverride():
class UserSessionOverride:
"""Overrides sucessurl to lead to settings."""
def get_success_url(self):
@@ -586,11 +577,13 @@ class UserSessionOverride():
class CustomSessionDeleteView(UserSessionOverride, SessionDeleteView):
"""Revert to settings after session delete."""
pass
class CustomSessionDeleteOtherView(UserSessionOverride, SessionDeleteOtherView):
"""Revert to settings after session delete."""
pass
@@ -620,8 +613,10 @@ class AppearanceSelectView(RedirectView):
def get_user_theme(self):
"""Get current user color theme."""
try:
user_theme = ColorTheme.objects.filter(user=self.request.user).get()
except ColorTheme.DoesNotExist:
user_theme = common_models.ColorTheme.objects.filter(
user=self.request.user
).get()
except common_models.ColorTheme.DoesNotExist:
user_theme = None
return user_theme
@@ -635,11 +630,15 @@ class AppearanceSelectView(RedirectView):
# Create theme entry if user did not select one yet
if not user_theme:
user_theme = ColorTheme()
user_theme = common_models.ColorTheme()
user_theme.user = request.user
user_theme.name = theme
user_theme.save()
if theme:
try:
user_theme.name = theme
user_theme.save()
except Exception:
pass
return redirect(reverse_lazy('settings'))
@@ -647,24 +646,18 @@ class AppearanceSelectView(RedirectView):
class DatabaseStatsView(AjaxView):
"""View for displaying database statistics."""
ajax_template_name = "stats.html"
ajax_form_title = _("System Information")
ajax_template_name = 'stats.html'
ajax_form_title = _('System Information')
class AboutView(AjaxView):
"""A view for displaying InvenTree version information"""
"""A view for displaying InvenTree version information."""
ajax_template_name = "about.html"
ajax_form_title = _("About InvenTree")
ajax_template_name = 'about.html'
ajax_form_title = _('About InvenTree')
class NotificationsView(TemplateView):
"""View for showing notifications."""
template_name = "InvenTree/notifications/notifications.html"
# Custom 2FA removal form to allow custom redirect URL
class CustomTwoFactorRemove(TwoFactorRemove):
"""Specify custom URL redirect."""
success_url = reverse_lazy("settings")
template_name = 'InvenTree/notifications/notifications.html'

View File

@@ -10,6 +10,8 @@ import os # pragma: no cover
from django.core.wsgi import get_wsgi_application # pragma: no cover
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "InvenTree.settings") # pragma: no cover
os.environ.setdefault(
'DJANGO_SETTINGS_MODULE', 'InvenTree.settings'
) # pragma: no cover
application = get_wsgi_application() # pragma: no cover

8
InvenTree/_testfolder/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
# Files used for testing
dummy_image.*
_tmp.csv
part_image_123abc.png
label.pdf
label.png
my_special*
_tests*.txt

View File

@@ -4,9 +4,9 @@ from django.contrib import admin
from import_export.admin import ImportExportModelAdmin
from import_export.fields import Field
import import_export.widgets as widgets
from import_export import widgets
from build.models import Build, BuildItem
from build.models import Build, BuildLine, BuildItem
from InvenTree.admin import InvenTreeResource
import part.models
@@ -51,6 +51,7 @@ class BuildResource(InvenTreeResource):
notes = Field(attribute='notes')
@admin.register(Build)
class BuildAdmin(ImportExportModelAdmin):
"""Class for managing the Build model via the admin interface"""
@@ -83,22 +84,34 @@ class BuildAdmin(ImportExportModelAdmin):
]
@admin.register(BuildItem)
class BuildItemAdmin(admin.ModelAdmin):
"""Class for managing the BuildItem model via the admin interface"""
"""Class for managing the BuildItem model via the admin interface."""
list_display = (
'build',
'stock_item',
'quantity'
)
autocomplete_fields = [
'build',
'bom_item',
'build_line',
'stock_item',
'install_into',
]
admin.site.register(Build, BuildAdmin)
admin.site.register(BuildItem, BuildItemAdmin)
@admin.register(BuildLine)
class BuildLineAdmin(admin.ModelAdmin):
"""Class for managing the BuildLine model via the admin interface"""
list_display = (
'build',
'bom_item',
'quantity',
)
search_fields = [
'build__title',
'build__reference',
'bom_item__sub_part__name',
]

View File

@@ -1,6 +1,7 @@
"""JSON API for the Build app."""
from django.urls import include, path, re_path
from django.db.models import F, Q
from django.urls import include, path
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.models import User
@@ -9,14 +10,16 @@ from rest_framework.exceptions import ValidationError
from django_filters.rest_framework import DjangoFilterBackend
from django_filters import rest_framework as rest_filters
from InvenTree.api import AttachmentMixin, APIDownloadMixin, ListCreateDestroyAPIView, MetadataView, StatusView
from InvenTree.api import AttachmentMixin, APIDownloadMixin, ListCreateDestroyAPIView, MetadataView
from generic.states.api import StatusView
from InvenTree.helpers import str2bool, isNull, DownloadFile
from InvenTree.status_codes import BuildStatus
from InvenTree.status_codes import BuildStatus, BuildStatusGroups
from InvenTree.mixins import CreateAPI, RetrieveUpdateDestroyAPI, ListCreateAPI
import common.models
import build.admin
import build.serializers
from build.models import Build, BuildItem, BuildOrderAttachment
from build.models import Build, BuildLine, BuildItem, BuildOrderAttachment
import part.models
from users.models import Owner
from InvenTree.filters import SEARCH_ORDER_FILTER_ALIAS
@@ -32,6 +35,7 @@ class BuildFilter(rest_filters.FilterSet):
'parent',
'sales_order',
'part',
'issued_by',
]
status = rest_filters.NumberFilter(label='Status')
@@ -41,9 +45,8 @@ class BuildFilter(rest_filters.FilterSet):
def filter_active(self, queryset, name, value):
"""Filter the queryset to either include or exclude orders which are active."""
if str2bool(value):
return queryset.filter(status__in=BuildStatus.ACTIVE_CODES)
else:
return queryset.exclude(status__in=BuildStatus.ACTIVE_CODES)
return queryset.filter(status__in=BuildStatusGroups.ACTIVE_CODES)
return queryset.exclude(status__in=BuildStatusGroups.ACTIVE_CODES)
overdue = rest_filters.BooleanFilter(label='Build is overdue', method='filter_overdue')
@@ -51,8 +54,7 @@ class BuildFilter(rest_filters.FilterSet):
"""Filter the queryset to either include or exclude orders which are overdue."""
if str2bool(value):
return queryset.filter(Build.OVERDUE_FILTER)
else:
return queryset.exclude(Build.OVERDUE_FILTER)
return queryset.exclude(Build.OVERDUE_FILTER)
assigned_to_me = rest_filters.BooleanFilter(label='assigned_to_me', method='filter_assigned_to_me')
@@ -65,8 +67,7 @@ class BuildFilter(rest_filters.FilterSet):
if value:
return queryset.filter(responsible__in=owners)
else:
return queryset.exclude(responsible__in=owners)
return queryset.exclude(responsible__in=owners)
assigned_to = rest_filters.NumberFilter(label='responsible', method='filter_responsible')
@@ -87,6 +88,19 @@ class BuildFilter(rest_filters.FilterSet):
lookup_expr="iexact"
)
project_code = rest_filters.ModelChoiceFilter(
queryset=common.models.ProjectCode.objects.all(),
field_name='project_code'
)
has_project_code = rest_filters.BooleanFilter(label='has_project_code', method='filter_has_project_code')
def filter_has_project_code(self, queryset, name, value):
"""Filter by whether or not the order has a project code"""
if str2bool(value):
return queryset.exclude(project_code=None)
return queryset.filter(project_code=None)
class BuildList(APIDownloadMixin, ListCreateAPI):
"""API endpoint for accessing a list of Build objects.
@@ -112,11 +126,13 @@ class BuildList(APIDownloadMixin, ListCreateAPI):
'completed',
'issued_by',
'responsible',
'project_code',
'priority',
]
ordering_field_aliases = {
'reference': ['reference_int', 'reference'],
'project_code': ['project_code__code'],
}
ordering = '-reference'
@@ -127,6 +143,7 @@ class BuildList(APIDownloadMixin, ListCreateAPI):
'part__name',
'part__IPN',
'part__description',
'project_code__code',
'priority',
]
@@ -213,7 +230,6 @@ class BuildDetail(RetrieveUpdateDestroyAPI):
def destroy(self, request, *args, **kwargs):
"""Only allow deletion of a BuildOrder if the build status is CANCELLED"""
build = self.get_object()
if build.status != BuildStatus.CANCELLED:
@@ -250,6 +266,125 @@ class BuildUnallocate(CreateAPI):
return ctx
class BuildLineFilter(rest_filters.FilterSet):
"""Custom filterset for the BuildLine API endpoint."""
class Meta:
"""Meta information for the BuildLineFilter class."""
model = BuildLine
fields = [
'build',
'bom_item',
]
# Fields on related models
consumable = rest_filters.BooleanFilter(label=_('Consumable'), field_name='bom_item__consumable')
optional = rest_filters.BooleanFilter(label=_('Optional'), field_name='bom_item__optional')
tracked = rest_filters.BooleanFilter(label=_('Tracked'), field_name='bom_item__sub_part__trackable')
allocated = rest_filters.BooleanFilter(label=_('Allocated'), method='filter_allocated')
def filter_allocated(self, queryset, name, value):
"""Filter by whether each BuildLine is fully allocated"""
if str2bool(value):
return queryset.filter(allocated__gte=F('quantity'))
return queryset.filter(allocated__lt=F('quantity'))
available = rest_filters.BooleanFilter(label=_('Available'), method='filter_available')
def filter_available(self, queryset, name, value):
"""Filter by whether there is sufficient stock available for each BuildLine:
To determine this, we need to know:
- The quantity required for each BuildLine
- The quantity available for each BuildLine
- The quantity allocated for each BuildLine
"""
flt = Q(quantity__lte=F('total_available_stock') + F('allocated'))
if str2bool(value):
return queryset.filter(flt)
return queryset.exclude(flt)
class BuildLineEndpoint:
"""Mixin class for BuildLine API endpoints."""
queryset = BuildLine.objects.all()
serializer_class = build.serializers.BuildLineSerializer
def get_source_build(self) -> Build:
"""Return the source Build object for the BuildLine queryset.
This source build is used to filter the available stock for each BuildLine.
- If this is a "detail" view, use the build associated with the line
- If this is a "list" view, use the build associated with the request
"""
raise NotImplementedError("get_source_build must be implemented in the child class")
def get_queryset(self):
"""Override queryset to select-related and annotate"""
queryset = super().get_queryset()
source_build = self.get_source_build()
queryset = build.serializers.BuildLineSerializer.annotate_queryset(queryset, build=source_build)
return queryset
class BuildLineList(BuildLineEndpoint, ListCreateAPI):
"""API endpoint for accessing a list of BuildLine objects"""
filterset_class = BuildLineFilter
filter_backends = SEARCH_ORDER_FILTER_ALIAS
ordering_fields = [
'part',
'allocated',
'reference',
'quantity',
'consumable',
'optional',
'unit_quantity',
'available_stock',
]
ordering_field_aliases = {
'part': 'bom_item__sub_part__name',
'reference': 'bom_item__reference',
'unit_quantity': 'bom_item__quantity',
'consumable': 'bom_item__consumable',
'optional': 'bom_item__optional',
}
search_fields = [
'bom_item__sub_part__name',
'bom_item__reference',
]
def get_source_build(self) -> Build:
"""Return the target build for the BuildLine queryset."""
try:
build_id = self.request.query_params.get('build', None)
if build_id:
build = Build.objects.get(pk=build_id)
return build
except (Build.DoesNotExist, AttributeError, ValueError):
pass
return None
class BuildLineDetail(BuildLineEndpoint, RetrieveUpdateDestroyAPI):
"""API endpoint for detail view of a BuildLine object."""
def get_source_build(self) -> Build:
"""Return the target source location for the BuildLine queryset."""
return None
class BuildOrderContextMixin:
"""Mixin class which adds build order as serializer context variable."""
@@ -276,6 +411,19 @@ class BuildOutputCreate(BuildOrderContextMixin, CreateAPI):
serializer_class = build.serializers.BuildOutputCreateSerializer
class BuildOutputScrap(BuildOrderContextMixin, CreateAPI):
"""API endpoint for scrapping build output(s)."""
queryset = Build.objects.none()
serializer_class = build.serializers.BuildOutputScrapSerializer
def get_serializer_context(self):
"""Add extra context information to the endpoint serializer."""
ctx = super().get_serializer_context()
ctx['to_complete'] = False
return ctx
class BuildOutputComplete(BuildOrderContextMixin, CreateAPI):
"""API endpoint for completing build outputs."""
@@ -359,9 +507,8 @@ class BuildItemFilter(rest_filters.FilterSet):
"""Metaclass option"""
model = BuildItem
fields = [
'build',
'build_line',
'stock_item',
'bom_item',
'install_into',
]
@@ -370,14 +517,18 @@ class BuildItemFilter(rest_filters.FilterSet):
field_name='stock_item__part',
)
build = rest_filters.ModelChoiceFilter(
queryset=build.models.Build.objects.all(),
field_name='build_line__build',
)
tracked = rest_filters.BooleanFilter(label='Tracked', method='filter_tracked')
def filter_tracked(self, queryset, name, value):
"""Filter the queryset based on whether build items are tracked"""
if str2bool(value):
return queryset.exclude(install_into=None)
else:
return queryset.filter(install_into=None)
return queryset.filter(install_into=None)
class BuildItemList(ListCreateAPI):
@@ -395,10 +546,9 @@ class BuildItemList(ListCreateAPI):
try:
params = self.request.query_params
kwargs['part_detail'] = str2bool(params.get('part_detail', False))
kwargs['build_detail'] = str2bool(params.get('build_detail', False))
kwargs['location_detail'] = str2bool(params.get('location_detail', False))
kwargs['stock_detail'] = str2bool(params.get('stock_detail', True))
for key in ['part_detail', 'location_detail', 'stock_detail', 'build_detail']:
if key in params:
kwargs[key] = str2bool(params.get(key, False))
except AttributeError:
pass
@@ -409,9 +559,8 @@ class BuildItemList(ListCreateAPI):
queryset = BuildItem.objects.all()
queryset = queryset.select_related(
'bom_item',
'bom_item__sub_part',
'build',
'build_line',
'build_line__build',
'install_into',
'stock_item',
'stock_item__location',
@@ -421,7 +570,7 @@ class BuildItemList(ListCreateAPI):
return queryset
def filter_queryset(self, queryset):
"""Customm query filtering for the BuildItem list."""
"""Custom query filtering for the BuildItem list."""
queryset = super().filter_queryset(queryset)
params = self.request.query_params
@@ -449,10 +598,6 @@ class BuildAttachmentList(AttachmentMixin, ListCreateDestroyAPIView):
queryset = BuildOrderAttachment.objects.all()
serializer_class = build.serializers.BuildAttachmentSerializer
filter_backends = [
DjangoFilterBackend,
]
filterset_fields = [
'build',
]
@@ -468,37 +613,44 @@ class BuildAttachmentDetail(AttachmentMixin, RetrieveUpdateDestroyAPI):
build_api_urls = [
# Attachments
re_path(r'^attachment/', include([
path(r'<int:pk>/', BuildAttachmentDetail.as_view(), name='api-build-attachment-detail'),
re_path(r'^.*$', BuildAttachmentList.as_view(), name='api-build-attachment-list'),
path('attachment/', include([
path('<int:pk>/', BuildAttachmentDetail.as_view(), name='api-build-attachment-detail'),
path('', BuildAttachmentList.as_view(), name='api-build-attachment-list'),
])),
# Build lines
path('line/', include([
path('<int:pk>/', BuildLineDetail.as_view(), name='api-build-line-detail'),
path('', BuildLineList.as_view(), name='api-build-line-list'),
])),
# Build Items
re_path(r'^item/', include([
path(r'<int:pk>/', include([
re_path(r'^metadata/', MetadataView.as_view(), {'model': BuildItem}, name='api-build-item-metadata'),
re_path(r'^.*$', BuildItemDetail.as_view(), name='api-build-item-detail'),
path('item/', include([
path('<int:pk>/', include([
path('metadata/', MetadataView.as_view(), {'model': BuildItem}, name='api-build-item-metadata'),
path('', BuildItemDetail.as_view(), name='api-build-item-detail'),
])),
re_path(r'^.*$', BuildItemList.as_view(), name='api-build-item-list'),
path('', BuildItemList.as_view(), name='api-build-item-list'),
])),
# Build Detail
path(r'<int:pk>/', include([
re_path(r'^allocate/', BuildAllocate.as_view(), name='api-build-allocate'),
re_path(r'^auto-allocate/', BuildAutoAllocate.as_view(), name='api-build-auto-allocate'),
re_path(r'^complete/', BuildOutputComplete.as_view(), name='api-build-output-complete'),
re_path(r'^create-output/', BuildOutputCreate.as_view(), name='api-build-output-create'),
re_path(r'^delete-outputs/', BuildOutputDelete.as_view(), name='api-build-output-delete'),
re_path(r'^finish/', BuildFinish.as_view(), name='api-build-finish'),
re_path(r'^cancel/', BuildCancel.as_view(), name='api-build-cancel'),
re_path(r'^unallocate/', BuildUnallocate.as_view(), name='api-build-unallocate'),
re_path(r'^metadata/', MetadataView.as_view(), {'model': Build}, name='api-build-metadata'),
re_path(r'^.*$', BuildDetail.as_view(), name='api-build-detail'),
path('<int:pk>/', include([
path('allocate/', BuildAllocate.as_view(), name='api-build-allocate'),
path('auto-allocate/', BuildAutoAllocate.as_view(), name='api-build-auto-allocate'),
path('complete/', BuildOutputComplete.as_view(), name='api-build-output-complete'),
path('create-output/', BuildOutputCreate.as_view(), name='api-build-output-create'),
path('delete-outputs/', BuildOutputDelete.as_view(), name='api-build-output-delete'),
path('scrap-outputs/', BuildOutputScrap.as_view(), name='api-build-output-scrap'),
path('finish/', BuildFinish.as_view(), name='api-build-finish'),
path('cancel/', BuildCancel.as_view(), name='api-build-cancel'),
path('unallocate/', BuildUnallocate.as_view(), name='api-build-unallocate'),
path('metadata/', MetadataView.as_view(), {'model': Build}, name='api-build-metadata'),
path('', BuildDetail.as_view(), name='api-build-detail'),
])),
# Build order status code information
re_path(r'status/', StatusView.as_view(), {StatusView.MODEL_REF: BuildStatus}, name='api-build-status-codes'),
path('status/', StatusView.as_view(), {StatusView.MODEL_REF: BuildStatus}, name='api-build-status-codes'),
# Build List
re_path(r'^.*$', BuildList.as_view(), name='api-build-list'),
path('', BuildList.as_view(), name='api-build-list'),
]

View File

@@ -3,16 +3,6 @@
from django.db import migrations, models
import django.db.models.deletion
import mptt.fields
from build.models import Build
def update_tree(apps, schema_editor):
# Update the Build MPTT model
Build.objects.rebuild()
def nupdate_tree(apps, schema_editor): # pragma: no cover
pass
class Migration(migrations.Migration):
@@ -53,5 +43,4 @@ class Migration(migrations.Migration):
field=models.PositiveIntegerField(db_index=True, default=0, editable=False),
preserve_default=False,
),
migrations.RunPython(update_tree, reverse_code=nupdate_tree),
]

Some files were not shown because too many files have changed in this diff Show More