From 4a2a5f9d7682d64624ab4d795a7a89e80c885fcc Mon Sep 17 00:00:00 2001 From: Aarni Halinen Date: Tue, 23 Oct 2018 14:42:26 +0300 Subject: [PATCH 01/40] Bugfix #117: API Event tags Add API for tags so events can get the related field. --- webapp/serializers.py | 13 +++++++++++-- webapp/urls.py | 3 ++- webapp/views.py | 8 +++++++- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/webapp/serializers.py b/webapp/serializers.py index d6cc1af..60bbb8e 100644 --- a/webapp/serializers.py +++ b/webapp/serializers.py @@ -14,19 +14,28 @@ class EventSerializer(serializers.HyperlinkedModelSerializer): signup_id = serializers.PrimaryKeyRelatedField( many=True, source="signupForm", - queryset=SignupForm.objects.all()) + queryset=SignupForm.objects.all() + ) + tag_id = serializers.PrimaryKeyRelatedField( + many=True, + source="tags", + queryset=Tag.objects.all() + ) class Meta: model = Event - fields = ('id', 'tags', 'visible', 'title', 'description', + fields = ('id', 'tag_id', 'tags', 'visible', 'title', 'description', 'content', 'start_time', 'end_time', 'signup_id', 'signupForm') depth = 1 def create(self, validated_data): signupForms = validated_data.pop('signupForm') + tags = validated_data.pop('tags') event = Event.objects.create(**validated_data) for form in signupForms: event.signupForm.add(form) + for tag in tags: + event.tags.add(tag) event.save() return event diff --git a/webapp/urls.py b/webapp/urls.py index 2ee139e..1126a82 100644 --- a/webapp/urls.py +++ b/webapp/urls.py @@ -19,7 +19,7 @@ from webapp.views import about_view # from webapp.views import contact_view from webapp.views import EventViewSet, SignupFormViewSet, SignupViewSet,\ - FeedViewSet, ContactsViewSet, SavedQuestionsViewSet, RootView + FeedViewSet, ContactsViewSet, SavedQuestionsViewSet, RootView, TagsViewSet class APIRouter(routers.DefaultRouter): @@ -33,6 +33,7 @@ router.register(r'signup', SignupViewSet) router.register(r'feed', FeedViewSet) router.register(r'contacts', ContactsViewSet) router.register(r'questions', SavedQuestionsViewSet) +router.register(r'tags', TagsViewSet) urlpatterns = [ url(r'^api/', include(router.urls)), diff --git a/webapp/views.py b/webapp/views.py index 5782b1c..802df3f 100644 --- a/webapp/views.py +++ b/webapp/views.py @@ -19,7 +19,7 @@ from rest_framework.reverse import reverse from dealer.git import git from webapp.models import Event, SignupForm, Signup, TemplateQuestion, Feed,\ - Committee, Official + Committee, Official, Tag from webapp.serializers import * from members.views.utils import * @@ -65,6 +65,12 @@ class ContactsViewSet(viewsets.ReadOnlyModelViewSet): permission_classes = [IsAuthenticatedOrReadOnly] +class TagsViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Tag.objects.all() + serializer_class = ContactsSerializer + permission_classes = [IsAuthenticatedOrReadOnly] + + # -- OLD CODEBASE -- # @require_http_methods(["GET"]) From 50268b98a9f65fc95d6c292610d9be56b69f22ec Mon Sep 17 00:00:00 2001 From: Aarni Halinen Date: Wed, 24 Oct 2018 20:54:08 +0300 Subject: [PATCH 02/40] Remove underscore.js --- infoscreen/templates/base.html | 1 - 1 file changed, 1 deletion(-) diff --git a/infoscreen/templates/base.html b/infoscreen/templates/base.html index c4c3c70..5562bfc 100644 --- a/infoscreen/templates/base.html +++ b/infoscreen/templates/base.html @@ -17,7 +17,6 @@ - From eb5659d0da64e96f67211bace979277c81bae192 Mon Sep 17 00:00:00 2001 From: Aarni Halinen Date: Wed, 24 Oct 2018 20:54:31 +0300 Subject: [PATCH 03/40] Minor cleanup of infoscreen --- infoscreen/admin.py | 7 +++---- infoscreen/views/admin_views.py | 25 +++++++++++-------------- infoscreen/views/public_views.py | 3 ++- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/infoscreen/admin.py b/infoscreen/admin.py index d7b314c..5b2bce0 100644 --- a/infoscreen/admin.py +++ b/infoscreen/admin.py @@ -1,10 +1,9 @@ """Admin site registers.""" from django.contrib import admin -from infoscreen.models import Rotation, InfoItem, InfoInstance -from infoscreen.models import ImageInfoItem, ExternalImageInfoItem, ABBInfoItem -from infoscreen.models import ExternalWebsiteInfoItem -from infoscreen.models import VideoInfoItem +from infoscreen.models import ( + Rotation, InfoItem, InfoInstance, ImageInfoItem, + ExternalImageInfoItem, ABBInfoItem, ExternalWebsiteInfoItem, VideoInfoItem) # Register your models here. admin.site.register(Rotation) diff --git a/infoscreen/views/admin_views.py b/infoscreen/views/admin_views.py index b8de20f..4163dc3 100644 --- a/infoscreen/views/admin_views.py +++ b/infoscreen/views/admin_views.py @@ -6,6 +6,7 @@ from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.http import require_http_methods from django.contrib.contenttypes.models import ContentType from django.contrib.auth.decorators import permission_required, login_required +from django.db import DatabaseError from infoscreen.models import UploadFileForm import sikweb.settings as settings @@ -14,14 +15,10 @@ import logging import threading import requests -from infoscreen.models import Rotation, InfoItem, InfoInstance -from infoscreen.models import (ABBInfoItem, ExternalImageInfoItem, - ImageInfoItem, SossoInfoItem, HslInfoItem) -from infoscreen.models import EventInfoItem -from infoscreen.models import ExternalWebsiteInfoItem -from infoscreen.models import ImageUploadForm -from infoscreen.models import ApyInfoItem -from infoscreen.models import VideoInfoItem +from infoscreen.models import ( + Rotation, InfoItem, InfoInstance, ABBInfoItem, ExternalImageInfoItem, + ImageInfoItem, SossoInfoItem, HslInfoItem, EventInfoItem, + ExternalWebsiteInfoItem, ImageUploadForm, ApyInfoItem, VideoInfoItem) @login_required(login_url='/admin/login') @@ -41,7 +38,7 @@ def create_item_generator(model): def create_item(request, *args, **kwargs): try: data = json.loads(request.body.decode("utf-8")) - except ValueError: + except json.JSONDecodeError: return HttpResponseBadRequest( '{"status":"failure","error":"invalid json supplied"}') try: @@ -71,7 +68,7 @@ def delete_item_generator(model): try: item.delete() return HttpResponse('{"status":"success"}') - except: + except DatabaseError: resp = HttpResponse('{"error" : "could not delete item"}') resp.status_code = 500 return resp @@ -97,7 +94,7 @@ def delete_info_item(request, *args, **kwargs): try: item.delete() return HttpResponse('{"status":"success"}') - except: + except DatabaseError: resp = HttpResponse('{"error" : "could not delete item"}') resp.status_code = 500 return resp @@ -145,14 +142,14 @@ def create_rotation(request, *args, **kwargs): """Create rotation.""" try: data = json.loads(request.body.decode("utf-8")) - except: + except json.JSONDecodeError: return HttpResponse('{"error": "bad post body!"}', status=400) try: name = data["name"] Rotation.objects.create(name=name) resp = HttpResponse(status=200) - except: + except DatabaseError: resp = HttpResponse( '{"error" : "could not create rotation!"}', status=400) @@ -171,7 +168,7 @@ def delete_rotation(request, *args, **kwargs): try: Rotation.objects.filter(id=id).delete() resp = HttpResponse(status=200) - except: + except DatabaseError: resp = HttpResponse( '{"error" : "could not delete rotation!"}', status=400) diff --git a/infoscreen/views/public_views.py b/infoscreen/views/public_views.py index b7c8b6a..9a7ac85 100644 --- a/infoscreen/views/public_views.py +++ b/infoscreen/views/public_views.py @@ -2,6 +2,7 @@ from django.shortcuts import render from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest from django.views.decorators.http import require_http_methods from django.conf import settings +from django.db import DatabaseError from infoscreen.models import Rotation, InfoItem, InfoInstance from infoscreen.hsl_fetcher import fetch as hsl_fetch @@ -23,7 +24,7 @@ def default(request, *args, **kwargs): """Try getting first rotation item.""" try: first = Rotation.objects.all()[0].id - except: + except DatabaseError: first = 0 return index(request, first, *args, **kwargs) From e43734e32e6e26c3a3d4d1027807d7154bd3f962 Mon Sep 17 00:00:00 2001 From: Aarni Halinen Date: Thu, 25 Oct 2018 14:36:51 +0300 Subject: [PATCH 04/40] Fix naive dates to aware datetimes --- members/views/members.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/members/views/members.py b/members/views/members.py index 5a0db48..dbd42dc 100644 --- a/members/views/members.py +++ b/members/views/members.py @@ -53,9 +53,9 @@ def member_list(request, *args, **kwargs): f_month = 9 now = timezone.now() if (now.month >= f_month): - filter_date = datetime.date(now.year, f_month, f_day) + filter_date = timezone.make_aware(datetime.datetime(now.year, f_month, f_day)) else: - filter_date = datetime.date(now.year - 1, f_month, f_day) + filter_date = timezone.make_aware(datetime.datetime(now.year - 1, f_month, f_day)) context = { 'table': table_html, 'member_count': len(members), From 395c597a7f4caea31c89262bbf36b6f8ed035eb3 Mon Sep 17 00:00:00 2001 From: Joel Lavikainen Date: Sat, 17 Nov 2018 17:34:36 +0200 Subject: [PATCH 05/40] Add tags to REST API --- webapp/serializers.py | 4 ++++ webapp/views.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/webapp/serializers.py b/webapp/serializers.py index 60bbb8e..583ddde 100644 --- a/webapp/serializers.py +++ b/webapp/serializers.py @@ -65,6 +65,10 @@ class SavedQuestionsSerializer(serializers.ModelSerializer): model = TemplateQuestion fields = ('id', 'name', 'question') +class TagSerializer(serializers.ModelSerializer): + class Meta: + tag = Tag + fields = ('slug', 'name', 'icon') class FeedSerializer(serializers.ModelSerializer): class Meta: diff --git a/webapp/views.py b/webapp/views.py index 802df3f..5b6e82c 100644 --- a/webapp/views.py +++ b/webapp/views.py @@ -67,7 +67,7 @@ class ContactsViewSet(viewsets.ReadOnlyModelViewSet): class TagsViewSet(viewsets.ReadOnlyModelViewSet): queryset = Tag.objects.all() - serializer_class = ContactsSerializer + serializer_class = TagSerializer permission_classes = [IsAuthenticatedOrReadOnly] From 7a58fb0af5c77bbf673b9293e0d27c1b5cc41243 Mon Sep 17 00:00:00 2001 From: Joel Lavikainen Date: Sat, 17 Nov 2018 17:52:35 +0200 Subject: [PATCH 06/40] Add tag id support for Feed in REST --- webapp/serializers.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/webapp/serializers.py b/webapp/serializers.py index 583ddde..be502fd 100644 --- a/webapp/serializers.py +++ b/webapp/serializers.py @@ -71,10 +71,24 @@ class TagSerializer(serializers.ModelSerializer): fields = ('slug', 'name', 'icon') class FeedSerializer(serializers.ModelSerializer): + tags = TagSerializer(many=True, read_only=True, required=True) + tag_id = serializers.PrimaryKeyRelatedField( + many = True, + source = "tags", + queryset=Tag.objects.all() + ) class Meta: model = Feed fields = ('id', 'tags', 'visible', 'title', 'description', 'content', 'publish_time', 'autohide') + + def create(self, validated_data): + tags_data = validated_data.pop('tags') + feed = Feed.objects.create(**validated_data) + for tag in tags_data: + feed.tags.add(tag) + feed.save() + return feed class ContactsSerializer(serializers.ModelSerializer): From d0f5bc223bdad6f1360fe42bda56ad3fedf86f49 Mon Sep 17 00:00:00 2001 From: Joel Lavikainen Date: Sat, 17 Nov 2018 17:57:06 +0200 Subject: [PATCH 07/40] Add id to Tag data --- webapp/serializers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webapp/serializers.py b/webapp/serializers.py index be502fd..2b9857c 100644 --- a/webapp/serializers.py +++ b/webapp/serializers.py @@ -66,9 +66,10 @@ class SavedQuestionsSerializer(serializers.ModelSerializer): fields = ('id', 'name', 'question') class TagSerializer(serializers.ModelSerializer): + id = serializers.ReadOnlyField() class Meta: tag = Tag - fields = ('slug', 'name', 'icon') + fields = ('id', 'slug', 'name', 'icon') class FeedSerializer(serializers.ModelSerializer): tags = TagSerializer(many=True, read_only=True, required=True) From 09a2e09c1b3de180ec100acf75af6cb21884e621 Mon Sep 17 00:00:00 2001 From: Joel Lavikainen Date: Sat, 17 Nov 2018 20:40:08 +0200 Subject: [PATCH 08/40] Add tests for Tag REST api --- webapp/tests.py | 53 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/webapp/tests.py b/webapp/tests.py index 1418e07..2c488e1 100644 --- a/webapp/tests.py +++ b/webapp/tests.py @@ -1,5 +1,56 @@ """Tests for webapp.""" from django.test import TestCase +from django.core.files import File +from rest_framework.test import APITestCase +from rest_framework import status +from webapp.models import Tag -# Create your tests here. +from collections import OrderedDict +from itertools import islice +import tempfile + + +class TagsTestCase(APITestCase): + + + def setUp(self): + self.icon = tempfile.NamedTemporaryFile(suffix=".jpg").name + Tag.objects.create(slug='Party', name='Bileet', icon=self.icon) + + + def test_get_single_tag(self): + self.assertEqual(Tag.objects.count(), 1) + response = self.client.get('/api/tags/', format='json') + self.assertTrue(status.is_success(response.status_code)) + + #We dont care about icon, so response is sliced + sliced_response = OrderedDict(islice(response.data[0].items(),2)) + self.assertEqual(sliced_response, {'slug': 'Party', 'name': 'Bileet'}) + def test_get_multiple_tags(self): + self.assertEqual(Tag.objects.count(), 1) + Tag.objects.create(slug='Freshmen', name='Fuksit', icon=self.icon) + Tag.objects.create(slug='International', name='Ulkkarit', icon=self.icon) + self.assertEqual(Tag.objects.count(), 3) + + response = self.client.get('/api/tags/', format='json') + self.assertTrue(status.is_success(response.status_code)) + + print(response.data) + #We dont care about icon, so response is sliced + sliced_response = OrderedDict(islice(response.data[0].items(),2)) + self.assertEqual(sliced_response, {'slug': 'Party', 'name': 'Bileet'}) + sliced_response = OrderedDict(islice(response.data[1].items(),2)) + self.assertEqual(sliced_response, {'slug': 'Freshmen', 'name': 'Fuksit'}) + sliced_response = OrderedDict(islice(response.data[2].items(),2)) + self.assertEqual(sliced_response, {'slug': 'International', 'name': 'Ulkkarit'}) + + + def test_create_tag(self): + self.assertEqual(Tag.objects.count(), 1) + response = self.client.post('/api/tags/', {'slug': 'Test', 'name': 'Testinimi', 'icon': self.icon}, format='multipart') + self.assertFalse(status.is_success(response.status_code)) + self.assertEqual(Tag.objects.count(), 1) + + + \ No newline at end of file From 8398928a974fbea139806872e63d1c06b7514dbf Mon Sep 17 00:00:00 2001 From: Joel Lavikainen Date: Sat, 17 Nov 2018 20:40:42 +0200 Subject: [PATCH 09/40] Add dummy data generation for tag objects --- webapp/management/commands/createdummydata.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/webapp/management/commands/createdummydata.py b/webapp/management/commands/createdummydata.py index 1571022..ed55dbb 100644 --- a/webapp/management/commands/createdummydata.py +++ b/webapp/management/commands/createdummydata.py @@ -3,6 +3,7 @@ import random from members.models import Member, Request from infoscreen.models import ExternalImageInfoItem, Rotation, InfoInstance +from webapp.models import Tag from misc.namegenerator import generate_names from django.core.management.base import BaseCommand, CommandError @@ -65,3 +66,15 @@ class Command(BaseCommand): POR=por, AYY=ayy, jas=jas) + + TAGS = ["Party", "International", "Freshmen", "Culture"] + TAG_COUNT = 2 + + for i in range(TAG_COUNT): + slug = TAGS[i] + str(random.randint(0, 10)) + name = slug + str(random.randint(0, 10)) + print(slug, name) + Tag.objects.create(slug=slug, + name=name, + icon="http://testiurl.com/kuva.jpg") + From 3294d6ffa0a666a2db9840485efed963091b5b59 Mon Sep 17 00:00:00 2001 From: Joel Lavikainen Date: Sat, 17 Nov 2018 20:41:45 +0200 Subject: [PATCH 10/40] Fix Tag serializer --- webapp/serializers.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/webapp/serializers.py b/webapp/serializers.py index 2b9857c..64a4f3f 100644 --- a/webapp/serializers.py +++ b/webapp/serializers.py @@ -65,24 +65,28 @@ class SavedQuestionsSerializer(serializers.ModelSerializer): model = TemplateQuestion fields = ('id', 'name', 'question') + class TagSerializer(serializers.ModelSerializer): id = serializers.ReadOnlyField() + class Meta: - tag = Tag + model = Tag fields = ('id', 'slug', 'name', 'icon') + class FeedSerializer(serializers.ModelSerializer): - tags = TagSerializer(many=True, read_only=True, required=True) + tags = TagSerializer(many=True, read_only=True) tag_id = serializers.PrimaryKeyRelatedField( - many = True, - source = "tags", + many=True, + source="tags", queryset=Tag.objects.all() ) + class Meta: model = Feed fields = ('id', 'tags', 'visible', 'title', 'description', 'content', 'publish_time', 'autohide') - + def create(self, validated_data): tags_data = validated_data.pop('tags') feed = Feed.objects.create(**validated_data) From 4d2584dee47d4cb80fb509050e8f4477bd97b82b Mon Sep 17 00:00:00 2001 From: Joel Lavikainen Date: Sat, 17 Nov 2018 21:45:26 +0200 Subject: [PATCH 11/40] Change Tag primary key to id --- webapp/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/models.py b/webapp/models.py index 4638cab..856d00a 100644 --- a/webapp/models.py +++ b/webapp/models.py @@ -20,7 +20,7 @@ VERBOSE_NAME = _('Webapp') class Tag(models.Model): """Model for tag.""" - slug = models.SlugField(primary_key=True) + slug = models.SlugField(unique=True) name = models.CharField(max_length=127) icon = models.ImageField() From 3c58d764bdb24a44d666ff39d3eaed94231a39c3 Mon Sep 17 00:00:00 2001 From: Joel Lavikainen Date: Sat, 17 Nov 2018 21:46:17 +0200 Subject: [PATCH 12/40] Fix Feed link to Tag --- webapp/serializers.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/webapp/serializers.py b/webapp/serializers.py index 64a4f3f..bc70e40 100644 --- a/webapp/serializers.py +++ b/webapp/serializers.py @@ -67,8 +67,6 @@ class SavedQuestionsSerializer(serializers.ModelSerializer): class TagSerializer(serializers.ModelSerializer): - id = serializers.ReadOnlyField() - class Meta: model = Tag fields = ('id', 'slug', 'name', 'icon') @@ -76,7 +74,7 @@ class TagSerializer(serializers.ModelSerializer): class FeedSerializer(serializers.ModelSerializer): tags = TagSerializer(many=True, read_only=True) - tag_id = serializers.PrimaryKeyRelatedField( + id = serializers.PrimaryKeyRelatedField( many=True, source="tags", queryset=Tag.objects.all() From dc2982965e21b43074569da3e777a9bafb7686dc Mon Sep 17 00:00:00 2001 From: Joel Lavikainen Date: Sat, 17 Nov 2018 22:30:03 +0200 Subject: [PATCH 13/40] Add migration related to tag change --- webapp/migrations/0048_auto_20181117_2128.py | 33 ++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 webapp/migrations/0048_auto_20181117_2128.py diff --git a/webapp/migrations/0048_auto_20181117_2128.py b/webapp/migrations/0048_auto_20181117_2128.py new file mode 100644 index 0000000..f7e2751 --- /dev/null +++ b/webapp/migrations/0048_auto_20181117_2128.py @@ -0,0 +1,33 @@ +# Generated by Django 2.0.7 on 2018-11-17 19:28 + +from django.db import migrations, models + +i = 1 +def gen_uuid(apps, schema_editor): + tag = apps.get_model('webapp', 'Tag') + for row in tag.objects.all(): + global i + row.id = i + i += 1 + row.save() + +class Migration(migrations.Migration): + + dependencies = [ + ('webapp', '0047_auto_20180710_2110'), + ] + + operations = [ + migrations.AddField( + model_name='tag', + name='id', + field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + preserve_default=False, + ), + migrations.AlterField( + model_name='tag', + name='slug', + field=models.SlugField(unique=True), + ), + migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop), + ] From 3cfa0df43b1698d98e41c13a64bd0ffc7c639c43 Mon Sep 17 00:00:00 2001 From: Joel Lavikainen Date: Sat, 17 Nov 2018 22:40:43 +0200 Subject: [PATCH 14/40] Add missing lines to coverage report --- .coveragerc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.coveragerc b/.coveragerc index e7fcd1f..0a5fbc3 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,3 +1,5 @@ +[report] +show_missing = True [run] omit = */migrations/* From d99f9c53f711bdc88d34c39fec0d0a4ea3da3a2f Mon Sep 17 00:00:00 2001 From: Joel Lavikainen Date: Sun, 18 Nov 2018 04:21:01 +0200 Subject: [PATCH 15/40] Add migration with fix --- webapp/migrations/0048_auto_20181117_2128.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/webapp/migrations/0048_auto_20181117_2128.py b/webapp/migrations/0048_auto_20181117_2128.py index f7e2751..43a13a3 100644 --- a/webapp/migrations/0048_auto_20181117_2128.py +++ b/webapp/migrations/0048_auto_20181117_2128.py @@ -30,4 +30,8 @@ class Migration(migrations.Migration): field=models.SlugField(unique=True), ), migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop), + # Check if this fixes the database in production, not compliant with sqlite + # https://code.djangoproject.com/ticket/25012 + # migrations.RunSQL('ALTER TABLE webapp_basefeed_tags ALTER tag_id TYPE int ALTER tag_id REFERENCES ("id")'), + ] From 0617dd6a934aa44670a410024e695d0241f586f7 Mon Sep 17 00:00:00 2001 From: Joel Lavikainen Date: Sun, 18 Nov 2018 04:29:04 +0200 Subject: [PATCH 16/40] Add generation of field data --- webapp/management/commands/createdummydata.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/webapp/management/commands/createdummydata.py b/webapp/management/commands/createdummydata.py index ed55dbb..f10c1c6 100644 --- a/webapp/management/commands/createdummydata.py +++ b/webapp/management/commands/createdummydata.py @@ -3,7 +3,7 @@ import random from members.models import Member, Request from infoscreen.models import ExternalImageInfoItem, Rotation, InfoInstance -from webapp.models import Tag +from webapp.models import Tag, Feed from misc.namegenerator import generate_names from django.core.management.base import BaseCommand, CommandError @@ -78,3 +78,17 @@ class Command(BaseCommand): name=name, icon="http://testiurl.com/kuva.jpg") + FEED_COUNT = 3 + + for i in range(FEED_COUNT): + title = "ds" + str(random.randint(0, 15)) + description = "dsg" + str(random.randint(0, 20)) + content = "fdfd" + Feed.objects.create(title=title, + visible=True, + description=description, + content=content) + tag1 = Tag.objects.get(id=1) + Feed.objects.get(title=title).tags.add(tag1) + tag2 = Tag.objects.get(id=1) + Feed.objects.get(title=title).tags.add(tag2) From b7beb72409c2698f1f66e0667f3407a37454aa11 Mon Sep 17 00:00:00 2001 From: Joel Lavikainen Date: Sun, 18 Nov 2018 04:39:31 +0200 Subject: [PATCH 17/40] Fix linting errors --- webapp/migrations/0048_auto_20181117_2128.py | 13 ++-- webapp/serializers.py | 16 ++-- webapp/tests.py | 81 ++++++++++++++++---- webapp/views.py | 2 +- 4 files changed, 82 insertions(+), 30 deletions(-) diff --git a/webapp/migrations/0048_auto_20181117_2128.py b/webapp/migrations/0048_auto_20181117_2128.py index 43a13a3..9e378a1 100644 --- a/webapp/migrations/0048_auto_20181117_2128.py +++ b/webapp/migrations/0048_auto_20181117_2128.py @@ -3,6 +3,8 @@ from django.db import migrations, models i = 1 + + def gen_uuid(apps, schema_editor): tag = apps.get_model('webapp', 'Tag') for row in tag.objects.all(): @@ -11,6 +13,7 @@ def gen_uuid(apps, schema_editor): i += 1 row.save() + class Migration(migrations.Migration): dependencies = [ @@ -18,17 +21,17 @@ class Migration(migrations.Migration): ] operations = [ + migrations.AlterField( + model_name='tag', + name='slug', + field=models.SlugField(unique=True), + ), migrations.AddField( model_name='tag', name='id', field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), preserve_default=False, ), - migrations.AlterField( - model_name='tag', - name='slug', - field=models.SlugField(unique=True), - ), migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop), # Check if this fixes the database in production, not compliant with sqlite # https://code.djangoproject.com/ticket/25012 diff --git a/webapp/serializers.py b/webapp/serializers.py index bc70e40..1eae195 100644 --- a/webapp/serializers.py +++ b/webapp/serializers.py @@ -73,22 +73,24 @@ class TagSerializer(serializers.ModelSerializer): class FeedSerializer(serializers.ModelSerializer): - tags = TagSerializer(many=True, read_only=True) - id = serializers.PrimaryKeyRelatedField( - many=True, - source="tags", - queryset=Tag.objects.all() - ) + # tags = TagSerializer(many=True, read_only=False) + # tags = serializers.PrimaryKeyRelatedField( + # many=True, + # queryset=Tag.objects.all(), + # read_only=False + # ) class Meta: model = Feed - fields = ('id', 'tags', 'visible', 'title', 'description', + fields = ('tags', 'visible', 'title', 'description', 'content', 'publish_time', 'autohide') def create(self, validated_data): + print("validated data: ", validated_data) tags_data = validated_data.pop('tags') feed = Feed.objects.create(**validated_data) for tag in tags_data: + print(tag) feed.tags.add(tag) feed.save() return feed diff --git a/webapp/tests.py b/webapp/tests.py index 2c488e1..8546318 100644 --- a/webapp/tests.py +++ b/webapp/tests.py @@ -4,7 +4,8 @@ from django.test import TestCase from django.core.files import File from rest_framework.test import APITestCase from rest_framework import status -from webapp.models import Tag +from webapp.models import Tag, Feed +from webapp.serializers import TagSerializer, FeedSerializer from collections import OrderedDict from itertools import islice @@ -12,21 +13,20 @@ import tempfile class TagsTestCase(APITestCase): - def setUp(self): self.icon = tempfile.NamedTemporaryFile(suffix=".jpg").name Tag.objects.create(slug='Party', name='Bileet', icon=self.icon) - def test_get_single_tag(self): self.assertEqual(Tag.objects.count(), 1) response = self.client.get('/api/tags/', format='json') self.assertTrue(status.is_success(response.status_code)) - - #We dont care about icon, so response is sliced - sliced_response = OrderedDict(islice(response.data[0].items(),2)) - self.assertEqual(sliced_response, {'slug': 'Party', 'name': 'Bileet'}) + + # We dont care about icon, so response is sliced + sliced_response = OrderedDict(islice(response.data[0].items(), 3)) + self.assertEqual(sliced_response, {'id': 1, 'slug': 'Party', 'name': 'Bileet'}) + def test_get_multiple_tags(self): self.assertEqual(Tag.objects.count(), 1) Tag.objects.create(slug='Freshmen', name='Fuksit', icon=self.icon) @@ -35,16 +35,14 @@ class TagsTestCase(APITestCase): response = self.client.get('/api/tags/', format='json') self.assertTrue(status.is_success(response.status_code)) - - print(response.data) - #We dont care about icon, so response is sliced - sliced_response = OrderedDict(islice(response.data[0].items(),2)) - self.assertEqual(sliced_response, {'slug': 'Party', 'name': 'Bileet'}) - sliced_response = OrderedDict(islice(response.data[1].items(),2)) - self.assertEqual(sliced_response, {'slug': 'Freshmen', 'name': 'Fuksit'}) - sliced_response = OrderedDict(islice(response.data[2].items(),2)) - self.assertEqual(sliced_response, {'slug': 'International', 'name': 'Ulkkarit'}) + # We dont care about icon, so response is sliced + sliced_response = OrderedDict(islice(response.data[0].items(), 3)) + self.assertEqual(sliced_response, {'id': 1, 'slug': 'Party', 'name': 'Bileet'}) + sliced_response = OrderedDict(islice(response.data[1].items(), 3)) + self.assertEqual(sliced_response, {'id': 2, 'slug': 'Freshmen', 'name': 'Fuksit'}) + sliced_response = OrderedDict(islice(response.data[2].items(), 3)) + self.assertEqual(sliced_response, {'id': 3, 'slug': 'International', 'name': 'Ulkkarit'}) def test_create_tag(self): self.assertEqual(Tag.objects.count(), 1) @@ -52,5 +50,54 @@ class TagsTestCase(APITestCase): self.assertFalse(status.is_success(response.status_code)) self.assertEqual(Tag.objects.count(), 1) + def test_invalid_tag(self): + self.assertEqual(Tag.objects.count(), 1) + response = self.client.get('/api/tags/15', format='json', follow=True) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - \ No newline at end of file + +class FeedTestCase(APITestCase): + + def setUp(self): + self.icon = tempfile.NamedTemporaryFile(suffix=".jpg").name + Tag.objects.create(slug='testtag1', name='test1', icon=self.icon) + tag1 = Tag.objects.get(slug="testtag1") + Tag.objects.create(slug="testtag2", name='test2', icon=self.icon) + tag2 = Tag.objects.get(slug="testtag2") + self.assertEqual(Tag.objects.count(), 2) + + Feed.objects.create(title="TestFeed", visible=True, description="diidadaapa", content="lorem ipsum") + Feed.objects.get(title="TestFeed").tags.add(tag1) + Feed.objects.get(title="TestFeed").tags.add(tag2) + self.assertEqual(Feed.objects.count(), 1) + self.assertEqual(Feed.objects.all()[0].tags.count(), 2) + + def test_get_feed(self): + response = self.client.get('/api/feed/', format='json') + self.assertTrue(status.is_success(response.status_code)) + + feeds = Feed.objects.all() + serializer = FeedSerializer(feeds, many=True) + + # Remove icon info because they are different when sent compared to what is returned + for tag in serializer.data[0]['tags']: + tag['icon'] = "empty" + for tag in response.data[0]['tags']: + tag['icon'] = "empty" + self.assertEqual(response.data, serializer.data) + + def test_post_feed(self): + Tag.objects.create(slug="test1", name="testsds") + Tag.objects.create(slug="test2", name="testsdsd") + tag1_id = Tag.objects.get(slug="test1").id + tag2_id = Tag.objects.get(slug="test2").id + + data = {'tags': [tag1_id, tag2_id], 'title': 'testtitle', 'visible': 'True', 'description': 'liirumlaarum', 'content': 'lorem ipsum'} + response = self.client.post('/api/feed/', data, format='multipart') + self.assertTrue(status.is_success(response.status_code)) + + self.assertEqual(Feed.objects.count(), 2) + + created = Feed.objects.get(title="testtitle") + print(created.tags) + # self.assertEqual(created.tags.count(), 2) diff --git a/webapp/views.py b/webapp/views.py index 5b6e82c..6f9c02b 100644 --- a/webapp/views.py +++ b/webapp/views.py @@ -56,7 +56,7 @@ class SavedQuestionsViewSet(viewsets.ModelViewSet): class FeedViewSet(viewsets.ModelViewSet): queryset = Feed.objects.all() serializer_class = FeedSerializer - permission_classes = [IsAuthenticatedOrReadOnly] + permission_classes = [] class ContactsViewSet(viewsets.ReadOnlyModelViewSet): From bf4c5bf3d9cd3df8fe191f6a6bac76acbd04cb73 Mon Sep 17 00:00:00 2001 From: Joel Lavikainen Date: Sun, 18 Nov 2018 04:43:49 +0200 Subject: [PATCH 18/40] Fix feed testcase --- webapp/tests.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/webapp/tests.py b/webapp/tests.py index 8546318..0a9f1fa 100644 --- a/webapp/tests.py +++ b/webapp/tests.py @@ -79,11 +79,6 @@ class FeedTestCase(APITestCase): feeds = Feed.objects.all() serializer = FeedSerializer(feeds, many=True) - # Remove icon info because they are different when sent compared to what is returned - for tag in serializer.data[0]['tags']: - tag['icon'] = "empty" - for tag in response.data[0]['tags']: - tag['icon'] = "empty" self.assertEqual(response.data, serializer.data) def test_post_feed(self): From 90f430c95cc57c61fab70df144d83459167cade6 Mon Sep 17 00:00:00 2001 From: Aarni Halinen Date: Sun, 18 Nov 2018 04:50:34 +0200 Subject: [PATCH 19/40] Remove PK slug from ancient migration --- webapp/migrations/0007_auto_20170607_1815.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/migrations/0007_auto_20170607_1815.py b/webapp/migrations/0007_auto_20170607_1815.py index 71660fd..359da53 100644 --- a/webapp/migrations/0007_auto_20170607_1815.py +++ b/webapp/migrations/0007_auto_20170607_1815.py @@ -30,7 +30,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Tag', fields=[ - ('slug', models.SlugField(primary_key=True, serialize=False)), + ('slug', models.SlugField(serialize=False)), ('name', models.CharField(max_length=127)), ('icon', models.ImageField(upload_to='')), ], From 642aa9847f38f6e4d4cd8aa8148782797e36aae1 Mon Sep 17 00:00:00 2001 From: Joel Lavikainen Date: Sun, 18 Nov 2018 04:58:59 +0200 Subject: [PATCH 20/40] Revert model change --- webapp/migrations/0048_auto_20181117_2128.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/webapp/migrations/0048_auto_20181117_2128.py b/webapp/migrations/0048_auto_20181117_2128.py index 9e378a1..4200baa 100644 --- a/webapp/migrations/0048_auto_20181117_2128.py +++ b/webapp/migrations/0048_auto_20181117_2128.py @@ -26,13 +26,7 @@ class Migration(migrations.Migration): name='slug', field=models.SlugField(unique=True), ), - migrations.AddField( - model_name='tag', - name='id', - field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), - preserve_default=False, - ), - migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop), + # migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop), # Check if this fixes the database in production, not compliant with sqlite # https://code.djangoproject.com/ticket/25012 # migrations.RunSQL('ALTER TABLE webapp_basefeed_tags ALTER tag_id TYPE int ALTER tag_id REFERENCES ("id")'), From bf5620c3b6c0a337d22901e3d54ea0bbbffef45c Mon Sep 17 00:00:00 2001 From: Aarni Halinen Date: Sun, 18 Nov 2018 13:31:48 +0200 Subject: [PATCH 21/40] Revert "Remove PK slug from ancient migration" This reverts commit 90f430c95cc57c61fab70df144d83459167cade6. --- webapp/migrations/0007_auto_20170607_1815.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/migrations/0007_auto_20170607_1815.py b/webapp/migrations/0007_auto_20170607_1815.py index 359da53..71660fd 100644 --- a/webapp/migrations/0007_auto_20170607_1815.py +++ b/webapp/migrations/0007_auto_20170607_1815.py @@ -30,7 +30,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Tag', fields=[ - ('slug', models.SlugField(serialize=False)), + ('slug', models.SlugField(primary_key=True, serialize=False)), ('name', models.CharField(max_length=127)), ('icon', models.ImageField(upload_to='')), ], From 316ab679f854b9162319270f853c7ed3ec584f80 Mon Sep 17 00:00:00 2001 From: Aarni Halinen Date: Sun, 18 Nov 2018 13:33:30 +0200 Subject: [PATCH 22/40] Remove webapp migration 0048 --- webapp/migrations/0048_auto_20181117_2128.py | 34 -------------------- 1 file changed, 34 deletions(-) delete mode 100644 webapp/migrations/0048_auto_20181117_2128.py diff --git a/webapp/migrations/0048_auto_20181117_2128.py b/webapp/migrations/0048_auto_20181117_2128.py deleted file mode 100644 index 4200baa..0000000 --- a/webapp/migrations/0048_auto_20181117_2128.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 2.0.7 on 2018-11-17 19:28 - -from django.db import migrations, models - -i = 1 - - -def gen_uuid(apps, schema_editor): - tag = apps.get_model('webapp', 'Tag') - for row in tag.objects.all(): - global i - row.id = i - i += 1 - row.save() - - -class Migration(migrations.Migration): - - dependencies = [ - ('webapp', '0047_auto_20180710_2110'), - ] - - operations = [ - migrations.AlterField( - model_name='tag', - name='slug', - field=models.SlugField(unique=True), - ), - # migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop), - # Check if this fixes the database in production, not compliant with sqlite - # https://code.djangoproject.com/ticket/25012 - # migrations.RunSQL('ALTER TABLE webapp_basefeed_tags ALTER tag_id TYPE int ALTER tag_id REFERENCES ("id")'), - - ] From 97e6ce3d8d04dfa36dad90a8e9fc11bccf549fdd Mon Sep 17 00:00:00 2001 From: Aarni Halinen Date: Sun, 18 Nov 2018 13:46:31 +0200 Subject: [PATCH 23/40] Fix webapp migrations --- webapp/migrations/0048_auto_20181118_1336.py | 17 +++++ webapp/migrations/0049_auto_20181118_1344.py | 79 ++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 webapp/migrations/0048_auto_20181118_1336.py create mode 100644 webapp/migrations/0049_auto_20181118_1344.py diff --git a/webapp/migrations/0048_auto_20181118_1336.py b/webapp/migrations/0048_auto_20181118_1336.py new file mode 100644 index 0000000..7cf8c1b --- /dev/null +++ b/webapp/migrations/0048_auto_20181118_1336.py @@ -0,0 +1,17 @@ +# Generated by Django 2.0.7 on 2018-11-18 11:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('webapp', '0047_auto_20180710_2110'), + ] + + operations = [ + migrations.DeleteModel('tag'), + migrations.DeleteModel('feed'), + migrations.DeleteModel('event'), + migrations.DeleteModel('basefeed'), + ] diff --git a/webapp/migrations/0049_auto_20181118_1344.py b/webapp/migrations/0049_auto_20181118_1344.py new file mode 100644 index 0000000..b9e2add --- /dev/null +++ b/webapp/migrations/0049_auto_20181118_1344.py @@ -0,0 +1,79 @@ +# Generated by Django 2.0.7 on 2018-11-18 11:44 + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import webapp.utils + + +class Migration(migrations.Migration): + + dependencies = [ + ('webapp', '0048_auto_20181118_1336'), + ] + + operations = [ + migrations.CreateModel( + name='BaseFeed', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('visible', models.BooleanField(default=True)), + ('title', models.CharField(max_length=255)), + ('title_fi', models.CharField(max_length=255, null=True)), + ('title_en', models.CharField(max_length=255, null=True)), + ('description', models.CharField(max_length=255)), + ('description_fi', models.CharField(max_length=255, null=True)), + ('description_en', models.CharField(max_length=255, null=True)), + ('content', models.TextField()), + ('content_fi', models.TextField(null=True)), + ('content_en', models.TextField(null=True)), + ], + ), + migrations.CreateModel( + name='Tag', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('slug', models.SlugField(unique=True)), + ('name', models.CharField(max_length=127)), + ('name_fi', models.CharField(max_length=127, null=True)), + ('name_en', models.CharField(max_length=127, null=True)), + ('icon', models.ImageField(upload_to='')), + ], + options={ + 'verbose_name': 'Tag', + 'verbose_name_plural': 'Tags', + }, + ), + migrations.CreateModel( + name='Event', + fields=[ + ('basefeed_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='webapp.BaseFeed')), + ('start_time', models.DateTimeField(default=django.utils.timezone.now)), + ('end_time', models.DateTimeField(default=django.utils.timezone.now)), + ('signupForm', models.ManyToManyField(blank=True, to='webapp.SignupForm')), + ], + options={ + 'verbose_name': 'Event', + 'verbose_name_plural': 'Events', + }, + bases=('webapp.basefeed',), + ), + migrations.CreateModel( + name='Feed', + fields=[ + ('basefeed_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='webapp.BaseFeed')), + ('publish_time', models.DateTimeField(default=django.utils.timezone.now)), + ('autohide', models.DateTimeField(default=webapp.utils.month_from_now)), + ], + options={ + 'verbose_name': 'Feed', + 'verbose_name_plural': 'Feeds', + }, + bases=('webapp.basefeed',), + ), + migrations.AddField( + model_name='basefeed', + name='tags', + field=models.ManyToManyField(blank=True, related_name='feeds', to='webapp.Tag'), + ), + ] From 1f8e9e582ad56c448757177f3357bdd2f6147d77 Mon Sep 17 00:00:00 2001 From: Joel Lavikainen Date: Sun, 18 Nov 2018 17:52:34 +0200 Subject: [PATCH 24/40] Make tag tests more generic --- webapp/tests.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/webapp/tests.py b/webapp/tests.py index 0a9f1fa..1d0c016 100644 --- a/webapp/tests.py +++ b/webapp/tests.py @@ -25,7 +25,8 @@ class TagsTestCase(APITestCase): # We dont care about icon, so response is sliced sliced_response = OrderedDict(islice(response.data[0].items(), 3)) - self.assertEqual(sliced_response, {'id': 1, 'slug': 'Party', 'name': 'Bileet'}) + tag1 = Tag.objects.get(slug="Party") + self.assertEqual(sliced_response, {'id': tag1.id, 'slug': 'Party', 'name': 'Bileet'}) def test_get_multiple_tags(self): self.assertEqual(Tag.objects.count(), 1) @@ -37,12 +38,15 @@ class TagsTestCase(APITestCase): self.assertTrue(status.is_success(response.status_code)) # We dont care about icon, so response is sliced + tag1 = Tag.objects.get(slug="Party") sliced_response = OrderedDict(islice(response.data[0].items(), 3)) - self.assertEqual(sliced_response, {'id': 1, 'slug': 'Party', 'name': 'Bileet'}) + self.assertEqual(sliced_response, {'id': tag1.id, 'slug': 'Party', 'name': 'Bileet'}) sliced_response = OrderedDict(islice(response.data[1].items(), 3)) - self.assertEqual(sliced_response, {'id': 2, 'slug': 'Freshmen', 'name': 'Fuksit'}) + tag2 = Tag.objects.get(slug="Freshmen") + self.assertEqual(sliced_response, {'id': tag2.id, 'slug': 'Freshmen', 'name': 'Fuksit'}) sliced_response = OrderedDict(islice(response.data[2].items(), 3)) - self.assertEqual(sliced_response, {'id': 3, 'slug': 'International', 'name': 'Ulkkarit'}) + tag3 = Tag.objects.get(slug="International") + self.assertEqual(sliced_response, {'id': tag3.id, 'slug': 'International', 'name': 'Ulkkarit'}) def test_create_tag(self): self.assertEqual(Tag.objects.count(), 1) From 65aac3daf115ac755ff8c0f6893906aefd6a17f0 Mon Sep 17 00:00:00 2001 From: Aarni Halinen Date: Sun, 18 Nov 2018 14:36:15 +0200 Subject: [PATCH 25/40] Add LimitOffsetPagination --- sikweb/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sikweb/base.py b/sikweb/base.py index bbb197c..460f759 100644 --- a/sikweb/base.py +++ b/sikweb/base.py @@ -232,6 +232,8 @@ REST_FRAMEWORK = { 'burst': '60/min', 'sustained': '1000/day' }, + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', + 'PAGE_SIZE': 10, } # Email settings (tested working with gmail) From 55fdf8f60ded471bda517814cf4b958acf2e79d8 Mon Sep 17 00:00:00 2001 From: Aarni Halinen Date: Sun, 18 Nov 2018 15:29:51 +0200 Subject: [PATCH 26/40] Add filtering for Event and Signup ViewSets --- requirements.txt | 3 ++- sikweb/base.py | 4 ++++ webapp/views.py | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8d18fcf..4865fcd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -35,4 +35,5 @@ pyexcel==0.5.8 pyexcel-xlsx==0.5.5 django-import-export==0.7.0 openpyxl==2.4.11 -django-app-namespace-template-loader==0.4.1 \ No newline at end of file +django-app-namespace-template-loader==0.4.1 +django-filter==2.0.0 \ No newline at end of file diff --git a/sikweb/base.py b/sikweb/base.py index 460f759..1619797 100644 --- a/sikweb/base.py +++ b/sikweb/base.py @@ -103,6 +103,7 @@ INSTALLED_APPS = [ 'auditlog', 'phonenumber_field', 'import_export', + 'django_filters', ] IMPORT_EXPORT_USE_TRANSACTIONS = True @@ -234,6 +235,9 @@ REST_FRAMEWORK = { }, 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', 'PAGE_SIZE': 10, + 'DEFAULT_FILTER_BACKENDS': ( + 'django_filters.rest_framework.DjangoFilterBackend', + ), } # Email settings (tested working with gmail) diff --git a/webapp/views.py b/webapp/views.py index 6f9c02b..04cdf71 100644 --- a/webapp/views.py +++ b/webapp/views.py @@ -13,6 +13,8 @@ from rest_framework import viewsets, routers from rest_framework.permissions import IsAuthenticatedOrReadOnly from rest_framework.response import Response from rest_framework.reverse import reverse +from django_filters import rest_framework as filters +from rest_framework.filters import SearchFilter, OrderingFilter # import logging # import requests @@ -33,18 +35,27 @@ class EventViewSet(viewsets.ModelViewSet): queryset = Event.objects.all() serializer_class = EventSerializer permission_classes = [IsAuthenticatedOrReadOnly] + filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter) + filter_fields = '__all__' + search_fields = '__all__' class SignupFormViewSet(viewsets.ModelViewSet): queryset = SignupForm.objects.all() serializer_class = SignupFormSerializer permission_classes = [IsAuthenticatedOrReadOnly] + filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter) + filter_fields = '__all__' + search_fields = '__all__' class SignupViewSet(viewsets.ModelViewSet): queryset = Signup.objects.all() serializer_class = SignupSerializer permission_classes = [] + filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter) + filter_fields = '__all__' + search_fields = '__all__' class SavedQuestionsViewSet(viewsets.ModelViewSet): @@ -57,6 +68,9 @@ class FeedViewSet(viewsets.ModelViewSet): queryset = Feed.objects.all() serializer_class = FeedSerializer permission_classes = [] + filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter) + filter_fields = '__all__' + search_fields = '__all__' class ContactsViewSet(viewsets.ReadOnlyModelViewSet): From e1220d17bb5d447beb977c676030b657a35eb7d6 Mon Sep 17 00:00:00 2001 From: Aarni Halinen Date: Sun, 18 Nov 2018 17:54:06 +0200 Subject: [PATCH 27/40] Add filtering to REST API --- webapp/migrations/0050_signupform_visible.py | 18 +++++++++++ webapp/models.py | 1 + webapp/views.py | 32 ++++++++++++++++---- 3 files changed, 45 insertions(+), 6 deletions(-) create mode 100644 webapp/migrations/0050_signupform_visible.py diff --git a/webapp/migrations/0050_signupform_visible.py b/webapp/migrations/0050_signupform_visible.py new file mode 100644 index 0000000..5e666ab --- /dev/null +++ b/webapp/migrations/0050_signupform_visible.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.7 on 2018-11-18 15:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('webapp', '0049_auto_20181118_1344'), + ] + + operations = [ + migrations.AddField( + model_name='signupform', + name='visible', + field=models.BooleanField(default=True), + ), + ] diff --git a/webapp/models.py b/webapp/models.py index 856d00a..716942c 100644 --- a/webapp/models.py +++ b/webapp/models.py @@ -93,6 +93,7 @@ class SignupForm(models.Model): end = models.DateTimeField(default=timezone.now) # question = JSONField() questions = models.CharField(max_length=255) + visible = models.BooleanField(default=True) class Meta: verbose_name = _('Signup form') diff --git a/webapp/views.py b/webapp/views.py index 04cdf71..1b70e31 100644 --- a/webapp/views.py +++ b/webapp/views.py @@ -15,7 +15,7 @@ from rest_framework.response import Response from rest_framework.reverse import reverse from django_filters import rest_framework as filters from rest_framework.filters import SearchFilter, OrderingFilter - +from rest_framework import permissions # import logging # import requests from dealer.git import git @@ -26,6 +26,14 @@ from webapp.serializers import * from members.views.utils import * +class IsPostOrIsAuthenticated(permissions.BasePermission): + + def has_permission(self, request, view): + if request.method == 'POST': + return True + return request.user and request.user.is_authenticated + + # -- REST API -- # class RootView(routers.APIRootView): permission_classes = [IsAuthenticatedOrReadOnly] @@ -39,6 +47,9 @@ class EventViewSet(viewsets.ModelViewSet): filter_fields = '__all__' search_fields = '__all__' + def get_queryset(self): + return Event.objects.filter(visible=True, end_time__gt=timezone.now()).order_by('start_time') + class SignupFormViewSet(viewsets.ModelViewSet): queryset = SignupForm.objects.all() @@ -48,14 +59,20 @@ class SignupFormViewSet(viewsets.ModelViewSet): filter_fields = '__all__' search_fields = '__all__' + def get_queryset(self): + return SignupForm.objects.filter(visible=True, end__gt=timezone.now()).order_by('start') + class SignupViewSet(viewsets.ModelViewSet): queryset = Signup.objects.all() serializer_class = SignupSerializer - permission_classes = [] - filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter) - filter_fields = '__all__' - search_fields = '__all__' + permission_classes = [IsPostOrIsAuthenticated] + # filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter) + # filter_fields = '__all__' + # search_fields = '__all__' + + # def get_queryset(self): + # return Signup.objects.filter(visible=True, end_time__gt=timezone.now()).order_by('start_time') class SavedQuestionsViewSet(viewsets.ModelViewSet): @@ -67,11 +84,14 @@ class SavedQuestionsViewSet(viewsets.ModelViewSet): class FeedViewSet(viewsets.ModelViewSet): queryset = Feed.objects.all() serializer_class = FeedSerializer - permission_classes = [] + permission_classes = [IsAuthenticatedOrReadOnly] filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter) filter_fields = '__all__' search_fields = '__all__' + def get_queryset(self): + return Feed.objects.filter(visible=True, autohide__gt=timezone.now()).order_by('publish_time') + class ContactsViewSet(viewsets.ReadOnlyModelViewSet): queryset = Official.objects.all() From d3e60138401ebc75fde5562c1f5f3028abd9bb02 Mon Sep 17 00:00:00 2001 From: Aarni Halinen Date: Sun, 18 Nov 2018 18:12:41 +0200 Subject: [PATCH 28/40] Fix API test by getting results from json --- webapp/tests.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/webapp/tests.py b/webapp/tests.py index 1d0c016..68d2a43 100644 --- a/webapp/tests.py +++ b/webapp/tests.py @@ -24,7 +24,7 @@ class TagsTestCase(APITestCase): self.assertTrue(status.is_success(response.status_code)) # We dont care about icon, so response is sliced - sliced_response = OrderedDict(islice(response.data[0].items(), 3)) + sliced_response = OrderedDict(islice(response.data['results'][0].items(), 3)) tag1 = Tag.objects.get(slug="Party") self.assertEqual(sliced_response, {'id': tag1.id, 'slug': 'Party', 'name': 'Bileet'}) @@ -39,12 +39,12 @@ class TagsTestCase(APITestCase): # We dont care about icon, so response is sliced tag1 = Tag.objects.get(slug="Party") - sliced_response = OrderedDict(islice(response.data[0].items(), 3)) + sliced_response = OrderedDict(islice(response.data['results'][0].items(), 3)) self.assertEqual(sliced_response, {'id': tag1.id, 'slug': 'Party', 'name': 'Bileet'}) - sliced_response = OrderedDict(islice(response.data[1].items(), 3)) + sliced_response = OrderedDict(islice(response.data['results'][1].items(), 3)) tag2 = Tag.objects.get(slug="Freshmen") self.assertEqual(sliced_response, {'id': tag2.id, 'slug': 'Freshmen', 'name': 'Fuksit'}) - sliced_response = OrderedDict(islice(response.data[2].items(), 3)) + sliced_response = OrderedDict(islice(response.data['results'][2].items(), 3)) tag3 = Tag.objects.get(slug="International") self.assertEqual(sliced_response, {'id': tag3.id, 'slug': 'International', 'name': 'Ulkkarit'}) @@ -83,7 +83,7 @@ class FeedTestCase(APITestCase): feeds = Feed.objects.all() serializer = FeedSerializer(feeds, many=True) - self.assertEqual(response.data, serializer.data) + self.assertEqual(response.data['results'], serializer.data) def test_post_feed(self): Tag.objects.create(slug="test1", name="testsds") From 1ecf3c368be36425f837d3ca172b2597d4655e4d Mon Sep 17 00:00:00 2001 From: Aarni Halinen Date: Sun, 18 Nov 2018 18:58:12 +0200 Subject: [PATCH 29/40] Add authentication for post test --- webapp/tests.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/webapp/tests.py b/webapp/tests.py index 68d2a43..7879e70 100644 --- a/webapp/tests.py +++ b/webapp/tests.py @@ -2,8 +2,10 @@ from django.test import TestCase from django.core.files import File +from django.contrib.auth.models import User from rest_framework.test import APITestCase from rest_framework import status +from rest_framework.test import force_authenticate from webapp.models import Tag, Feed from webapp.serializers import TagSerializer, FeedSerializer @@ -76,6 +78,9 @@ class FeedTestCase(APITestCase): self.assertEqual(Feed.objects.count(), 1) self.assertEqual(Feed.objects.all()[0].tags.count(), 2) + username, password = 'test_admin', 'password123' + self.authClient = User.objects.create_superuser(username, 'myemail@test.com', password) + def test_get_feed(self): response = self.client.get('/api/feed/', format='json') self.assertTrue(status.is_success(response.status_code)) @@ -92,9 +97,15 @@ class FeedTestCase(APITestCase): tag2_id = Tag.objects.get(slug="test2").id data = {'tags': [tag1_id, tag2_id], 'title': 'testtitle', 'visible': 'True', 'description': 'liirumlaarum', 'content': 'lorem ipsum'} + # Try post without authentication response = self.client.post('/api/feed/', data, format='multipart') + self.assertTrue(status.is_client_error(response.status_code)) + self.assertEqual(Feed.objects.count(), 1) + # Authenticate + self.client.force_authenticate(user=self.authClient) + response = self.client.post('/api/feed/', data, format='multipart') + # Return success and check object was created self.assertTrue(status.is_success(response.status_code)) - self.assertEqual(Feed.objects.count(), 2) created = Feed.objects.get(title="testtitle") From bab5c88061782520fc6568d1a76ccae5586cb98e Mon Sep 17 00:00:00 2001 From: jaine Date: Mon, 14 Jan 2019 19:15:02 +0200 Subject: [PATCH 30/40] Add help in error case in mac installation instruction --- linux_install.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/linux_install.md b/linux_install.md index b96fce1..e816eb9 100644 --- a/linux_install.md +++ b/linux_install.md @@ -55,3 +55,20 @@ bash setup.sh and follow the instructions. ## Done! + +## In case of error on macOS Mojave 10.14 + +If you get an error saying + +``` +The headers or library files could not be found for zlib, +a required dependency when compiling Pillow from source. +``` + +run + +``` +xcode-select --install +sudo installer -pkg /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg -target / +``` + From bb3476a8ad8371b4cb36757784cb7fed938d8a83 Mon Sep 17 00:00:00 2001 From: jaine Date: Mon, 14 Jan 2019 19:55:04 +0200 Subject: [PATCH 31/40] Updated ohlhafv page css and img --- ohlhafv/static/ohlhafv/css/base.css | 11 +- ohlhafv/static/ohlhafv/css/nav.css | 8 + ohlhafv/static/ohlhafv/img/heevit.svg | 473 ++++++++++++++++++++++++++ ohlhafv/templates/header.html | 2 +- 4 files changed, 488 insertions(+), 6 deletions(-) create mode 100644 ohlhafv/static/ohlhafv/img/heevit.svg diff --git a/ohlhafv/static/ohlhafv/css/base.css b/ohlhafv/static/ohlhafv/css/base.css index 03a8496..3f7b72a 100644 --- a/ohlhafv/static/ohlhafv/css/base.css +++ b/ohlhafv/static/ohlhafv/css/base.css @@ -1,5 +1,6 @@ html, body { - background-color: rgb(252, 225, 69); + background: #fdd504; + background: linear-gradient(#fdaa02, #fdd504) no-repeat center center fixed; } body { @@ -13,7 +14,7 @@ body { border-color: black; color: black; - border-radius: 0; + border-radius: 8px; -webkit-appearance: none; box-shadow: 10px 10px rgba(0, 0, 0, 0.5); @@ -48,8 +49,8 @@ h3 { } .navbar { - border-radius: 0; - background-color: rgb(252, 225, 69); + border-radius: 8px; + background-color: #c1272d; box-shadow: 0 0; } @@ -97,4 +98,4 @@ h3 { h6 { color: black; -} \ No newline at end of file +} diff --git a/ohlhafv/static/ohlhafv/css/nav.css b/ohlhafv/static/ohlhafv/css/nav.css index 82e804a..22bfd8d 100644 --- a/ohlhafv/static/ohlhafv/css/nav.css +++ b/ohlhafv/static/ohlhafv/css/nav.css @@ -8,4 +8,12 @@ .navbar-light .navbar-nav .nav-link { color: black; + border-radius: 8px; + padding: 10px 15px 10px 15px; + margin: 5px; + background-color: #fdaa02; +} + +a.nav-item.nav-link:hover { + background-color: #fc8606; } diff --git a/ohlhafv/static/ohlhafv/img/heevit.svg b/ohlhafv/static/ohlhafv/img/heevit.svg new file mode 100644 index 0000000..f711e47 --- /dev/null +++ b/ohlhafv/static/ohlhafv/img/heevit.svg @@ -0,0 +1,473 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ohlhafv/templates/header.html b/ohlhafv/templates/header.html index be251ad..5561ceb 100644 --- a/ohlhafv/templates/header.html +++ b/ohlhafv/templates/header.html @@ -3,6 +3,6 @@
From ec829a5683f89977a0781b5ecd1d65d46f52c652 Mon Sep 17 00:00:00 2001 From: Jan Tuomi Date: Tue, 15 Jan 2019 14:00:20 +0200 Subject: [PATCH 32/40] Update .gitignore and requirements.txt --- .gitignore | 3 ++- requirements.txt | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index c6c008f..0bc8854 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,5 @@ settings.json .vscode/ .DS_Store *.code-workspace -sik_test \ No newline at end of file +sik_test +venv/ \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 4865fcd..e1b0308 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ ptyprocess==0.5.1 pytz==2016.4 simplegeneric==0.8.1 traitlets==4.2.1 -Pillow==4.3.0 +Pillow==5.4.1 requests==2.11.1 django-nocaptcha-recaptcha==0.0.19 django-cors-headers==2.0.1 @@ -19,7 +19,7 @@ djangorestframework-jwt==1.11.0 coverage==4.3.4 django-nose==1.4.5 nose-exclude==0.5.0 -psycopg2==2.7.3.1 +psycopg2-binary==2.7.6.1 django-bootstrap3==8.2.3 django-tables2==1.6.1 pycodestyle==2.3.1 @@ -31,9 +31,9 @@ django-autocomplete-light==3.2.10 six==1.10.0 django-suit==0.2.26 telepot==12.3 -pyexcel==0.5.8 -pyexcel-xlsx==0.5.5 +pyexcel==0.5.10 +pyexcel-xlsx==0.5.6 django-import-export==0.7.0 -openpyxl==2.4.11 +openpyxl==2.5.12 django-app-namespace-template-loader==0.4.1 django-filter==2.0.0 \ No newline at end of file From e7862df57c508272ed2a3cedb1fab9ac9d84bb38 Mon Sep 17 00:00:00 2001 From: Aarni Halinen Date: Tue, 15 Jan 2019 15:25:40 +0200 Subject: [PATCH 33/40] Use Python 3.5 image consistently --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index cced68f..7533761 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3 +FROM python:3.5 ENV PYTHONUNBUFFERED 1 ENV IS_DOCKER 1 RUN mkdir /code From 168de8088b11c2c7256d3d5ee4fe2295bf030bbd Mon Sep 17 00:00:00 2001 From: Jan Tuomi Date: Tue, 15 Jan 2019 15:41:00 +0200 Subject: [PATCH 34/40] Update Django to 2.1.5 and setup JWT auth --- requirements.txt | 4 ++-- webapp/urls.py | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index e1b0308..046d48d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ backports.shutil-get-terminal-size==1.0.0 decorator==4.0.9 -Django==2.0.7 +Django==2.1.5 ipython==4.2.0 ipython-genutils==0.1.0 pexpect==4.1.0 @@ -36,4 +36,4 @@ pyexcel-xlsx==0.5.6 django-import-export==0.7.0 openpyxl==2.5.12 django-app-namespace-template-loader==0.4.1 -django-filter==2.0.0 \ No newline at end of file +django-filter==2.0.0 diff --git a/webapp/urls.py b/webapp/urls.py index 1126a82..4d13adf 100644 --- a/webapp/urls.py +++ b/webapp/urls.py @@ -2,6 +2,8 @@ from django.conf.urls import url, include from rest_framework import routers +from rest_framework_jwt.views import obtain_jwt_token + # from rest_framework.urlpatterns import format_suffix_patterns # from django.conf import settings # from django.utils.translation import ugettext_lazy as _ @@ -37,6 +39,8 @@ router.register(r'tags', TagsViewSet) urlpatterns = [ url(r'^api/', include(router.urls)), + url(r'^api/api-token-auth/', obtain_jwt_token), + # login stuff # url(r'^login$', login_view), # url(r'^logout$', logout_view), From b56ff6c5cd5bb2b3f69fe43213e86c85495534a7 Mon Sep 17 00:00:00 2001 From: Aarni Halinen Date: Tue, 15 Jan 2019 15:48:53 +0200 Subject: [PATCH 35/40] Rollback pyexcel dependencies --- requirements.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 046d48d..c83c6b4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -31,9 +31,9 @@ django-autocomplete-light==3.2.10 six==1.10.0 django-suit==0.2.26 telepot==12.3 -pyexcel==0.5.10 -pyexcel-xlsx==0.5.6 +pyexcel==0.5.8 +pyexcel-xlsx==0.5.5 django-import-export==0.7.0 -openpyxl==2.5.12 +openpyxl==2.4.11 django-app-namespace-template-loader==0.4.1 -django-filter==2.0.0 +django-filter==2.0.0 \ No newline at end of file From a81096fa13110a8dbdbca10128a6872db28b87a8 Mon Sep 17 00:00:00 2001 From: Aarni Halinen Date: Tue, 15 Jan 2019 16:02:55 +0200 Subject: [PATCH 36/40] Update pyexcel to most recent version Updating openpyxl or pyexcel-xlsx results error in tablib dependency --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index c83c6b4..a5764be 100644 --- a/requirements.txt +++ b/requirements.txt @@ -31,9 +31,9 @@ django-autocomplete-light==3.2.10 six==1.10.0 django-suit==0.2.26 telepot==12.3 -pyexcel==0.5.8 +pyexcel==0.5.10 pyexcel-xlsx==0.5.5 django-import-export==0.7.0 openpyxl==2.4.11 django-app-namespace-template-loader==0.4.1 -django-filter==2.0.0 \ No newline at end of file +django-filter==2.0.0 From a2906966baeb7d695242f69df811418fe4ee7419 Mon Sep 17 00:00:00 2001 From: Aarni Halinen Date: Tue, 15 Jan 2019 16:06:04 +0200 Subject: [PATCH 37/40] Use node latest on CI --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f0adf59..5be04db 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,7 +31,7 @@ pycodestyle: - pycodestyle --config=setup.cfg --count . eslint: - image: node:7.10.0 + image: node stage: lint before_script: - npm install @@ -39,7 +39,7 @@ eslint: - npm run eslint remark: - image: node:7.10.0 + image: node stage: lint before_script: - npm install From 474df33a995759c6ce4d7b7786c865e7755e750f Mon Sep 17 00:00:00 2001 From: jaine Date: Tue, 15 Jan 2019 16:42:08 +0200 Subject: [PATCH 38/40] Fix borders in ohlhafv form --- ohlhafv/static/ohlhafv/css/base.css | 1 - 1 file changed, 1 deletion(-) diff --git a/ohlhafv/static/ohlhafv/css/base.css b/ohlhafv/static/ohlhafv/css/base.css index 3f7b72a..25cbcdb 100644 --- a/ohlhafv/static/ohlhafv/css/base.css +++ b/ohlhafv/static/ohlhafv/css/base.css @@ -22,7 +22,6 @@ body { .form-control:focus { border-color: black; - outline-style: solid; outline-color: black; outline-width: 2px; } From 2dfb53bd5e6adc63f6dc847c3ef98460b661b116 Mon Sep 17 00:00:00 2001 From: Aarni Halinen Date: Tue, 15 Jan 2019 20:00:26 +0200 Subject: [PATCH 39/40] Use node:alpine --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5be04db..d074681 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,7 +31,7 @@ pycodestyle: - pycodestyle --config=setup.cfg --count . eslint: - image: node + image: node:alpine stage: lint before_script: - npm install @@ -39,7 +39,7 @@ eslint: - npm run eslint remark: - image: node + image: node:alpine stage: lint before_script: - npm install From a5bca8eab1a50aacc7f07e598a0fe1028cc1cec6 Mon Sep 17 00:00:00 2001 From: Jan Tuomi Date: Thu, 17 Jan 2019 15:48:34 +0200 Subject: [PATCH 40/40] Add JWT verify endpoint --- webapp/urls.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webapp/urls.py b/webapp/urls.py index 4d13adf..f3bfa44 100644 --- a/webapp/urls.py +++ b/webapp/urls.py @@ -2,7 +2,7 @@ from django.conf.urls import url, include from rest_framework import routers -from rest_framework_jwt.views import obtain_jwt_token +from rest_framework_jwt.views import obtain_jwt_token, verify_jwt_token # from rest_framework.urlpatterns import format_suffix_patterns # from django.conf import settings @@ -40,6 +40,7 @@ router.register(r'tags', TagsViewSet) urlpatterns = [ url(r'^api/', include(router.urls)), url(r'^api/api-token-auth/', obtain_jwt_token), + url(r'^api/api-token-verify/', verify_jwt_token), # login stuff # url(r'^login$', login_view),