Compare commits

...

15 Commits

Author SHA1 Message Date
Oliver 62f44d06bf
Add missing 'remove stock' action (#3610) (#3611)
* Add missing 'remove stock' action

* Add some unit tests for the stock item view

(cherry picked from commit 993f36c98f)
2022-08-25 21:31:23 +10:00
Oliver ee5238b13e
StockItem page template fix (#3601) (#3604)
- Allow damaged / quarantined items to be adjusted
- Fix template rendering logic

(cherry picked from commit 6adfc91c5c)
2022-08-24 17:17:46 +10:00
Oliver 2db9b28063
Bug fix for loading asset files in reports (#3596) (#3598)
- Pathlib does not play well with SafeString
- Enforce string type' when loading an asset file
- Add unit tests

(cherry picked from commit 12509203d6)
2022-08-24 12:01:37 +10:00
Oliver 30beb12c81
Fix dimensions for label templates (#3578) (#3580)
- Disable localization in certain areas
- Different localization settings could mess with label generation

(cherry picked from commit c8de2efd9d)
2022-08-19 12:35:24 +10:00
Oliver 644bcb263f
fix: invalid chas in cache key (#3574) (#3577)
(cherry picked from commit efafa3960b)

Co-authored-by: wolflu05 <76838159+wolflu05@users.noreply.github.com>
2022-08-19 11:35:49 +10:00
Oliver 2ae1d1c663
fix typo in variable name (#3541) (#3543)
this fixes broken e-mail from configuration.

(cherry picked from commit d102a87eff)

Co-authored-by: Jacob Siverskog <jacob@teenage.engineering>
2022-08-16 13:08:33 +10:00
Oliver 951225b420
Fix bug in exporting records (#3545) (#3552)
Introduced in #3392

(cherry picked from commit 858d48afe7)

Co-authored-by: miggland <miggland@users.noreply.github.com>
2022-08-16 13:08:11 +10:00
Oliver 836ec3289d
Fix weasyprint version (#3539) (#3540)
* Pin weasyprint version

- Revert to 54.3
- Fixes https://github.com/inventree/InvenTree/issues/3528

* Simplify label printing for multiple pages

* Simplify PDF generation for multiple report outputs

* Add content wrapper div for base label template

- Allows more extensibility

(cherry picked from commit 87e7112326)
2022-08-15 12:22:10 +10:00
Oliver 3482b81e8a
Bump version to 0.8.2 (#3504) 2022-08-09 13:38:33 +10:00
Oliver fb97385b23
load admin first (#3484) (#3492)
(cherry picked from commit 9182f62bc4)
2022-08-08 07:47:27 +10:00
Oliver 276ad572c5
Url field fix (#3488) (#3491)
* Updates for automated metadata extraction

* Update link field for StockItem model

- Increase max_length to 200 characters
- Custom migration
- Updates for InvenTreeUrlField model

* Adding unit tests

* Bug fix for metadata.py

(cherry picked from commit 63d221854b)
2022-08-08 07:47:17 +10:00
Oliver 1e7f4dcb4b
Improved loading for custom logo (#3489) (#3490)
- First check the 'static' directory
- Second check the 'media' directory (backwards compatibility)
- Third use the default logo

(cherry picked from commit 83b471b4f7)
2022-08-07 23:16:57 +10:00
Oliver 72bec42c0f
Depenency updates (#3472) (#3481)
* update requirements

* automate everything

(cherry picked from commit 12a321ed4f)

Co-authored-by: Matthias Mair <code@mjmair.com>
2022-08-06 10:02:44 +10:00
Oliver 8e5202b39a
Improvements to version check CI script (#3455) (#3457)
* Improvements to version check CI script

* Fix typo

* Allow duplicate version tags for 'stable' branch

(cherry picked from commit 08d93e0727)
2022-08-02 13:34:08 +10:00
Oliver 63664714ca
Return early if themes dir does not exist (#3453)
* Return early if themes dir does not exist

(cherry picked from commit 3e1cdcdb07)

* Bump version number to 0.8.1
2022-08-02 10:38:36 +10:00
27 changed files with 408 additions and 174 deletions

22
.github/workflows/update.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: Update dependency files regularly
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * *"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- 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
- uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: "[Bot] Updated dependency"
branch: dep-update

View File

@ -6,7 +6,6 @@ from decimal import Decimal
from django import forms from django import forms
from django.core import validators from django.core import validators
from django.db import models as models from django.db import models as models
from django.forms.fields import URLField as FormURLField
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from djmoney.forms.fields import MoneyField from djmoney.forms.fields import MoneyField
@ -23,26 +22,28 @@ class InvenTreeRestURLField(RestURLField):
"""Custom field for DRF with custom scheme vaildators.""" """Custom field for DRF with custom scheme vaildators."""
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""Update schemes.""" """Update schemes."""
# Enforce 'max length' parameter in form validation
if 'max_length' not in kwargs:
kwargs['max_length'] = 200
super().__init__(**kwargs) super().__init__(**kwargs)
self.validators[-1].schemes = allowable_url_schemes() self.validators[-1].schemes = allowable_url_schemes()
class InvenTreeURLFormField(FormURLField):
"""Custom URL form field with custom scheme validators."""
default_validators = [validators.URLValidator(schemes=allowable_url_schemes())]
class InvenTreeURLField(models.URLField): class InvenTreeURLField(models.URLField):
"""Custom URL field which has custom scheme validators.""" """Custom URL field which has custom scheme validators."""
validators = [validators.URLValidator(schemes=allowable_url_schemes())] default_validators = [validators.URLValidator(schemes=allowable_url_schemes())]
def formfield(self, **kwargs): def __init__(self, **kwargs):
"""Return a Field instance for this field.""" """Initialization method for InvenTreeURLField"""
return super().formfield(**{
'form_class': InvenTreeURLFormField # Max length for InvenTreeURLField defaults to 200
}) if 'max_length' not in kwargs:
kwargs['max_length'] = 200
super().__init__(**kwargs)
def money_kwargs(): def money_kwargs():

View File

@ -12,6 +12,7 @@ from wsgiref.util import FileWrapper
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
from django.contrib.staticfiles.storage import StaticFilesStorage
from django.core.exceptions import FieldError, ValidationError from django.core.exceptions import FieldError, ValidationError
from django.core.files.storage import default_storage from django.core.files.storage import default_storage
from django.core.validators import URLValidator from django.core.validators import URLValidator
@ -241,12 +242,22 @@ def getLogoImage(as_file=False, custom=True):
"""Return the path to the logo-file.""" """Return the path to the logo-file."""
if custom and settings.CUSTOM_LOGO: if custom and settings.CUSTOM_LOGO:
if as_file: static_storage = StaticFilesStorage()
return f"file://{default_storage.path(settings.CUSTOM_LOGO)}"
else:
return default_storage.url(settings.CUSTOM_LOGO)
if static_storage.exists(settings.CUSTOM_LOGO):
storage = static_storage
elif default_storage.exists(settings.CUSTOM_LOGO):
storage = default_storage
else: else:
storage = None
if storage is not None:
if as_file:
return f"file://{storage.path(settings.CUSTOM_LOGO)}"
else:
return storage.url(settings.CUSTOM_LOGO)
# If we have got to this point, return the default logo
if as_file: if as_file:
path = settings.STATIC_ROOT.joinpath('img/inventree.png') path = settings.STATIC_ROOT.joinpath('img/inventree.png')
return f"file://{path}" return f"file://{path}"

View File

@ -116,6 +116,12 @@ class InvenTreeMetadata(SimpleMetadata):
model_class = None 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',
]
try: try:
model_class = serializer.Meta.model model_class = serializer.Meta.model
@ -148,10 +154,7 @@ class InvenTreeMetadata(SimpleMetadata):
elif name in model_default_values: elif name in model_default_values:
serializer_info[name]['default'] = model_default_values[name] serializer_info[name]['default'] = model_default_values[name]
# Attributes to copy from the model to the field (if they don't exist) for attr in extra_attributes:
attributes = ['help_text']
for attr in attributes:
if attr not in serializer_info[name]: if attr not in serializer_info[name]:
if hasattr(field, attr): if hasattr(field, attr):
@ -172,8 +175,9 @@ class InvenTreeMetadata(SimpleMetadata):
# This is used to automatically filter AJAX requests # 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()
if 'help_text' not in serializer_info[name] and hasattr(relation.model_field, 'help_text'): for attr in extra_attributes:
serializer_info[name]['help_text'] = relation.model_field.help_text 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: if name in model_default_values:
serializer_info[name]['default'] = model_default_values[name] serializer_info[name]['default'] = model_default_values[name]

View File

@ -16,6 +16,7 @@ import sys
from pathlib import Path from pathlib import Path
import django.conf.locale import django.conf.locale
from django.contrib.staticfiles.storage import StaticFilesStorage
from django.core.files.storage import default_storage from django.core.files.storage import default_storage
from django.http import Http404 from django.http import Http404
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -135,6 +136,8 @@ MEDIA_URL = '/media/'
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
# Admin site integration
'django.contrib.admin',
# InvenTree apps # InvenTree apps
'build.apps.BuildConfig', 'build.apps.BuildConfig',
@ -150,7 +153,6 @@ INSTALLED_APPS = [
'InvenTree.apps.InvenTreeConfig', # InvenTree app runs last 'InvenTree.apps.InvenTreeConfig', # InvenTree app runs last
# Core django modules # Core django modules
'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'user_sessions', # db user sessions 'user_sessions', # db user sessions
@ -676,7 +678,7 @@ EMAIL_SUBJECT_PREFIX = get_setting('INVENTREE_EMAIL_PREFIX', 'email.prefix', '[I
EMAIL_USE_TLS = get_boolean_setting('INVENTREE_EMAIL_TLS', 'email.tls', False) EMAIL_USE_TLS = get_boolean_setting('INVENTREE_EMAIL_TLS', 'email.tls', False)
EMAIL_USE_SSL = get_boolean_setting('INVENTREE_EMAIL_SSL', 'email.ssl', False) EMAIL_USE_SSL = get_boolean_setting('INVENTREE_EMAIL_SSL', 'email.ssl', False)
DEFUALT_FROM_EMAIL = get_setting('INVENTREE_EMAIL_SENDER', 'email.sender', '') DEFAULT_FROM_EMAIL = get_setting('INVENTREE_EMAIL_SENDER', 'email.sender', '')
EMAIL_USE_LOCALTIME = False EMAIL_USE_LOCALTIME = False
EMAIL_TIMEOUT = 60 EMAIL_TIMEOUT = 60
@ -820,9 +822,21 @@ CUSTOMIZE = get_setting('INVENTREE_CUSTOMIZE', 'customize', {})
CUSTOM_LOGO = get_setting('INVENTREE_CUSTOM_LOGO', 'customize.logo', None) CUSTOM_LOGO = get_setting('INVENTREE_CUSTOM_LOGO', 'customize.logo', None)
# check that the logo-file exsists in media """
if CUSTOM_LOGO and not default_storage.exists(CUSTOM_LOGO): # pragma: no cover Check for the existence of a 'custom logo' file:
logger.warning(f"The custom logo file '{CUSTOM_LOGO}' could not be found in the default media storage") - Check the 'static' directory
- Check the 'media' directory (legacy)
"""
if CUSTOM_LOGO:
static_storage = StaticFilesStorage()
if static_storage.exists(CUSTOM_LOGO):
logger.info(f"Loading custom logo from static directory: {CUSTOM_LOGO}")
elif default_storage.exists(CUSTOM_LOGO):
logger.info(f"Loading custom logo from media directory: {CUSTOM_LOGO}")
else:
logger.warning(f"The custom logo file '{CUSTOM_LOGO}' could not be found in the static or media directories")
CUSTOM_LOGO = False CUSTOM_LOGO = False
if DEBUG: if DEBUG:

View File

@ -13,7 +13,7 @@ import common.models
from InvenTree.api_version import INVENTREE_API_VERSION from InvenTree.api_version import INVENTREE_API_VERSION
# InvenTree software version # InvenTree software version
INVENTREE_SW_VERSION = "0.8.0" INVENTREE_SW_VERSION = "0.8.2"
def inventreeInstanceName(): def inventreeInstanceName():

View File

@ -132,7 +132,7 @@ class BaseInvenTreeSetting(models.Model):
for k, v in kwargs.items(): for k, v in kwargs.items():
key += f"_{k}:{v}" key += f"_{k}:{v}"
return key return key.replace(" ", "")
@classmethod @classmethod
def allValues(cls, user=None, exclude_hidden=False): def allValues(cls, user=None, exclude_hidden=False):
@ -1775,12 +1775,13 @@ class ColorTheme(models.Model):
@classmethod @classmethod
def get_color_themes_choices(cls): def get_color_themes_choices(cls):
"""Get all color themes from static folder.""" """Get all color themes from static folder."""
if settings.TESTING and not os.path.exists(settings.STATIC_COLOR_THEMES_DIR): if not os.path.exists(settings.STATIC_COLOR_THEMES_DIR):
logger.error('Theme directory does not exsist') logger.error('Theme directory does not exsist')
return [] return []
# Get files list from css/color-themes/ folder # Get files list from css/color-themes/ folder
files_list = [] files_list = []
for file in os.listdir(settings.STATIC_COLOR_THEMES_DIR): for file in os.listdir(settings.STATIC_COLOR_THEMES_DIR):
files_list.append(os.path.splitext(file)) files_list.append(os.path.splitext(file))

View File

@ -158,16 +158,12 @@ class LabelPrintMixin:
pages = [] pages = []
if len(outputs) > 1:
# If more than one output is generated, merge them into a single file
for output in outputs: for output in outputs:
doc = output.get_document() doc = output.get_document()
for page in doc.pages: for page in doc.pages:
pages.append(page) pages.append(page)
pdf = outputs[0].get_document().copy(pages).write_pdf() pdf = outputs[0].get_document().copy(pages).write_pdf()
else:
pdf = outputs[0].get_document().write_pdf()
inline = common.models.InvenTreeUserSetting.get_setting('LABEL_INLINE', user=request.user) inline = common.models.InvenTreeUserSetting.get_setting('LABEL_INLINE', user=request.user)

View File

@ -1,10 +1,13 @@
{% load l10n %}
{% load report %} {% load report %}
{% load barcode %} {% load barcode %}
<head> <head>
<style> <style>
@page { @page {
{% localize off %}
size: {{ width }}mm {{ height }}mm; size: {{ width }}mm {{ height }}mm;
{% endlocalize %}
{% block margin %} {% block margin %}
margin: 0mm; margin: 0mm;
{% endblock %} {% endblock %}
@ -15,6 +18,8 @@
margin: 0mm; margin: 0mm;
color: #000; color: #000;
background-color: #FFF; background-color: #FFF;
page-break-before: always;
page-break-after: always;
} }
img { img {
@ -22,14 +27,23 @@
image-rendering: pixelated; image-rendering: pixelated;
} }
.content {
width: 100%;
break-after: always;
position: relative;
}
{% block style %} {% block style %}
/* User-defined styles can go here */
{% endblock %} {% endblock %}
</style> </style>
</head> </head>
<body> <body>
<div class='content'>
{% block content %} {% block content %}
<!-- Label data rendered here! --> <!-- Label data rendered here! -->
{% endblock %} {% endblock %}
</div>
</body> </body>

View File

@ -1,5 +1,6 @@
{% extends "label/label_base.html" %} {% extends "label/label_base.html" %}
{% load l10n %}
{% load barcode %} {% load barcode %}
{% block style %} {% block style %}
@ -8,15 +9,19 @@
position: fixed; position: fixed;
left: 0mm; left: 0mm;
top: 0mm; top: 0mm;
{% localize off %}
height: {{ height }}mm; height: {{ height }}mm;
width: {{ height }}mm; width: {{ height }}mm;
{% endlocalize %}
} }
.part { .part {
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
display: inline; display: inline;
position: absolute; position: absolute;
{% localize off %}
left: {{ height }}mm; left: {{ height }}mm;
{% endlocalize %}
top: 2mm; top: 2mm;
} }

View File

@ -1,5 +1,6 @@
{% extends "label/label_base.html" %} {% extends "label/label_base.html" %}
{% load l10n %}
{% load barcode %} {% load barcode %}
{% block style %} {% block style %}
@ -8,15 +9,19 @@
position: fixed; position: fixed;
left: 0mm; left: 0mm;
top: 0mm; top: 0mm;
{% localize off %}
height: {{ height }}mm; height: {{ height }}mm;
width: {{ height }}mm; width: {{ height }}mm;
{% endlocalize %}
} }
.part { .part {
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
display: inline; display: inline;
position: absolute; position: absolute;
{% localize off %}
left: {{ height }}mm; left: {{ height }}mm;
{% endlocalize %}
top: 2mm; top: 2mm;
} }

View File

@ -1,5 +1,6 @@
{% extends "label/label_base.html" %} {% extends "label/label_base.html" %}
{% load l10n %}
{% load barcode %} {% load barcode %}
{% block style %} {% block style %}
@ -8,8 +9,10 @@
position: fixed; position: fixed;
left: 0mm; left: 0mm;
top: 0mm; top: 0mm;
{% localize off %}
height: {{ height }}mm; height: {{ height }}mm;
width: {{ height }}mm; width: {{ height }}mm;
{% endlocalize %}
} }
{% endblock %} {% endblock %}

View File

@ -1,5 +1,6 @@
{% extends "label/label_base.html" %} {% extends "label/label_base.html" %}
{% load l10n %}
{% load barcode %} {% load barcode %}
{% block style %} {% block style %}
@ -8,8 +9,10 @@
position: fixed; position: fixed;
left: 0mm; left: 0mm;
top: 0mm; top: 0mm;
{% localize off %}
height: {{ height }}mm; height: {{ height }}mm;
width: {{ height }}mm; width: {{ height }}mm;
{% endlocalize %}
} }
{% endblock %} {% endblock %}

View File

@ -1,5 +1,6 @@
{% extends "label/label_base.html" %} {% extends "label/label_base.html" %}
{% load l10n %}
{% load barcode %} {% load barcode %}
{% block style %} {% block style %}
@ -8,15 +9,19 @@
position: fixed; position: fixed;
left: 0mm; left: 0mm;
top: 0mm; top: 0mm;
{% localize off %}
height: {{ height }}mm; height: {{ height }}mm;
width: {{ height }}mm; width: {{ height }}mm;
{% endlocalize %}
} }
.loc { .loc {
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
display: inline; display: inline;
position: absolute; position: absolute;
{% localize off %}
left: {{ height }}mm; left: {{ height }}mm;
{% endlocalize %}
top: 2mm; top: 2mm;
} }

View File

@ -233,17 +233,12 @@ class ReportPrintMixin:
pages = [] pages = []
try: try:
if len(outputs) > 1:
# If more than one output is generated, merge them into a single file
for output in outputs: for output in outputs:
doc = output.get_document() doc = output.get_document()
for page in doc.pages: for page in doc.pages:
pages.append(page) pages.append(page)
pdf = outputs[0].get_document().copy(pages).write_pdf() pdf = outputs[0].get_document().copy(pages).write_pdf()
else:
pdf = outputs[0].get_document().write_pdf()
except TemplateDoesNotExist as e: except TemplateDoesNotExist as e:

View File

@ -5,7 +5,7 @@ import os
from django import template from django import template
from django.conf import settings from django.conf import settings
from django.utils.safestring import mark_safe from django.utils.safestring import SafeString, mark_safe
import InvenTree.helpers import InvenTree.helpers
from common.models import InvenTreeSetting from common.models import InvenTreeSetting
@ -28,11 +28,15 @@ def asset(filename):
Raises: Raises:
FileNotFoundError if file does not exist FileNotFoundError if file does not exist
""" """
if type(filename) is SafeString:
# Prepend an empty string to enforce 'stringiness'
filename = '' + filename
# If in debug mode, return URL to the image, not a local file # If in debug mode, return URL to the image, not a local file
debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE') debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE')
# Test if the file actually exists # Test if the file actually exists
full_path = settings.MEDIA_ROOT.joinpath('report', 'assets', filename) full_path = settings.MEDIA_ROOT.joinpath('report', 'assets', filename).resolve()
if not full_path.exists() or not full_path.is_file(): if not full_path.exists() or not full_path.is_file():
raise FileNotFoundError(f"Asset file '{filename}' does not exist") raise FileNotFoundError(f"Asset file '{filename}' does not exist")
@ -55,6 +59,10 @@ def uploaded_image(filename, replace_missing=True, replacement_file='blank_image
A fully qualified path to the image A fully qualified path to the image
""" """
if type(filename) is SafeString:
# Prepend an empty string to enforce 'stringiness'
filename = '' + filename
# If in debug mode, return URL to the image, not a local file # If in debug mode, return URL to the image, not a local file
debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE') debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE')

View File

@ -9,6 +9,7 @@ from django.core.cache import cache
from django.http.response import StreamingHttpResponse from django.http.response import StreamingHttpResponse
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
from django.utils.safestring import SafeString
from PIL import Image from PIL import Image
@ -49,6 +50,10 @@ class ReportTagTest(TestCase):
asset = report_tags.asset('test.txt') asset = report_tags.asset('test.txt')
self.assertEqual(asset, '/media/report/assets/test.txt') self.assertEqual(asset, '/media/report/assets/test.txt')
# Ensure that a 'safe string' also works
asset = report_tags.asset(SafeString('test.txt'))
self.assertEqual(asset, '/media/report/assets/test.txt')
self.debug_mode(False) self.debug_mode(False)
asset = report_tags.asset('test.txt') asset = report_tags.asset('test.txt')
self.assertEqual(asset, f'file://{asset_dir}/test.txt') self.assertEqual(asset, f'file://{asset_dir}/test.txt')
@ -87,10 +92,17 @@ class ReportTagTest(TestCase):
img = report_tags.uploaded_image('part/images/test.jpg') img = report_tags.uploaded_image('part/images/test.jpg')
self.assertEqual(img, '/media/part/images/test.jpg') self.assertEqual(img, '/media/part/images/test.jpg')
# Ensure that a 'safe string' also works
img = report_tags.uploaded_image(SafeString('part/images/test.jpg'))
self.assertEqual(img, '/media/part/images/test.jpg')
self.debug_mode(False) self.debug_mode(False)
img = report_tags.uploaded_image('part/images/test.jpg') img = report_tags.uploaded_image('part/images/test.jpg')
self.assertEqual(img, f'file://{img_path.joinpath("test.jpg")}') self.assertEqual(img, f'file://{img_path.joinpath("test.jpg")}')
img = report_tags.uploaded_image(SafeString('part/images/test.jpg'))
self.assertEqual(img, f'file://{img_path.joinpath("test.jpg")}')
def test_part_image(self): def test_part_image(self):
"""Unit tests for the 'part_image' tag""" """Unit tests for the 'part_image' tag"""

View File

@ -0,0 +1,19 @@
# Generated by Django 3.2.15 on 2022-08-07 02:38
import InvenTree.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('stock', '0081_auto_20220801_0044'),
]
operations = [
migrations.AlterField(
model_name='stockitem',
name='link',
field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', max_length=200, verbose_name='External Link'),
),
]

View File

@ -647,7 +647,7 @@ class StockItem(MetadataMixin, MPTTModel):
link = InvenTreeURLField( link = InvenTreeURLField(
verbose_name=_('External Link'), verbose_name=_('External Link'),
max_length=125, blank=True, blank=True, max_length=200,
help_text=_("Link to external URL") help_text=_("Link to external URL")
) )

View File

@ -75,24 +75,20 @@
<div class='btn-group'> <div class='btn-group'>
<button id='stock-actions' title='{% trans "Stock adjustment actions" %}' class='btn btn-outline-secondary dropdown-toggle' type='button' data-bs-toggle='dropdown'><span class='fas fa-boxes'></span> <span class='caret'></span></button> <button id='stock-actions' title='{% trans "Stock adjustment actions" %}' class='btn btn-outline-secondary dropdown-toggle' type='button' data-bs-toggle='dropdown'><span class='fas fa-boxes'></span> <span class='caret'></span></button>
<ul class='dropdown-menu' role='menu'> <ul class='dropdown-menu' role='menu'>
{% if item.can_adjust_location %}
{% if not item.serialized %} {% if not item.serialized %}
{% if item.in_stock %}
<li><a class='dropdown-item' href='#' id='stock-count' title='{% trans "Count stock" %}'><span class='fas fa-check-circle icon-green'></span> {% trans "Count stock" %}</a></li> <li><a class='dropdown-item' href='#' id='stock-count' title='{% trans "Count stock" %}'><span class='fas fa-check-circle icon-green'></span> {% trans "Count stock" %}</a></li>
{% endif %}
{% if not item.customer %} {% if not item.customer %}
<li><a class='dropdown-item' href='#' id='stock-add' title='{% trans "Add stock" %}'><span class='fas fa-plus-circle icon-green'></span> {% trans "Add stock" %}</a></li> <li><a class='dropdown-item' href='#' id='stock-add' title='{% trans "Add stock" %}'><span class='fas fa-plus-circle icon-green'></span> {% trans "Add stock" %}</a></li>
{% endif %}
{% if item.in_stock %}
<li><a class='dropdown-item' href='#' id='stock-remove' title='{% trans "Remove stock" %}'><span class='fas fa-minus-circle icon-red'></span> {% trans "Remove stock" %}</a></li> <li><a class='dropdown-item' href='#' id='stock-remove' title='{% trans "Remove stock" %}'><span class='fas fa-minus-circle icon-red'></span> {% trans "Remove stock" %}</a></li>
{% endif %} {% endif %}
{% if item.in_stock and item.part.trackable %} {% if item.part.trackable %}
<li><a class='dropdown-item' href='#' id='stock-serialize' title='{% trans "Serialize stock" %}'><span class='fas fa-hashtag'></span> {% trans "Serialize stock" %}</a> </li> <li><a class='dropdown-item' href='#' id='stock-serialize' title='{% trans "Serialize stock" %}'><span class='fas fa-hashtag'></span> {% trans "Serialize stock" %}</a> </li>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if item.in_stock and item.can_adjust_location %}
<li><a class='dropdown-item' href='#' id='stock-move' title='{% trans "Transfer stock" %}'><span class='fas fa-exchange-alt icon-blue'></span> {% trans "Transfer stock" %}</a></li> <li><a class='dropdown-item' href='#' id='stock-move' title='{% trans "Transfer stock" %}'><span class='fas fa-exchange-alt icon-blue'></span> {% trans "Transfer stock" %}</a></li>
{% endif %} {% endif %}
{% if item.in_stock and item.can_adjust_location and item.part.salable and not item.customer %} {% if item.part.salable and not item.customer %}
<li><a class='dropdown-item' href='#' id='stock-assign-to-customer' title='{% trans "Assign to customer" %}'><span class='fas fa-user-tie'></span> {% trans "Assign to customer" %}</a></li> <li><a class='dropdown-item' href='#' id='stock-assign-to-customer' title='{% trans "Assign to customer" %}'><span class='fas fa-user-tie'></span> {% trans "Assign to customer" %}</a></li>
{% endif %} {% endif %}
{% if item.customer %} {% if item.customer %}
@ -100,13 +96,12 @@
{% endif %} {% endif %}
{% if item.belongs_to %} {% if item.belongs_to %}
<li><a class='dropdown-item' href='#' id='stock-uninstall' title='{% trans "Uninstall stock item" %}'><span class='fas fa-unlink'></span> {% trans "Uninstall" %}</a></li> <li><a class='dropdown-item' href='#' id='stock-uninstall' title='{% trans "Uninstall stock item" %}'><span class='fas fa-unlink'></span> {% trans "Uninstall" %}</a></li>
{% else %} {% endif %}
{% if item.part.get_used_in %} {% if item.part.get_used_in %}
<!-- <!--
<li><a class='dropdown-item' href='#' id='stock-install-in' title='{% trans "Install stock item" %}'><span class='fas fa-link'></span> {% trans "Install" %}</a></li> <li><a class='dropdown-item' href='#' id='stock-install-in' title='{% trans "Install stock item" %}'><span class='fas fa-link'></span> {% trans "Install" %}</a></li>
--> -->
{% endif %} {% endif %}
{% endif %}
</ul> </ul>
</div> </div>
{% endif %} {% endif %}
@ -618,37 +613,7 @@ $("#stock-convert").click(function() {
}); });
{% endif %} {% endif %}
{% if item.in_stock %} {% if item.customer %}
$("#stock-assign-to-customer").click(function() {
inventreeGet('{% url "api-stock-detail" item.pk %}', {}, {
success: function(response) {
assignStockToCustomer(
[response],
{
success: function() {
location.reload();
},
}
);
}
});
});
$("#stock-move").click(function() {
itemAdjust("move");
});
$("#stock-count").click(function() {
itemAdjust('count');
});
$('#stock-remove').click(function() {
itemAdjust('take');
});
{% else %}
$("#stock-return-from-customer").click(function() { $("#stock-return-from-customer").click(function() {
constructForm('{% url "api-stock-item-return" item.pk %}', { constructForm('{% url "api-stock-item-return" item.pk %}', {
@ -666,6 +631,37 @@ $("#stock-return-from-customer").click(function() {
}); });
}); });
{% else %}
$("#stock-assign-to-customer").click(function() {
inventreeGet('{% url "api-stock-detail" item.pk %}', {}, {
success: function(response) {
assignStockToCustomer(
[response],
{
success: function() {
location.reload();
},
}
);
}
});
});
{% endif %}
{% if item.can_adjust_location %}
$("#stock-move").click(function() {
itemAdjust("move");
});
$("#stock-count").click(function() {
itemAdjust('count');
});
$('#stock-remove').click(function() {
itemAdjust('take');
});
{% endif %} {% endif %}

View File

@ -31,6 +31,57 @@ class StockListTest(StockViewTestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
class StockDetailTest(StockViewTestCase):
"""Unit test for the 'stock detail' page"""
def test_basic_info(self):
"""Test that basic stock item info is rendered"""
url = reverse('stock-item-detail', kwargs={'pk': 1})
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
html = str(response.content)
# Part name
self.assertIn('Stock Item: M2x4 LPHS', html)
# Quantity
self.assertIn('<h5>Available Quantity</h5>', html)
self.assertIn('<h5>4000 </h5>', html)
# Batch code
self.assertIn('Batch', html)
self.assertIn('<td>B123</td>', html)
# Actions to check
actions = [
"id=\\\'stock-count\\\' title=\\\'Count stock\\\'",
"id=\\\'stock-add\\\' title=\\\'Add stock\\\'",
"id=\\\'stock-remove\\\' title=\\\'Remove stock\\\'",
"id=\\\'stock-move\\\' title=\\\'Transfer stock\\\'",
"id=\\\'stock-duplicate\\\'",
"id=\\\'stock-edit\\\'",
"id=\\\'stock-delete\\\'",
]
# Initially we should not have any of the required permissions
for act in actions:
self.assertNotIn(act, html)
# Give the user all the permissions
self.assignRole('stock.add')
self.assignRole('stock.change')
self.assignRole('stock.delete')
response = self.client.get(url)
html = str(response.content)
for act in actions:
self.assertIn(act, html)
class StockOwnershipTest(StockViewTestCase): class StockOwnershipTest(StockViewTestCase):
"""Tests for stock ownership views.""" """Tests for stock ownership views."""

View File

@ -44,6 +44,45 @@ class StockTest(InvenTreeTestCase):
Part.objects.rebuild() Part.objects.rebuild()
StockItem.objects.rebuild() StockItem.objects.rebuild()
def test_link(self):
"""Test the link URL field validation"""
item = StockItem.objects.get(pk=1)
# Check that invalid URLs fail
for bad_url in [
'test.com',
'httpx://abc.xyz',
'https:google.com',
]:
with self.assertRaises(ValidationError):
item.link = bad_url
item.save()
item.full_clean()
# Check that valid URLs pass
for good_url in [
'https://test.com',
'https://digikey.com/datasheets?file=1010101010101.bin',
'ftp://download.com:8080/file.aspx',
]:
item.link = good_url
item.save()
item.full_clean()
# A long URL should fail
long_url = 'https://website.co.uk?query=' + 'a' * 173
with self.assertRaises(ValidationError):
item.link = long_url
item.full_clean()
# Shorten by a single character, will pass
long_url = long_url[:-1]
item.link = long_url
item.save()
def test_expiry(self): def test_expiry(self):
"""Test expiry date functionality for StockItem model.""" """Test expiry date functionality for StockItem model."""
today = datetime.datetime.now().date() today = datetime.datetime.now().date()

View File

@ -87,8 +87,16 @@ if __name__ == '__main__':
# GITHUB_REF may be either 'refs/heads/<branch>' or 'refs/heads/<tag>' # GITHUB_REF may be either 'refs/heads/<branch>' or 'refs/heads/<tag>'
GITHUB_REF = os.environ['GITHUB_REF'] GITHUB_REF = os.environ['GITHUB_REF']
GITHUB_REF_NAME = os.environ['GITHUB_REF_NAME']
GITHUB_BASE_REF = os.environ['GITHUB_BASE_REF'] GITHUB_BASE_REF = os.environ['GITHUB_BASE_REF']
# Print out version information, makes debugging actions *much* easier!
print(f"GITHUB_REF: {GITHUB_REF}")
print(f"GITHUB_REF_NAME: {GITHUB_REF_NAME}")
print(f"GITHUB_REF_TYPE: {GITHUB_REF_TYPE}")
print(f"GITHUB_BASE_REF: {GITHUB_BASE_REF}")
version_file = os.path.join(here, '..', 'InvenTree', 'InvenTree', 'version.py') version_file = os.path.join(here, '..', 'InvenTree', 'InvenTree', 'version.py')
version = None version = None
@ -109,8 +117,19 @@ if __name__ == '__main__':
print(f"InvenTree Version: '{version}'") print(f"InvenTree Version: '{version}'")
# Check version number and look for existing versions # Check version number and look for existing versions
# Note that on a 'tag' (release) we *must* allow duplicate versions, as this *is* the version that has just been released # If a release is found which matches the current tag, throw an error
highest_release = check_version_number(version, allow_duplicate=GITHUB_REF_TYPE == 'tag')
allow_duplicate = False
# Note: on a 'tag' (release) we *must* allow duplicate versions, as this *is* the version that has just been released
if GITHUB_REF_TYPE == 'tag':
allow_duplicate = True
# Note: on a push to 'stable' branch we also allow duplicates
if GITHUB_BASE_REF == 'stable':
allow_duplicate = True
highest_release = check_version_number(version, allow_duplicate=allow_duplicate)
# Determine which docker tag we are going to use # Determine which docker tag we are going to use
docker_tags = None docker_tags = None

View File

@ -10,6 +10,10 @@ asgiref==3.5.2 \
# via # via
# -c requirements.txt # -c requirements.txt
# django # django
build==0.8.0 \
--hash=sha256:19b0ed489f92ace6947698c3ca8436cb0556a66e2aa2d34cd70e2a5d27cd0437 \
--hash=sha256:887a6d471c901b1a6e6574ebaeeebb45e5269a79d095fe9a8f88d6614ed2e5f0
# via pip-tools
certifi==2022.6.15 \ certifi==2022.6.15 \
--hash=sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d \ --hash=sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d \
--hash=sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412 --hash=sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412
@ -20,9 +24,9 @@ cfgv==3.3.1 \
--hash=sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426 \ --hash=sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426 \
--hash=sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736 --hash=sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736
# via pre-commit # via pre-commit
charset-normalizer==2.0.12 \ charset-normalizer==2.1.0 \
--hash=sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597 \ --hash=sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5 \
--hash=sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df --hash=sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413
# via # via
# -c requirements.txt # -c requirements.txt
# requests # requests
@ -90,13 +94,13 @@ coveralls==2.1.2 \
--hash=sha256:4430b862baabb3cf090d36d84d331966615e4288d8a8c5957e0fd456d0dd8bd6 \ --hash=sha256:4430b862baabb3cf090d36d84d331966615e4288d8a8c5957e0fd456d0dd8bd6 \
--hash=sha256:b3b60c17b03a0dee61952a91aed6f131e0b2ac8bd5da909389c53137811409e1 --hash=sha256:b3b60c17b03a0dee61952a91aed6f131e0b2ac8bd5da909389c53137811409e1
# via -r requirements-dev.in # via -r requirements-dev.in
distlib==0.3.4 \ distlib==0.3.5 \
--hash=sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b \ --hash=sha256:a7f75737c70be3b25e2bee06288cec4e4c221de18455b2dd037fe2a795cab2fe \
--hash=sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579 --hash=sha256:b710088c59f06338ca514800ad795a132da19fda270e3ce4affc74abf955a26c
# via virtualenv # via virtualenv
django==3.2.14 \ django==3.2.15 \
--hash=sha256:677182ba8b5b285a4e072f3ac17ceee6aff1b5ce77fd173cc5b6a2d3dc022fcf \ --hash=sha256:115baf5049d5cf4163e43492cdc7139c306ed6d451e7d3571fe9612903903713 \
--hash=sha256:a8681e098fa60f7c33a4b628d6fcd3fe983a0939ff1301ecacac21d0b38bad56 --hash=sha256:f71934b1a822f14a86c9ac9634053689279cd04ae69cb6ade4a59471b886582b
# via # via
# -c requirements.txt # -c requirements.txt
# django-debug-toolbar # django-debug-toolbar
@ -115,9 +119,9 @@ filelock==3.7.1 \
--hash=sha256:37def7b658813cda163b56fc564cdc75e86d338246458c4c28ae84cabefa2404 \ --hash=sha256:37def7b658813cda163b56fc564cdc75e86d338246458c4c28ae84cabefa2404 \
--hash=sha256:3a0fd85166ad9dbab54c9aec96737b744106dc5f15c0b09a6744a445299fcf04 --hash=sha256:3a0fd85166ad9dbab54c9aec96737b744106dc5f15c0b09a6744a445299fcf04
# via virtualenv # via virtualenv
flake8==4.0.1 \ flake8==5.0.4 \
--hash=sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d \ --hash=sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db \
--hash=sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d --hash=sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248
# via # via
# -r requirements-dev.in # -r requirements-dev.in
# flake8-docstrings # flake8-docstrings
@ -126,9 +130,9 @@ flake8-docstrings==1.6.0 \
--hash=sha256:99cac583d6c7e32dd28bbfbef120a7c0d1b6dde4adb5a9fd441c4227a6534bde \ --hash=sha256:99cac583d6c7e32dd28bbfbef120a7c0d1b6dde4adb5a9fd441c4227a6534bde \
--hash=sha256:9fe7c6a306064af8e62a055c2f61e9eb1da55f84bb39caef2b84ce53708ac34b --hash=sha256:9fe7c6a306064af8e62a055c2f61e9eb1da55f84bb39caef2b84ce53708ac34b
# via -r requirements-dev.in # via -r requirements-dev.in
identify==2.5.1 \ identify==2.5.3 \
--hash=sha256:0dca2ea3e4381c435ef9c33ba100a78a9b40c0bab11189c7cf121f75815efeaa \ --hash=sha256:25851c8c1370effb22aaa3c987b30449e9ff0cece408f810ae6ce408fdd20893 \
--hash=sha256:3d11b16f3fe19f52039fb7e39c9c884b21cb1b586988114fbe42671f03de3e82 --hash=sha256:887e7b91a1be152b0d46bbf072130235a8117392b9f1828446079a816a05ef44
# via pre-commit # via pre-commit
idna==3.3 \ idna==3.3 \
--hash=sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff \ --hash=sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff \
@ -140,46 +144,54 @@ isort==5.10.1 \
--hash=sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7 \ --hash=sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7 \
--hash=sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951 --hash=sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951
# via -r requirements-dev.in # via -r requirements-dev.in
mccabe==0.6.1 \ mccabe==0.7.0 \
--hash=sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42 \ --hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \
--hash=sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f --hash=sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e
# via flake8 # via flake8
nodeenv==1.7.0 \ nodeenv==1.7.0 \
--hash=sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e \ --hash=sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e \
--hash=sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b --hash=sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b
# via pre-commit # via pre-commit
pep517==0.12.0 \ packaging==21.3 \
--hash=sha256:931378d93d11b298cf511dd634cf5ea4cb249a28ef84160b3247ee9afb4e8ab0 \ --hash=sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb \
--hash=sha256:dd884c326898e2c6e11f9e0b64940606a93eb10ea022a2e067959f3a110cf161 --hash=sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522
# via pip-tools # via build
pep8-naming==0.13.0 \ pep517==0.13.0 \
--hash=sha256:069ea20e97f073b3e6d4f789af2a57816f281ca64b86210c7d471117a4b6bfd0 \ --hash=sha256:4ba4446d80aed5b5eac6509ade100bff3e7943a8489de249654a5ae9b33ee35b \
--hash=sha256:9f38e6dcf867a1fb7ad47f5ff72c0ddae544a6cf64eb9f7600b7b3c0bb5980b5 --hash=sha256:ae69927c5c172be1add9203726d4b84cf3ebad1edcd5f71fcdc746e66e829f59
# via build
pep8-naming==0.13.1 \
--hash=sha256:3af77cdaa9c7965f7c85a56cd579354553c9bbd3fdf3078a776f12db54dd6944 \
--hash=sha256:f7867c1a464fe769be4f972ef7b79d6df1d9aff1b1f04ecf738d471963d3ab9c
# via -r requirements-dev.in # via -r requirements-dev.in
pip-tools==6.6.2 \ pip-tools==6.8.0 \
--hash=sha256:6b486548e5a139e30e4c4a225b3b7c2d46942a9f6d1a91143c21b1de4d02fd9b \ --hash=sha256:39e8aee465446e02278d80dbebd4325d1dd8633248f43213c73a25f58e7d8a55 \
--hash=sha256:f638503a9f77d98d9a7d72584b1508d3f82ed019b8fab24f4e5ad078c1b8c95e --hash=sha256:3e5cd4acbf383d19bdfdeab04738b6313ebf4ad22ce49bf529c729061eabfab8
# via -r requirements-dev.in # via -r requirements-dev.in
platformdirs==2.5.2 \ platformdirs==2.5.2 \
--hash=sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788 \ --hash=sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788 \
--hash=sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19 --hash=sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19
# via virtualenv # via virtualenv
pre-commit==2.19.0 \ pre-commit==2.20.0 \
--hash=sha256:10c62741aa5704faea2ad69cb550ca78082efe5697d6f04e5710c3c229afdd10 \ --hash=sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7 \
--hash=sha256:4233a1e38621c87d9dda9808c6606d7e7ba0e087cd56d3fe03202a01d2919615 --hash=sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959
# via -r requirements-dev.in # via -r requirements-dev.in
pycodestyle==2.8.0 \ pycodestyle==2.9.1 \
--hash=sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20 \ --hash=sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785 \
--hash=sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f --hash=sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b
# via flake8 # via flake8
pydocstyle==6.1.1 \ pydocstyle==6.1.1 \
--hash=sha256:1d41b7c459ba0ee6c345f2eb9ae827cab14a7533a88c5c6f7e94923f72df92dc \ --hash=sha256:1d41b7c459ba0ee6c345f2eb9ae827cab14a7533a88c5c6f7e94923f72df92dc \
--hash=sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4 --hash=sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4
# via flake8-docstrings # via flake8-docstrings
pyflakes==2.4.0 \ pyflakes==2.5.0 \
--hash=sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c \ --hash=sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2 \
--hash=sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e --hash=sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3
# via flake8 # via flake8
pyparsing==3.0.9 \
--hash=sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb \
--hash=sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc
# via packaging
pytz==2022.1 \ pytz==2022.1 \
--hash=sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7 \ --hash=sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7 \
--hash=sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c --hash=sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c
@ -223,18 +235,12 @@ pyyaml==6.0 \
# via # via
# -c requirements.txt # -c requirements.txt
# pre-commit # pre-commit
requests==2.28.0 \ requests==2.28.1 \
--hash=sha256:bc7861137fbce630f17b03d3ad02ad0bf978c844f3536d0edda6499dafce2b6f \ --hash=sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983 \
--hash=sha256:d568723a7ebd25875d8d1eaf5dfa068cd2fc8194b2e483d7b1f7c81918dbec6b --hash=sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349
# via # via
# -c requirements.txt # -c requirements.txt
# coveralls # coveralls
six==1.16.0 \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
# via
# -c requirements.txt
# virtualenv
snowballstemmer==2.2.0 \ snowballstemmer==2.2.0 \
--hash=sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1 \ --hash=sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1 \
--hash=sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a --hash=sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a
@ -253,20 +259,22 @@ toml==0.10.2 \
tomli==2.0.1 \ tomli==2.0.1 \
--hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
--hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
# via pep517 # via
typing-extensions==4.2.0 \ # build
--hash=sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708 \ # pep517
--hash=sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376 typing-extensions==4.3.0 \
--hash=sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02 \
--hash=sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6
# via django-test-migrations # via django-test-migrations
urllib3==1.26.9 \ urllib3==1.26.11 \
--hash=sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14 \ --hash=sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc \
--hash=sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e --hash=sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a
# via # via
# -c requirements.txt # -c requirements.txt
# requests # requests
virtualenv==20.15.0 \ virtualenv==20.16.3 \
--hash=sha256:4c44b1d77ca81f8368e2d7414f9b20c428ad16b343ac6d226206c5b84e2b4fcc \ --hash=sha256:4193b7bc8a6cd23e4eb251ac64f29b4398ab2c233531e66e40b19a6b7b0d30c1 \
--hash=sha256:804cce4de5b8a322f099897e308eecc8f6e2951f1a8e7e2b3598dff865f01336 --hash=sha256:d86ea0bb50e06252d79e6c241507cb904fcd66090c3271381372d6221a3970f9
# via pre-commit # via pre-commit
wheel==0.37.1 \ wheel==0.37.1 \
--hash=sha256:4bdcd7d840138086126cd09254dc6195fb4fc6f01c050a1d7236f2630db1d22a \ --hash=sha256:4bdcd7d840138086126cd09254dc6195fb4fc6f01c050a1d7236f2630db1d22a \

View File

@ -33,6 +33,7 @@ rapidfuzz==0.7.6 # Fuzzy string matching
sentry-sdk # Error reporting (optional) sentry-sdk # Error reporting (optional)
setuptools # Standard depenedency setuptools # Standard depenedency
tablib[xls,xlsx,yaml] # Support for XLS and XLSX formats tablib[xls,xlsx,yaml] # Support for XLS and XLSX formats
weasyprint==54.3 # PDF generation
# Fixed sub-dependencies # Fixed sub-dependencies
py-moneyed<2.0 # For django-money # FIXED 2022-06-18 as we need `moneyed.localization` py-moneyed<2.0 # For django-money # FIXED 2022-06-18 as we need `moneyed.localization`

View File

@ -10,7 +10,7 @@ asgiref==3.5.2
# via django # via django
babel==2.10.3 babel==2.10.3
# via py-moneyed # via py-moneyed
bleach[css]==5.0.0 bleach[css]==5.0.1
# via django-markdownify # via django-markdownify
blessed==1.19.1 blessed==1.19.1
# via django-q # via django-q
@ -20,11 +20,11 @@ certifi==2022.6.15
# via # via
# requests # requests
# sentry-sdk # sentry-sdk
cffi==1.15.0 cffi==1.15.1
# via # via
# cryptography # cryptography
# weasyprint # weasyprint
charset-normalizer==2.0.12 charset-normalizer==2.1.0
# via requests # via requests
coreapi==2.3.3 coreapi==2.3.3
# via -r requirements.in # via -r requirements.in
@ -42,7 +42,7 @@ defusedxml==0.7.1
# python3-openid # python3-openid
diff-match-patch==20200713 diff-match-patch==20200713
# via django-import-export # via django-import-export
django==3.2.14 django==3.2.15
# via # via
# -r requirements.in # -r requirements.in
# django-allauth # django-allauth
@ -121,7 +121,7 @@ djangorestframework==3.13.1
# via -r requirements.in # via -r requirements.in
et-xmlfile==1.1.0 et-xmlfile==1.1.0
# via openpyxl # via openpyxl
fonttools[woff]==4.33.3 fonttools[woff]==4.34.4
# via weasyprint # via weasyprint
gunicorn==20.1.0 gunicorn==20.1.0
# via -r requirements.in # via -r requirements.in
@ -135,7 +135,7 @@ itypes==1.2.0
# via coreapi # via coreapi
jinja2==3.1.2 jinja2==3.1.2
# via coreschema # via coreschema
markdown==3.3.7 markdown==3.4.1
# via django-markdownify # via django-markdownify
markuppy==1.14 markuppy==1.14
# via tablib # via tablib
@ -149,7 +149,7 @@ openpyxl==3.0.10
# via tablib # via tablib
pdf2image==1.16.0 pdf2image==1.16.0
# via -r requirements.in # via -r requirements.in
pillow==9.1.1 pillow==9.2.0
# via # via
# -r requirements.in # -r requirements.in
# django-stdimage # django-stdimage
@ -194,14 +194,14 @@ redis==3.5.3
# via # via
# django-q # django-q
# django-redis # django-redis
requests==2.28.0 requests==2.28.1
# via # via
# coreapi # coreapi
# django-allauth # django-allauth
# requests-oauthlib # requests-oauthlib
requests-oauthlib==1.3.1 requests-oauthlib==1.3.1
# via django-allauth # via django-allauth
sentry-sdk==1.6.0 sentry-sdk==1.9.0
# via -r requirements.in # via -r requirements.in
six==1.16.0 six==1.16.0
# via # via
@ -224,14 +224,16 @@ tinycss2==1.1.1
# weasyprint # weasyprint
uritemplate==4.1.1 uritemplate==4.1.1
# via coreapi # via coreapi
urllib3==1.26.9 urllib3==1.26.11
# via # via
# requests # requests
# sentry-sdk # sentry-sdk
wcwidth==0.2.5 wcwidth==0.2.5
# via blessed # via blessed
weasyprint==55.0 weasyprint==54.3
# via django-weasyprint # via
# -r requirements.in
# django-weasyprint
webencodings==0.5.1 webencodings==0.5.1
# via # via
# bleach # bleach
@ -242,7 +244,7 @@ xlrd==2.0.1
# via tablib # via tablib
xlwt==1.3.0 xlwt==1.3.0
# via tablib # via tablib
zipp==3.8.0 zipp==3.8.1
# via importlib-metadata # via importlib-metadata
zopfli==0.2.1 zopfli==0.2.1
# via fonttools # via fonttools

View File

@ -264,7 +264,7 @@ def export_records(c, filename='data.json', overwrite=False, include_permissions
print(f"Exporting database records to file '{filename}'") print(f"Exporting database records to file '{filename}'")
if filename.exists() and overwrite is False: if Path(filename).is_file() and overwrite is False:
response = input("Warning: file already exists. Do you want to overwrite? [y/N]: ") response = input("Warning: file already exists. Do you want to overwrite? [y/N]: ")
response = str(response).strip().lower() response = str(response).strip().lower()