Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
# See https://pre-commit.com for more information
repos:
- repo: https://github.com/jedie/cli-base-utilities
rev: v0.27.3
rev: v0.29.1
hooks:
- id: update-readme-history
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,13 @@ To make a new release, do this:

[comment]: <> (✂✂✂ auto generated history start ✂✂✂)

* [**dev**](https://github.com/jedie/PyInventory/compare/v0.24.0...main)
* 2026-01-30 - Update requirements
* [v0.24.1](https://github.com/jedie/PyInventory/compare/v0.24.0...v0.24.1)
* 2026-04-09 - Update code styles
* 2026-04-09 - Update requirements
* 2026-04-09 - Apply project updates
* 2026-04-08 - Bump cryptography from 46.0.6 to 46.0.7
* 2026-03-30 - Bump pygments from 2.19.2 to 2.20.0
* 2026-03-29 - Bump cryptography from 46.0.4 to 46.0.6
* [v0.24.0](https://github.com/jedie/PyInventory/compare/v0.23.2...v0.24.0)
* 2025-12-02 - Use facets always and auto hide empty item filters
* 2025-12-02 - Update requirements
Expand Down
2 changes: 1 addition & 1 deletion inventory/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"""

# See https://packaging.python.org/en/latest/specifications/version-specifiers/
__version__ = '0.24.0'
__version__ = '0.24.1'
__author__ = 'Jens Diemer <PyInventory@jensdiemer.de>'
17 changes: 8 additions & 9 deletions inventory/admin/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,11 @@ def choices(self, changelist):
'display': f'{val} ({count})',
}

if self.include_empty_choice:
if count := facet_counts['__c']:
yield {
'selected': bool(self.lookup_val_isnull),
'query_string': changelist.get_query_string(
{self.lookup_kwarg_isnull: 'True'}, [self.lookup_kwarg]
),
'display': f'{self.empty_value_display} ({count})',
}
if self.include_empty_choice and (count := facet_counts['__c']):
yield {
'selected': bool(self.lookup_val_isnull),
'query_string': changelist.get_query_string(
{self.lookup_kwarg_isnull: 'True'}, [self.lookup_kwarg]
),
'display': f'{self.empty_value_display} ({count})',
}
4 changes: 2 additions & 2 deletions inventory/models/links.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ def update_response_info(self):

try:
r = requests.get(url=self.url, allow_redirects=True, timeout=10)
except Exception as err:
logger.exception('Error get %s: %s', self.url, err)
except Exception:
logger.exception('Error get %s', self.url)
self.status_code = None
self.page_title = None
return
Expand Down
2 changes: 1 addition & 1 deletion inventory/persistent_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def __init__(self, field, request, params, model, model_admin, field_path):
opts: Options = model._meta

# Our own parameter to display all choices:
self.lookup_kwarg_display_all = '%s__all' % field_path
self.lookup_kwarg_display_all = f'{field_path}__all'
display_all = bool(params.pop(self.lookup_kwarg_display_all, None))

super().__init__(field, request, params, model, model_admin, field_path)
Expand Down
87 changes: 43 additions & 44 deletions inventory/tests/test_item_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,53 +24,52 @@ def test_basics(self):
token2_instance = UserMediaTokenModel.objects.get(user=pyinventory_user2)
assert repr(token2_instance) == (f"<UserMediaTokenModel: user:2 token:'user2token' ({token2_instance.pk})>")

with tempfile.TemporaryDirectory() as temp:
with override_settings(MEDIA_ROOT=temp):
with mock.patch('secrets.token_urlsafe', return_value='12345678901234567890'):
image_instance = baker.make(ItemImageModel, user=pyinventory_user1, _create_files=True)
with tempfile.TemporaryDirectory() as temp, override_settings(MEDIA_ROOT=temp):
with mock.patch('secrets.token_urlsafe', return_value='12345678901234567890'):
image_instance = baker.make(ItemImageModel, user=pyinventory_user1, _create_files=True)

assert image_instance.image is not None
url = image_instance.image.url
assert url == '/media/user1token/12345678901234567890/mock_img.jpeg'
assert image_instance.image is not None
url = image_instance.image.url
assert url == '/media/user1token/12345678901234567890/mock_img.jpeg'

# HTTP -> HTTPS redirect:
response = self.client.get('/media/user1token/12345678901234567890/mock_img.jpeg', secure=False)
self.assertRedirects(
response,
status_code=301,
expected_url='https://testserver/media/user1token/12345678901234567890/mock_img.jpeg',
fetch_redirect_response=False,
)
# HTTP -> HTTPS redirect:
response = self.client.get('/media/user1token/12345678901234567890/mock_img.jpeg', secure=False)
self.assertRedirects(
response,
status_code=301,
expected_url='https://testserver/media/user1token/12345678901234567890/mock_img.jpeg',
fetch_redirect_response=False,
)

# Anonymous has no access:
response = self.client.get(
'/media/user1token/12345678901234567890/mock_img.jpeg',
secure=True,
)
assert response.status_code == 403
# Anonymous has no access:
response = self.client.get(
'/media/user1token/12345678901234567890/mock_img.jpeg',
secure=True,
)
assert response.status_code == 403

# Can't access with wrong user:
self.client.force_login(pyinventory_user2)
response = self.client.get(
'/media/user1token/12345678901234567890/mock_img.jpeg',
secure=True,
)
assert response.status_code == 403
# Can't access with wrong user:
self.client.force_login(pyinventory_user2)
response = self.client.get(
'/media/user1token/12345678901234567890/mock_img.jpeg',
secure=True,
)
assert response.status_code == 403

# Can access with the right user:
self.client.force_login(pyinventory_user1)
response = self.client.get(
'/media/user1token/12345678901234567890/mock_img.jpeg',
secure=True,
)
assert response.status_code == 200
assert isinstance(response, FileResponse)
assert response.getvalue() == image_instance.image.open('rb').read()
# Can access with the right user:
self.client.force_login(pyinventory_user1)
response = self.client.get(
'/media/user1token/12345678901234567890/mock_img.jpeg',
secure=True,
)
assert response.status_code == 200
assert isinstance(response, FileResponse)
assert response.getvalue() == image_instance.image.open('rb').read()

# Test whats happen, if token was deleted
UserMediaTokenModel.objects.all().delete()
response = self.client.get(
'/media/user1token/12345678901234567890/mock_img.jpeg',
secure=True,
)
assert response.status_code == 400 # SuspiciousOperation -> HttpResponseBadRequest
# Test whats happen, if token was deleted
UserMediaTokenModel.objects.all().delete()
response = self.client.get(
'/media/user1token/12345678901234567890/mock_img.jpeg',
secure=True,
)
assert response.status_code == 400 # SuspiciousOperation -> HttpResponseBadRequest
5 changes: 2 additions & 3 deletions inventory_project/settings/local.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# ruff: noqa: F405

"""
Django settings for local development
Expand All @@ -7,7 +6,7 @@
import os as __os
import sys as __sys

from inventory_project.settings.prod import * # noqa
from inventory_project.settings.prod import *


# SECURITY WARNING: don't run with debug turned on in production!
Expand Down Expand Up @@ -59,7 +58,7 @@
MIDDLEWARE.append('debug_toolbar.middleware.DebugToolbarMiddleware')

DEBUG_TOOLBAR_PATCH_SETTINGS = True
from debug_toolbar.settings import CONFIG_DEFAULTS as DEBUG_TOOLBAR_CONFIG # noqa
from debug_toolbar.settings import CONFIG_DEFAULTS as DEBUG_TOOLBAR_CONFIG


# Disable some more panels that will slow down the page:
Expand Down
1 change: 0 additions & 1 deletion inventory_project/settings/prod.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@
'django_tools.serve_media_app.apps.UserMediaFilesConfig',
# https://github.com/jedie/django-tools/tree/master/django_tools/model_version_protect
'django_tools.model_version_protect.apps.ModelVersionProtectConfig',
#
'inventory.apps.AppConfig',
]

Expand Down
4 changes: 1 addition & 3 deletions inventory_project/settings/tests.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import os


# ruff: noqa: F405

"""
Django settings for running tests
"""
from inventory_project.settings.prod import * # noqa
from inventory_project.settings.prod import *


ALLOWED_HOSTS = ['testserver']
Expand Down
2 changes: 1 addition & 1 deletion inventory_project/tests/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class TempImageFile:
def __init__(self, prefix='test_image', format='png', size=(1, 1)):
self.format = format
self.image_size = size
self.temp = tempfile.NamedTemporaryFile(prefix=prefix, suffix=f'.{format}')
self.temp = tempfile.NamedTemporaryFile(prefix=prefix, suffix=f'.{format}') # noqa: SIM115

def __enter__(self):
self.temp_file = self.temp.__enter__()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ collectstatic findstatic runserver
django.core
===========
check flush optimizemigration squashmigrations
compilemessages inspectdb sendtestemail startapp
createcachetable loaddata showmigrations startproject
dbshell makemessages sqlflush test
diffsettings makemigrations sqlmigrate testserver
dumpdata migrate sqlsequencereset
compilemessages inspectdb sendtestemail startapp
createcachetable loaddata showmigrations startproject
dbshell makemessages sqlflush test
diffsettings makemigrations sqlmigrate testserver
dumpdata migrate sqlsequencereset

import_export
=============
Expand All @@ -44,8 +44,8 @@ seed_data tree

manage_django_project
=====================
code_style make_messages playwright run_dev_server
coverage nox project_info update_req
code_style make_messages playwright run_dev_server
coverage nox project_info update_req
install pip_audit publish update_test_snapshot_files

reversion
Expand All @@ -59,3 +59,4 @@ initial_tags
Uncategorized
=============
alias help history macro quit set shortcuts

4 changes: 1 addition & 3 deletions inventory_project/tests/test_migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@


class TestMigrations(TestCase):
databases = [
'default',
]
databases = ('default',)

@override_settings(MIGRATION_MODULES={})
def test_missing_migrations(self):
Expand Down
27 changes: 27 additions & 0 deletions noxfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#
# https://github.com/wntrblm/nox/
# Documentation: https://nox.thea.codes/
#
import nox
from nox.sessions import Session


PYTHON_VERSIONS = ('3.14', '3.13', '3.12', '3.11')


@nox.session(
python=PYTHON_VERSIONS,
venv_backend='uv',
download_python='auto',
)
def tests(session: Session):
session.install('uv')
session.run(
'uv',
'sync',
'--all-extras',
'--python',
session.python,
env={'UV_PROJECT_ENVIRONMENT': session.virtualenv.location},
)
session.run('python', '-m', 'coverage', 'run', '--context', f'py{session.python}')
37 changes: 26 additions & 11 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,28 @@ dev = [
"django-override-storage", # https://github.com/danifus/django-override-storage
]


[tool.uv]
# Don't install bleed-edge versions of dependencies, to avoid undetected issues and supply chain attacks.
exclude-newer = "1 week"

[tool.uv.exclude-newer-package]
# Exclude own packages from the "exclude-newer" rule and
# add external packages temporarily to fix known issues or current CVEs
cryptography = "2026-04-08T12:00:00Z"
django = "2026-04-08T12:00:00Z"


[tool.cli_base.pip_audit]
strict=true
require_hashes=true
ignore-vuln=[
# e.g.: "CVE-2026-1234",
]

[tool.cli_base]
version_module_name = "inventory" # Used by "update-readme-history" pre-commit hook

[project.urls]
Documentation = "https://github.com/jedie/PyInventory"
Source = "https://github.com/jedie/PyInventory"
Expand Down Expand Up @@ -92,16 +114,6 @@ packages = ["inventory", "inventory_project"]
path = "inventory/__init__.py"


[tool.cli_base]
version_module_name = "inventory" # Used by "update-readme-history" pre-commit hook


[tool.cli_base.pip_audit]
strict=true
require_hashes=true
ignore-vuln=[]


[tool.ruff]
# https://docs.astral.sh/ruff/configuration/
line-length = 120
Expand All @@ -119,6 +131,8 @@ extend-select = [
ignore = [
"E402", # https://docs.astral.sh/ruff/rules/module-import-not-at-top-of-file/
"E731", # https://docs.astral.sh/ruff/rules/lambda-assignment/
"PLW1508", # Invalid type for environment variable default
"C408", # Unnecessary `dict()` call (rewrite as a literal)
]

[tool.ruff.lint.isort]
Expand All @@ -134,7 +148,7 @@ branch = true
parallel = true
concurrency = ["multiprocessing"]
source = ['.']
command_line = '-m unittest --verbose --locals --buffer'
command_line = '-m unittest --verbose --locals --buffer --shuffle --parallel'

[tool.coverage.report]
omit = ['.*', '*/tests/*', '*/migrations/*']
Expand Down Expand Up @@ -169,6 +183,7 @@ applied_migrations = [
"46497a1", # 2025-09-05T08:45:19+02:00
"38082ef", # 2025-10-16T10:28:32+02:00
"95a3003", # 2025-11-28T22:00:56+01:00
"3cbc419", # 2026-04-09T17:37:01+02:00
]

[manageprojects.cookiecutter_context.cookiecutter]
Expand Down
Loading
Loading