mirror of
https://github.com/inventree/InvenTree.git
synced 2025-09-18 08:31:33 +00:00
Calendar export (#3858)
* Basic implementation of iCal feed * Add calendar download to API * Improve comments, remove unused outputs * Basic implementation of iCal feed * Add calendar download to API * Improve comments, remove unused outputs * Improve comment * Implement filter include_completed * update requirements.txt with pip-compile --output-file=requirements.txt requirements.in -U * Fix less than filter * Change URL to include calendar.ics * Fix filtering of orders * Remove URL/functions for calendar in views * Lint * More lint * Even more style fixes * Updated requirements-dev.txt because of style fail * Now? * Fine, fix it manually * Gaaah * Fix with same method as in common/settings.py * Fix setting name; improve name of calendar endpoint * Adapt InvenTreeAPITester get function to match post, etc (required for calendar test) * Merge * Reduce requirements.txt * Update requirements-dev.txt * Update tests * Set expected codes in API calendar test * SO completion can not work without line items; set a target date on existing orders instead * Correct method to PATCH * Well that didn't work for some reason.. try with cancelled orders instead * Make sure there are more completed orders than other orders in test * Correct wrong variable * Lint * Use correct status code * Add test for unauthorized access to calendar * Add a working test for unauthorised access * Put the correct test in place, fix Lint * Revert changes to requirements-dev, which appear magically... * Lint * Add test for basic auth * make sample simpler * Increment API version Co-authored-by: Matthias Mair <code@mjmair.com>
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
"""Tests for the Order API."""
|
||||
|
||||
import base64
|
||||
import io
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.urls import reverse
|
||||
|
||||
from icalendar import Calendar
|
||||
from rest_framework import status
|
||||
|
||||
import order.models as models
|
||||
@@ -409,6 +411,111 @@ class PurchaseOrderTest(OrderTest):
|
||||
order = models.PurchaseOrder.objects.get(pk=1)
|
||||
self.assertEqual(order.get_metadata('yam'), 'yum')
|
||||
|
||||
def test_po_calendar(self):
|
||||
"""Test the calendar export endpoint"""
|
||||
|
||||
# Create required purchase orders
|
||||
self.assignRole('purchase_order.add')
|
||||
|
||||
for i in range(1, 9):
|
||||
self.post(
|
||||
reverse('api-po-list'),
|
||||
{
|
||||
'reference': f'PO-1100000{i}',
|
||||
'supplier': 1,
|
||||
'description': f'Calendar PO {i}',
|
||||
'target_date': f'2024-12-{i:02d}',
|
||||
},
|
||||
expected_code=201
|
||||
)
|
||||
|
||||
# Get some of these orders with target date, complete or cancel them
|
||||
for po in models.PurchaseOrder.objects.filter(target_date__isnull=False):
|
||||
if po.reference in ['PO-11000001', 'PO-11000002', 'PO-11000003', 'PO-11000004']:
|
||||
# Set issued status for these POs
|
||||
self.post(
|
||||
reverse('api-po-issue', kwargs={'pk': po.pk}),
|
||||
{},
|
||||
expected_code=201
|
||||
)
|
||||
|
||||
if po.reference in ['PO-11000001', 'PO-11000002']:
|
||||
# Set complete status for these POs
|
||||
self.post(
|
||||
reverse('api-po-complete', kwargs={'pk': po.pk}),
|
||||
{
|
||||
'accept_incomplete': True,
|
||||
},
|
||||
expected_code=201
|
||||
)
|
||||
|
||||
elif po.reference in ['PO-11000005', 'PO-11000006']:
|
||||
# Set cancel status for these POs
|
||||
self.post(
|
||||
reverse('api-po-cancel', kwargs={'pk': po.pk}),
|
||||
{
|
||||
'accept_incomplete': True,
|
||||
},
|
||||
expected_code=201
|
||||
)
|
||||
|
||||
url = reverse('api-po-so-calendar', kwargs={'ordertype': 'purchase-order'})
|
||||
|
||||
# Test without completed orders
|
||||
response = self.get(url, expected_code=200, format=None)
|
||||
|
||||
number_orders = len(models.PurchaseOrder.objects.filter(target_date__isnull=False).filter(status__lt=PurchaseOrderStatus.COMPLETE))
|
||||
|
||||
# Transform content to a Calendar object
|
||||
calendar = Calendar.from_ical(response.content)
|
||||
n_events = 0
|
||||
# Count number of events in calendar
|
||||
for component in calendar.walk():
|
||||
if component.name == 'VEVENT':
|
||||
n_events += 1
|
||||
|
||||
self.assertGreaterEqual(n_events, 1)
|
||||
self.assertEqual(number_orders, n_events)
|
||||
|
||||
# Test with completed orders
|
||||
response = self.get(url, data={'include_completed': 'True'}, expected_code=200, format=None)
|
||||
|
||||
number_orders_incl_completed = len(models.PurchaseOrder.objects.filter(target_date__isnull=False))
|
||||
|
||||
self.assertGreater(number_orders_incl_completed, number_orders)
|
||||
|
||||
# Transform content to a Calendar object
|
||||
calendar = Calendar.from_ical(response.content)
|
||||
n_events = 0
|
||||
# Count number of events in calendar
|
||||
for component in calendar.walk():
|
||||
if component.name == 'VEVENT':
|
||||
n_events += 1
|
||||
|
||||
self.assertGreaterEqual(n_events, 1)
|
||||
self.assertEqual(number_orders_incl_completed, n_events)
|
||||
|
||||
def test_po_calendar_noauth(self):
|
||||
"""Test accessing calendar without authorization"""
|
||||
self.client.logout()
|
||||
response = self.client.get(reverse('api-po-so-calendar', kwargs={'ordertype': 'purchase-order'}), format='json')
|
||||
|
||||
self.assertEqual(response.status_code, 401)
|
||||
|
||||
resp_dict = response.json()
|
||||
self.assertEqual(resp_dict['detail'], "Authentication credentials were not provided.")
|
||||
|
||||
def test_po_calendar_auth(self):
|
||||
"""Test accessing calendar with header authorization"""
|
||||
self.client.logout()
|
||||
base64_token = base64.b64encode(f'{self.username}:{self.password}'.encode('ascii')).decode('ascii')
|
||||
response = self.client.get(
|
||||
reverse('api-po-so-calendar', kwargs={'ordertype': 'purchase-order'}),
|
||||
format='json',
|
||||
HTTP_AUTHORIZATION=f'basic {base64_token}'
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class PurchaseOrderLineItemTest(OrderTest):
|
||||
"""Unit tests for PurchaseOrderLineItems."""
|
||||
@@ -1077,6 +1184,67 @@ class SalesOrderTest(OrderTest):
|
||||
order = models.SalesOrder.objects.get(pk=1)
|
||||
self.assertEqual(order.get_metadata('xyz'), 'abc')
|
||||
|
||||
def test_so_calendar(self):
|
||||
"""Test the calendar export endpoint"""
|
||||
|
||||
# Create required sales orders
|
||||
self.assignRole('sales_order.add')
|
||||
|
||||
for i in range(1, 9):
|
||||
self.post(
|
||||
reverse('api-so-list'),
|
||||
{
|
||||
'reference': f'SO-1100000{i}',
|
||||
'customer': 4,
|
||||
'description': f'Calendar SO {i}',
|
||||
'target_date': f'2024-12-{i:02d}',
|
||||
},
|
||||
expected_code=201
|
||||
)
|
||||
|
||||
# Cancel a few orders - these will not show in incomplete view below
|
||||
for so in models.SalesOrder.objects.filter(target_date__isnull=False):
|
||||
if so.reference in ['SO-11000006', 'SO-11000007', 'SO-11000008', 'SO-11000009']:
|
||||
self.post(
|
||||
reverse('api-so-cancel', kwargs={'pk': so.pk}),
|
||||
expected_code=201
|
||||
)
|
||||
|
||||
url = reverse('api-po-so-calendar', kwargs={'ordertype': 'sales-order'})
|
||||
|
||||
# Test without completed orders
|
||||
response = self.get(url, expected_code=200, format=None)
|
||||
|
||||
number_orders = len(models.SalesOrder.objects.filter(target_date__isnull=False).filter(status__lt=SalesOrderStatus.SHIPPED))
|
||||
|
||||
# Transform content to a Calendar object
|
||||
calendar = Calendar.from_ical(response.content)
|
||||
n_events = 0
|
||||
# Count number of events in calendar
|
||||
for component in calendar.walk():
|
||||
if component.name == 'VEVENT':
|
||||
n_events += 1
|
||||
|
||||
self.assertGreaterEqual(n_events, 1)
|
||||
self.assertEqual(number_orders, n_events)
|
||||
|
||||
# Test with completed orders
|
||||
response = self.get(url, data={'include_completed': 'True'}, expected_code=200, format=None)
|
||||
|
||||
number_orders_incl_complete = len(models.SalesOrder.objects.filter(target_date__isnull=False))
|
||||
self.assertGreater(number_orders_incl_complete, number_orders)
|
||||
|
||||
# Transform content to a Calendar object
|
||||
calendar = Calendar.from_ical(response.content)
|
||||
n_events = 0
|
||||
# Count number of events in calendar
|
||||
for component in calendar.walk():
|
||||
if component.name == 'VEVENT':
|
||||
n_events += 1
|
||||
|
||||
self.assertGreaterEqual(n_events, 1)
|
||||
self.assertEqual(number_orders_incl_complete, n_events)
|
||||
|
||||
|
||||
class SalesOrderLineItemTest(OrderTest):
|
||||
"""Tests for the SalesOrderLineItem API."""
|
||||
|
Reference in New Issue
Block a user