added api endpoints and tests

This commit is contained in:
vabene1111 2020-07-08 23:19:39 +02:00
parent 692811f84f
commit e83565b1f2
8 changed files with 136 additions and 14 deletions

View File

@ -0,0 +1,18 @@
# Generated by Django 3.0.7 on 2020-07-08 21:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0072_step_show_as_header'),
]
operations = [
migrations.AlterField(
model_name='sync',
name='last_checked',
field=models.DateTimeField(null=True),
),
]

View File

@ -93,7 +93,7 @@ class Sync(models.Model):
storage = models.ForeignKey(Storage, on_delete=models.PROTECT)
path = models.CharField(max_length=512, default="")
active = models.BooleanField(default=True)
last_checked = models.DateTimeField()
last_checked = models.DateTimeField(null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

View File

@ -27,9 +27,14 @@ class CustomDecimalField(serializers.Field):
class UserNameSerializer(serializers.ModelSerializer):
username = serializers.SerializerMethodField('get_user_label')
def get_user_label(self, obj):
return obj.get_user_name()
class Meta:
model = User
fields = ('id', 'username', 'first_name', 'last_name')
fields = ('id', 'username')
class UserPreferenceSerializer(serializers.ModelSerializer):
@ -42,7 +47,12 @@ class UserPreferenceSerializer(serializers.ModelSerializer):
class StorageSerializer(serializers.ModelSerializer):
class Meta:
model = Storage
fields = ('id', 'name', 'method', 'username', 'created_by')
fields = ('id', 'name', 'method', 'username', 'password', 'token', 'created_by')
extra_kwargs = {
'password': {'write_only': True},
'token': {'write_only': True},
}
class SyncSerializer(serializers.ModelSerializer):

View File

@ -470,11 +470,7 @@
updateUserNames: function () {
return this.$http.get("{% url 'api:username-list' %}?filter_list=[" + this.user_id_update + ']').then((response) => {
for (let u of response.data) {
let name = u.username
if (`${u.first_name} ${u.last_name}` !== ' ') {
name = `${u.first_name} ${u.last_name}`
}
this.$set(this.user_names, u.id, name);
this.$set(this.user_names, u.id, u.username);
}
}).catch((err) => {

View File

@ -0,0 +1,61 @@
import json
from django.contrib import auth
from django.db.models import ProtectedError
from django.urls import reverse
from cookbook.models import Storage, Sync
from cookbook.tests.views.test_views import TestViews
class TestApiStorage(TestViews):
def setUp(self):
super(TestApiStorage, self).setUp()
self.storage = Storage.objects.create(
name='Test Storage',
username='test',
password='password',
token='token',
url='url',
created_by=auth.get_user(self.admin_client_1)
)
def test_storage_list(self):
# verify view permissions are applied accordingly
self.batch_requests([(self.anonymous_client, 403), (self.guest_client_1, 403), (self.user_client_1, 403), (self.admin_client_1, 200), (self.superuser_client, 200)],
reverse('api:storage-list'))
# verify storage is returned
r = self.admin_client_1.get(reverse('api:storage-list'))
self.assertEqual(r.status_code, 200)
response = json.loads(r.content)
self.assertEqual(len(response), 1)
storage_response = response[0]
self.assertEqual(storage_response['name'], self.storage.name)
self.assertFalse('password' in storage_response)
self.assertFalse('token' in storage_response)
def test_storage_update(self):
# can update storage as admin
r = self.admin_client_1.patch(reverse('api:storage-detail', args={self.storage.id}), {'name': 'new', 'password': 'new_password'}, content_type='application/json')
response = json.loads(r.content)
self.assertEqual(r.status_code, 200)
self.assertEqual(response['name'], 'new')
# verify password was updated (write only field)
self.storage.refresh_from_db()
self.assertEqual(self.storage.password, 'new_password')
def test_storage_delete(self):
# can delete storage as admin
r = self.admin_client_1.delete(reverse('api:storage-detail', args={self.storage.id}))
self.assertEqual(r.status_code, 204)
self.assertEqual(Storage.objects.count(), 0)
self.storage = Storage.objects.create(created_by=auth.get_user(self.admin_client_1), name='test protect')
Sync.objects.create(storage=self.storage, )
# test if deleting a storage with existing sync fails (as sync protects storage)
with self.assertRaises(ProtectedError):
self.admin_client_1.delete(reverse('api:storage-detail', args={self.storage.id}))

View File

@ -0,0 +1,27 @@
import json
from django.contrib import auth
from django.urls import reverse
from cookbook.models import UserPreference
from cookbook.tests.views.test_views import TestViews
class TestApiUsername(TestViews):
def setUp(self):
super(TestApiUsername, self).setUp()
def test_forbidden_methods(self):
r = self.user_client_1.post(reverse('api:username-list'))
self.assertEqual(r.status_code, 405)
r = self.user_client_1.put(reverse('api:username-detail', args=[auth.get_user(self.user_client_1).pk]))
self.assertEqual(r.status_code, 405)
r = self.user_client_1.delete(reverse('api:username-detail', args=[auth.get_user(self.user_client_1).pk]))
self.assertEqual(r.status_code, 405)
def test_username_list(self):
self.batch_requests([(self.anonymous_client, 403), (self.guest_client_1, 200), (self.user_client_1, 200), (self.admin_client_1, 200), (self.superuser_client, 200)],
reverse('api:username-list'))

View File

@ -9,7 +9,10 @@ from cookbook.views import api, import_export
from cookbook.helper import dal
router = routers.DefaultRouter()
router.register(r'user-name', api.UserNameViewSet, basename='username')
router.register(r'user-preference', api.UserPreferenceViewSet)
router.register(r'storage', api.StorageViewSet)
router.register(r'unit', api.UnitViewSet)
router.register(r'food', api.FoodViewSet)
router.register(r'step', api.StepViewSet)
@ -19,7 +22,7 @@ router.register(r'ingredient', api.IngredientViewSet)
router.register(r'meal-plan', api.MealPlanViewSet)
router.register(r'meal-type', api.MealTypeViewSet)
router.register(r'view-log', api.ViewLogViewSet)
router.register(r'user-name', api.UserNameViewSet, basename='username')
urlpatterns = [
path('', views.index, name='index'),

View File

@ -21,16 +21,16 @@ from rest_framework.mixins import RetrieveModelMixin, UpdateModelMixin, ListMode
from rest_framework.parsers import JSONParser, FileUploadParser, MultiPartParser
from rest_framework.response import Response
from cookbook.helper.permission_helper import group_required, CustomIsOwner, CustomIsAdmin, CustomIsUser
from cookbook.helper.permission_helper import group_required, CustomIsOwner, CustomIsAdmin, CustomIsUser, CustomIsGuest
from cookbook.helper.recipe_url_import import get_from_html
from cookbook.models import Recipe, Sync, Storage, CookLog, MealPlan, MealType, ViewLog, UserPreference, RecipeBook, Ingredient, Food, Step, Keyword, Unit
from cookbook.provider.dropbox import Dropbox
from cookbook.provider.nextcloud import Nextcloud
from cookbook.serializer import MealPlanSerializer, MealTypeSerializer, RecipeSerializer, ViewLogSerializer, UserNameSerializer, UserPreferenceSerializer, RecipeBookSerializer, IngredientSerializer, FoodSerializer, StepSerializer, \
KeywordSerializer, RecipeImageSerializer
KeywordSerializer, RecipeImageSerializer, StorageSerializer
class UserNameViewSet(viewsets.ModelViewSet):
class UserNameViewSet(viewsets.ReadOnlyModelViewSet):
"""
list:
optional parameters
@ -39,11 +39,11 @@ class UserNameViewSet(viewsets.ModelViewSet):
"""
queryset = User.objects.all()
serializer_class = UserNameSerializer
permission_classes = [permissions.IsAuthenticated]
permission_classes = [CustomIsGuest]
http_method_names = ['get']
def get_queryset(self):
queryset = User.objects.all()
queryset = self.queryset
try:
filter_list = self.request.query_params.get('filter_list', None)
if filter_list is not None:
@ -70,6 +70,13 @@ class UserPreferenceViewSet(viewsets.ModelViewSet):
return self.queryset.filter(user=self.request.user)
class StorageViewSet(viewsets.ModelViewSet):
# TODO handle delete protect error and adjust test
queryset = Storage.objects.all()
serializer_class = StorageSerializer
permission_classes = [CustomIsAdmin, ]
class RecipeBookViewSet(RetrieveModelMixin, UpdateModelMixin, ListModelMixin, viewsets.GenericViewSet):
queryset = RecipeBook.objects.all()
serializer_class = RecipeBookSerializer