diff --git a/.github/workflows/qc_checks.yaml b/.github/workflows/qc_checks.yaml index 0fb1a400a6..558e42584e 100644 --- a/.github/workflows/qc_checks.yaml +++ b/.github/workflows/qc_checks.yaml @@ -170,9 +170,9 @@ jobs: id: breaking_changes uses: oasdiff/oasdiff-action/diff@1c611ffb1253a72924624aa4fb662e302b3565d3 # pin@main with: - base: 'api.yaml' - revision: 'src/backend/InvenTree/schema.yml' - format: 'html' + base: "api.yaml" + revision: "src/backend/InvenTree/schema.yml" + format: "html" - name: Echoing diff to step env: DIFF: ${{ steps.breaking_changes.outputs.diff }} @@ -194,6 +194,23 @@ jobs: version="$(python3 .github/scripts/version_check.py only_version 2>&1)" echo "Version: $version" echo "version=$version" >> "$GITHUB_OUTPUT" + - name: Extract settings / tags + run: invoke int.export-definitions + - name: Upload settings + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # pin@v4.6.0 + with: + name: inventree_settings.json + path: src/backend/InvenTree/inventree_settings.json + - name: Upload tags + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # pin@v4.6.0 + with: + name: inventree_tags.yml + path: src/backend/InvenTree/inventree_tags.yml + - name: Upload filters + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # pin@v4.6.0 + with: + name: inventree_filters.yml + path: src/backend/InvenTree/inventree_filters.yml schema-push: name: Push new schema @@ -210,15 +227,20 @@ jobs: repository: inventree/schema token: ${{ secrets.SCHEMA_PAT }} persist-credentials: true + - name: Create artifact directory + run: mkdir -p artifact - name: Download schema artifact uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # pin@v4.1.8 with: - name: schema.yml - - name: Move schema to correct location + path: artifact + - name: Move files to correct location run: | echo "Version: $version" mkdir export/${version} - mv schema.yml export/${version}/api.yaml + mv artifact/schema.yml export/${version}/api.yaml + mv artifact/inventree_settings.json export/${version}/inventree_settings.json + mv artifact/inventree_tags.yml export/${version}/inventree_tags.yml + mv artifact/inventree_filters.yml export/${version}/inventree_filters.yml - uses: stefanzweifel/git-auto-commit-action@e348103e9026cc0eee72ae06630dbe30c8bf7a79 # pin@v5.1.0 name: Commit schema changes with: @@ -518,7 +540,6 @@ jobs: chmod +rw /home/runner/work/InvenTree/db.sqlite3 invoke migrate - platform_ui: name: Tests - Platform UI runs-on: ubuntu-20.04 @@ -609,7 +630,7 @@ jobs: zizmor: name: Security [Zizmor] runs-on: ubuntu-20.04 - needs: ['pre-commit', 'paths-filter'] + needs: ["pre-commit", "paths-filter"] if: needs.paths-filter.outputs.cicd == 'true' || needs.paths-filter.outputs.force == 'true' permissions: diff --git a/docs/docs/report/helpers.md b/docs/docs/report/helpers.md index b235005078..7883637a2f 100644 --- a/docs/docs/report/helpers.md +++ b/docs/docs/report/helpers.md @@ -512,3 +512,9 @@ A [Part Parameter](../part/parameter.md) has the following available attributes: | Data | The *value* of the parameter (e.g. "123.4") | | Units | The *units* of the parameter (e.g. "km") | | Template | A reference to a [PartParameterTemplate](../part/parameter.md#parameter-templates) | + +## List of tags and filters + +The following tags and filters are available. + +{{ tags_and_filters() }} diff --git a/docs/main.py b/docs/main.py index 19ead8d865..70c6cbb126 100644 --- a/docs/main.py +++ b/docs/main.py @@ -31,6 +31,8 @@ for key in [ # Cached settings dict values global GLOBAL_SETTINGS global USER_SETTINGS +global TAGS +global FILTERS # Read in the InvenTree settings file here = os.path.dirname(__file__) @@ -42,6 +44,13 @@ with open(settings_file, encoding='utf-8') as sf: GLOBAL_SETTINGS = settings['global'] USER_SETTINGS = settings['user'] +# Tags +with open(os.path.join(here, 'inventree_tags.yml'), encoding='utf-8') as f: + TAGS = yaml.load(f, yaml.BaseLoader) +# Filters +with open(os.path.join(here, 'inventree_filters.yml'), encoding='utf-8') as f: + FILTERS = yaml.load(f, yaml.BaseLoader) + def get_repo_url(raw=False): """Return the repository URL for the current project.""" @@ -303,3 +312,25 @@ def define_env(env): setting = USER_SETTINGS[key] return rendersetting(key, setting) + + @env.macro + def tags_and_filters(): + """Return a list of all tags and filters.""" + global TAGS + global FILTERS + + ret_data = '' + for ref in [['Tags', TAGS], ['Filters', FILTERS]]: + ret_data += f'### {ref[0]}\n\n| Namespace | Name | Description |\n| --- | --- | --- |\n' + for value in ref[1]: + title = ( + value['title'] + .replace('\n', ' ') + .replace('<', '<') + .replace('>', '>') + ) + ret_data += f'| {value["library"]} | {value["name"]} | {title} |\n' + ret_data += '\n' + ret_data += '\n' + + return ret_data diff --git a/readthedocs.yml b/readthedocs.yml index f9f48ec0e2..b4b513ac02 100644 --- a/readthedocs.yml +++ b/readthedocs.yml @@ -17,6 +17,6 @@ build: - echo "Generating API schema file" - pip install -U invoke - invoke migrate - - invoke int.export-settings-definitions --filename docs/inventree_settings.json --overwrite + - invoke int.export-definitions --basedir "docs" - invoke dev.schema --filename docs/schema.yml --ignore-warnings - python docs/extract_schema.py docs/schema.yml diff --git a/src/backend/InvenTree/InvenTree/management/commands/export_filters.py b/src/backend/InvenTree/InvenTree/management/commands/export_filters.py new file mode 100644 index 0000000000..093f7d4340 --- /dev/null +++ b/src/backend/InvenTree/InvenTree/management/commands/export_filters.py @@ -0,0 +1,58 @@ +"""Custom management command to export all filters. + +This is used to generate a YAML file which contains all of the filters available in InvenTree. +""" + +from django.contrib.admindocs import utils +from django.core.exceptions import ImproperlyConfigured +from django.core.management.base import BaseCommand +from django.template.engine import Engine + +import yaml + + +class Command(BaseCommand): + """Extract filter information, and export to a YAML file.""" + + def add_arguments(self, parser): + """Add custom arguments for this command.""" + parser.add_argument( + 'filename', type=str, help='Output filename for filter definitions' + ) + + def handle(self, *args, **kwargs): + """Export filter information to a YAML file.""" + filters = discover_filters() + # Write + filename = kwargs.get('filename', 'inventree_filters.yml') + with open(filename, 'w', encoding='utf-8') as f: + yaml.dump(filters, f, indent=4) + print(f"Exported InvenTree filter definitions to '{filename}'") + + +def discover_filters(): + """Discover all available filters. + + This function is a copy of a function from the Django 'admindocs' module in django.contrib.admindocs.views.TemplateFilterIndexView + """ + filters = [] + try: + engine = Engine.get_default() + except ImproperlyConfigured: + # Non-trivial TEMPLATES settings aren't supported (#24125). + pass + else: + app_libs = sorted(engine.template_libraries.items()) + builtin_libs = [('', lib) for lib in engine.template_builtins] + for module_name, library in builtin_libs + app_libs: + for filter_name, filter_func in library.filters.items(): + title, body, metadata = utils.parse_docstring(filter_func.__doc__) + tag_library = module_name.split('.')[-1] + filters.append({ + 'name': filter_name, + 'title': title, + 'body': body, + 'meta': metadata, + 'library': tag_library, + }) + return filters diff --git a/src/backend/InvenTree/InvenTree/management/commands/export_tags.py b/src/backend/InvenTree/InvenTree/management/commands/export_tags.py new file mode 100644 index 0000000000..bf8afdf05d --- /dev/null +++ b/src/backend/InvenTree/InvenTree/management/commands/export_tags.py @@ -0,0 +1,59 @@ +"""Custom management command to export all tags. + +This is used to generate a YAML file which contains all of the tags available in InvenTree. +""" + +from django.contrib.admindocs import utils +from django.core.exceptions import ImproperlyConfigured +from django.core.management.base import BaseCommand +from django.template.engine import Engine + +import yaml + + +class Command(BaseCommand): + """Extract tag information, and export to a YAML file.""" + + def add_arguments(self, parser): + """Add custom arguments for this command.""" + parser.add_argument( + 'filename', type=str, help='Output filename for tag definitions' + ) + + def handle(self, *args, **kwargs): + """Export tag information to a YAML file.""" + tags = discover_tags() + + # Write + filename = kwargs.get('filename', 'inventree_tags.yml') + with open(filename, 'w', encoding='utf-8') as f: + yaml.dump(tags, f, indent=4) + print(f"Exported InvenTree tag definitions to '{filename}'") + + +def discover_tags(): + """Discover all available tags. + + This function is a copy of a function from the Django 'admindocs' module in django.contrib.admindocs.views.TemplateTagIndexView + """ + tags = [] + try: + engine = Engine.get_default() + except ImproperlyConfigured: + # Non-trivial TEMPLATES settings aren't supported (#24125). + pass + else: + app_libs = sorted(engine.template_libraries.items()) + builtin_libs = [('', lib) for lib in engine.template_builtins] + for module_name, library in builtin_libs + app_libs: + for tag_name, tag_func in library.tags.items(): + title, body, metadata = utils.parse_docstring(tag_func.__doc__) + tag_library = module_name.split('.')[-1] + tags.append({ + 'name': tag_name, + 'title': title, + 'body': body, + 'meta': metadata, + 'library': tag_library, + }) + return tags diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index 9e962f5d30..17c60cea60 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -255,6 +255,7 @@ INVENTREE_ADMIN_URL = get_setting( INSTALLED_APPS = [ # Admin site integration 'django.contrib.admin', + 'django.contrib.admindocs', # InvenTree apps 'build.apps.BuildConfig', 'common.apps.CommonConfig', diff --git a/src/backend/InvenTree/InvenTree/urls.py b/src/backend/InvenTree/InvenTree/urls.py index e1770fb490..b0af89d6fc 100644 --- a/src/backend/InvenTree/InvenTree/urls.py +++ b/src/backend/InvenTree/InvenTree/urls.py @@ -188,6 +188,7 @@ if settings.INVENTREE_ADMIN_ENABLED: urlpatterns += [ path(f'{admin_url}/error_log/', include('error_report.urls')), + path(f'{admin_url}/doc/', include('django.contrib.admindocs.urls')), path(f'{admin_url}/', admin.site.urls, name='inventree-admin'), ] diff --git a/src/backend/requirements.in b/src/backend/requirements.in index 6a86b23fc2..461bf00328 100644 --- a/src/backend/requirements.in +++ b/src/backend/requirements.in @@ -33,6 +33,7 @@ djangorestframework-simplejwt[crypto] # JWT authentication django-xforwardedfor-middleware # IP forwarding metadata dj-rest-auth==7.0.0 # Authentication API endpoints # FIXED 2024-12-22 due to https://github.com/inventree/InvenTree/issues/8707 dulwich # pure Python git integration +docutils # Documentation utilities for auto admin docs drf-spectacular # DRF API documentation feedparser # RSS newsfeed parser gunicorn # Gunicorn web server diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index 9af2062075..4bd6ac9c7e 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -546,6 +546,10 @@ djangorestframework-simplejwt[crypto]==5.4.0 \ --hash=sha256:7aec953db9ed4163430c16d086eecb0f028f814ce6bba62b06c25919261e9077 \ --hash=sha256:cccecce1a0e1a4a240fae80da73e5fc23055bababb8b67de88fa47cd36822320 # via -r src/backend/requirements.in +docutils==0.21.2 \ + --hash=sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f \ + --hash=sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2 + # via -r src/backend/requirements.in drf-spectacular==0.28.0 \ --hash=sha256:2c778a47a40ab2f5078a7c42e82baba07397bb35b074ae4680721b2805943061 \ --hash=sha256:856e7edf1056e49a4245e87a61e8da4baff46c83dbc25be1da2df77f354c7cb4 diff --git a/tasks.py b/tasks.py index 46d6014be3..e6e7ee8d4c 100644 --- a/tasks.py +++ b/tasks.py @@ -1194,6 +1194,30 @@ def export_settings_definitions(c, filename='inventree_settings.json', overwrite manage(c, f'export_settings_definitions {filename}', pty=True) +@task(help={'basedir': 'Export to a base directory (default = False)'}) +def export_definitions(c, basedir: str = ''): + """Export various definitions.""" + if basedir != '' and basedir.endswith('/') is False: + basedir += '/' + + filenames = [ + Path(basedir + 'inventree_settings.json').resolve(), + Path(basedir + 'inventree_tags.yml').resolve(), + Path(basedir + 'inventree_filters.yml').resolve(), + ] + + info('Exporting definitions...') + export_settings_definitions(c, overwrite=True, filename=filenames[0]) + + check_file_existence(filenames[1], overwrite=True) + manage(c, f'export_tags {filenames[1]}', pty=True) + + check_file_existence(filenames[2], overwrite=True) + manage(c, f'export_filters {filenames[2]}', pty=True) + + info('Exporting definitions complete') + + @task(default=True) def version(c): """Show the current version of InvenTree.""" @@ -1584,6 +1608,7 @@ internal = Collection( clean_settings, clear_generated, export_settings_definitions, + export_definitions, frontend_build, frontend_check, frontend_compile,