Merge remote-tracking branch 'inventree/master'

This commit is contained in:
Oliver Walters 2022-07-03 20:48:32 +10:00
commit 6a0da08f3f
72 changed files with 19130 additions and 17588 deletions

View File

@ -15,6 +15,10 @@ inputs:
required: false required: false
description: 'Install the InvenTree requirements?' description: 'Install the InvenTree requirements?'
default: 'false' default: 'false'
dev-install:
required: false
description: 'Install the InvenTree development requirements?'
default: 'false'
update: update:
required: false required: false
description: 'Should a full update cycle be run?' description: 'Should a full update cycle be run?'
@ -72,6 +76,10 @@ runs:
sudo apt-get install ${{ inputs.apt-dependency }} sudo apt-get install ${{ inputs.apt-dependency }}
# Invoke commands # Invoke commands
- name: Install dev requirements
if: ${{ inputs.dev-install == 'true' ||inputs.install == 'true' }}
shell: bash
run: pip install -r requirements-dev.txt
- name: Run invoke install - name: Run invoke install
if: ${{ inputs.install == 'true' }} if: ${{ inputs.install == 'true' }}
shell: bash shell: bash

View File

@ -39,10 +39,13 @@ jobs:
python3 ci/version_check.py python3 ci/version_check.py
echo "git_commit_hash=$(git rev-parse --short HEAD)" >> $GITHUB_ENV echo "git_commit_hash=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "git_commit_date=$(git show -s --format=%ci)" >> $GITHUB_ENV echo "git_commit_date=$(git show -s --format=%ci)" >> $GITHUB_ENV
- name: Run Unit Tests - name: Build Docker Image
run: | run: |
docker-compose build docker-compose build
- name: Run Unit Tests
run: |
docker-compose run inventree-dev-server invoke update docker-compose run inventree-dev-server invoke update
docker-compose run inventree-dev-server invoke setup-dev
docker-compose up -d docker-compose up -d
docker-compose run inventree-dev-server invoke wait docker-compose run inventree-dev-server invoke wait
docker-compose run inventree-dev-server invoke test docker-compose run inventree-dev-server invoke test

View File

@ -34,7 +34,7 @@ jobs:
- name: Enviroment Setup - name: Enviroment Setup
uses: ./.github/actions/setup uses: ./.github/actions/setup
with: with:
install: true dev-install: true
- name: Run flake8 - name: Run flake8
run: flake8 InvenTree --extend-ignore=D run: flake8 InvenTree --extend-ignore=D
@ -119,6 +119,7 @@ jobs:
uses: ./.github/actions/setup uses: ./.github/actions/setup
with: with:
apt-dependency: gettext poppler-utils apt-dependency: gettext poppler-utils
dev-install: true
update: true update: true
- name: Download Python Code For `${{ env.wrapper_name }}` - name: Download Python Code For `${{ env.wrapper_name }}`
run: git clone --depth 1 https://github.com/inventree/${{ env.wrapper_name }} ./${{ env.wrapper_name }} run: git clone --depth 1 https://github.com/inventree/${{ env.wrapper_name }} ./${{ env.wrapper_name }}
@ -168,6 +169,7 @@ jobs:
uses: ./.github/actions/setup uses: ./.github/actions/setup
with: with:
apt-dependency: gettext poppler-utils apt-dependency: gettext poppler-utils
dev-install: true
update: true update: true
- name: Coverage Tests - name: Coverage Tests
run: invoke coverage run: invoke coverage
@ -218,6 +220,7 @@ jobs:
with: with:
apt-dependency: gettext poppler-utils libpq-dev apt-dependency: gettext poppler-utils libpq-dev
pip-dependency: psycopg2 django-redis>=5.0.0 pip-dependency: psycopg2 django-redis>=5.0.0
dev-install: true
update: true update: true
- name: Run Tests - name: Run Tests
run: invoke test run: invoke test
@ -261,6 +264,7 @@ jobs:
with: with:
apt-dependency: gettext poppler-utils libmysqlclient-dev apt-dependency: gettext poppler-utils libmysqlclient-dev
pip-dependency: mysqlclient pip-dependency: mysqlclient
dev-install: true
update: true update: true
- name: Run Tests - name: Run Tests
run: invoke test run: invoke test

View File

@ -28,3 +28,14 @@ repos:
rev: '5.10.1' rev: '5.10.1'
hooks: hooks:
- id: isort - id: isort
- repo: https://github.com/jazzband/pip-tools
rev: 6.6.2
hooks:
- id: pip-compile
name: pip-compile requirements-dev.in
args: [--generate-hashes, requirements-dev.in, -o, requirements-dev.txt]
files: ^requirements-dev\.(in|txt)$
- id: pip-compile
name: pip-compile requirements.txt
args: [requirements.in, -o, requirements.txt]
files: ^requirements\.(in|txt)$

View File

@ -33,6 +33,7 @@ class InvenTreeMoneySerializer(MoneyField):
"""Overrite default values.""" """Overrite default values."""
kwargs["max_digits"] = kwargs.get("max_digits", 19) kwargs["max_digits"] = kwargs.get("max_digits", 19)
kwargs["decimal_places"] = kwargs.get("decimal_places", 4) kwargs["decimal_places"] = kwargs.get("decimal_places", 4)
kwargs["required"] = kwargs.get("required", False)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)

View File

@ -253,7 +253,6 @@ INSTALLED_APPS = [
'django_cleanup.apps.CleanupConfig', # Automatically delete orphaned MEDIA files 'django_cleanup.apps.CleanupConfig', # Automatically delete orphaned MEDIA files
'mptt', # Modified Preorder Tree Traversal 'mptt', # Modified Preorder Tree Traversal
'markdownify', # Markdown template rendering 'markdownify', # Markdown template rendering
'django_admin_shell', # Python shell for the admin interface
'djmoney', # django-money integration 'djmoney', # django-money integration
'djmoney.contrib.exchange', # django-money exchange rates 'djmoney.contrib.exchange', # django-money exchange rates
'error_report', # Error reporting in the admin interface 'error_report', # Error reporting in the admin interface
@ -532,7 +531,7 @@ if "postgres" in db_engine: # pragma: no cover
# https://docs.djangoproject.com/en/3.2/ref/databases/#isolation-level # https://docs.djangoproject.com/en/3.2/ref/databases/#isolation-level
if "isolation_level" not in db_options: if "isolation_level" not in db_options:
serializable = _is_true( serializable = _is_true(
os.getenv("INVENTREE_DB_ISOLATION_SERIALIZABLE", "true") os.getenv("INVENTREE_DB_ISOLATION_SERIALIZABLE", "false")
) )
db_options["isolation_level"] = ( db_options["isolation_level"] = (
ISOLATION_LEVEL_SERIALIZABLE ISOLATION_LEVEL_SERIALIZABLE
@ -904,11 +903,13 @@ MARKDOWNIFY_BLEACH = False
SENTRY_ENABLED = get_setting('INVENTREE_SENTRY_ENABLED', CONFIG.get('sentry_enabled', False)) SENTRY_ENABLED = get_setting('INVENTREE_SENTRY_ENABLED', CONFIG.get('sentry_enabled', False))
SENTRY_DSN = get_setting('INVENTREE_SENTRY_DSN', CONFIG.get('sentry_dsn', INVENTREE_DSN)) SENTRY_DSN = get_setting('INVENTREE_SENTRY_DSN', CONFIG.get('sentry_dsn', INVENTREE_DSN))
SENTRY_SAMPLE_RATE = float(get_setting('INVENTREE_SENTRY_SAMPLE_RATE', CONFIG.get('sentry_sample_rate', 0.1)))
if SENTRY_ENABLED and SENTRY_DSN: # pragma: no cover if SENTRY_ENABLED and SENTRY_DSN: # pragma: no cover
sentry_sdk.init( sentry_sdk.init(
dsn=SENTRY_DSN, dsn=SENTRY_DSN,
integrations=[DjangoIntegration(), ], integrations=[DjangoIntegration(), ],
traces_sample_rate=1.0 if DEBUG else 0.15, traces_sample_rate=1.0 if DEBUG else SENTRY_SAMPLE_RATE,
send_default_pii=True send_default_pii=True
) )
inventree_tags = { inventree_tags = {

View File

@ -61,7 +61,7 @@ def raise_warning(msg):
warnings.warn(msg) warnings.warn(msg)
def offload_task(taskname, *args, force_sync=False, **kwargs): def offload_task(taskname, *args, force_async=False, force_sync=False, **kwargs):
"""Create an AsyncTask if workers are running. This is different to a 'scheduled' task, in that it only runs once! """Create an AsyncTask if workers are running. This is different to a 'scheduled' task, in that it only runs once!
If workers are not running or force_sync flag If workers are not running or force_sync flag
@ -79,7 +79,7 @@ def offload_task(taskname, *args, force_sync=False, **kwargs):
except (OperationalError, ProgrammingError): # pragma: no cover except (OperationalError, ProgrammingError): # pragma: no cover
raise_warning(f"Could not offload task '{taskname}' - database not ready") raise_warning(f"Could not offload task '{taskname}' - database not ready")
if is_worker_running() and not force_sync: # pragma: no cover if force_async or (is_worker_running() and not force_sync):
# Running as asynchronous task # Running as asynchronous task
try: try:
task = AsyncTask(taskname, *args, **kwargs) task = AsyncTask(taskname, *args, **kwargs)

View File

@ -9,6 +9,7 @@ from unittest import mock
import django.core.exceptions as django_exceptions import django.core.exceptions as django_exceptions
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.sites.models import Site
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.test import TestCase, override_settings from django.test import TestCase, override_settings
@ -19,9 +20,11 @@ from djmoney.money import Money
import InvenTree.tasks import InvenTree.tasks
from common.models import InvenTreeSetting from common.models import InvenTreeSetting
from common.settings import currency_codes from common.settings import currency_codes
from stock.models import StockLocation from part.models import Part, PartCategory
from stock.models import StockItem, StockLocation
from . import config, helpers, ready, status, version from . import config, helpers, ready, status, version
from .tasks import offload_task
from .validators import validate_overage, validate_part_name from .validators import validate_overage, validate_part_name
@ -604,3 +607,61 @@ class TestInstanceName(helpers.InvenTreeTestCase):
InvenTreeSetting.set_setting("INVENTREE_INSTANCE", "Testing title", self.user) InvenTreeSetting.set_setting("INVENTREE_INSTANCE", "Testing title", self.user)
self.assertEqual(version.inventreeInstanceTitle(), 'Testing title') self.assertEqual(version.inventreeInstanceTitle(), 'Testing title')
# The site should also be changed
site_obj = Site.objects.all().order_by('id').first()
self.assertEqual(site_obj.name, 'Testing title')
def test_instance_url(self):
"""Test instance url settings."""
# Set up required setting
InvenTreeSetting.set_setting("INVENTREE_BASE_URL", "http://127.1.2.3", self.user)
# The site should also be changed
site_obj = Site.objects.all().order_by('id').first()
self.assertEqual(site_obj.domain, 'http://127.1.2.3')
class TestOffloadTask(helpers.InvenTreeTestCase):
"""Tests for offloading tasks to the background worker"""
fixtures = [
'category',
'part',
'location',
'stock',
]
def test_offload_tasks(self):
"""Test that we can offload various tasks to the background worker thread.
This set of tests also ensures that various types of objects
can be encoded by the django-q serialization layer!
Note that as the background worker is not actually running for the tests,
the call to 'offload_task' won't really *do* anything!
However, it serves as a validation that object serialization works!
Ref: https://github.com/inventree/InvenTree/pull/3273
"""
offload_task(
'dummy_tasks.parts',
part=Part.objects.get(pk=1),
cat=PartCategory.objects.get(pk=1),
force_async=True
)
offload_task(
'dummy_tasks.stock',
item=StockItem.objects.get(pk=1),
loc=StockLocation.objects.get(pk=1),
force_async=True
)
offload_task(
'dummy_task.numbers',
1, 2, 3, 4, 5,
force_async=True
)

View File

@ -152,7 +152,6 @@ frontendpatterns = [
# admin sites # admin sites
re_path(f'^{settings.INVENTREE_ADMIN_URL}/error_log/', include('error_report.urls')), re_path(f'^{settings.INVENTREE_ADMIN_URL}/error_log/', include('error_report.urls')),
re_path(f'^{settings.INVENTREE_ADMIN_URL}/shell/', include('django_admin_shell.urls')),
re_path(f'^{settings.INVENTREE_ADMIN_URL}/', admin.site.urls, name='inventree-admin'), re_path(f'^{settings.INVENTREE_ADMIN_URL}/', admin.site.urls, name='inventree-admin'),
# DB user sessions # DB user sessions

View File

@ -420,7 +420,7 @@ class BuildItemList(ListCreateAPI):
DjangoFilterBackend, DjangoFilterBackend,
] ]
filter_fields = [ filterset_fields = [
'build', 'build',
'stock_item', 'stock_item',
'bom_item', 'bom_item',
@ -438,7 +438,7 @@ class BuildAttachmentList(AttachmentMixin, ListCreateDestroyAPIView):
DjangoFilterBackend, DjangoFilterBackend,
] ]
filter_fields = [ filterset_fields = [
'build', 'build',
] ]

View File

@ -1018,6 +1018,20 @@ class Build(MPTTModel, ReferenceIndexingMixin):
"""Returns True if the un-tracked parts are fully allocated for this BuildOrder.""" """Returns True if the un-tracked parts are fully allocated for this BuildOrder."""
return self.is_fully_allocated(None) return self.is_fully_allocated(None)
def has_overallocated_parts(self, output):
"""Check if parts have been 'over-allocated' against the specified output.
Note: If output=None, test un-tracked parts
"""
bom_items = self.tracked_bom_items if output else self.untracked_bom_items
for bom_item in bom_items:
if self.allocated_quantity(bom_item, output) > self.required_quantity(bom_item, output):
return True
return False
def unallocated_bom_items(self, output): def unallocated_bom_items(self, output):
"""Return a list of bom items which have *not* been fully allocated against a particular output.""" """Return a list of bom items which have *not* been fully allocated against a particular output."""
unallocated = [] unallocated = []

View File

@ -466,6 +466,22 @@ class BuildCancelSerializer(serializers.Serializer):
class BuildCompleteSerializer(serializers.Serializer): class BuildCompleteSerializer(serializers.Serializer):
"""DRF serializer for marking a BuildOrder as complete.""" """DRF serializer for marking a BuildOrder as complete."""
accept_overallocated = serializers.BooleanField(
label=_('Accept Overallocated'),
help_text=_('Accept stock items which have been overallocated to this build order'),
required=False,
default=False,
)
def validate_accept_overallocated(self, value):
"""Check if the 'accept_overallocated' field is required"""
build = self.context['build']
if build.has_overallocated_parts(output=None) and not value:
raise ValidationError(_('Some stock items have been overallocated'))
return value
accept_unallocated = serializers.BooleanField( accept_unallocated = serializers.BooleanField(
label=_('Accept Unallocated'), label=_('Accept Unallocated'),
help_text=_('Accept that stock items have not been fully allocated to this build order'), help_text=_('Accept that stock items have not been fully allocated to this build order'),

View File

@ -289,7 +289,7 @@ class NotificationList(BulkDeleteMixin, ListAPI):
'message', 'message',
] ]
filter_fields = [ filterset_fields = [
'category', 'category',
'read', 'read',
] ]

View File

@ -21,6 +21,7 @@ from django.contrib.auth.models import Group, User
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.humanize.templatetags.humanize import naturaltime from django.contrib.humanize.templatetags.humanize import naturaltime
from django.contrib.sites.models import Site
from django.core.cache import cache from django.core.cache import cache
from django.core.exceptions import AppRegistryNotReady, ValidationError from django.core.exceptions import AppRegistryNotReady, ValidationError
from django.core.validators import MinValueValidator, URLValidator from django.core.validators import MinValueValidator, URLValidator
@ -82,6 +83,14 @@ class BaseInvenTreeSetting(models.Model):
super().save() super().save()
# Get after_save action
setting = self.get_setting_definition(self.key, *args, **kwargs)
after_save = setting.get('after_save', None)
# Execute if callable
if callable(after_save):
after_save(self)
@property @property
def cache_key(self): def cache_key(self):
"""Generate a unique cache key for this settings object""" """Generate a unique cache key for this settings object"""
@ -735,6 +744,20 @@ def settings_group_options():
return [('', _('No group')), *[(str(a.id), str(a)) for a in Group.objects.all()]] return [('', _('No group')), *[(str(a.id), str(a)) for a in Group.objects.all()]]
def update_instance_url(setting):
"""Update the first site objects domain to url."""
site_obj = Site.objects.all().order_by('id').first()
site_obj.domain = setting.value
site_obj.save()
def update_instance_name(setting):
"""Update the first site objects name to instance name."""
site_obj = Site.objects.all().order_by('id').first()
site_obj.name = setting.value
site_obj.save()
class InvenTreeSetting(BaseInvenTreeSetting): class InvenTreeSetting(BaseInvenTreeSetting):
"""An InvenTreeSetting object is a key:value pair used for storing single values (e.g. one-off settings values). """An InvenTreeSetting object is a key:value pair used for storing single values (e.g. one-off settings values).
@ -782,6 +805,7 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'name': _('Server Instance Name'), 'name': _('Server Instance Name'),
'default': 'InvenTree', 'default': 'InvenTree',
'description': _('String descriptor for the server instance'), 'description': _('String descriptor for the server instance'),
'after_save': update_instance_name,
}, },
'INVENTREE_INSTANCE_TITLE': { 'INVENTREE_INSTANCE_TITLE': {
@ -809,6 +833,7 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'description': _('Base URL for server instance'), 'description': _('Base URL for server instance'),
'validator': EmptyURLValidator(), 'validator': EmptyURLValidator(),
'default': '', 'default': '',
'after_save': update_instance_url,
}, },
'INVENTREE_DEFAULT_CURRENCY': { 'INVENTREE_DEFAULT_CURRENCY': {

View File

@ -296,6 +296,13 @@ class InvenTreeNotificationBodies:
) )
"""Send when a new order (build, sale or purchase) was created.""" """Send when a new order (build, sale or purchase) was created."""
ItemsReceived = NotificationBody(
name=_("Items Received"),
slug='purchase_order.items_received',
message=_('Items have been received against a purchase order'),
template='email/purchase_order_received.html',
)
def trigger_notification(obj, category=None, obj_ref='pk', **kwargs): def trigger_notification(obj, category=None, obj_ref='pk', **kwargs):
"""Send out a notification.""" """Send out a notification."""

View File

@ -133,6 +133,7 @@ class SettingsTest(InvenTreeTestCase):
'choices', 'choices',
'units', 'units',
'requires_restart', 'requires_restart',
'after_save',
] ]
for k in setting.keys(): for k in setting.keys():

View File

@ -46,7 +46,7 @@ class CompanyList(ListCreateAPI):
filters.OrderingFilter, filters.OrderingFilter,
] ]
filter_fields = [ filterset_fields = [
'is_customer', 'is_customer',
'is_manufacturer', 'is_manufacturer',
'is_supplier', 'is_supplier',
@ -169,7 +169,7 @@ class ManufacturerPartAttachmentList(AttachmentMixin, ListCreateDestroyAPIView):
DjangoFilterBackend, DjangoFilterBackend,
] ]
filter_fields = [ filterset_fields = [
'manufacturer_part', 'manufacturer_part',
] ]
@ -233,7 +233,7 @@ class ManufacturerPartParameterList(ListCreateDestroyAPIView):
filters.OrderingFilter, filters.OrderingFilter,
] ]
filter_fields = [ filterset_fields = [
'name', 'name',
'value', 'value',
'units', 'units',
@ -333,7 +333,7 @@ class SupplierPartList(ListCreateDestroyAPIView):
filters.OrderingFilter, filters.OrderingFilter,
] ]
filter_fields = [ filterset_fields = [
] ]
search_fields = [ search_fields = [
@ -345,6 +345,7 @@ class SupplierPartList(ListCreateDestroyAPIView):
'part__IPN', 'part__IPN',
'part__name', 'part__name',
'part__description', 'part__description',
'part__keywords',
] ]
@ -377,7 +378,7 @@ class SupplierPriceBreakList(ListCreateAPI):
DjangoFilterBackend, DjangoFilterBackend,
] ]
filter_fields = [ filterset_fields = [
'part', 'part',
] ]

View File

@ -0,0 +1,20 @@
# Generated by Django 3.2.13 on 2022-06-27 23:08
import company.models
from django.db import migrations
import stdimage.models
class Migration(migrations.Migration):
dependencies = [
('company', '0045_alter_company_notes'),
]
operations = [
migrations.AlterField(
model_name='company',
name='image',
field=stdimage.models.StdImageField(blank=True, force_min_size=False, null=True, upload_to=company.models.rename_company_image, variations={'preview': (256, 256), 'thumbnail': (128, 128)}, verbose_name='Image'),
),
]

View File

@ -14,7 +14,7 @@
<h4>{% trans "Supplier Parts" %}</h4> <h4>{% trans "Supplier Parts" %}</h4>
{% include "spacer.html" %} {% include "spacer.html" %}
<div class='btn-group' role='group'> <div class='btn-group' role='group'>
{% if roles.purchase_order.add %} {% if roles.purchase_order.add and not part.virtual %}
<button class="btn btn-success" id='supplier-part-create' title='{% trans "Create new supplier part" %}'> <button class="btn btn-success" id='supplier-part-create' title='{% trans "Create new supplier part" %}'>
<span class='fas fa-plus-circle'></span> {% trans "New Supplier Part" %} <span class='fas fa-plus-circle'></span> {% trans "New Supplier Part" %}
</button> </button>
@ -61,7 +61,7 @@
<h4>{% trans "Manufacturer Parts" %}</h4> <h4>{% trans "Manufacturer Parts" %}</h4>
{% include "spacer.html" %} {% include "spacer.html" %}
<div class='btn-group' role='group'> <div class='btn-group' role='group'>
{% if roles.purchase_order.add %} {% if roles.purchase_order.add and not part.virtual %}
<button type="button" class="btn btn-success" id='manufacturer-part-create' title='{% trans "Create new manufacturer part" %}'> <button type="button" class="btn btn-success" id='manufacturer-part-create' title='{% trans "Create new manufacturer part" %}'>
<span class='fas fa-plus-circle'></span> {% trans "New Manufacturer Part" %} <span class='fas fa-plus-circle'></span> {% trans "New Manufacturer Part" %}
</button> </button>

View File

@ -31,7 +31,7 @@ class LabelListView(ListAPI):
filters.SearchFilter filters.SearchFilter
] ]
filter_fields = [ filterset_fields = [
'enabled', 'enabled',
] ]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -70,7 +70,7 @@ class GeneralExtraLineList:
'reference' 'reference'
] ]
filter_fields = [ filterset_fields = [
'order', 'order',
] ]
@ -540,7 +540,7 @@ class SalesOrderAttachmentList(AttachmentMixin, ListCreateDestroyAPIView):
rest_filters.DjangoFilterBackend, rest_filters.DjangoFilterBackend,
] ]
filter_fields = [ filterset_fields = [
'order', 'order',
] ]
@ -672,7 +672,7 @@ class SalesOrderList(APIDownloadMixin, ListCreateAPI):
'reference': ['reference_int', 'reference'], 'reference': ['reference_int', 'reference'],
} }
filter_fields = [ filterset_fields = [
'customer', 'customer',
] ]
@ -814,7 +814,7 @@ class SalesOrderLineItemList(ListCreateAPI):
'reference', 'reference',
] ]
filter_fields = [ filterset_fields = [
'order', 'order',
'part', 'part',
] ]
@ -990,7 +990,7 @@ class SalesOrderAllocationList(ListAPI):
] ]
# Default filterable fields # Default filterable fields
filter_fields = [ filterset_fields = [
] ]
@ -1069,7 +1069,7 @@ class PurchaseOrderAttachmentList(AttachmentMixin, ListCreateDestroyAPIView):
rest_filters.DjangoFilterBackend, rest_filters.DjangoFilterBackend,
] ]
filter_fields = [ filterset_fields = [
'order', 'order',
] ]

View File

@ -0,0 +1,26 @@
# Generated by Django 3.2.13 on 2022-06-28 01:33
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('stock', '0077_alter_stockitem_notes'),
('part', '0079_alter_part_notes'),
('order', '0070_auto_20220620_0728'),
]
operations = [
migrations.AlterField(
model_name='salesorderallocation',
name='item',
field=models.ForeignKey(help_text='Select stock item to allocate', limit_choices_to={'belongs_to': None, 'part__salable': True, 'part__virtual': False, 'sales_order': None}, on_delete=django.db.models.deletion.CASCADE, related_name='sales_order_allocations', to='stock.stockitem', verbose_name='Item'),
),
migrations.AlterField(
model_name='salesorderlineitem',
name='part',
field=models.ForeignKey(help_text='Part', limit_choices_to={'salable': True, 'virtual': False}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sales_order_line_items', to='part.part', verbose_name='Part'),
),
]

View File

@ -24,6 +24,7 @@ from mptt.models import TreeForeignKey
import InvenTree.helpers import InvenTree.helpers
import InvenTree.ready import InvenTree.ready
from common.notifications import InvenTreeNotificationBodies
from common.settings import currency_code_default from common.settings import currency_code_default
from company.models import Company, SupplierPart from company.models import Company, SupplierPart
from InvenTree.exceptions import log_error from InvenTree.exceptions import log_error
@ -560,6 +561,14 @@ class PurchaseOrder(Order):
self.received_by = user self.received_by = user
self.complete_order() # This will save the model self.complete_order() # This will save the model
# Issue a notification to interested parties, that this order has been "updated"
notify_responsible(
self,
PurchaseOrder,
exclude=user,
content=InvenTreeNotificationBodies.ItemsReceived,
)
@receiver(post_save, sender=PurchaseOrder, dispatch_uid='purchase_order_post_save') @receiver(post_save, sender=PurchaseOrder, dispatch_uid='purchase_order_post_save')
def after_save_purchase_order(sender, instance: PurchaseOrder, created: bool, **kwargs): def after_save_purchase_order(sender, instance: PurchaseOrder, created: bool, **kwargs):
@ -1094,6 +1103,22 @@ class SalesOrderLineItem(OrderLineItem):
"""Return the API URL associated with the SalesOrderLineItem model""" """Return the API URL associated with the SalesOrderLineItem model"""
return reverse('api-so-line-list') return reverse('api-so-line-list')
def clean(self):
"""Perform extra validation steps for this SalesOrderLineItem instance"""
super().clean()
if self.part:
if self.part.virtual:
raise ValidationError({
'part': _("Virtual part cannot be assigned to a sales order")
})
if not self.part.salable:
raise ValidationError({
'part': _("Only salable parts can be assigned to a sales order")
})
order = models.ForeignKey( order = models.ForeignKey(
SalesOrder, SalesOrder,
on_delete=models.CASCADE, on_delete=models.CASCADE,
@ -1102,7 +1127,16 @@ class SalesOrderLineItem(OrderLineItem):
help_text=_('Sales Order') help_text=_('Sales Order')
) )
part = models.ForeignKey('part.Part', on_delete=models.SET_NULL, related_name='sales_order_line_items', null=True, verbose_name=_('Part'), help_text=_('Part'), limit_choices_to={'salable': True}) part = models.ForeignKey(
'part.Part', on_delete=models.SET_NULL,
related_name='sales_order_line_items',
null=True,
verbose_name=_('Part'),
help_text=_('Part'),
limit_choices_to={
'salable': True,
'virtual': False,
})
sale_price = InvenTreeModelMoneyField( sale_price = InvenTreeModelMoneyField(
max_digits=19, max_digits=19,
@ -1400,6 +1434,7 @@ class SalesOrderAllocation(models.Model):
related_name='sales_order_allocations', related_name='sales_order_allocations',
limit_choices_to={ limit_choices_to={
'part__salable': True, 'part__salable': True,
'part__virtual': False,
'belongs_to': None, 'belongs_to': None,
'sales_order': None, 'sales_order': None,
}, },

View File

@ -135,7 +135,7 @@ class CategoryList(ListCreateAPI):
filters.OrderingFilter, filters.OrderingFilter,
] ]
filter_fields = [ filterset_fields = [
] ]
ordering_fields = [ ordering_fields = [
@ -281,7 +281,7 @@ class PartSalePriceList(ListCreateAPI):
DjangoFilterBackend DjangoFilterBackend
] ]
filter_fields = [ filterset_fields = [
'part', 'part',
] ]
@ -304,7 +304,7 @@ class PartInternalPriceList(ListCreateAPI):
DjangoFilterBackend DjangoFilterBackend
] ]
filter_fields = [ filterset_fields = [
'part', 'part',
] ]
@ -319,7 +319,7 @@ class PartAttachmentList(AttachmentMixin, ListCreateDestroyAPIView):
DjangoFilterBackend, DjangoFilterBackend,
] ]
filter_fields = [ filterset_fields = [
'part', 'part',
] ]
@ -866,7 +866,8 @@ class PartFilter(rest_filters.FilterSet):
def filter_in_bom(self, queryset, name, part): def filter_in_bom(self, queryset, name, part):
"""Limit queryset to parts in the BOM for the specified part""" """Limit queryset to parts in the BOM for the specified part"""
queryset = queryset.filter(id__in=part.get_parts_in_bom()) bom_parts = part.get_parts_in_bom()
queryset = queryset.filter(id__in=[p.pk for p in bom_parts])
return queryset return queryset
is_template = rest_filters.BooleanFilter() is_template = rest_filters.BooleanFilter()
@ -1412,7 +1413,7 @@ class PartParameterTemplateList(ListCreateAPI):
filters.SearchFilter, filters.SearchFilter,
] ]
filter_fields = [ filterset_fields = [
'name', 'name',
] ]
@ -1477,7 +1478,7 @@ class PartParameterList(ListCreateAPI):
DjangoFilterBackend DjangoFilterBackend
] ]
filter_fields = [ filterset_fields = [
'part', 'part',
'template', 'template',
] ]
@ -1758,7 +1759,7 @@ class BomList(ListCreateDestroyAPIView):
filters.OrderingFilter, filters.OrderingFilter,
] ]
filter_fields = [ filterset_fields = [
] ]
@ -1857,7 +1858,7 @@ class BomItemSubstituteList(ListCreateAPI):
filters.OrderingFilter, filters.OrderingFilter,
] ]
filter_fields = [ filterset_fields = [
'part', 'part',
'bom_item', 'bom_item',
] ]

View File

@ -0,0 +1,20 @@
# Generated by Django 3.2.13 on 2022-06-27 23:08
from django.db import migrations
import part.models
import stdimage.models
class Migration(migrations.Migration):
dependencies = [
('part', '0079_alter_part_notes'),
]
operations = [
migrations.AlterField(
model_name='part',
name='image',
field=stdimage.models.StdImageField(blank=True, force_min_size=False, null=True, upload_to=part.models.rename_part_image, variations={'preview': (256, 256), 'thumbnail': (128, 128)}, verbose_name='Image'),
),
]

View File

@ -20,7 +20,7 @@
<h4>{% trans "Part Stock" %}</h4> <h4>{% trans "Part Stock" %}</h4>
{% include "spacer.html" %} {% include "spacer.html" %}
<div class='btn-group' role='group'> <div class='btn-group' role='group'>
{% if roles.stock.add %} {% if roles.stock.add and not part.virtual %}
<button type='button' class='btn btn-success' id='new-stock-item' title='{% trans "Create new stock item" %}'> <button type='button' class='btn btn-success' id='new-stock-item' title='{% trans "Create new stock item" %}'>
<span class='fas fa-plus-circle'></span> {% trans "New Stock Item" %} <span class='fas fa-plus-circle'></span> {% trans "New Stock Item" %}
</button> </button>

View File

@ -400,6 +400,21 @@ class PartAPITest(InvenTreeAPITestCase):
for part in response.data: for part in response.data:
self.assertEqual(part['category'], 2) self.assertEqual(part['category'], 2)
def test_filter_by_in_bom(self):
"""Test that we can filter part list by the 'in_bom_for' parameter"""
url = reverse('api-part-list')
response = self.get(
url,
{
'in_bom_for': 100,
},
expected_code=200,
)
self.assertEqual(len(response.data), 4)
def test_filter_by_related(self): def test_filter_by_related(self):
"""Test that we can filter by the 'related' status""" """Test that we can filter by the 'related' status"""
url = reverse('api-part-list') url = reverse('api-part-list')

View File

@ -1,14 +1,13 @@
"""Utility file to enable simper imports.""" """Utility file to enable simper imports."""
from .helpers import MixinImplementationError, MixinNotImplementedError from .helpers import MixinImplementationError, MixinNotImplementedError
from .plugin import IntegrationPluginBase, InvenTreePlugin from .plugin import InvenTreePlugin
from .registry import registry from .registry import registry
__all__ = [ __all__ = [
'registry', 'registry',
'InvenTreePlugin', 'InvenTreePlugin',
'IntegrationPluginBase',
'MixinNotImplementedError', 'MixinNotImplementedError',
'MixinImplementationError', 'MixinImplementationError',
] ]

View File

@ -62,7 +62,7 @@ class PluginList(ListAPI):
filters.OrderingFilter, filters.OrderingFilter,
] ]
filter_fields = [ filterset_fields = [
'active', 'active',
] ]
@ -140,7 +140,7 @@ class PluginSettingList(ListAPI):
DjangoFilterBackend, DjangoFilterBackend,
] ]
filter_fields = [ filterset_fields = [
'plugin__active', 'plugin__active',
'plugin__key', 'plugin__key',
] ]

View File

@ -298,16 +298,3 @@ class InvenTreePlugin(MixinBase, MetaBase):
self.package = package self.package = package
self.sign_state = sign_state self.sign_state = sign_state
# endregion # endregion
class IntegrationPluginBase(InvenTreePlugin):
"""Legacy base class for plugins.
Do not use!
"""
def __init__(self, *args, **kwargs):
"""Send warning about using this reference."""
# TODO remove in 0.8.0
warnings.warn("This import is deprecated - use InvenTreePlugin", DeprecationWarning)
super().__init__(*args, **kwargs)

View File

@ -5,7 +5,7 @@ from datetime import datetime
from django.test import TestCase from django.test import TestCase
import plugin.templatetags.plugin_extras as plugin_tags import plugin.templatetags.plugin_extras as plugin_tags
from plugin import IntegrationPluginBase, InvenTreePlugin, registry from plugin import InvenTreePlugin, registry
from plugin.samples.integration.another_sample import (NoIntegrationPlugin, from plugin.samples.integration.another_sample import (NoIntegrationPlugin,
WrongIntegrationPlugin) WrongIntegrationPlugin)
from plugin.samples.integration.sample import SampleIntegrationPlugin from plugin.samples.integration.sample import SampleIntegrationPlugin
@ -162,11 +162,3 @@ class InvenTreePluginTests(TestCase):
self.assertEqual(self.plugin_old.slug, 'old') self.assertEqual(self.plugin_old.slug, 'old')
# check default value is used # check default value is used
self.assertEqual(self.plugin_old.get_meta_value('ABC', 'ABCD', '123'), '123') self.assertEqual(self.plugin_old.get_meta_value('ABC', 'ABCD', '123'), '123')
# check usage of the old class fires
class OldPlugin(IntegrationPluginBase):
pass
with self.assertWarns(DeprecationWarning):
plg = OldPlugin()
self.assertIsInstance(plg, InvenTreePlugin)

View File

@ -34,7 +34,7 @@ class ReportListView(ListAPI):
filters.SearchFilter, filters.SearchFilter,
] ]
filter_fields = [ filterset_fields = [
'enabled', 'enabled',
] ]

View File

@ -283,7 +283,7 @@ class StockLocationList(ListCreateAPI):
filters.OrderingFilter, filters.OrderingFilter,
] ]
filter_fields = [ filterset_fields = [
] ]
search_fields = [ search_fields = [
@ -1064,7 +1064,7 @@ class StockAttachmentList(AttachmentMixin, ListCreateDestroyAPIView):
filters.SearchFilter, filters.SearchFilter,
] ]
filter_fields = [ filterset_fields = [
'stock_item', 'stock_item',
] ]
@ -1095,7 +1095,7 @@ class StockItemTestResultList(ListCreateDestroyAPIView):
filters.OrderingFilter, filters.OrderingFilter,
] ]
filter_fields = [ filterset_fields = [
'test', 'test',
'user', 'user',
'result', 'result',
@ -1302,7 +1302,7 @@ class StockTrackingList(ListAPI):
filters.OrderingFilter, filters.OrderingFilter,
] ]
filter_fields = [ filterset_fields = [
'item', 'item',
'user', 'user',
] ]

View File

@ -342,6 +342,7 @@ class StockItem(MetadataMixin, MPTTModel):
- Unique serial number requirement - Unique serial number requirement
- Adds a transaction note when the item is first created. - Adds a transaction note when the item is first created.
""" """
self.validate_unique() self.validate_unique()
self.clean() self.clean()
@ -439,6 +440,7 @@ class StockItem(MetadataMixin, MPTTModel):
The following validation checks are performed: The following validation checks are performed:
- The 'part' and 'supplier_part.part' fields cannot point to the same Part object - The 'part' and 'supplier_part.part' fields cannot point to the same Part object
- The 'part' is not virtual
- The 'part' does not belong to itself - The 'part' does not belong to itself
- Quantity must be 1 if the StockItem has a serial number - Quantity must be 1 if the StockItem has a serial number
""" """
@ -453,12 +455,18 @@ class StockItem(MetadataMixin, MPTTModel):
self.batch = self.batch.strip() self.batch = self.batch.strip()
try: try:
# Trackable parts must have integer values for quantity field!
if self.part.trackable: if self.part.trackable:
# Trackable parts must have integer values for quantity field!
if self.quantity != int(self.quantity): if self.quantity != int(self.quantity):
raise ValidationError({ raise ValidationError({
'quantity': _('Quantity must be integer value for trackable parts') 'quantity': _('Quantity must be integer value for trackable parts')
}) })
# Virtual parts cannot have stock items created against them
if self.part.virtual:
raise ValidationError({
'part': _("Stock item cannot be created for virtual parts"),
})
except PartModels.Part.DoesNotExist: except PartModels.Part.DoesNotExist:
# For some reason the 'clean' process sometimes throws errors because self.part does not exist # For some reason the 'clean' process sometimes throws errors because self.part does not exist
# It *seems* that this only occurs in unit testing, though. # It *seems* that this only occurs in unit testing, though.
@ -582,7 +590,8 @@ class StockItem(MetadataMixin, MPTTModel):
part = models.ForeignKey( part = models.ForeignKey(
'part.Part', on_delete=models.CASCADE, 'part.Part', on_delete=models.CASCADE,
verbose_name=_('Base Part'), verbose_name=_('Base Part'),
related_name='stock_items', help_text=_('Base part'), related_name='stock_items',
help_text=_('Base part'),
limit_choices_to={ limit_choices_to={
'virtual': False 'virtual': False
}) })

View File

@ -79,6 +79,21 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
- Includes serialization for the item location - Includes serialization for the item location
""" """
part = serializers.PrimaryKeyRelatedField(
queryset=part_models.Part.objects.all(),
many=False, allow_null=False,
help_text=_("Base Part"),
label=_("Part"),
)
def validate_part(self, part):
"""Ensure the provided Part instance is valid"""
if part.virtual:
raise ValidationError(_("Stock item cannot be created for virtual parts"))
return part
def update(self, instance, validated_data): def update(self, instance, validated_data):
"""Custom update method to pass the user information through to the instance.""" """Custom update method to pass the user information through to the instance."""
instance._user = self.context['user'] instance._user = self.context['user']
@ -168,7 +183,11 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
def get_purchase_price_string(self, obj): def get_purchase_price_string(self, obj):
"""Return purchase price as string.""" """Return purchase price as string."""
return str(obj.purchase_price) if obj.purchase_price else '-' if obj.purchase_price:
obj.purchase_price.decimal_places_display = 4
return str(obj.purchase_price)
return '-'
purchase_order_reference = serializers.CharField(source='purchase_order.reference', read_only=True) purchase_order_reference = serializers.CharField(source='purchase_order.reference', read_only=True)
sales_order_reference = serializers.CharField(source='sales_order.reference', read_only=True) sales_order_reference = serializers.CharField(source='sales_order.reference', read_only=True)

View File

@ -0,0 +1,23 @@
{% extends "account/base.html" %}
{% load i18n %}
{% load inventree_extras %}
{% block page_title %}
{% inventree_title %} | {% trans "Permission Denied" %}
{% endblock %}
{% block content %}
<h3>{% trans "Authentication Failure" %}</h3>
<div class='alert alert-danger alert-block'>
{% trans "You have been logged out from InvenTree." %}
</div>
<hr>
<div class='btn-group float-right' role='group'>
<a type='button' class='btn btn-primary' href='{% url "account_login" %}'>
<span class='fas fa-sign-in-alt'></span> {% trans "Login" %}
</a>
</div>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends "email/email.html" %}
{% load i18n %}
{% load inventree_extras %}
{% block title %}
{{ message }}
{% if link %}
<p>{% trans "Click on the following link to view this order" %}: <a href='{{ link }}'>{{ link }}</a></p>
{% endif %}
{% endblock title %}

View File

@ -284,6 +284,7 @@ function loadAttachmentTable(url, options) {
}, },
{ {
field: 'upload_date', field: 'upload_date',
sortable: true,
title: '{% trans "Upload Date" %}', title: '{% trans "Upload Date" %}',
formatter: function(value, row) { formatter: function(value, row) {
var html = renderDate(value); var html = renderDate(value);

View File

@ -174,6 +174,7 @@ function completeBuildOrder(build_id, options={}) {
var fields = { var fields = {
accept_unallocated: {}, accept_unallocated: {},
accept_overallocated: {},
accept_incomplete: {}, accept_incomplete: {},
}; };

View File

@ -429,19 +429,27 @@ function getAvailableTableFilters(tableKey) {
title: '{% trans "Include subcategories" %}', title: '{% trans "Include subcategories" %}',
description: '{% trans "Include parts in subcategories" %}', description: '{% trans "Include parts in subcategories" %}',
}, },
has_ipn: {
type: 'bool',
title: '{% trans "Has IPN" %}',
description: '{% trans "Part has internal part number" %}',
},
active: { active: {
type: 'bool', type: 'bool',
title: '{% trans "Active" %}', title: '{% trans "Active" %}',
description: '{% trans "Show active parts" %}', description: '{% trans "Show active parts" %}',
}, },
is_template: { assembly: {
type: 'bool', type: 'bool',
title: '{% trans "Template" %}', title: '{% trans "Assembly" %}',
},
unallocated_stock: {
type: 'bool',
title: '{% trans "Available stock" %}',
},
component: {
type: 'bool',
title: '{% trans "Component" %}',
},
has_ipn: {
type: 'bool',
title: '{% trans "Has IPN" %}',
description: '{% trans "Part has internal part number" %}',
}, },
has_stock: { has_stock: {
type: 'bool', type: 'bool',
@ -451,34 +459,30 @@ function getAvailableTableFilters(tableKey) {
type: 'bool', type: 'bool',
title: '{% trans "Low stock" %}', title: '{% trans "Low stock" %}',
}, },
unallocated_stock: { purchaseable: {
type: 'bool', type: 'bool',
title: '{% trans "Available stock" %}', title: '{% trans "Purchasable" %}',
},
assembly: {
type: 'bool',
title: '{% trans "Assembly" %}',
},
component: {
type: 'bool',
title: '{% trans "Component" %}',
},
starred: {
type: 'bool',
title: '{% trans "Subscribed" %}',
}, },
salable: { salable: {
type: 'bool', type: 'bool',
title: '{% trans "Salable" %}', title: '{% trans "Salable" %}',
}, },
starred: {
type: 'bool',
title: '{% trans "Subscribed" %}',
},
is_template: {
type: 'bool',
title: '{% trans "Template" %}',
},
trackable: { trackable: {
type: 'bool', type: 'bool',
title: '{% trans "Trackable" %}', title: '{% trans "Trackable" %}',
}, },
purchaseable: { virtual: {
type: 'bool', type: 'bool',
title: '{% trans "Purchasable" %}', title: '{% trans "Virtual" %}',
}, }
}; };
} }

View File

@ -374,6 +374,11 @@ $.fn.inventreeTable = function(options) {
options.pageList = [25, 50, 100, 250, 'all']; options.pageList = [25, 50, 100, 250, 'all'];
options.totalField = 'count'; options.totalField = 'count';
options.dataField = 'results'; options.dataField = 'results';
if (options.sidePagination == null) {
options.sidePagination = 'server';
}
} else { } else {
options.pagination = false; options.pagination = false;
} }

View File

@ -9,7 +9,7 @@ invoke>=1.4.0 # Invoke build tool
psycopg2>=2.9.1 psycopg2>=2.9.1
mysqlclient>=2.0.3 mysqlclient>=2.0.3
pgcli>=3.1.0 pgcli>=3.1.0
mariadb>=1.0.7 mariadb>=1.0.7,<1.1.0
# gunicorn web server # gunicorn web server
gunicorn>=20.1.0 gunicorn>=20.1.0

13
requirements-dev.in Normal file
View File

@ -0,0 +1,13 @@
# Dev requirements for InvenTree
-c requirements.txt
coverage # Unit test coverage
coveralls==2.1.2 # Coveralls linking (for tracking coverage) # PINNED 2022-06-28 - Old version needed for correct upload
django-debug-toolbar # Debug / profiling toolbar
django-test-migrations # Unit testing for database migrations
flake8 # PEP checking
flake8-docstrings # docstring format testing
isort # python import sorting
pep8-naming # PEP naming convention extension
pip-tools # Compile pip requirements
pre-commit # Git pre-commit
setuptools # Standard depenedency

279
requirements-dev.txt Normal file
View File

@ -0,0 +1,279 @@
#
# This file is autogenerated by pip-compile with python 3.9
# To update, run:
#
# pip-compile --generate-hashes --output-file=requirements-dev.txt requirements-dev.in
#
asgiref==3.5.2 \
--hash=sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4 \
--hash=sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424
# via
# -c requirements.txt
# django
certifi==2022.6.15 \
--hash=sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d \
--hash=sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412
# via
# -c requirements.txt
# requests
cfgv==3.3.1 \
--hash=sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426 \
--hash=sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736
# via pre-commit
charset-normalizer==2.0.12 \
--hash=sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597 \
--hash=sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df
# via
# -c requirements.txt
# requests
click==8.1.3 \
--hash=sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e \
--hash=sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48
# via pip-tools
coverage==5.5 \
--hash=sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c \
--hash=sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6 \
--hash=sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45 \
--hash=sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a \
--hash=sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03 \
--hash=sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529 \
--hash=sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a \
--hash=sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a \
--hash=sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2 \
--hash=sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6 \
--hash=sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759 \
--hash=sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53 \
--hash=sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a \
--hash=sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4 \
--hash=sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff \
--hash=sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502 \
--hash=sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793 \
--hash=sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb \
--hash=sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905 \
--hash=sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821 \
--hash=sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b \
--hash=sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81 \
--hash=sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0 \
--hash=sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b \
--hash=sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3 \
--hash=sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184 \
--hash=sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701 \
--hash=sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a \
--hash=sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82 \
--hash=sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638 \
--hash=sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5 \
--hash=sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083 \
--hash=sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6 \
--hash=sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90 \
--hash=sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465 \
--hash=sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a \
--hash=sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3 \
--hash=sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e \
--hash=sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066 \
--hash=sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf \
--hash=sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b \
--hash=sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae \
--hash=sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669 \
--hash=sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873 \
--hash=sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b \
--hash=sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6 \
--hash=sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb \
--hash=sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160 \
--hash=sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c \
--hash=sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079 \
--hash=sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d \
--hash=sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6
# via
# -r requirements-dev.in
# coveralls
coveralls==2.1.2 \
--hash=sha256:4430b862baabb3cf090d36d84d331966615e4288d8a8c5957e0fd456d0dd8bd6 \
--hash=sha256:b3b60c17b03a0dee61952a91aed6f131e0b2ac8bd5da909389c53137811409e1
# via -r requirements-dev.in
distlib==0.3.4 \
--hash=sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b \
--hash=sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579
# via virtualenv
django==3.2.13 \
--hash=sha256:6d93497a0a9bf6ba0e0b1a29cccdc40efbfc76297255b1309b3a884a688ec4b6 \
--hash=sha256:b896ca61edc079eb6bbaa15cf6071eb69d6aac08cce5211583cfb41515644fdf
# via
# -c requirements.txt
# django-debug-toolbar
django-debug-toolbar==3.5.0 \
--hash=sha256:89a52128309eb4da12738801ff0c202d2ff8730d1c3225fac6acf630c303e661 \
--hash=sha256:97965f2630692de316ea0c1ca5bfa81660d7ba13146dbc6be2059cf55b35d0e5
# via -r requirements-dev.in
django-test-migrations==1.2.0 \
--hash=sha256:874884ff4e980583cd9f1c986bb9fbfe72b436e759be23004e4f52c26a15f363 \
--hash=sha256:9e8b9b4364fef70dde10a5f85c5a75d447ca2189ec648325610fab1268daec97
# via -r requirements-dev.in
docopt==0.6.2 \
--hash=sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491
# via coveralls
filelock==3.7.1 \
--hash=sha256:37def7b658813cda163b56fc564cdc75e86d338246458c4c28ae84cabefa2404 \
--hash=sha256:3a0fd85166ad9dbab54c9aec96737b744106dc5f15c0b09a6744a445299fcf04
# via virtualenv
flake8==4.0.1 \
--hash=sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d \
--hash=sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d
# via
# -r requirements-dev.in
# flake8-docstrings
# pep8-naming
flake8-docstrings==1.6.0 \
--hash=sha256:99cac583d6c7e32dd28bbfbef120a7c0d1b6dde4adb5a9fd441c4227a6534bde \
--hash=sha256:9fe7c6a306064af8e62a055c2f61e9eb1da55f84bb39caef2b84ce53708ac34b
# via -r requirements-dev.in
identify==2.5.1 \
--hash=sha256:0dca2ea3e4381c435ef9c33ba100a78a9b40c0bab11189c7cf121f75815efeaa \
--hash=sha256:3d11b16f3fe19f52039fb7e39c9c884b21cb1b586988114fbe42671f03de3e82
# via pre-commit
idna==3.3 \
--hash=sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff \
--hash=sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d
# via
# -c requirements.txt
# requests
isort==5.10.1 \
--hash=sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7 \
--hash=sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951
# via -r requirements-dev.in
mccabe==0.6.1 \
--hash=sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42 \
--hash=sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f
# via flake8
nodeenv==1.7.0 \
--hash=sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e \
--hash=sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b
# via pre-commit
pep517==0.12.0 \
--hash=sha256:931378d93d11b298cf511dd634cf5ea4cb249a28ef84160b3247ee9afb4e8ab0 \
--hash=sha256:dd884c326898e2c6e11f9e0b64940606a93eb10ea022a2e067959f3a110cf161
# via pip-tools
pep8-naming==0.13.0 \
--hash=sha256:069ea20e97f073b3e6d4f789af2a57816f281ca64b86210c7d471117a4b6bfd0 \
--hash=sha256:9f38e6dcf867a1fb7ad47f5ff72c0ddae544a6cf64eb9f7600b7b3c0bb5980b5
# via -r requirements-dev.in
pip-tools==6.6.2 \
--hash=sha256:6b486548e5a139e30e4c4a225b3b7c2d46942a9f6d1a91143c21b1de4d02fd9b \
--hash=sha256:f638503a9f77d98d9a7d72584b1508d3f82ed019b8fab24f4e5ad078c1b8c95e
# via -r requirements-dev.in
platformdirs==2.5.2 \
--hash=sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788 \
--hash=sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19
# via virtualenv
pre-commit==2.19.0 \
--hash=sha256:10c62741aa5704faea2ad69cb550ca78082efe5697d6f04e5710c3c229afdd10 \
--hash=sha256:4233a1e38621c87d9dda9808c6606d7e7ba0e087cd56d3fe03202a01d2919615
# via -r requirements-dev.in
pycodestyle==2.8.0 \
--hash=sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20 \
--hash=sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f
# via flake8
pydocstyle==6.1.1 \
--hash=sha256:1d41b7c459ba0ee6c345f2eb9ae827cab14a7533a88c5c6f7e94923f72df92dc \
--hash=sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4
# via flake8-docstrings
pyflakes==2.4.0 \
--hash=sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c \
--hash=sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e
# via flake8
pytz==2022.1 \
--hash=sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7 \
--hash=sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c
# via
# -c requirements.txt
# django
pyyaml==6.0 \
--hash=sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293 \
--hash=sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b \
--hash=sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57 \
--hash=sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b \
--hash=sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4 \
--hash=sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07 \
--hash=sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba \
--hash=sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9 \
--hash=sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287 \
--hash=sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513 \
--hash=sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0 \
--hash=sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0 \
--hash=sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92 \
--hash=sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f \
--hash=sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 \
--hash=sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc \
--hash=sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c \
--hash=sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86 \
--hash=sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4 \
--hash=sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c \
--hash=sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34 \
--hash=sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b \
--hash=sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c \
--hash=sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb \
--hash=sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737 \
--hash=sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3 \
--hash=sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d \
--hash=sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53 \
--hash=sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78 \
--hash=sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803 \
--hash=sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a \
--hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \
--hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5
# via
# -c requirements.txt
# pre-commit
requests==2.28.0 \
--hash=sha256:bc7861137fbce630f17b03d3ad02ad0bf978c844f3536d0edda6499dafce2b6f \
--hash=sha256:d568723a7ebd25875d8d1eaf5dfa068cd2fc8194b2e483d7b1f7c81918dbec6b
# via
# -c requirements.txt
# coveralls
six==1.16.0 \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
# via
# -c requirements.txt
# virtualenv
snowballstemmer==2.2.0 \
--hash=sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1 \
--hash=sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a
# via pydocstyle
sqlparse==0.4.2 \
--hash=sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae \
--hash=sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d
# via
# -c requirements.txt
# django
# django-debug-toolbar
toml==0.10.2 \
--hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \
--hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f
# via pre-commit
tomli==2.0.1 \
--hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
--hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
# via pep517
typing-extensions==4.2.0 \
--hash=sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708 \
--hash=sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376
# via django-test-migrations
urllib3==1.26.9 \
--hash=sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14 \
--hash=sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e
# via
# -c requirements.txt
# requests
virtualenv==20.15.0 \
--hash=sha256:4c44b1d77ca81f8368e2d7414f9b20c428ad16b343ac6d226206c5b84e2b4fcc \
--hash=sha256:804cce4de5b8a322f099897e308eecc8f6e2951f1a8e7e2b3598dff865f01336
# via pre-commit
wheel==0.37.1 \
--hash=sha256:4bdcd7d840138086126cd09254dc6195fb4fc6f01c050a1d7236f2630db1d22a \
--hash=sha256:e9a504e793efbca1b8e0e9cb979a249cf4a0a7b5b8c9e8b65a5e39d49529c1c4
# via pip-tools
# WARNING: The following packages were not pinned, but pip requires them to be
# pinned when the requirements file includes hashes. Consider using the --allow-unsafe flag.
# pip
# setuptools

37
requirements.in Normal file
View File

@ -0,0 +1,37 @@
# Please keep this list sorted - if you pin a version provide a reason
Django<4 # Django package
coreapi # API documentation for djangorestframework
django-allauth # SSO for external providers via OpenID
django-allauth-2fa # MFA / 2FA
django-cleanup # Automated deletion of old / unused uploaded files
django-cors-headers # CORS headers extension for DRF
django-crispy-forms # Form helpers
django-error-report # Error report viewer for the admin interface
django-filter # Extended filtering options
django-formtools # Form wizard tools
django-import-export==2.5.0 # Data import / export for admin interface
django-maintenance-mode # Shut down application while reloading etc.
django-markdownify # Markdown rendering
django-money<3.0.0 # Django app for currency management # FIXED 2022-06-26 to make sure py-moneyed is not conflicting
django-mptt==0.11.0 # Modified Preorder Tree Traversal
django-redis>=5.0.0 # Redis integration
django-q # Background task scheduling
django-sql-utils # Advanced query annotation / aggregation
django-sslserver # Secure HTTP development server
django-stdimage<6.0.0 # Advanced ImageField management # FIXED 2022-06-29 6.0.0 breaks serialization for django-q
django-user-sessions # user sessions in DB
django-weasyprint # django weasyprint integration
djangorestframework # DRF framework
django-xforwardedfor-middleware # IP forwarding metadata
gunicorn # Gunicorn web server
pdf2image # PDF to image conversion
pillow # Image manipulation
python-barcode[images] # Barcode generator
qrcode[pil] # QR code generator
rapidfuzz==0.7.6 # Fuzzy string matching
sentry-sdk # Error reporting (optional)
setuptools # Standard depenedency
tablib[xls,xlsx,yaml] # Support for XLS and XLSX formats
# Fixed sub-dependencies
py-moneyed<2.0 # For django-money # FIXED 2022-06-18 as we need `moneyed.localization`

View File

@ -1,51 +1,249 @@
# Please keep this list sorted #
Django==3.2.13 # Django package # This file is autogenerated by pip-compile with python 3.9
bleach==4.1.0 # HTML santization # To update, run:
certifi # Certifi is (most likely) installed through one of the requirements above #
coreapi==2.3.0 # API documentation # pip-compile --output-file=requirements.txt requirements.in
coverage==5.3 # Unit test coverage #
coveralls==2.1.2 # Coveralls linking (for Travis) arrow==1.2.2
cryptography==3.4.8 # Cryptography support # via django-q
django-admin-shell==0.1.2 # Python shell for the admin interface asgiref==3.5.2
django-allauth==0.48.0 # SSO for external providers via OpenID # via django
django-allauth-2fa==0.9 # MFA / 2FA # IMPORTANT: Do only change after reviewing GHSA-8j76-mm54-52xq babel==2.10.3
django-cleanup==5.1.0 # Manage deletion of old / unused uploaded files # via py-moneyed
django-cors-headers==3.2.0 # CORS headers extension for DRF bleach[css]==5.0.0
django-crispy-forms==1.11.2 # Form helpers # via django-markdownify
django-debug-toolbar==3.2.4 # Debug / profiling toolbar blessed==1.19.1
django-error-report==0.2.0 # Error report viewer for the admin interface # via django-q
django-filter==2.4.0 # Extended filtering options brotli==1.0.9
django-formtools==2.3 # Form wizard tools # via fonttools
django-import-export==2.5.0 # Data import / export for admin interface certifi==2022.6.15
django-maintenance-mode==0.16.1 # Shut down application while reloading etc. # via
django-markdownify==0.8.0 # Markdown rendering # requests
django-money==1.1 # Django app for currency management # sentry-sdk
django-mptt==0.11.0 # Modified Preorder Tree Traversal cffi==1.15.0
django-redis>=5.0.0 # Redis integration # via
django-q==1.3.4 # Background task scheduling # cryptography
django-sql-utils==0.5.0 # Advanced query annotation / aggregation # weasyprint
django-sslserver==0.22 # Secure HTTP development server charset-normalizer==2.0.12
django-stdimage==5.1.1 # Advanced ImageField management # via requests
django-test-migrations==1.1.0 # Unit testing for database migrations coreapi==2.3.3
django-user-sessions==1.7.1 # user sessions in DB # via -r requirements.in
django-weasyprint==2.1.0 # django weasyprint integration coreschema==0.0.4
djangorestframework==3.12.4 # DRF framework # via coreapi
django-xforwardedfor-middleware==2.0 # IP forwarding metadata cryptography==37.0.2
flake8==3.8.3 # PEP checking # via pyjwt
flake8-docstrings==1.6.0 # docstring format testing cssselect2==0.6.0
gunicorn>=20.1.0 # Gunicorn web server # via weasyprint
importlib_metadata # Backport for importlib.metadata defusedxml==0.7.1
isort==5.10.1 # DEV: python import sorting # via
markdown==3.3.4 # Force particular version of markdown # odfpy
pdf2image==1.16.0 # PDF to image conversion # python3-openid
pep8-naming==0.11.1 # PEP naming convention extension diff-match-patch==20200713
pre-commit==2.19.0 # Git pre-commit # via django-import-export
pillow==9.1.1 # Image manipulation django==3.2.13
py-moneyed==0.8.0 # Specific version requirement for py-moneyed # via
pygments==2.7.4 # Syntax highlighting # -r requirements.in
python-barcode[images]==0.13.1 # Barcode generator # django-allauth
qrcode[pil]==6.1 # QR code generator # django-allauth-2fa
rapidfuzz==0.7.6 # Fuzzy string matching # django-cors-headers
sentry-sdk==1.5.12 # Error reporting (optional) # django-error-report
tablib[xls,xlsx,yaml] # Support for XLS and XLSX formats # django-filter
weasyprint==55.0 # PDF generation library # django-formtools
# django-import-export
# django-js-asset
# django-markdownify
# django-money
# django-mptt
# django-otp
# django-picklefield
# django-q
# django-redis
# django-sql-utils
# django-sslserver
# django-stdimage
# django-user-sessions
# django-weasyprint
# django-xforwardedfor-middleware
# djangorestframework
django-allauth==0.51.0
# via
# -r requirements.in
# django-allauth-2fa
django-allauth-2fa==0.9
# via -r requirements.in
django-cleanup==6.0.0
# via -r requirements.in
django-cors-headers==3.13.0
# via -r requirements.in
django-crispy-forms==1.14.0
# via -r requirements.in
django-error-report==0.2.0
# via -r requirements.in
django-filter==22.1
# via -r requirements.in
django-formtools==2.3
# via -r requirements.in
django-import-export==2.5.0
# via -r requirements.in
django-js-asset==2.0.0
# via django-mptt
django-maintenance-mode==0.16.3
# via -r requirements.in
django-markdownify==0.9.2
# via -r requirements.in
django-money==2.1.1
# via -r requirements.in
django-mptt==0.11.0
# via -r requirements.in
django-otp==1.1.3
# via django-allauth-2fa
django-picklefield==3.1
# via django-q
django-q==1.3.9
# via -r requirements.in
django-redis==5.2.0
# via -r requirements.in
django-sql-utils==0.6.1
# via -r requirements.in
django-sslserver==0.22
# via -r requirements.in
django-stdimage==5.3.0
# via -r requirements.in
django-user-sessions==1.7.1
# via -r requirements.in
django-weasyprint==2.1.0
# via -r requirements.in
django-xforwardedfor-middleware==2.0
# via -r requirements.in
djangorestframework==3.13.1
# via -r requirements.in
et-xmlfile==1.1.0
# via openpyxl
fonttools[woff]==4.33.3
# via weasyprint
gunicorn==20.1.0
# via -r requirements.in
html5lib==1.1
# via weasyprint
idna==3.3
# via requests
importlib-metadata==4.12.0
# via markdown
itypes==1.2.0
# via coreapi
jinja2==3.1.2
# via coreschema
markdown==3.3.7
# via django-markdownify
markuppy==1.14
# via tablib
markupsafe==2.1.1
# via jinja2
oauthlib==3.2.0
# via requests-oauthlib
odfpy==1.4.1
# via tablib
openpyxl==3.0.10
# via tablib
pdf2image==1.16.0
# via -r requirements.in
pillow==9.1.1
# via
# -r requirements.in
# django-stdimage
# pdf2image
# python-barcode
# qrcode
# weasyprint
py-moneyed==1.2
# via
# -r requirements.in
# django-money
pycparser==2.21
# via cffi
pydyf==0.2.0
# via weasyprint
pyjwt[crypto]==2.4.0
# via django-allauth
pyphen==0.12.0
# via weasyprint
python-barcode[images]==0.14.0
# via -r requirements.in
python-dateutil==2.8.2
# via arrow
python-fsutil==0.6.1
# via django-maintenance-mode
python3-openid==3.2.0
# via django-allauth
pytz==2022.1
# via
# babel
# django
# djangorestframework
pyyaml==6.0
# via tablib
qrcode[pil]==7.3.1
# via
# -r requirements.in
# django-allauth-2fa
rapidfuzz==0.7.6
# via -r requirements.in
redis==3.5.3
# via
# django-q
# django-redis
requests==2.28.0
# via
# coreapi
# django-allauth
# requests-oauthlib
requests-oauthlib==1.3.1
# via django-allauth
sentry-sdk==1.6.0
# via -r requirements.in
six==1.16.0
# via
# bleach
# blessed
# html5lib
# python-dateutil
sqlparse==0.4.2
# via
# django
# django-sql-utils
tablib[html,ods,xls,xlsx,yaml]==3.2.1
# via
# -r requirements.in
# django-import-export
tinycss2==1.1.1
# via
# bleach
# cssselect2
# weasyprint
uritemplate==4.1.1
# via coreapi
urllib3==1.26.9
# via
# requests
# sentry-sdk
wcwidth==0.2.5
# via blessed
weasyprint==55.0
# via django-weasyprint
webencodings==0.5.1
# via
# bleach
# cssselect2
# html5lib
# tinycss2
xlrd==2.0.1
# via tablib
xlwt==1.3.0
# via tablib
zipp==3.8.0
# via importlib-metadata
zopfli==0.2.1
# via fonttools
# The following packages are considered to be unsafe in a requirements file:
# setuptools

View File

@ -111,10 +111,10 @@ def install(c):
@task @task
def setup_dev(c): def setup_dev(c):
"""Sets up everything needed for the dev enviroment.""" """Sets up everything needed for the dev enviroment."""
print("Installing required python packages from 'requirements.txt'") print("Installing required python packages from 'requirements-dev.txt'")
# Install required Python packages with PIP # Install required Python packages with PIP
c.run('pip3 install -U -r requirements.txt') c.run('pip3 install -U -r requirements-dev.txt')
# Install pre-commit hook # Install pre-commit hook
c.run('pre-commit install') c.run('pre-commit install')