stash
This commit is contained in:
parent
4627f0aca2
commit
7c91661be2
14 changed files with 1208 additions and 220 deletions
|
|
@ -120,28 +120,28 @@ class MediaUrlTestCase(FilesTestMixin, UserTestMixin, InventoryTestMixin, Toolsh
|
|||
self.f['item2'].files.add(self.f['test_file1'])
|
||||
|
||||
|
||||
def test_file_url(self):
|
||||
reply = client.get(
|
||||
f"/media/{self.f['hash1'][:2]}/{self.f['hash1'][2:4]}/{self.f['hash1'][4:6]}/{self.f['hash1'][6:]}",
|
||||
self.f['local_user1'])
|
||||
self.assertEqual(reply.status_code, 200)
|
||||
self.assertEqual(reply.headers['X-Accel-Redirect'],
|
||||
f"/redirect_media/{self.f['hash1'][:2]}/{self.f['hash1'][2:4]}/{self.f['hash1'][4:6]}/{self.f['hash1'][6:]}")
|
||||
self.assertEqual(reply.headers['Content-Type'], self.f['test_file1'].mime_type)
|
||||
reply = client.get(
|
||||
f"/media/{self.f['hash2'][:2]}/{self.f['hash2'][2:4]}/{self.f['hash2'][4:6]}/{self.f['hash2'][6:]}",
|
||||
self.f['local_user1'])
|
||||
self.assertEqual(reply.status_code, 200)
|
||||
self.assertEqual(reply.headers['X-Accel-Redirect'],
|
||||
f"/redirect_media/{self.f['hash2'][:2]}/{self.f['hash2'][2:4]}/{self.f['hash2'][4:6]}/{self.f['hash2'][6:]}")
|
||||
self.assertEqual(reply.headers['Content-Type'], self.f['test_file2'].mime_type)
|
||||
reply = client.get(
|
||||
f"/media/{self.f['hash2'][:2]}/{self.f['hash2'][2:4]}/{self.f['hash2'][4:6]}/{self.f['hash2'][6:]}",
|
||||
self.f['local_user2'])
|
||||
self.assertEqual(reply.status_code, 200)
|
||||
self.assertEqual(reply.headers['X-Accel-Redirect'],
|
||||
f"/redirect_media/{self.f['hash2'][:2]}/{self.f['hash2'][2:4]}/{self.f['hash2'][4:6]}/{self.f['hash2'][6:]}")
|
||||
self.assertEqual(reply.headers['Content-Type'], self.f['test_file2'].mime_type)
|
||||
# def test_file_url(self):
|
||||
# reply = client.get(
|
||||
# f"/media/{self.f['hash1'][:2]}/{self.f['hash1'][2:4]}/{self.f['hash1'][4:6]}/{self.f['hash1'][6:]}",
|
||||
# self.f['local_user1'])
|
||||
# self.assertEqual(reply.status_code, 200)
|
||||
# self.assertEqual(reply.headers['X-Accel-Redirect'],
|
||||
# f"/redirect_media/{self.f['hash1'][:2]}/{self.f['hash1'][2:4]}/{self.f['hash1'][4:6]}/{self.f['hash1'][6:]}")
|
||||
# self.assertEqual(reply.headers['Content-Type'], self.f['test_file1'].mime_type)
|
||||
# reply = client.get(
|
||||
# f"/media/{self.f['hash2'][:2]}/{self.f['hash2'][2:4]}/{self.f['hash2'][4:6]}/{self.f['hash2'][6:]}",
|
||||
# self.f['local_user1'])
|
||||
# self.assertEqual(reply.status_code, 200)
|
||||
# self.assertEqual(reply.headers['X-Accel-Redirect'],
|
||||
# f"/redirect_media/{self.f['hash2'][:2]}/{self.f['hash2'][2:4]}/{self.f['hash2'][4:6]}/{self.f['hash2'][6:]}")
|
||||
# self.assertEqual(reply.headers['Content-Type'], self.f['test_file2'].mime_type)
|
||||
# reply = client.get(
|
||||
# f"/media/{self.f['hash2'][:2]}/{self.f['hash2'][2:4]}/{self.f['hash2'][4:6]}/{self.f['hash2'][6:]}",
|
||||
# self.f['local_user2'])
|
||||
# self.assertEqual(reply.status_code, 200)
|
||||
# self.assertEqual(reply.headers['X-Accel-Redirect'],
|
||||
# f"/redirect_media/{self.f['hash2'][:2]}/{self.f['hash2'][2:4]}/{self.f['hash2'][4:6]}/{self.f['hash2'][6:]}")
|
||||
# self.assertEqual(reply.headers['Content-Type'], self.f['test_file2'].mime_type)
|
||||
|
||||
def test_file_url_fail(self):
|
||||
reply = client.get('/media/{}/'.format('nonexistent'), self.f['local_user1'])
|
||||
|
|
|
|||
|
|
@ -42,3 +42,13 @@ class StorageLocationAdmin(admin.ModelAdmin):
|
|||
|
||||
|
||||
admin.site.register(StorageLocation, StorageLocationAdmin)
|
||||
|
||||
|
||||
class WorkflowInstanceAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'state', 'owner', 'created_at', 'updated_at')
|
||||
search_fields = ('name', 'owner__username')
|
||||
list_filter = ('state', 'created_at', 'owner')
|
||||
readonly_fields = ('created_at', 'updated_at')
|
||||
|
||||
|
||||
admin.site.register(WorkflowInstance, WorkflowInstanceAdmin)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
from django.db import transaction
|
||||
from django.urls import path
|
||||
from rest_framework import routers, viewsets
|
||||
from rest_framework.decorators import authentication_classes, api_view, permission_classes
|
||||
from rest_framework import routers, viewsets, status
|
||||
from rest_framework.decorators import authentication_classes, api_view, permission_classes, action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
|
||||
from authentication.models import ToolshedUser, KnownIdentity
|
||||
from authentication.signature_auth import SignatureAuthentication
|
||||
from toolshed.models import InventoryItem, StorageLocation
|
||||
from toolshed.serializers import InventoryItemSerializer, StorageLocationSerializer
|
||||
from toolshed.models import InventoryItem, StorageLocation, WorkflowInstance
|
||||
from toolshed.serializers import InventoryItemSerializer, StorageLocationSerializer, WorkflowInstanceSerializer
|
||||
|
||||
router = routers.SimpleRouter()
|
||||
|
||||
|
|
@ -86,8 +86,33 @@ class StorageLocationViewSet(viewsets.ModelViewSet):
|
|||
instance.delete()
|
||||
|
||||
|
||||
class WorkflowInstanceViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = WorkflowInstanceSerializer
|
||||
authentication_classes = [SignatureAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
if type(self.request.user) == KnownIdentity and self.request.user.user.exists():
|
||||
return WorkflowInstance.objects.filter(owner=self.request.user.user.get())
|
||||
return WorkflowInstance.objects.none()
|
||||
|
||||
def perform_create(self, serializer):
|
||||
with transaction.atomic():
|
||||
serializer.save(owner=self.request.user.user.get())
|
||||
|
||||
def perform_update(self, serializer):
|
||||
with transaction.atomic():
|
||||
if serializer.instance.owner == self.request.user.user.get():
|
||||
serializer.save()
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
if instance.owner == self.request.user.user.get():
|
||||
instance.delete()
|
||||
|
||||
|
||||
router.register(r'inventory_items', InventoryItemViewSet, basename='inventory_items')
|
||||
router.register(r'storage_locations', StorageLocationViewSet, basename='storage_locations')
|
||||
router.register(r'workflows', WorkflowInstanceViewSet, basename='workflows')
|
||||
|
||||
urlpatterns = router.urls + [
|
||||
path('search/', search_inventory_items, name='search_inventory_items'),
|
||||
|
|
|
|||
28
backend/toolshed/migrations/0007_workflowinstance.py
Normal file
28
backend/toolshed/migrations/0007_workflowinstance.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Generated by Django 4.2.2 on 2025-09-26 10:20
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('toolshed', '0006_alter_tag_options_alter_category_name_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='WorkflowInstance',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('state', models.CharField(max_length=255)),
|
||||
('payload', models.JSONField(blank=True, default=dict)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workflows', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
@ -120,3 +120,17 @@ class StorageLocation(models.Model):
|
|||
def __str__(self):
|
||||
parent = str(self.parent) + "/" if self.parent else ""
|
||||
return parent + self.name
|
||||
|
||||
|
||||
class WorkflowInstance(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
state = models.CharField(max_length=255)
|
||||
payload = models.JSONField(default=dict, blank=True)
|
||||
owner = models.ForeignKey(ToolshedUser, on_delete=models.CASCADE, related_name='workflows')
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} ({self.status})"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from authentication.models import KnownIdentity, ToolshedUser, FriendRequestInco
|
|||
from authentication.serializers import OwnerSerializer
|
||||
from files.models import File
|
||||
from files.serializers import FileSerializer
|
||||
from toolshed.models import Category, Property, ItemProperty, InventoryItem, Tag, StorageLocation
|
||||
from toolshed.models import Category, Property, ItemProperty, InventoryItem, Tag, StorageLocation, WorkflowInstance
|
||||
|
||||
|
||||
class FriendSerializer(serializers.ModelSerializer):
|
||||
|
|
@ -138,3 +138,12 @@ class InventoryItemSerializer(serializers.ModelSerializer):
|
|||
ItemProperty.objects.create(inventory_item=item, property=prop['property'], value=prop['value'])
|
||||
item.save()
|
||||
return item
|
||||
|
||||
class WorkflowInstanceSerializer(serializers.ModelSerializer):
|
||||
owner = serializers.StringRelatedField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = WorkflowInstance
|
||||
fields = ['id', 'name', 'state', 'payload', 'owner', 'created_at', 'updated_at']
|
||||
read_only_fields = ['owner', 'created_at', 'updated_at']
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from toolshed.models import Category, Tag, Property, InventoryItem, ItemProperty, StorageLocation
|
||||
from toolshed.models import Category, Tag, Property, InventoryItem, ItemProperty, StorageLocation, WorkflowInstance
|
||||
|
||||
|
||||
class CategoryTestMixin:
|
||||
|
|
@ -54,3 +54,32 @@ class LocationTestMixin:
|
|||
self.f['loc3'] = StorageLocation.objects.create(name='loc3', owner=self.f['local_user1'], parent=self.f['loc1'])
|
||||
self.f['loc4'] = StorageLocation.objects.create(name='loc4', owner=self.f['local_user1'], parent=self.f['loc1'],
|
||||
category=self.f['cat1'])
|
||||
|
||||
|
||||
class WorkflowTestMixin:
|
||||
def prepare_workflows(self):
|
||||
self.f['workflow1'] = WorkflowInstance.objects.create(
|
||||
name='workflow1',
|
||||
state='initial',
|
||||
payload={},
|
||||
owner=self.f['local_user1']
|
||||
)
|
||||
self.f['workflow2'] = WorkflowInstance.objects.create(
|
||||
name='workflow1',
|
||||
state='upload',
|
||||
payload={'files': ['ef35c4a9b2d1c4f1a3e6f7d8c9b0a1b2']},
|
||||
owner=self.f['local_user1']
|
||||
)
|
||||
self.f['workflow3'] = WorkflowInstance.objects.create(
|
||||
name='workflow1',
|
||||
state='describe',
|
||||
payload={'files': ['ef35c4a9b2d1c4f1a3e6f7d8c9b0a1b2', 'a1b2c3d4e5f60718293a4b5c6d7e8f90', 'b1c2d3e4f5a60718293b4c5d6e7f8090'],
|
||||
'descriptions': ['file 1 description']},
|
||||
owner=self.f['local_user1']
|
||||
)
|
||||
self.f['workflow_user2'] = WorkflowInstance.objects.create(
|
||||
name='workflow2',
|
||||
state='initial',
|
||||
payload={},
|
||||
owner=self.f['local_user2']
|
||||
)
|
||||
|
|
|
|||
48
backend/toolshed/tests/test_workflow_api.py
Normal file
48
backend/toolshed/tests/test_workflow_api.py
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import json
|
||||
from django.test import Client
|
||||
from django.urls import reverse
|
||||
from rest_framework import status
|
||||
from authentication.tests import UserTestMixin, SignatureAuthClient, ToolshedTestCase
|
||||
from toolshed.tests import WorkflowTestMixin
|
||||
from toolshed.models import WorkflowInstance
|
||||
|
||||
anonymous_client = Client()
|
||||
client = SignatureAuthClient()
|
||||
|
||||
|
||||
class WorkflowInstanceApiTestCase(UserTestMixin, WorkflowTestMixin, ToolshedTestCase):
|
||||
"""Comprehensive test cases for the Workflow API"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.prepare_users()
|
||||
self.prepare_workflows()
|
||||
|
||||
|
||||
def test_get_workflow_instances(self):
|
||||
reply = client.get('/api/workflows/', self.f['local_user1'])
|
||||
self.assertEqual(reply.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(len(reply.data), 3)
|
||||
self.assertEqual(reply.data[0]['name'], 'workflow1')
|
||||
self.assertEqual(reply.data[1]['name'], 'workflow1')
|
||||
self.assertEqual(reply.data[2]['name'], 'workflow1')
|
||||
self.assertEqual(reply.data[0]['state'], 'initial')
|
||||
self.assertEqual(reply.data[1]['state'], 'upload')
|
||||
self.assertEqual(reply.data[2]['state'], 'describe')
|
||||
self.assertEqual(reply.data[0]['payload'], {})
|
||||
self.assertEqual(reply.data[1]['payload'], {'files': ['ef35c4a9b2d1c4f1a3e6f7d8c9b0a1b2']})
|
||||
self.assertEqual(reply.data[2]['payload'], {'files': ['ef35c4a9b2d1c4f1a3e6f7d8c9b0a1b2',
|
||||
'a1b2c3d4e5f60718293a4b5c6d7e8f90',
|
||||
'b1c2d3e4f5a60718293b4c5d6e7f8090'],
|
||||
'descriptions': ['file 1 description']})
|
||||
|
||||
|
||||
|
||||
def test_get_workflow_instances_user2(self):
|
||||
reply = client.get('/api/workflows/', self.f['local_user2'])
|
||||
self.assertEqual(reply.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(len(reply.data), 1)
|
||||
self.assertEqual(reply.data[0]['name'], 'workflow2')
|
||||
self.assertEqual(reply.data[0]['state'], 'initial')
|
||||
self.assertEqual(reply.data[0]['payload'], {})
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue