This commit is contained in:
parent
7e1b92a162
commit
f738f5f485
6 changed files with 449 additions and 2 deletions
|
@ -33,6 +33,7 @@ urlpatterns = [
|
|||
path('auth/', include('authentication.api')),
|
||||
path('admin/', include('hostadmin.api')),
|
||||
path('api/', include('toolshed.api.friend')),
|
||||
path('api/', include('toolshed.api.inventory')),
|
||||
path('api/', include('toolshed.api.info')),
|
||||
path('docs/', schema_view.with_ui('swagger', cache_timeout=0), name='api-docs'),
|
||||
]
|
||||
|
|
63
backend/toolshed/api/inventory.py
Normal file
63
backend/toolshed/api/inventory.py
Normal file
|
@ -0,0 +1,63 @@
|
|||
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.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
|
||||
from authentication.models import ToolshedUser
|
||||
from authentication.signature_auth import SignatureAuthentication
|
||||
from toolshed.models import InventoryItem
|
||||
from toolshed.serializers import InventoryItemSerializer
|
||||
|
||||
router = routers.SimpleRouter()
|
||||
|
||||
|
||||
def inventory_items(identity):
|
||||
try:
|
||||
user = identity.user.get()
|
||||
if user:
|
||||
for item in user.inventory_items.all():
|
||||
yield item
|
||||
except ToolshedUser.DoesNotExist:
|
||||
pass
|
||||
for friend in identity.friends.all():
|
||||
if friend_user := friend.user.get():
|
||||
for item in friend_user.inventory_items.all():
|
||||
yield item
|
||||
|
||||
|
||||
class InventoryItemViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = InventoryItemSerializer
|
||||
authentication_classes = [SignatureAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
return InventoryItem.objects.filter(owner=self.request.user.user.get())
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(owner=self.request.user.user.get())
|
||||
|
||||
def perform_update(self, serializer):
|
||||
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()
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
@authentication_classes([SignatureAuthentication])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def search_inventory_items(request):
|
||||
query = request.query_params.get('query')
|
||||
if query:
|
||||
return Response(InventoryItemSerializer(inventory_items(request.user), many=True).data)
|
||||
return Response({'error': 'No query provided.'}, status=400)
|
||||
|
||||
|
||||
router.register(r'inventory_items', InventoryItemViewSet, basename='inventory_items')
|
||||
|
||||
urlpatterns = router.urls + [
|
||||
path('search/', search_inventory_items, name='search_inventory_items'),
|
||||
]
|
|
@ -0,0 +1,63 @@
|
|||
# Generated by Django 4.2.2 on 2023-06-22 02:37
|
||||
|
||||
from django.conf import settings
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('toolshed', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='InventoryItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('is_deleted', models.BooleanField(default=False)),
|
||||
('deleted_at', models.DateTimeField(blank=True, null=True)),
|
||||
('published', models.BooleanField(default=False)),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('description', models.TextField()),
|
||||
('availability_policy', models.CharField(max_length=255)),
|
||||
('owned_quantity', models.IntegerField(default=1, validators=[django.core.validators.MinValueValidator(0)])),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='inventory_items', to='toolshed.category')),
|
||||
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inventory_items', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ItemTag',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('inventory_item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='toolshed.inventoryitem')),
|
||||
('tag', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='toolshed.tag')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ItemProperty',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('value', models.CharField(max_length=255)),
|
||||
('inventory_item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='toolshed.inventoryitem')),
|
||||
('property', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='toolshed.property')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inventoryitem',
|
||||
name='properties',
|
||||
field=models.ManyToManyField(through='toolshed.ItemProperty', to='toolshed.property'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inventoryitem',
|
||||
name='tags',
|
||||
field=models.ManyToManyField(related_name='inventory_items', through='toolshed.ItemTag', to='toolshed.tag'),
|
||||
),
|
||||
]
|
|
@ -45,3 +45,28 @@ class Tag(models.Model):
|
|||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class InventoryItem(SoftDeleteModel):
|
||||
published = models.BooleanField(default=False)
|
||||
name = models.CharField(max_length=255)
|
||||
description = models.TextField()
|
||||
category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True, blank=True,
|
||||
related_name='inventory_items')
|
||||
availability_policy = models.CharField(max_length=255)
|
||||
owned_quantity = models.IntegerField(default=1, validators=[MinValueValidator(0)])
|
||||
owner = models.ForeignKey(ToolshedUser, on_delete=models.CASCADE, related_name='inventory_items')
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
tags = models.ManyToManyField('Tag', through='ItemTag', related_name='inventory_items')
|
||||
properties = models.ManyToManyField('Property', through='ItemProperty')
|
||||
|
||||
|
||||
class ItemProperty(models.Model):
|
||||
property = models.ForeignKey(Property, on_delete=models.CASCADE)
|
||||
inventory_item = models.ForeignKey(InventoryItem, on_delete=models.CASCADE)
|
||||
value = models.CharField(max_length=255)
|
||||
|
||||
|
||||
class ItemTag(models.Model):
|
||||
tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
|
||||
inventory_item = models.ForeignKey(InventoryItem, on_delete=models.CASCADE)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from rest_framework import serializers
|
||||
from authentication.models import KnownIdentity
|
||||
from toolshed.models import Category, Property
|
||||
from authentication.models import KnownIdentity, ToolshedUser
|
||||
from toolshed.models import Category, Property, ItemProperty, InventoryItem, Tag
|
||||
|
||||
|
||||
class FriendSerializer(serializers.ModelSerializer):
|
||||
|
@ -29,3 +29,69 @@ class CategorySerializer(serializers.ModelSerializer):
|
|||
|
||||
def to_representation(self, instance):
|
||||
return str(instance)
|
||||
|
||||
def to_internal_value(self, data):
|
||||
return Category.objects.get(name=data.split("/")[-1])
|
||||
|
||||
|
||||
class InventoryItemOwnerSerializer(serializers.ReadOnlyField):
|
||||
class Meta:
|
||||
model = ToolshedUser
|
||||
fields = '__all__'
|
||||
|
||||
def to_representation(self, value):
|
||||
return value.username + '@' + value.domain
|
||||
|
||||
|
||||
class ItemPropertySerializer(serializers.ModelSerializer):
|
||||
property = PropertySerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ItemProperty
|
||||
fields = ['property', 'value']
|
||||
|
||||
def to_representation(self, instance):
|
||||
return {'value': instance.value, 'name': instance.property.name}
|
||||
|
||||
def to_internal_value(self, data):
|
||||
prop = Property.objects.get(name=data['name'])
|
||||
value = data['value']
|
||||
return {'property': prop, 'value': value}
|
||||
|
||||
|
||||
class InventoryItemSerializer(serializers.ModelSerializer):
|
||||
owner = InventoryItemOwnerSerializer(read_only=True)
|
||||
tags = serializers.SlugRelatedField(many=True, required=False, queryset=Tag.objects.all(), slug_field='name')
|
||||
properties = ItemPropertySerializer(many=True, required=False, source='itemproperty_set')
|
||||
category = CategorySerializer(required=False, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
model = InventoryItem
|
||||
fields = ['id', 'name', 'description', 'owner', 'category', 'availability_policy', 'owned_quantity', 'owner',
|
||||
'tags', 'properties']
|
||||
|
||||
def create(self, validated_data):
|
||||
tags = validated_data.pop('tags', [])
|
||||
props = validated_data.pop('itemproperty_set', [])
|
||||
item = InventoryItem.objects.create(**validated_data)
|
||||
for tag in tags:
|
||||
item.tags.add(tag, through_defaults={})
|
||||
for prop in props:
|
||||
ItemProperty.objects.create(inventory_item=item, property=prop['property'], value=prop['value'])
|
||||
item.save()
|
||||
return item
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
tags = validated_data.pop('tags', [])
|
||||
props = validated_data.pop('itemproperty_set', [])
|
||||
item = super().update(instance, validated_data)
|
||||
item.tags.clear()
|
||||
item.properties.clear()
|
||||
if 'category' not in validated_data:
|
||||
item.category = None
|
||||
for tag in tags:
|
||||
item.tags.add(tag)
|
||||
for prop in props:
|
||||
ItemProperty.objects.create(inventory_item=item, property=prop['property'], value=prop['value'])
|
||||
item.save()
|
||||
return item
|
||||
|
|
229
backend/toolshed/tests/test_inventory.py
Normal file
229
backend/toolshed/tests/test_inventory.py
Normal file
|
@ -0,0 +1,229 @@
|
|||
from authentication.models import ToolshedUser
|
||||
from authentication.tests import UserTestCase, SignatureAuthClient
|
||||
from toolshed.models import InventoryItem, Tag, Property, ItemProperty, Category
|
||||
|
||||
client = SignatureAuthClient()
|
||||
|
||||
|
||||
class InventoryTestCase(UserTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user1 = ToolshedUser.objects.get(username="testuser")
|
||||
self.user2 = ToolshedUser.objects.get(username="testuser2")
|
||||
self.user1.friends.add(self.user2.public_identity)
|
||||
self.user2.friends.add(self.user1.public_identity)
|
||||
self.cat1 = Category.objects.create(name='cat1')
|
||||
self.cat1.save()
|
||||
self.cat2 = Category.objects.create(name='cat2')
|
||||
self.cat2.save()
|
||||
self.tag1 = Tag.objects.create(name='tag1', category=self.cat1)
|
||||
self.tag1.save()
|
||||
self.tag2 = Tag.objects.create(name='tag2', category=self.cat1)
|
||||
self.tag2.save()
|
||||
self.tag3 = Tag.objects.create(name='tag3')
|
||||
self.tag3.save()
|
||||
self.prop1 = Property.objects.create(name='prop1')
|
||||
self.prop1.save()
|
||||
self.prop2 = Property.objects.create(name='prop2')
|
||||
self.prop2.save()
|
||||
self.prop3 = Property.objects.create(name='prop3', category=self.cat1)
|
||||
self.prop3.save()
|
||||
|
||||
InventoryItem.objects.create(owner=self.user1, owned_quantity=1, name='test1', description='test',
|
||||
category=self.cat1, availability_policy='friends').save()
|
||||
item2 = InventoryItem.objects.create(owner=self.user1, owned_quantity=1, name='test2', description='test2',
|
||||
category=self.cat1, availability_policy='friends')
|
||||
item2.save()
|
||||
item2.tags.add(self.tag1, through_defaults={})
|
||||
item2.tags.add(self.tag2, through_defaults={})
|
||||
ItemProperty.objects.create(inventory_item=item2, property=self.prop1, value='value1').save()
|
||||
ItemProperty.objects.create(inventory_item=item2, property=self.prop2, value='value2').save()
|
||||
|
||||
def test_get_inventory(self):
|
||||
reply = client.get('/api/inventory_items/', self.user1)
|
||||
self.assertEqual(reply.status_code, 200)
|
||||
self.assertEqual(len(reply.json()), 2)
|
||||
self.assertEqual(reply.json()[0]['name'], 'test1')
|
||||
self.assertEqual(reply.json()[0]['description'], 'test')
|
||||
self.assertEqual(reply.json()[0]['owned_quantity'], 1)
|
||||
self.assertEqual(reply.json()[0]['tags'], [])
|
||||
self.assertEqual(reply.json()[0]['properties'], [])
|
||||
self.assertEqual(reply.json()[0]['category'], 'cat1')
|
||||
self.assertEqual(reply.json()[0]['availability_policy'], 'friends')
|
||||
self.assertEqual(reply.json()[1]['name'], 'test2')
|
||||
self.assertEqual(reply.json()[1]['description'], 'test2')
|
||||
self.assertEqual(reply.json()[1]['owned_quantity'], 1)
|
||||
self.assertEqual(reply.json()[1]['tags'], ['tag1', 'tag2'])
|
||||
self.assertEqual(reply.json()[1]['properties'],
|
||||
[{'name': 'prop1', 'value': 'value1'}, {'name': 'prop2', 'value': 'value2'}])
|
||||
self.assertEqual(reply.json()[1]['category'], 'cat1')
|
||||
self.assertEqual(reply.json()[1]['availability_policy'], 'friends')
|
||||
|
||||
def test_post_new_item(self):
|
||||
reply = client.post('/api/inventory_items/', self.user1, {
|
||||
'availability_policy': 'friends',
|
||||
'category': 'cat2',
|
||||
'name': 'test3',
|
||||
'description': 'test',
|
||||
'owned_quantity': 1,
|
||||
'image': '',
|
||||
'tags': ['tag1', 'tag2'],
|
||||
'properties': [{'name': 'prop1', 'value': 'value3'}, {'name': 'prop2', 'value': 'value4'}]
|
||||
})
|
||||
self.assertEqual(reply.status_code, 201)
|
||||
self.assertEqual(InventoryItem.objects.count(), 3)
|
||||
item = InventoryItem.objects.get(name='test3')
|
||||
self.assertEqual(item.availability_policy, 'friends')
|
||||
self.assertEqual(item.category, Category.objects.get(name='cat2'))
|
||||
self.assertEqual(item.name, 'test3')
|
||||
self.assertEqual(item.description, 'test')
|
||||
self.assertEqual(item.owned_quantity, 1)
|
||||
self.assertEqual([t for t in item.tags.all()], [self.tag1, self.tag2])
|
||||
self.assertEqual([p for p in item.properties.all()], [self.prop1, self.prop2])
|
||||
self.assertEqual([p.value for p in item.itemproperty_set.all()], ['value3', 'value4'])
|
||||
|
||||
def test_post_new_item2(self):
|
||||
reply = client.post('/api/inventory_items/', self.user1, {
|
||||
'availability_policy': 'friends',
|
||||
'name': 'test3',
|
||||
'description': 'test',
|
||||
'owned_quantity': 1,
|
||||
'image': '',
|
||||
})
|
||||
self.assertEqual(reply.status_code, 201)
|
||||
self.assertEqual(InventoryItem.objects.count(), 3)
|
||||
item = InventoryItem.objects.get(name='test3')
|
||||
self.assertEqual(item.availability_policy, 'friends')
|
||||
self.assertEqual(item.category, None)
|
||||
self.assertEqual(item.name, 'test3')
|
||||
self.assertEqual(item.description, 'test')
|
||||
self.assertEqual(item.owned_quantity, 1)
|
||||
self.assertEqual([t for t in item.tags.all()], [])
|
||||
self.assertEqual([p for p in item.properties.all()], [])
|
||||
|
||||
def test_post_new_item3(self):
|
||||
reply = client.post('/api/inventory_items/', self.user1, {
|
||||
'availability_policy': 'friends',
|
||||
'name': 'test3',
|
||||
'description': 'test',
|
||||
'owned_quantity': 1,
|
||||
'image': '',
|
||||
'category': None,
|
||||
})
|
||||
self.assertEqual(reply.status_code, 201)
|
||||
self.assertEqual(InventoryItem.objects.count(), 3)
|
||||
item = InventoryItem.objects.get(name='test3')
|
||||
self.assertEqual(item.availability_policy, 'friends')
|
||||
self.assertEqual(item.category, None)
|
||||
self.assertEqual(item.name, 'test3')
|
||||
self.assertEqual(item.description, 'test')
|
||||
self.assertEqual(item.owned_quantity, 1)
|
||||
self.assertEqual([t for t in item.tags.all()], [])
|
||||
self.assertEqual([p for p in item.properties.all()], [])
|
||||
|
||||
def test_put_item(self):
|
||||
reply = client.put('/api/inventory_items/1/', self.user1, {
|
||||
'availability_policy': 'friends',
|
||||
'name': 'test4',
|
||||
'description': 'new description',
|
||||
'owned_quantity': 100,
|
||||
'image': '',
|
||||
'tags': ['tag1', 'tag2', 'tag3'],
|
||||
'properties': [{'name': 'prop1', 'value': 'value5'}, {'name': 'prop2', 'value': 'value6'},
|
||||
{'name': 'prop3', 'value': 'value7'}]
|
||||
})
|
||||
self.assertEqual(reply.status_code, 200)
|
||||
self.assertEqual(InventoryItem.objects.count(), 2)
|
||||
item = InventoryItem.objects.get(id=1)
|
||||
self.assertEqual(item.availability_policy, 'friends')
|
||||
self.assertEqual(item.category, None)
|
||||
self.assertEqual(item.name, 'test4')
|
||||
self.assertEqual(item.description, 'new description')
|
||||
self.assertEqual(item.owned_quantity, 100)
|
||||
self.assertEqual([t for t in item.tags.all()], [self.tag1, self.tag2, self.tag3])
|
||||
self.assertEqual([p for p in item.properties.all()], [self.prop1, self.prop2, self.prop3])
|
||||
self.assertEqual([p.value for p in item.itemproperty_set.all()], ['value5', 'value6', 'value7'])
|
||||
|
||||
def test_patch_item(self):
|
||||
reply = client.patch('/api/inventory_items/1/', self.user1, {
|
||||
'description': 'new description2',
|
||||
'category': 'cat1',
|
||||
'owned_quantity': 100,
|
||||
'tags': ['tag3'],
|
||||
'properties': [{'name': 'prop3', 'value': 'value8'}]
|
||||
})
|
||||
self.assertEqual(reply.status_code, 200)
|
||||
self.assertEqual(InventoryItem.objects.count(), 2)
|
||||
item = InventoryItem.objects.get(id=1)
|
||||
self.assertEqual(item.availability_policy, 'friends')
|
||||
self.assertEqual(item.category, Category.objects.get(name='cat1'))
|
||||
self.assertEqual(item.name, 'test1')
|
||||
self.assertEqual(item.description, 'new description2')
|
||||
self.assertEqual(item.owned_quantity, 100)
|
||||
self.assertEqual([t for t in item.tags.all()], [self.tag3])
|
||||
self.assertEqual([p for p in item.properties.all()], [self.prop3])
|
||||
self.assertEqual([p.value for p in item.itemproperty_set.all()], ['value8'])
|
||||
|
||||
def test_patch_item2(self):
|
||||
reply = client.patch('/api/inventory_items/1/', self.user1, {
|
||||
'description': 'new description2',
|
||||
'category': None,
|
||||
'owned_quantity': 100,
|
||||
'tags': [],
|
||||
'properties': []
|
||||
})
|
||||
self.assertEqual(reply.status_code, 200)
|
||||
self.assertEqual(InventoryItem.objects.count(), 2)
|
||||
item = InventoryItem.objects.get(id=1)
|
||||
self.assertEqual(item.availability_policy, 'friends')
|
||||
self.assertEqual(item.category, None)
|
||||
self.assertEqual(item.name, 'test1')
|
||||
self.assertEqual(item.description, 'new description2')
|
||||
self.assertEqual(item.owned_quantity, 100)
|
||||
self.assertEqual([t for t in item.tags.all()], [])
|
||||
self.assertEqual([p for p in item.properties.all()], [])
|
||||
|
||||
def test_delete_item(self):
|
||||
reply = client.delete('/api/inventory_items/1/', self.user1)
|
||||
self.assertEqual(reply.status_code, 204)
|
||||
self.assertEqual(InventoryItem.objects.count(), 1)
|
||||
self.assertEqual(InventoryItem.objects.get(id=2).name, 'test2')
|
||||
self.assertEqual(InventoryItem.objects.filter(name='test1').count(), 0)
|
||||
|
||||
def test_search_items(self):
|
||||
reply = client.get('/api/search/?query=test', self.user1)
|
||||
self.assertEqual(reply.status_code, 200)
|
||||
self.assertEqual(len(reply.json()), 2)
|
||||
self.assertEqual(reply.json()[0]['name'], 'test1')
|
||||
self.assertEqual(reply.json()[0]['description'], 'test')
|
||||
self.assertEqual(reply.json()[0]['owned_quantity'], 1)
|
||||
self.assertEqual(reply.json()[0]['tags'], [])
|
||||
self.assertEqual(reply.json()[0]['properties'], [])
|
||||
self.assertEqual(reply.json()[0]['category'], 'cat1')
|
||||
self.assertEqual(reply.json()[0]['availability_policy'], 'friends')
|
||||
self.assertEqual(reply.json()[1]['name'], 'test2')
|
||||
self.assertEqual(reply.json()[1]['description'], 'test2')
|
||||
self.assertEqual(reply.json()[1]['owned_quantity'], 1)
|
||||
self.assertEqual(reply.json()[1]['tags'], ['tag1', 'tag2'])
|
||||
self.assertEqual(reply.json()[1]['properties'],
|
||||
[{'name': 'prop1', 'value': 'value1'}, {'name': 'prop2', 'value': 'value2'}])
|
||||
self.assertEqual(reply.json()[1]['category'], 'cat1')
|
||||
self.assertEqual(reply.json()[1]['availability_policy'], 'friends')
|
||||
|
||||
def test_search_items2(self):
|
||||
reply = client.get('/api/search/?query=test', self.user2)
|
||||
self.assertEqual(reply.status_code, 200)
|
||||
self.assertEqual(len(reply.json()), 2)
|
||||
self.assertEqual(reply.json()[0]['name'], 'test1')
|
||||
self.assertEqual(reply.json()[1]['name'], 'test2')
|
||||
|
||||
def test_search_items_fail(self):
|
||||
reply = client.get('/api/search/', self.user1)
|
||||
self.assertEqual(reply.status_code, 400)
|
||||
self.assertEqual(reply.json()['error'], 'No query provided.')
|
||||
|
||||
def test_search_items_fail2(self):
|
||||
reply = client.get('/api/search/?query=test', self.ext_user1)
|
||||
self.assertEqual(reply.status_code, 200)
|
||||
self.assertEqual(len(reply.json()), 0)
|
Loading…
Reference in a new issue