add Tags, Properties and Categories
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
91cd5c57b3
commit
7e1b92a162
10 changed files with 414 additions and 1 deletions
|
@ -18,7 +18,7 @@ python3 -m venv venv
|
|||
source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
python configure.py
|
||||
python manage.py runserver 0.0.0.0:800 --insecure
|
||||
python manage.py runserver 0.0.0.0:8000 --insecure
|
||||
```
|
||||
to run this in properly in production, you need to configure a webserver to serve the static files and proxy the requests to the backend, then run the backend with just `python manage.py runserver` without the `--insecure` flag.
|
||||
|
||||
|
|
|
@ -33,5 +33,6 @@ urlpatterns = [
|
|||
path('auth/', include('authentication.api')),
|
||||
path('admin/', include('hostadmin.api')),
|
||||
path('api/', include('toolshed.api.friend')),
|
||||
path('api/', include('toolshed.api.info')),
|
||||
path('docs/', schema_view.with_ui('swagger', cache_timeout=0), name='api-docs'),
|
||||
]
|
||||
|
|
68
backend/toolshed/api/info.py
Normal file
68
backend/toolshed/api/info.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
from django.urls import path
|
||||
from rest_framework.decorators import api_view, permission_classes, authentication_classes
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
|
||||
from hostadmin.models import Domain
|
||||
from authentication.signature_auth import SignatureAuthentication
|
||||
from toolshed.models import Tag, Property, Category
|
||||
from toolshed.serializers import CategorySerializer, PropertySerializer
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
@permission_classes([])
|
||||
@authentication_classes([])
|
||||
def list_domains(request, format=None): # /domains/
|
||||
domains = [domain.name for domain in Domain.objects.filter(open_registration=True)]
|
||||
return Response(domains)
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
@authentication_classes([SignatureAuthentication])
|
||||
def list_tags(format=None): # /tags/
|
||||
tags = [tag.name for tag in Tag.objects.all()]
|
||||
return Response(tags)
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
@authentication_classes([SignatureAuthentication])
|
||||
def list_properties(request, format=None): # /properties/
|
||||
return Response(PropertySerializer(Property.objects.all(), many=True).data)
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
@authentication_classes([SignatureAuthentication])
|
||||
def list_categories(request, format=None): # /categories/
|
||||
return Response(CategorySerializer(Category.objects.all(), many=True).data)
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
@authentication_classes([SignatureAuthentication])
|
||||
def list_availability_policies(request, format=None): # /availability_policies/
|
||||
policies = ['private', 'friends', 'internal', 'public']
|
||||
return Response(policies)
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
@authentication_classes([SignatureAuthentication])
|
||||
def combined_info(request, format=None): # /info/
|
||||
tags = [tag.name for tag in Tag.objects.all()]
|
||||
properties = [property.name for property in Property.objects.all()]
|
||||
categories = [category.name for category in Category.objects.all()]
|
||||
policies = ['private', 'friends', 'internal', 'public']
|
||||
return Response({'tags': tags, 'properties': properties, 'policies': policies, 'categories': categories})
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('availability_policies/', list_availability_policies, name='availability_policies'),
|
||||
path('properties/', list_properties, name='propertylist'),
|
||||
path('categories/', list_categories, name='categorylist'),
|
||||
path('domains/', list_domains, name='domainlist'),
|
||||
path('tags/', list_tags, name='taglist'),
|
||||
path('info/', combined_info, name='info'),
|
||||
]
|
59
backend/toolshed/migrations/0001_initial.py
Normal file
59
backend/toolshed/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
# Generated by Django 4.2.2 on 2023-06-22 02:03
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Category',
|
||||
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)),
|
||||
('name', models.CharField(max_length=255, unique=True)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('origin', models.CharField(max_length=255)),
|
||||
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='toolshed.category')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'categories',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Tag',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('origin', models.CharField(max_length=255)),
|
||||
('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tags', to='toolshed.category')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Property',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('unit_symbol', models.CharField(blank=True, max_length=16, null=True)),
|
||||
('unit_name', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('unit_name_plural', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('base2_prefix', models.BooleanField(default=False)),
|
||||
('dimensions', models.IntegerField(default=1, validators=[django.core.validators.MinValueValidator(1)])),
|
||||
('origin', models.CharField(max_length=255)),
|
||||
('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='properties', to='toolshed.category')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'properties',
|
||||
},
|
||||
),
|
||||
]
|
47
backend/toolshed/models.py
Normal file
47
backend/toolshed/models.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
from django.db import models
|
||||
from django.core.validators import MinValueValidator
|
||||
from django_softdelete.models import SoftDeleteModel
|
||||
|
||||
from authentication.models import ToolshedUser, KnownIdentity
|
||||
|
||||
|
||||
class Category(SoftDeleteModel):
|
||||
name = models.CharField(max_length=255, unique=True)
|
||||
description = models.TextField(null=True, blank=True)
|
||||
parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')
|
||||
origin = models.CharField(max_length=255, null=False, blank=False)
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = 'categories'
|
||||
|
||||
def __str__(self):
|
||||
parent = str(self.parent) + "/" if self.parent else ""
|
||||
return parent + self.name
|
||||
|
||||
|
||||
class Property(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
description = models.TextField(null=True, blank=True)
|
||||
category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True, blank=True, related_name='properties')
|
||||
unit_symbol = models.CharField(max_length=16, null=True, blank=True)
|
||||
unit_name = models.CharField(max_length=255, null=True, blank=True)
|
||||
unit_name_plural = models.CharField(max_length=255, null=True, blank=True)
|
||||
base2_prefix = models.BooleanField(default=False)
|
||||
dimensions = models.IntegerField(null=False, blank=False, default=1, validators=[MinValueValidator(1)])
|
||||
origin = models.CharField(max_length=255)
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = 'properties'
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Tag(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
description = models.TextField(null=True, blank=True)
|
||||
category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True, blank=True, related_name='tags')
|
||||
origin = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
|
@ -1,5 +1,6 @@
|
|||
from rest_framework import serializers
|
||||
from authentication.models import KnownIdentity
|
||||
from toolshed.models import Category, Property
|
||||
|
||||
|
||||
class FriendSerializer(serializers.ModelSerializer):
|
||||
|
@ -11,3 +12,20 @@ class FriendSerializer(serializers.ModelSerializer):
|
|||
|
||||
def get_username(self, obj):
|
||||
return obj.username + '@' + obj.domain
|
||||
|
||||
|
||||
class PropertySerializer(serializers.ModelSerializer):
|
||||
category = serializers.SlugRelatedField(queryset=Category.objects.all(), slug_field='name')
|
||||
|
||||
class Meta:
|
||||
model = Property
|
||||
fields = ['name', 'description', 'category', 'unit_symbol', 'unit_name', 'unit_name_plural', 'base2_prefix']
|
||||
|
||||
|
||||
class CategorySerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Category
|
||||
fields = ['name']
|
||||
|
||||
def to_representation(self, instance):
|
||||
return str(instance)
|
||||
|
|
54
backend/toolshed/tests/test_api.py
Normal file
54
backend/toolshed/tests/test_api.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
from django.test import TestCase, Client
|
||||
from authentication.tests import UserTestCase, SignatureAuthClient
|
||||
from toolshed.models import Category, Tag, Property
|
||||
|
||||
anonymous_client = Client()
|
||||
client = SignatureAuthClient()
|
||||
|
||||
|
||||
class CombinedApiTestCase(UserTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.cat1 = Category.objects.create(name='cat1')
|
||||
self.cat2 = Category.objects.create(name='cat2')
|
||||
self.cat3 = Category.objects.create(name='cat3')
|
||||
self.tag1 = Tag.objects.create(name='tag1')
|
||||
self.tag2 = Tag.objects.create(name='tag2')
|
||||
self.tag3 = Tag.objects.create(name='tag3')
|
||||
self.prop1 = Property.objects.create(name='prop1')
|
||||
self.prop2 = Property.objects.create(name='prop2')
|
||||
self.prop3 = Property.objects.create(name='prop3')
|
||||
|
||||
|
||||
def test_domains_anonymous(self):
|
||||
response = anonymous_client.get('/api/domains/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), ['example.com'])
|
||||
|
||||
def test_domains_authenticated(self):
|
||||
response = client.get('/api/domains/', self.local_user1)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), ['example.com'])
|
||||
|
||||
def test_combined_api_anonymous(self):
|
||||
response = anonymous_client.get('/api/info/')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_combined_api(self):
|
||||
response = client.get('/api/info/', self.local_user1)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json()['policies'], ['private', 'friends', 'internal', 'public'])
|
||||
self.assertEqual(response.json()['categories'], ['cat1', 'cat2', 'cat3'])
|
||||
self.assertEqual(response.json()['tags'], ['tag1', 'tag2', 'tag3'])
|
||||
self.assertEqual(response.json()['properties'], ['prop1', 'prop2', 'prop3'])
|
||||
|
||||
def test_policy_api_anonymous(self):
|
||||
response = anonymous_client.get('/api/availability_policies/')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_policy_api(self):
|
||||
response = client.get('/api/availability_policies/', self.local_user1)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), ['private', 'friends', 'internal', 'public'])
|
||||
|
53
backend/toolshed/tests/test_category.py
Normal file
53
backend/toolshed/tests/test_category.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
from authentication.tests import UserTestCase, SignatureAuthClient
|
||||
from toolshed.models import Category
|
||||
|
||||
client = SignatureAuthClient()
|
||||
|
||||
|
||||
class CategoryTestCase(UserTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.cat1 = Category.objects.create(name='cat1')
|
||||
self.cat1.save()
|
||||
self.cat2 = Category.objects.create(name='cat2')
|
||||
self.cat2.save()
|
||||
self.cat3 = Category.objects.create(name='cat3')
|
||||
self.cat3.save()
|
||||
|
||||
def test_get_categories(self):
|
||||
subcat1 = Category.objects.create(name='subcat1', parent=self.cat1)
|
||||
subcat1.save()
|
||||
subcat2 = Category.objects.create(name='subcat2', parent=self.cat1)
|
||||
subcat2.save()
|
||||
subcat3 = Category.objects.create(name='subcat3', parent=subcat1)
|
||||
subcat3.save()
|
||||
self.assertEqual(self.cat1.children.count(), 2)
|
||||
self.assertEqual(self.cat1.children.first(), subcat1)
|
||||
self.assertEqual(self.cat1.children.last(), subcat2)
|
||||
self.assertEqual(subcat1.parent, self.cat1)
|
||||
self.assertEqual(subcat2.parent, self.cat1)
|
||||
self.assertEqual(subcat3.parent, subcat1)
|
||||
self.assertEqual(str(subcat1), 'cat1/subcat1')
|
||||
self.assertEqual(str(subcat2), 'cat1/subcat2')
|
||||
self.assertEqual(str(subcat3), 'cat1/subcat1/subcat3')
|
||||
|
||||
|
||||
class CategoryApiTestCase(UserTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.cat1 = Category.objects.create(name='cat1')
|
||||
self.cat1.save()
|
||||
self.cat2 = Category.objects.create(name='cat2')
|
||||
self.cat2.save()
|
||||
self.cat3 = Category.objects.create(name='cat3')
|
||||
self.cat3.save()
|
||||
|
||||
def test_get_categories(self):
|
||||
reply = client.get('/api/categories/', self.local_user1)
|
||||
self.assertEqual(reply.status_code, 200)
|
||||
self.assertEqual(len(reply.json()), 3)
|
||||
self.assertEqual(reply.json()[0], 'cat1')
|
||||
self.assertEqual(reply.json()[1], 'cat2')
|
||||
self.assertEqual(reply.json()[2], 'cat3')
|
55
backend/toolshed/tests/test_property.py
Normal file
55
backend/toolshed/tests/test_property.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
from authentication.tests import UserTestCase, SignatureAuthClient
|
||||
from toolshed.models import Property, Category
|
||||
|
||||
client = SignatureAuthClient()
|
||||
|
||||
|
||||
class PropertyTestCase(UserTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.cat1 = Category.objects.create(name='cat1')
|
||||
self.cat1.save()
|
||||
self.prop1 = Property.objects.create(name='prop1')
|
||||
self.prop1.save()
|
||||
self.prop2 = Property.objects.create(name='prop2', description='prop2 description', category=self.cat1)
|
||||
self.prop2.save()
|
||||
self.prop3 = Property.objects.create(name='prop3', description='prop3 description', category=self.cat1)
|
||||
self.prop3.save()
|
||||
|
||||
def test_properties(self):
|
||||
self.assertEqual(len(Property.objects.all()), 3)
|
||||
self.assertEqual(self.prop1.name, 'prop1')
|
||||
self.assertEqual(self.prop1.description, None)
|
||||
self.assertEqual(self.prop1.category, None)
|
||||
self.assertEqual(self.prop2.name, 'prop2')
|
||||
self.assertEqual(self.prop2.description, 'prop2 description')
|
||||
self.assertEqual(self.prop2.category, self.cat1)
|
||||
self.assertEqual(self.prop3.name, 'prop3')
|
||||
self.assertEqual(self.prop3.description, 'prop3 description')
|
||||
self.assertEqual(self.prop3.category, self.cat1)
|
||||
self.assertEqual(str(self.prop1), 'prop1')
|
||||
self.assertEqual(str(self.prop2), 'prop2')
|
||||
self.assertEqual(str(self.prop3), 'prop3')
|
||||
self.assertEqual(self.cat1.properties.count(), 2)
|
||||
self.assertEqual(self.cat1.properties.first(), self.prop2)
|
||||
self.assertEqual(self.cat1.properties.last(), self.prop3)
|
||||
|
||||
|
||||
class PropertyApiTestCase(UserTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.cat1 = Category.objects.create(name='cat1')
|
||||
self.cat1.save()
|
||||
self.cat2 = Category.objects.create(name='cat2')
|
||||
self.cat2.save()
|
||||
self.cat3 = Category.objects.create(name='cat3')
|
||||
self.cat3.save()
|
||||
|
||||
def test_get_properties(self):
|
||||
reply = client.get('/api/properties/', self.local_user1)
|
||||
self.assertEqual(reply.status_code, 200)
|
||||
self.assertEqual(len(reply.json()), 0)
|
||||
prop1 = Property.objects.create(name='prop1')
|
||||
prop1.save()
|
58
backend/toolshed/tests/test_tag.py
Normal file
58
backend/toolshed/tests/test_tag.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
from authentication.tests import UserTestCase, SignatureAuthClient
|
||||
from toolshed.models import Tag, Category
|
||||
|
||||
client = SignatureAuthClient()
|
||||
|
||||
|
||||
class TagTestCase(UserTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.cat1 = Category.objects.create(name='cat1')
|
||||
self.cat1.save()
|
||||
self.tag1 = Tag.objects.create(name='tag1', description='tag1 description', category=self.cat1)
|
||||
self.tag1.save()
|
||||
self.tag2 = Tag.objects.create(name='tag2', description='tag2 description', category=self.cat1)
|
||||
self.tag2.save()
|
||||
self.tag3 = Tag.objects.create(name='tag3')
|
||||
self.tag3.save()
|
||||
|
||||
def test_tags(self):
|
||||
self.assertEqual(len(Tag.objects.all()), 3)
|
||||
self.assertEqual(self.tag1.name, 'tag1')
|
||||
self.assertEqual(self.tag1.description, 'tag1 description')
|
||||
self.assertEqual(self.tag1.category, self.cat1)
|
||||
self.assertEqual(self.tag2.name, 'tag2')
|
||||
self.assertEqual(self.tag2.description, 'tag2 description')
|
||||
self.assertEqual(self.tag2.category, self.cat1)
|
||||
self.assertEqual(self.tag3.name, 'tag3')
|
||||
self.assertEqual(self.tag3.description, None)
|
||||
self.assertEqual(self.tag3.category, None)
|
||||
self.assertEqual(str(self.tag1), 'tag1')
|
||||
self.assertEqual(str(self.tag2), 'tag2')
|
||||
self.assertEqual(str(self.tag3), 'tag3')
|
||||
self.assertEqual(self.cat1.tags.count(), 2)
|
||||
self.assertEqual(self.cat1.tags.first(), self.tag1)
|
||||
self.assertEqual(self.cat1.tags.last(), self.tag2)
|
||||
|
||||
|
||||
class TagApiTestCase(UserTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.cat1 = Category.objects.create(name='cat1')
|
||||
self.cat1.save()
|
||||
self.tag1 = Tag.objects.create(name='tag1', description='tag1 description', category=self.cat1)
|
||||
self.tag1.save()
|
||||
self.tag2 = Tag.objects.create(name='tag2', description='tag2 description', category=self.cat1)
|
||||
self.tag2.save()
|
||||
self.tag3 = Tag.objects.create(name='tag3')
|
||||
self.tag3.save()
|
||||
|
||||
def test_get_tags(self):
|
||||
reply = client.get('/api/tags/', self.local_user1)
|
||||
self.assertEqual(reply.status_code, 200)
|
||||
self.assertEqual(len(reply.json()), 3)
|
||||
self.assertEqual(reply.json()[0], 'tag1')
|
||||
self.assertEqual(reply.json()[1], 'tag2')
|
||||
self.assertEqual(reply.json()[2], 'tag3')
|
Loading…
Reference in a new issue