Coverage for src/backend/InvenTree/common/setting/system.py: 42%

92 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 17:47 +0000

1"""System settings definition.""" 

2 

3import json 

4import os 

5import re 

6import uuid 

7 

8from django.conf import settings as django_settings 

9from django.contrib.auth.models import Group 

10from django.core.exceptions import FieldDoesNotExist, ValidationError 

11from django.core.validators import MaxValueValidator, MinValueValidator, URLValidator 

12from django.utils.translation import gettext_lazy as _ 

13 

14from jinja2.sandbox import SandboxedEnvironment 

15 

16import build.validators 

17import common.currency 

18import common.validators 

19import order.validators 

20import report.helpers 

21from common.setting.type import InvenTreeSettingsKeyType 

22 

23 

24def validate_part_name_format(value): 

25 """Validate part name format. 

26 

27 Make sure that each template container has a field of Part Model 

28 """ 

29 # Make sure that the field_name exists in Part model 

30 from part.models import Part 

31 

32 jinja_template_regex = re.compile(r'{{.*?}}') 

33 field_name_regex = re.compile(r'(?<=part\.)[A-z]+') 

34 

35 for jinja_template in jinja_template_regex.findall(str(value)): 

36 # make sure at least one and only one field is present inside the parser 

37 field_names = field_name_regex.findall(jinja_template) 

38 if len(field_names) < 1: 

39 raise ValidationError({ 

40 'value': 'At least one field must be present inside a jinja template container i.e {{}}' 

41 }) 

42 

43 for field_name in field_names: 

44 try: 

45 Part._meta.get_field(field_name) 

46 except FieldDoesNotExist: 

47 raise ValidationError({ 

48 'value': f'{field_name} does not exist in Part Model' 

49 }) 

50 

51 # Attempt to render the template with a dummy Part instance 

52 # Use pk=1 to ensure conditional checks like {% if part.pk %} are evaluated 

53 p = Part(pk=1, name='test part', description='some test part') 

54 

55 try: 

56 SandboxedEnvironment().from_string(value).render({'part': p}) 

57 except Exception as exc: 

58 raise ValidationError({'value': str(exc)}) 

59 

60 return True 

61 

62 

63def update_instance_name(setting): 

64 """Update the first site objects name to instance name.""" 

65 if not django_settings.SITE_MULTI: 65 ↛ 68line 65 didn't jump to line 68 because the condition on line 65 was always true

66 return 

67 

68 try: 

69 from django.contrib.sites.models import Site 

70 except (ImportError, RuntimeError): 

71 # Multi-site support not enabled 

72 return 

73 

74 site_obj = Site.objects.all().order_by('id').first() 

75 site_obj.name = setting.value 

76 site_obj.save() 

77 

78 

79def update_instance_url(setting): 

80 """Update the first site objects domain to url.""" 

81 if not django_settings.SITE_MULTI: 

82 return 

83 

84 try: 

85 from django.contrib.sites.models import Site 

86 except (ImportError, RuntimeError): 

87 # Multi-site support not enabled 

88 return 

89 

90 site_obj = Site.objects.all().order_by('id').first() 

91 site_obj.domain = setting.value 

92 site_obj.save() 

93 

94 

95def settings_group_options(): 

96 """Build up group tuple for settings based on your choices.""" 

97 return [('', _('No group')), *[(str(a.id), str(a)) for a in Group.objects.all()]] 

98 

99 

100def reload_plugin_registry(setting): 

101 """When a core plugin setting is changed, reload the plugin registry.""" 

102 from common.models import logger 

103 from plugin import registry 

104 

105 logger.info("Reloading plugin registry due to change in setting '%s'", setting.key) 

106 

107 registry.reload_plugins(full_reload=True, force_reload=True, collect=True) 

108 

109 

110def enforce_mfa(setting): 

111 """Enforce multifactor authentication for all users.""" 

112 from allauth.usersessions.models import UserSession 

113 

114 from common.models import logger 

115 

116 logger.info( 

117 'Enforcing multifactor authentication for all users by signing out all sessions.' 

118 ) 

119 for session in UserSession.objects.all(): 

120 session.end() 

121 logger.info('All user sessions have been ended.') 

122 

123 

124def barcode_plugins() -> list: 

125 """Return a list of plugin choices which can be used for barcode generation.""" 

126 try: 

127 from plugin import PluginMixinEnum, registry 

128 

129 plugins = registry.with_mixin(PluginMixinEnum.BARCODE, active=True) 

130 except Exception: # pragma: no cover 

131 plugins = [] 

132 

133 return [ 

134 (plug.slug, plug.human_name) for plug in plugins if plug.has_barcode_generation 

135 ] 

136 

137 

138def default_uuid4() -> str: 

139 """Return a default UUID4 value.""" 

140 return str(uuid.uuid4()) 

141 

142 

143class BaseURLValidator(URLValidator): 

144 """Validator for the InvenTree base URL. 

145 

146 Rules: 

147 - Allow empty value 

148 - Allow value without specified TLD (top level domain) 

149 """ 

150 

151 def __init__(self, schemes=None, **kwargs): 

152 """Custom init routine.""" 

153 super().__init__(schemes, **kwargs) 

154 

155 # Override default host_re value - allow optional tld regex 

156 self.host_re = ( 

157 '(' 

158 + self.hostname_re 

159 + self.domain_re 

160 + f'({self.tld_re})?' 

161 + '|localhost)' 

162 ) 

163 

164 def __call__(self, value): 

165 """Make sure empty values pass.""" 

166 value = str(value).strip() 

167 

168 # If a configuration level value has been specified, prevent change 

169 if django_settings.SITE_URL and value != django_settings.SITE_URL: 

170 raise ValidationError(_('Site URL is locked by configuration')) 

171 

172 if len(value) == 0: 

173 pass 

174 

175 else: 

176 super().__call__(value) 

177 

178 

179class SystemSetId: 

180 """Shared system settings identifiers.""" 

181 

182 GLOBAL_WARNING = '_GLOBAL_WARNING' 

183 

184 

185SYSTEM_SETTINGS: dict[str, InvenTreeSettingsKeyType] = { 

186 'SERVER_RESTART_REQUIRED': { 

187 'name': _('Restart required'), 

188 'description': _('A setting has been changed which requires a server restart'), 

189 'default': False, 

190 'validator': bool, 

191 'hidden': True, 

192 }, 

193 '_PENDING_MIGRATIONS': { 

194 'name': _('Pending migrations'), 

195 'description': _('Number of pending database migrations'), 

196 'default': 0, 

197 'validator': int, 

198 }, 

199 SystemSetId.GLOBAL_WARNING: { 

200 'name': _('Active warning codes'), 

201 'description': _('A dict of active warning codes'), 

202 'validator': json.loads, 

203 'default': '{}', 

204 'hidden': True, 

205 }, 

206 'INVENTREE_INSTANCE_ID': { 

207 'name': _('Instance ID'), 

208 'description': _('Unique identifier for this InvenTree instance'), 

209 'default': default_uuid4, 

210 'hidden': True, 

211 }, 

212 'INVENTREE_ANNOUNCE_ID': { 

213 'name': _('Announce ID'), 

214 'description': _( 

215 'Announce the instance ID of the server in the server status info (unauthenticated)' 

216 ), 

217 'default': False, 

218 'validator': bool, 

219 }, 

220 'INVENTREE_INSTANCE': { 

221 'name': _('Server Instance Name'), 

222 'default': 'InvenTree', 

223 'description': _('String descriptor for the server instance'), 

224 'after_save': update_instance_name, 

225 }, 

226 'INVENTREE_INSTANCE_TITLE': { 

227 'name': _('Use instance name'), 

228 'description': _('Use the instance name in the title-bar'), 

229 'validator': bool, 

230 'default': False, 

231 }, 

232 'INVENTREE_RESTRICT_ABOUT': { 

233 'name': _('Restrict showing `about`'), 

234 'description': _('Show the `about` modal only to superusers'), 

235 'validator': bool, 

236 'default': False, 

237 }, 

238 'INVENTREE_SHOW_SUPERUSER_BANNER': { 

239 'name': _('Show superuser banner'), 

240 'description': _('Show a warning banner in the UI when logged in as superuser'), 

241 'validator': bool, 

242 'default': True, 

243 }, 

244 'INVENTREE_SHOW_ADMIN_BANNER': { 

245 'name': _('Show admin banner'), 

246 'description': _('Show a warning banner in the UI when logged in as admin'), 

247 'validator': bool, 

248 'default': False, 

249 }, 

250 'INVENTREE_COMPANY_NAME': { 

251 'name': _('Company name'), 

252 'description': _('Internal company name'), 

253 'default': 'My company name', 

254 }, 

255 'INVENTREE_BASE_URL': { 

256 'name': _('Base URL'), 

257 'description': _('Base URL for server instance'), 

258 'validator': BaseURLValidator(), 

259 'default': '', 

260 'after_save': update_instance_url, 

261 }, 

262 'INVENTREE_DEFAULT_CURRENCY': { 

263 'name': _('Default Currency'), 

264 'description': _('Select base currency for pricing calculations'), 

265 'default': 'USD', 

266 'choices': common.currency.currency_code_mappings, 

267 'after_save': common.currency.after_change_currency, 

268 }, 

269 'CURRENCY_CODES': { 

270 'name': _('Supported Currencies'), 

271 'description': _('List of supported currency codes'), 

272 'default': common.currency.currency_codes_default_list(), 

273 'validator': common.currency.validate_currency_codes, 

274 'after_save': common.currency.after_change_currency, 

275 }, 

276 'CURRENCY_UPDATE_INTERVAL': { 

277 'name': _('Currency Update Interval'), 

278 'description': _('How often to update exchange rates (set to zero to disable)'), 

279 'default': 1, 

280 'units': _('days'), 

281 'validator': [int, MinValueValidator(0)], 

282 }, 

283 'CURRENCY_UPDATE_PLUGIN': { 

284 'name': _('Currency Update Plugin'), 

285 'description': _('Currency update plugin to use'), 

286 'choices': common.currency.currency_exchange_plugins, 

287 'default': 'inventreecurrencyexchange', 

288 }, 

289 'INVENTREE_UPLOAD_MAX_SIZE': { 

290 'name': _('Upload Size Limit'), 

291 'description': _('Maximum allowable upload size for images and files'), 

292 'units': 'MB', 

293 'default': 10, 

294 'validator': [int, MinValueValidator(1)], 

295 }, 

296 'INVENTREE_STRICT_URLS': { 

297 'name': _('Strict URL Validation'), 

298 'description': _('Require schema specification when validating URLs'), 

299 'validator': bool, 

300 'default': True, 

301 }, 

302 'INVENTREE_UPDATE_CHECK_INTERVAL': { 

303 'name': _('Update Check Interval'), 

304 'description': _('How often to check for updates (set to zero to disable)'), 

305 'validator': [int, MinValueValidator(0)], 

306 'default': 7, 

307 'units': _('days'), 

308 }, 

309 'INVENTREE_BACKUP_ENABLE': { 

310 'name': _('Automatic Backup'), 

311 'description': _('Enable automatic backup of database and media files'), 

312 'validator': bool, 

313 'default': False, 

314 }, 

315 'INVENTREE_BACKUP_DAYS': { 

316 'name': _('Auto Backup Interval'), 

317 'description': _('Specify number of days between automated backup events'), 

318 'validator': [int, MinValueValidator(1)], 

319 'default': 1, 

320 'units': _('days'), 

321 }, 

322 'INVENTREE_DELETE_TASKS_DAYS': { 

323 'name': _('Task Deletion Interval'), 

324 'description': _( 

325 'Background task results will be deleted after specified number of days' 

326 ), 

327 'default': 30, 

328 'units': _('days'), 

329 'validator': [int, MinValueValidator(7)], 

330 }, 

331 'INVENTREE_DELETE_ERRORS_DAYS': { 

332 'name': _('Error Log Deletion Interval'), 

333 'description': _('Error logs will be deleted after specified number of days'), 

334 'default': 30, 

335 'units': _('days'), 

336 'validator': [int, MinValueValidator(7)], 

337 }, 

338 'INVENTREE_DELETE_NOTIFICATIONS_DAYS': { 

339 'name': _('Notification Deletion Interval'), 

340 'description': _( 

341 'User notifications will be deleted after specified number of days' 

342 ), 

343 'default': 30, 

344 'units': _('days'), 

345 'validator': [int, MinValueValidator(7)], 

346 }, 

347 'INVENTREE_DELETE_EMAIL_DAYS': { 

348 'name': _('Email Deletion Interval'), 

349 'description': _( 

350 'Email messages will be deleted after specified number of days' 

351 ), 

352 'default': 30, 

353 'units': _('days'), 

354 'validator': [int, MinValueValidator(7)], 

355 }, 

356 'INVENTREE_PROTECT_EMAIL_LOG': { 

357 'name': _('Protect Email Log'), 

358 'description': _('Prevent deletion of email log entries'), 

359 'default': False, 

360 'validator': bool, 

361 }, 

362 'BARCODE_ENABLE': { 

363 'name': _('Barcode Support'), 

364 'description': _('Enable barcode scanner support in the web interface'), 

365 'default': True, 

366 'validator': bool, 

367 }, 

368 'BARCODE_STORE_RESULTS': { 

369 'name': _('Store Barcode Results'), 

370 'description': _('Store barcode scan results in the database'), 

371 'default': False, 

372 'validator': bool, 

373 }, 

374 'BARCODE_RESULTS_MAX_NUM': { 

375 'name': _('Barcode Scans Maximum Count'), 

376 'description': _('Maximum number of barcode scan results to store'), 

377 'default': 100, 

378 'validator': [int, MinValueValidator(1)], 

379 }, 

380 'BARCODE_INPUT_DELAY': { 

381 'name': _('Barcode Input Delay'), 

382 'description': _('Barcode input processing delay time'), 

383 'default': 50, 

384 'validator': [int, MinValueValidator(1)], 

385 'units': 'ms', 

386 }, 

387 'BARCODE_WEBCAM_SUPPORT': { 

388 'name': _('Barcode Webcam Support'), 

389 'description': _('Allow barcode scanning via webcam in browser'), 

390 'default': True, 

391 'validator': bool, 

392 }, 

393 'BARCODE_SHOW_TEXT': { 

394 'name': _('Barcode Show Data'), 

395 'description': _('Display barcode data in browser as text'), 

396 'default': False, 

397 'validator': bool, 

398 }, 

399 'BARCODE_GENERATION_PLUGIN': { 

400 'name': _('Barcode Generation Plugin'), 

401 'description': _('Plugin to use for internal barcode data generation'), 

402 'choices': barcode_plugins, 

403 'default': 'inventreebarcode', 

404 }, 

405 'PART_ENABLE_LOCKING': { 

406 'name': _('Part Locking'), 

407 'description': _('Enable locking of parts to prevent modification'), 

408 'validator': bool, 

409 'default': True, 

410 }, 

411 'PART_ENABLE_REVISION': { 

412 'name': _('Part Revisions'), 

413 'description': _('Enable revision field for Part'), 

414 'validator': bool, 

415 'default': True, 

416 }, 

417 'PART_REVISION_ASSEMBLY_ONLY': { 

418 'name': _('Assembly Revision Only'), 

419 'description': _('Only allow revisions for assembly parts'), 

420 'validator': bool, 

421 'default': False, 

422 }, 

423 'PART_ALLOW_DELETE_FROM_ASSEMBLY': { 

424 'name': _('Allow Deletion from Assembly'), 

425 'description': _('Allow deletion of parts which are used in an assembly'), 

426 'validator': bool, 

427 'default': False, 

428 }, 

429 'PART_IPN_REGEX': { 

430 'name': _('IPN Regex'), 

431 'description': _('Regular expression pattern for matching Part IPN'), 

432 }, 

433 'PART_ALLOW_DUPLICATE_IPN': { 

434 'name': _('Allow Duplicate IPN'), 

435 'description': _('Allow multiple parts to share the same IPN'), 

436 'default': True, 

437 'validator': bool, 

438 }, 

439 'PART_ALLOW_EDIT_IPN': { 

440 'name': _('Allow Editing IPN'), 

441 'description': _('Allow changing the IPN value while editing a part'), 

442 'default': True, 

443 'validator': bool, 

444 }, 

445 'PART_COPY_BOM': { 

446 'name': _('Copy Part BOM Data'), 

447 'description': _('Copy BOM data by default when duplicating a part'), 

448 'default': True, 

449 'validator': bool, 

450 }, 

451 'PART_COPY_PARAMETERS': { 

452 'name': _('Copy Part Parameter Data'), 

453 'description': _('Copy parameter data by default when duplicating a part'), 

454 'default': True, 

455 'validator': bool, 

456 }, 

457 'PART_COPY_TESTS': { 

458 'name': _('Copy Part Test Data'), 

459 'description': _('Copy test data by default when duplicating a part'), 

460 'default': True, 

461 'validator': bool, 

462 }, 

463 'PART_CATEGORY_PARAMETERS': { 

464 'name': _('Copy Category Parameter Templates'), 

465 'description': _('Copy category parameter templates when creating a part'), 

466 'default': True, 

467 'validator': bool, 

468 }, 

469 'PART_TEMPLATE': { 

470 'name': _('Template'), 

471 'description': _('Parts are templates by default'), 

472 'default': False, 

473 'validator': bool, 

474 }, 

475 'PART_ASSEMBLY': { 

476 'name': _('Assembly'), 

477 'description': _('Parts can be assembled from other components by default'), 

478 'default': False, 

479 'validator': bool, 

480 }, 

481 'PART_COMPONENT': { 

482 'name': _('Component'), 

483 'description': _('Parts can be used as sub-components by default'), 

484 'default': True, 

485 'validator': bool, 

486 }, 

487 'PART_PURCHASEABLE': { 

488 'name': _('Purchaseable'), 

489 'description': _('Parts are purchaseable by default'), 

490 'default': True, 

491 'validator': bool, 

492 }, 

493 'PART_SALABLE': { 

494 'name': _('Salable'), 

495 'description': _('Parts are salable by default'), 

496 'default': False, 

497 'validator': bool, 

498 }, 

499 'PART_TRACKABLE': { 

500 'name': _('Trackable'), 

501 'description': _('Parts are trackable by default'), 

502 'default': False, 

503 'validator': bool, 

504 }, 

505 'PART_VIRTUAL': { 

506 'name': _('Virtual'), 

507 'description': _('Parts are virtual by default'), 

508 'default': False, 

509 'validator': bool, 

510 }, 

511 'PART_SHOW_RELATED': { 

512 'name': _('Show related parts'), 

513 'description': _('Display related parts for a part'), 

514 'default': True, 

515 'validator': bool, 

516 }, 

517 'PART_CREATE_INITIAL': { 

518 'name': _('Initial Stock Data'), 

519 'description': _('Allow creation of initial stock when adding a new part'), 

520 'default': False, 

521 'validator': bool, 

522 }, 

523 'PART_CREATE_SUPPLIER': { 

524 'name': _('Initial Supplier Data'), 

525 'description': _( 

526 'Allow creation of initial supplier data when adding a new part' 

527 ), 

528 'default': True, 

529 'validator': bool, 

530 }, 

531 'PART_NAME_FORMAT': { 

532 'name': _('Part Name Display Format'), 

533 'description': _('Format to display the part name'), 

534 'default': "{{ part.IPN if part.IPN }}{{ ' | ' if part.IPN }}{{ part.name }}{{ ' | ' if part.revision }}" 

535 '{{ part.revision if part.revision }}', 

536 'validator': validate_part_name_format, 

537 }, 

538 'PART_CATEGORY_DEFAULT_ICON': { 

539 'name': _('Part Category Default Icon'), 

540 'description': _('Part category default icon (empty means no icon)'), 

541 'default': '', 

542 'validator': common.validators.validate_icon, 

543 }, 

544 'PRICING_DECIMAL_PLACES_MIN': { 

545 'name': _('Minimum Pricing Decimal Places'), 

546 'description': _( 

547 'Minimum number of decimal places to display when rendering pricing data' 

548 ), 

549 'default': 0, 

550 'validator': [ 

551 int, 

552 MinValueValidator(0), 

553 MaxValueValidator(4), 

554 common.validators.validate_decimal_places_min, 

555 ], 

556 }, 

557 'PRICING_DECIMAL_PLACES': { 

558 'name': _('Maximum Pricing Decimal Places'), 

559 'description': _( 

560 'Maximum number of decimal places to display when rendering pricing data' 

561 ), 

562 'default': 6, 

563 'validator': [ 

564 int, 

565 MinValueValidator(2), 

566 MaxValueValidator(6), 

567 common.validators.validate_decimal_places_max, 

568 ], 

569 }, 

570 'PRICING_USE_SUPPLIER_PRICING': { 

571 'name': _('Use Supplier Pricing'), 

572 'description': _( 

573 'Include supplier price breaks in overall pricing calculations' 

574 ), 

575 'default': True, 

576 'validator': bool, 

577 }, 

578 'PRICING_PURCHASE_HISTORY_OVERRIDES_SUPPLIER': { 

579 'name': _('Purchase History Override'), 

580 'description': _( 

581 'Historical purchase order pricing overrides supplier price breaks' 

582 ), 

583 'default': False, 

584 'validator': bool, 

585 }, 

586 'PRICING_USE_STOCK_PRICING': { 

587 'name': _('Use Stock Item Pricing'), 

588 'description': _( 

589 'Use pricing from manually entered stock data for pricing calculations' 

590 ), 

591 'default': True, 

592 'validator': bool, 

593 }, 

594 'PRICING_STOCK_ITEM_AGE_DAYS': { 

595 'name': _('Stock Item Pricing Age'), 

596 'description': _( 

597 'Exclude stock items older than this number of days from pricing calculations' 

598 ), 

599 'default': 0, 

600 'units': _('days'), 

601 'validator': [int, MinValueValidator(0)], 

602 }, 

603 'PRICING_USE_VARIANT_PRICING': { 

604 'name': _('Use Variant Pricing'), 

605 'description': _('Include variant pricing in overall pricing calculations'), 

606 'default': True, 

607 'validator': bool, 

608 }, 

609 'PRICING_ACTIVE_VARIANTS': { 

610 'name': _('Active Variants Only'), 

611 'description': _( 

612 'Only use active variant parts for calculating variant pricing' 

613 ), 

614 'default': False, 

615 'validator': bool, 

616 }, 

617 'PRICING_AUTO_UPDATE': { 

618 'name': _('Auto Update Pricing'), 

619 'description': _( 

620 'Automatically update part pricing when internal data changes' 

621 ), 

622 'default': True, 

623 'validator': bool, 

624 }, 

625 'PRICING_UPDATE_DAYS': { 

626 'name': _('Pricing Rebuild Interval'), 

627 'description': _('Number of days before part pricing is automatically updated'), 

628 'units': _('days'), 

629 'default': 30, 

630 'validator': [int, MinValueValidator(0)], 

631 }, 

632 'PART_INTERNAL_PRICE': { 

633 'name': _('Internal Prices'), 

634 'description': _('Enable internal prices for parts'), 

635 'default': False, 

636 'validator': bool, 

637 }, 

638 'PART_BOM_USE_INTERNAL_PRICE': { 

639 'name': _('Internal Price Override'), 

640 'description': _( 

641 'If available, internal prices override price range calculations' 

642 ), 

643 'default': False, 

644 'validator': bool, 

645 }, 

646 'PART_BOM_ALLOW_ZERO_QUANTITY': { 

647 'name': _('Allow BOM Zero Quantity'), 

648 'description': _( 

649 'Accept a zero quantity for BOM item for part. Enables using setup quantity to define a quantity required per build, independent of build quantity' 

650 ), 

651 'default': False, 

652 'validator': bool, 

653 }, 

654 'LABEL_ENABLE': { 

655 'name': _('Enable label printing'), 

656 'description': _('Enable label printing from the web interface'), 

657 'default': True, 

658 'validator': bool, 

659 }, 

660 'LABEL_DPI': { 

661 'name': _('Label Image DPI'), 

662 'description': _( 

663 'DPI resolution when generating image files to supply to label printing plugins' 

664 ), 

665 'default': 300, 

666 'validator': [int, MinValueValidator(100)], 

667 }, 

668 'REPORT_ENABLE': { 

669 'name': _('Enable Reports'), 

670 'description': _('Enable generation of reports'), 

671 'default': False, 

672 'validator': bool, 

673 }, 

674 'REPORT_DEBUG_MODE': { 

675 'name': _('Debug Mode'), 

676 'description': _('Generate reports in debug mode (HTML output)'), 

677 'default': False, 

678 'validator': bool, 

679 }, 

680 'REPORT_FETCH_URLS': { 

681 'name': _('Report URL Fetching'), 

682 'description': _('Allow fetching of remote URLs when generating reports'), 

683 'default': False, 

684 'validator': bool, 

685 }, 

686 'REPORT_LOG_ERRORS': { 

687 'name': _('Log Report Errors'), 

688 'description': _('Log errors which occur when generating reports'), 

689 'default': False, 

690 'validator': bool, 

691 }, 

692 'REPORT_DEFAULT_PAGE_SIZE': { 

693 'name': _('Page Size'), 

694 'description': _('Default page size for PDF reports'), 

695 'default': 'A4', 

696 'choices': report.helpers.report_page_size_options, 

697 }, 

698 'PARAMETER_ENFORCE_UNITS': { 

699 'name': _('Enforce Parameter Units'), 

700 'description': _( 

701 'If units are provided, parameter values must match the specified units' 

702 ), 

703 'default': True, 

704 'validator': bool, 

705 }, 

706 'SERIAL_NUMBER_GLOBALLY_UNIQUE': { 

707 'name': _('Globally Unique Serials'), 

708 'description': _('Serial numbers for stock items must be globally unique'), 

709 'default': False, 

710 'validator': bool, 

711 }, 

712 'STOCK_DELETE_DEPLETED_DEFAULT': { 

713 'name': _('Delete Depleted Stock'), 

714 'description': _('Determines default behavior when a stock item is depleted'), 

715 'default': True, 

716 'validator': bool, 

717 }, 

718 'STOCK_ALLOW_EDIT_SERIAL': { 

719 'name': _('Allow Edit Serial Number'), 

720 'description': _('Allow editing of serial number for stock items'), 

721 'default': True, 

722 'validator': bool, 

723 }, 

724 'STOCK_ALLOW_DELETE_SERIALIZED': { 

725 'name': _('Delete Serialized Stock'), 

726 'description': _('Allow deletion of stock items which have a serial number'), 

727 'default': True, 

728 'validator': bool, 

729 }, 

730 'STOCK_BATCH_CODE_TEMPLATE': { 

731 'name': _('Batch Code Template'), 

732 'description': _('Template for generating default batch codes for stock items'), 

733 'default': '', 

734 }, 

735 'STOCK_ENABLE_EXPIRY': { 

736 'name': _('Stock Expiry'), 

737 'description': _('Enable stock expiry functionality'), 

738 'default': False, 

739 'validator': bool, 

740 }, 

741 'STOCK_ALLOW_EXPIRED_SALE': { 

742 'name': _('Sell Expired Stock'), 

743 'description': _('Allow sale of expired stock'), 

744 'default': False, 

745 'validator': bool, 

746 }, 

747 'STOCK_STALE_DAYS': { 

748 'name': _('Stock Stale Time'), 

749 'description': _( 

750 'Number of days stock items are considered stale before expiring' 

751 ), 

752 'default': 0, 

753 'units': _('days'), 

754 'validator': [int], 

755 }, 

756 'STOCK_ALLOW_EXPIRED_BUILD': { 

757 'name': _('Build Expired Stock'), 

758 'description': _('Allow building with expired stock'), 

759 'default': False, 

760 'validator': bool, 

761 }, 

762 'STOCK_OWNERSHIP_CONTROL': { 

763 'name': _('Stock Ownership Control'), 

764 'description': _('Enable ownership control over stock locations and items'), 

765 'default': False, 

766 'validator': bool, 

767 }, 

768 'STOCK_LOCATION_DEFAULT_ICON': { 

769 'name': _('Stock Location Default Icon'), 

770 'description': _('Stock location default icon (empty means no icon)'), 

771 'default': '', 

772 'validator': common.validators.validate_icon, 

773 }, 

774 'STOCK_SHOW_INSTALLED_ITEMS': { 

775 'name': _('Show Installed Stock Items'), 

776 'description': _('Display installed stock items in stock tables'), 

777 'default': False, 

778 'validator': bool, 

779 }, 

780 'STOCK_ENFORCE_BOM_INSTALLATION': { 

781 'name': _('Check BOM when installing items'), 

782 'description': _( 

783 'Installed stock items must exist in the BOM for the parent part' 

784 ), 

785 'default': True, 

786 'validator': bool, 

787 }, 

788 'STOCK_ALLOW_OUT_OF_STOCK_TRANSFER': { 

789 'name': _('Allow Out of Stock Transfer'), 

790 'description': _( 

791 'Allow stock items which are not in stock to be transferred between stock locations' 

792 ), 

793 'default': False, 

794 'validator': bool, 

795 }, 

796 'BUILDORDER_REFERENCE_PATTERN': { 

797 'name': _('Build Order Reference Pattern'), 

798 'description': _('Required pattern for generating Build Order reference field'), 

799 'default': 'BO-{ref:04d}', 

800 'validator': build.validators.validate_build_order_reference_pattern, 

801 }, 

802 'BUILDORDER_REQUIRE_RESPONSIBLE': { 

803 'name': _('Require Responsible Owner'), 

804 'description': _('A responsible owner must be assigned to each order'), 

805 'default': False, 

806 'validator': bool, 

807 }, 

808 'BUILDORDER_REQUIRE_ACTIVE_PART': { 

809 'name': _('Require Active Part'), 

810 'description': _('Prevent build order creation for inactive parts'), 

811 'default': False, 

812 'validator': bool, 

813 }, 

814 'BUILDORDER_REQUIRE_LOCKED_PART': { 

815 'name': _('Require Locked Part'), 

816 'description': _('Prevent build order creation for unlocked parts'), 

817 'default': False, 

818 'validator': bool, 

819 }, 

820 'BUILDORDER_REQUIRE_VALID_BOM': { 

821 'name': _('Require Valid BOM'), 

822 'description': _('Prevent build order creation unless BOM has been validated'), 

823 'default': False, 

824 'validator': bool, 

825 }, 

826 'BUILDORDER_REQUIRE_CLOSED_CHILDS': { 

827 'name': _('Require Closed Child Orders'), 

828 'description': _( 

829 'Prevent build order completion until all child orders are closed' 

830 ), 

831 'default': False, 

832 'validator': bool, 

833 }, 

834 'BUILDORDER_EXTERNAL_BUILDS': { 

835 'name': _('External Build Orders'), 

836 'description': _('Enable external build order functionality'), 

837 'default': False, 

838 'validator': bool, 

839 }, 

840 'BUILDORDER_EXTERNAL_REQUIRED': { 

841 'name': _('Require External Build Orders'), 

842 'description': _( 

843 'Require an external build order when ordering assembled parts from an external supplier' 

844 ), 

845 'default': False, 

846 'validator': bool, 

847 }, 

848 'PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS': { 

849 'name': _('Block Until Tests Pass'), 

850 'description': _( 

851 'Prevent build outputs from being completed until all required tests pass' 

852 ), 

853 'default': False, 

854 'validator': bool, 

855 }, 

856 'RETURNORDER_ENABLED': { 

857 'name': _('Enable Return Orders'), 

858 'description': _('Enable return order functionality in the user interface'), 

859 'validator': bool, 

860 'default': False, 

861 }, 

862 'RETURNORDER_REFERENCE_PATTERN': { 

863 'name': _('Return Order Reference Pattern'), 

864 'description': _( 

865 'Required pattern for generating Return Order reference field' 

866 ), 

867 'default': 'RMA-{ref:04d}', 

868 'validator': order.validators.validate_return_order_reference_pattern, 

869 }, 

870 'RETURNORDER_REQUIRE_RESPONSIBLE': { 

871 'name': _('Require Responsible Owner'), 

872 'description': _('A responsible owner must be assigned to each order'), 

873 'default': False, 

874 'validator': bool, 

875 }, 

876 'RETURNORDER_EDIT_COMPLETED_ORDERS': { 

877 'name': _('Edit Completed Return Orders'), 

878 'description': _( 

879 'Allow editing of return orders after they have been completed' 

880 ), 

881 'default': False, 

882 'validator': bool, 

883 }, 

884 'SALESORDER_REFERENCE_PATTERN': { 

885 'name': _('Sales Order Reference Pattern'), 

886 'description': _('Required pattern for generating Sales Order reference field'), 

887 'default': 'SO-{ref:04d}', 

888 'validator': order.validators.validate_sales_order_reference_pattern, 

889 }, 

890 'SALESORDER_REQUIRE_RESPONSIBLE': { 

891 'name': _('Require Responsible Owner'), 

892 'description': _('A responsible owner must be assigned to each order'), 

893 'default': False, 

894 'validator': bool, 

895 }, 

896 'SALESORDER_DEFAULT_SHIPMENT': { 

897 'name': _('Sales Order Default Shipment'), 

898 'description': _('Enable creation of default shipment with sales orders'), 

899 'default': False, 

900 'validator': bool, 

901 }, 

902 'SALESORDER_EDIT_COMPLETED_ORDERS': { 

903 'name': _('Edit Completed Sales Orders'), 

904 'description': _( 

905 'Allow editing of sales orders after they have been shipped or completed' 

906 ), 

907 'default': False, 

908 'validator': bool, 

909 }, 

910 'SALESORDER_SHIPMENT_REQUIRES_CHECK': { 

911 'name': _('Shipment Requires Checking'), 

912 'description': _( 

913 'Prevent completion of shipments until items have been checked' 

914 ), 

915 'default': False, 

916 'validator': bool, 

917 }, 

918 'SALESORDER_SHIP_COMPLETE': { 

919 'name': _('Mark Shipped Orders as Complete'), 

920 'description': _( 

921 'Sales orders marked as shipped will automatically be completed, bypassing the "shipped" status' 

922 ), 

923 'default': False, 

924 'validator': bool, 

925 }, 

926 'TRANSFERORDER_ENABLED': { 

927 'name': _('Enable Transfer Orders'), 

928 'description': _('Enable transfer order functionality in the user interface'), 

929 'validator': bool, 

930 'default': False, 

931 }, 

932 'TRANSFERORDER_REFERENCE_PATTERN': { 

933 'name': _('Transfer Order Reference Pattern'), 

934 'description': _( 

935 'Required pattern for generating Transfer Order reference field' 

936 ), 

937 'default': 'TO-{ref:04d}', 

938 'validator': order.validators.validate_transfer_order_reference_pattern, 

939 }, 

940 'TRANSFERORDER_REQUIRE_RESPONSIBLE': { 

941 'name': _('Require Responsible Owner'), 

942 'description': _('A responsible owner must be assigned to each order'), 

943 'default': False, 

944 'validator': bool, 

945 }, 

946 'SALESORDER_BLOCK_INCOMPLETE_ITEM_TESTS': { 

947 'name': _('Block Incomplete Item Tests'), 

948 'description': _( 

949 'Prevent allocation of stock items to sales orders if required item tests are incomplete' 

950 ), 

951 'default': False, 

952 'validator': bool, 

953 }, 

954 'PURCHASEORDER_REFERENCE_PATTERN': { 

955 'name': _('Purchase Order Reference Pattern'), 

956 'description': _( 

957 'Required pattern for generating Purchase Order reference field' 

958 ), 

959 'default': 'PO-{ref:04d}', 

960 'validator': order.validators.validate_purchase_order_reference_pattern, 

961 }, 

962 'PURCHASEORDER_REQUIRE_RESPONSIBLE': { 

963 'name': _('Require Responsible Owner'), 

964 'description': _('A responsible owner must be assigned to each order'), 

965 'default': False, 

966 'validator': bool, 

967 }, 

968 'PURCHASEORDER_EDIT_COMPLETED_ORDERS': { 

969 'name': _('Edit Completed Purchase Orders'), 

970 'description': _( 

971 'Allow editing of purchase orders after they have been shipped or completed' 

972 ), 

973 'default': False, 

974 'validator': bool, 

975 }, 

976 'PURCHASEORDER_CONVERT_CURRENCY': { 

977 'name': _('Convert Currency'), 

978 'description': _('Convert item value to base currency when receiving stock'), 

979 'default': False, 

980 'validator': bool, 

981 }, 

982 'PURCHASEORDER_AUTO_COMPLETE': { 

983 'name': _('Auto Complete Purchase Orders'), 

984 'description': _( 

985 'Automatically mark purchase orders as complete when all line items are received' 

986 ), 

987 'default': True, 

988 'validator': bool, 

989 }, 

990 # login / SSO 

991 'LOGIN_ENABLE_PWD_FORGOT': { 

992 'name': _('Enable password forgot'), 

993 'description': _('Enable password forgot function on the login pages'), 

994 'default': True, 

995 'validator': bool, 

996 }, 

997 'LOGIN_ENABLE_REG': { 

998 'name': _('Enable registration'), 

999 'description': _('Enable self-registration for users on the login pages'), 

1000 'default': False, 

1001 'validator': bool, 

1002 }, 

1003 'LOGIN_ENABLE_SSO': { 

1004 'name': _('Enable SSO'), 

1005 'description': _('Enable SSO on the login pages'), 

1006 'default': False, 

1007 'validator': bool, 

1008 }, 

1009 'LOGIN_ENABLE_SSO_REG': { 

1010 'name': _('Enable SSO registration'), 

1011 'description': _( 

1012 'Enable self-registration via SSO for users on the login pages' 

1013 ), 

1014 'default': False, 

1015 'validator': bool, 

1016 }, 

1017 'LOGIN_ENABLE_SSO_GROUP_SYNC': { 

1018 'name': _('Enable SSO group sync'), 

1019 'description': _( 

1020 'Enable synchronizing InvenTree groups with groups provided by the IdP' 

1021 ), 

1022 'default': False, 

1023 'validator': bool, 

1024 }, 

1025 'SSO_GROUP_KEY': { 

1026 'name': _('SSO group key'), 

1027 'description': _('The name of the groups claim attribute provided by the IdP'), 

1028 'default': 'groups', 

1029 'validator': str, 

1030 }, 

1031 'SSO_GROUP_MAP': { 

1032 'name': _('SSO group map'), 

1033 'description': _( 

1034 'A mapping from SSO groups to local InvenTree groups. If the local group does not exist, it will be created.' 

1035 ), 

1036 'validator': json.loads, 

1037 'default': '{}', 

1038 }, 

1039 'SSO_REMOVE_GROUPS': { 

1040 'name': _('Remove groups outside of SSO'), 

1041 'description': _( 

1042 'Whether groups assigned to the user should be removed if they are not backend by the IdP. Disabling this setting might cause security issues' 

1043 ), 

1044 'default': True, 

1045 'validator': bool, 

1046 }, 

1047 'LOGIN_MAIL_REQUIRED': { 

1048 'name': _('Email required'), 

1049 'description': _('Require user to supply mail on signup'), 

1050 'default': False, 

1051 'validator': bool, 

1052 }, 

1053 'LOGIN_SIGNUP_SSO_AUTO': { 

1054 'name': _('Auto-fill SSO users'), 

1055 'description': _('Automatically fill out user-details from SSO account-data'), 

1056 'default': True, 

1057 'validator': bool, 

1058 }, 

1059 'LOGIN_SIGNUP_MAIL_TWICE': { 

1060 'name': _('Mail twice'), 

1061 'description': _('On signup ask users twice for their mail'), 

1062 'default': False, 

1063 'validator': bool, 

1064 }, 

1065 'LOGIN_SIGNUP_PWD_TWICE': { 

1066 'name': _('Password twice'), 

1067 'description': _('On signup ask users twice for their password'), 

1068 'default': True, 

1069 'validator': bool, 

1070 }, 

1071 'LOGIN_SIGNUP_MAIL_RESTRICTION': { 

1072 'name': _('Allowed domains'), 

1073 'description': _( 

1074 'Restrict signup to certain domains (comma-separated, starting with @)' 

1075 ), 

1076 'default': '', 

1077 'before_save': common.validators.validate_email_domains, 

1078 }, 

1079 'SIGNUP_GROUP': { 

1080 'name': _('Group on signup'), 

1081 'description': _( 

1082 'Group to which new users are assigned on registration. If SSO group sync is enabled, this group is only set if no group can be assigned from the IdP.' 

1083 ), 

1084 'default': '', 

1085 'choices': settings_group_options, 

1086 }, 

1087 'LOGIN_ENFORCE_MFA': { 

1088 'name': _('Enforce MFA'), 

1089 'description': _('Users must use multifactor security.'), 

1090 'default': False, 

1091 'validator': bool, 

1092 'confirm': True, 

1093 'confirm_text': _( 

1094 'Enabling this setting will require all users to set up multifactor authentication. All sessions will be disconnected immediately.' 

1095 ), 

1096 'after_save': enforce_mfa, 

1097 }, 

1098 'PLUGIN_ON_STARTUP': { 

1099 'name': _('Check plugins on startup'), 

1100 'description': _( 

1101 'Check that all plugins are installed on startup - enable in container environments' 

1102 ), 

1103 'default': str(os.getenv('INVENTREE_DOCKER', 'False')).lower() in ['1', 'true'], 

1104 'validator': bool, 

1105 'requires_restart': True, 

1106 }, 

1107 'PLUGIN_UPDATE_CHECK': { 

1108 'name': _('Check for plugin updates'), 

1109 'description': _('Enable periodic checks for updates to installed plugins'), 

1110 'default': True, 

1111 'validator': bool, 

1112 }, 

1113 # Settings for plugin mixin features 

1114 'ENABLE_PLUGINS_URL': { 

1115 'name': _('Enable URL integration'), 

1116 'description': _('Enable plugins to add URL routes'), 

1117 'default': False, 

1118 'validator': bool, 

1119 'after_save': reload_plugin_registry, 

1120 }, 

1121 'ENABLE_PLUGINS_NAVIGATION': { 

1122 'name': _('Enable navigation integration'), 

1123 'description': _('Enable plugins to integrate into navigation'), 

1124 'default': False, 

1125 'validator': bool, 

1126 'after_save': reload_plugin_registry, 

1127 }, 

1128 'ENABLE_PLUGINS_APP': { 

1129 'name': _('Enable app integration'), 

1130 'description': _('Enable plugins to add apps'), 

1131 'default': False, 

1132 'validator': bool, 

1133 'after_save': reload_plugin_registry, 

1134 }, 

1135 'ENABLE_PLUGINS_SCHEDULE': { 

1136 'name': _('Enable schedule integration'), 

1137 'description': _('Enable plugins to run scheduled tasks'), 

1138 'default': False, 

1139 'validator': bool, 

1140 'after_save': reload_plugin_registry, 

1141 }, 

1142 'ENABLE_PLUGINS_EVENTS': { 

1143 'name': _('Enable event integration'), 

1144 'description': _('Enable plugins to respond to internal events'), 

1145 'default': False, 

1146 'validator': bool, 

1147 'after_save': reload_plugin_registry, 

1148 }, 

1149 'ENABLE_PLUGINS_INTERFACE': { 

1150 'name': _('Enable interface integration'), 

1151 'description': _('Enable plugins to integrate into the user interface'), 

1152 'default': False, 

1153 'validator': bool, 

1154 'after_save': reload_plugin_registry, 

1155 }, 

1156 'ENABLE_PLUGINS_MAILS': { 

1157 'name': _('Enable mail integration'), 

1158 'description': _('Enable plugins to process outgoing/incoming mails'), 

1159 'default': False, 

1160 'validator': bool, 

1161 'after_save': reload_plugin_registry, 

1162 }, 

1163 'PROJECT_CODES_ENABLED': { 

1164 'name': _('Enable project codes'), 

1165 'description': _('Enable project codes for tracking projects'), 

1166 'default': False, 

1167 'validator': bool, 

1168 }, 

1169 'STOCKTAKE_ENABLE': { 

1170 'name': _('Enable Stocktake'), 

1171 'description': _( 

1172 'Enable functionality for recording historical stock levels and value' 

1173 ), 

1174 'validator': bool, 

1175 'default': False, 

1176 }, 

1177 'STOCKTAKE_EXCLUDE_EXTERNAL': { 

1178 'name': _('Exclude External Locations'), 

1179 'description': _( 

1180 'Exclude stock items in external locations from stocktake calculations' 

1181 ), 

1182 'validator': bool, 

1183 'default': False, 

1184 }, 

1185 'STOCKTAKE_AUTO_DAYS': { 

1186 'name': _('Automatic Stocktake Period'), 

1187 'description': _('Number of days between automatic stocktake recording'), 

1188 'validator': [int, MinValueValidator(1)], 

1189 'default': 7, 

1190 'units': _('days'), 

1191 }, 

1192 'STOCKTAKE_DELETE_OLD_ENTRIES': { 

1193 'name': _('Delete Old Stocktake Entries'), 

1194 'description': _( 

1195 'Delete stocktake entries older than the specified number of days' 

1196 ), 

1197 'default': False, 

1198 'validator': bool, 

1199 }, 

1200 'STOCKTAKE_DELETE_DAYS': { 

1201 'name': _('Stocktake Deletion Interval'), 

1202 'description': _( 

1203 'Stocktake entries will be deleted after specified number of days' 

1204 ), 

1205 'default': 365, 

1206 'units': _('days'), 

1207 'validator': [int, MinValueValidator(30)], 

1208 }, 

1209 'STOCK_TRACKING_DELETE_OLD_ENTRIES': { 

1210 'name': _('Delete Old Stock Tracking Entries'), 

1211 'description': _( 

1212 'Delete stock tracking entries older than the specified number of days' 

1213 ), 

1214 'default': False, 

1215 'validator': bool, 

1216 }, 

1217 'STOCK_TRACKING_DELETE_DAYS': { 

1218 'name': _('Stock Tracking Deletion Interval'), 

1219 'description': _( 

1220 'Stock tracking entries will be deleted after specified number of days' 

1221 ), 

1222 'default': 365, 

1223 'units': _('days'), 

1224 'validator': [int, MinValueValidator(30)], 

1225 }, 

1226 'DISPLAY_FULL_NAMES': { 

1227 'name': _('Display Users full names'), 

1228 'description': _('Display Users full names instead of usernames'), 

1229 'default': False, 

1230 'validator': bool, 

1231 }, 

1232 'DISPLAY_PROFILE_INFO': { 

1233 'name': _('Display User Profiles'), 

1234 'description': _('Display Users Profiles on their profile page'), 

1235 'default': True, 

1236 'validator': bool, 

1237 }, 

1238 'WEEK_STARTS_ON': { 

1239 'name': _('Week Starts On'), 

1240 'description': _('Starting day of the week, for display in calendar views'), 

1241 'default': '1', 

1242 'choices': [ 

1243 ('0', _('Sunday')), 

1244 ('1', _('Monday')), 

1245 ('2', _('Tuesday')), 

1246 ('3', _('Wednesday')), 

1247 ('4', _('Thursday')), 

1248 ('5', _('Friday')), 

1249 ('6', _('Saturday')), 

1250 ], 

1251 }, 

1252 'CALENDAR_HORIZON_MONTHS': { 

1253 'name': _('Calendar Horizon'), 

1254 'description': _( 

1255 'Number of months into the future to display in calendar views' 

1256 ), 

1257 'default': 12, 

1258 'validator': [int, MinValueValidator(1)], 

1259 'units': _('months'), 

1260 }, 

1261 'TEST_STATION_DATA': { 

1262 'name': _('Enable Test Station Data'), 

1263 'description': _('Enable test station data collection for test results'), 

1264 'default': False, 

1265 'validator': bool, 

1266 }, 

1267 'MACHINE_PING_ENABLED': { 

1268 'name': _('Enable Machine Ping'), 

1269 'description': _( 

1270 'Enable periodic ping task of registered machines to check their status' 

1271 ), 

1272 'default': True, 

1273 'validator': bool, 

1274 }, 

1275}