From 83f3981648e6be555647f84b019afe6bb38e3a3f Mon Sep 17 00:00:00 2001 From: Holger Trampe Date: Sun, 17 May 2020 23:25:27 +0200 Subject: [PATCH] API und REST und WEBSOCKETS --- .gitignore | 4 ++ api/__init__.py | 0 api/admin.py | 3 + api/apps.py | 5 ++ api/migrations/__init__.py | 0 api/models.py | 3 + api/serializers.py | 19 ++++++ api/tests.py | 3 + api/urls.py | 11 ++++ api/views.py | 52 +++++++++++++++++ .../__pycache__/settings.cpython-38.pyc | Bin 4069 -> 4454 bytes .../__pycache__/urls.cpython-38.pyc | Bin 2287 -> 2458 bytes .../__pycache__/wsgi.cpython-38.pyc | Bin 583 -> 583 bytes digitaleagentur/routing.py | 12 ++++ digitaleagentur/settings.py | 55 ++++++++++++------ digitaleagentur/urls.py | 5 +- users/mainwebsocket.py | 47 +++++++++++++++ users/routing.py | 7 +++ users/signals.py | 22 ++++++- users/templates/users/base.html | 45 ++++++++------ users/templates/users/dashboard.html | 4 +- users/urls.py | 3 +- users/views.py | 4 +- 23 files changed, 259 insertions(+), 45 deletions(-) create mode 100644 api/__init__.py create mode 100644 api/admin.py create mode 100644 api/apps.py create mode 100644 api/migrations/__init__.py create mode 100644 api/models.py create mode 100644 api/serializers.py create mode 100644 api/tests.py create mode 100644 api/urls.py create mode 100644 api/views.py create mode 100644 digitaleagentur/routing.py create mode 100644 users/mainwebsocket.py create mode 100644 users/routing.py diff --git a/.gitignore b/.gitignore index f46a329..a70c028 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,10 @@ standards/migrations/* !standards/migrations/__init__.py standards/__pycache__/* +api/migrations/* +!api/migrations/__init__.py +api/__pycache__/* + tasks/migrations/* !tasks/migrations/__init__.py tasks/__pycache__/* diff --git a/api/__init__.py b/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/admin.py b/api/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/api/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/api/apps.py b/api/apps.py new file mode 100644 index 0000000..d87006d --- /dev/null +++ b/api/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ApiConfig(AppConfig): + name = 'api' diff --git a/api/migrations/__init__.py b/api/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/models.py b/api/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/api/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/api/serializers.py b/api/serializers.py new file mode 100644 index 0000000..22c12d9 --- /dev/null +++ b/api/serializers.py @@ -0,0 +1,19 @@ +from django.contrib.auth.models import User +from rest_framework import serializers +from rest_framework.validators import UniqueTogetherValidator +from standards.models import Standards + +class StandardsSerializer(serializers.ModelSerializer): + + username = serializers.SerializerMethodField('getCreatedByUser') + last_modified_on = serializers.SerializerMethodField('getFormatedLastModified') + + class Meta: + model = Standards + fields = ["id", "name", "last_modified_on", "username", "content"] + + def getCreatedByUser(self, standard): + return standard.created_standard_by.first_name + " " + standard.created_standard_by.first_name + + def getFormatedLastModified(self, standard): + return standard.last_modified_on.strftime("%d.%m.%Y %H:%M") \ No newline at end of file diff --git a/api/tests.py b/api/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/api/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/api/urls.py b/api/urls.py new file mode 100644 index 0000000..e95f90b --- /dev/null +++ b/api/urls.py @@ -0,0 +1,11 @@ +from django.urls import path +from .views import HelloView +from . import views + +app_name = 'api' +urlpatterns = [ + path('helloview/', HelloView.as_view(), name='api-helloview'), + path('getstandards/', views.getStandardList, name='api-getstandards'), + path('getsinglestandard/', views.getSingleStandard, name='api-getsinglestandards'), + +] \ No newline at end of file diff --git a/api/views.py b/api/views.py new file mode 100644 index 0000000..1b9af62 --- /dev/null +++ b/api/views.py @@ -0,0 +1,52 @@ +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework.permissions import IsAuthenticated # <-- Here +import json +from standards.models import Standards +from rest_framework import serializers +from .serializers import StandardsSerializer +from rest_framework.decorators import api_view, permission_classes +from rest_framework import status + +class HelloView(APIView): + permission_classes = (IsAuthenticated,) # <-- And here + + def post(self, request): + content = {'message': 'Hello, World!'} + return Response(content) + +''' +class GiveMeStandards(APIView): + + + def post(self, request): + standards = Standards.objects.filter(agency=request.user.profile.agency) + + content = {} + i = 0 + for s in standards: + content.update({i : s.name.encode("utf-8")}) + i += 1 + + content = {'standards': content} + return Response(content) + + + def post(self, request): + ser = StandardsSerializer(data=Standards.objects.filter(agency=self.request.user.profile.agency)) + return Response(ser) +''' + +@api_view(['POST', ]) +@permission_classes((IsAuthenticated,)) +def getStandardList(request): + standards = Standards.objects.filter(agency=request.user.profile.agency) + ser = StandardsSerializer(standards, many=True) + return Response(ser.data, status=status.HTTP_200_OK) + +@api_view(['POST', ]) +@permission_classes((IsAuthenticated,)) +def getSingleStandard(request, pk): + standard = Standards.objects.get(pk=int(pk)) + ser = StandardsSerializer(standard, many=False) + return Response(ser.data, status=status.HTTP_200_OK) \ No newline at end of file diff --git a/digitaleagentur/__pycache__/settings.cpython-38.pyc b/digitaleagentur/__pycache__/settings.cpython-38.pyc index 65edeee22883657efc73fbabf61d70b2cda50353..3e83899766a3eb34b54de1cc2195ff058e27420b 100644 GIT binary patch delta 1104 zcmZuv&u<$=6y6z+9shQcrcRtTZPU<#W7s%OXhKRM8)uU^aUA2dTl%0~7QgL!sqMAa zYgM(7a^Z}q_ShVV#07Cd@};Vf5PtyT-fIOHE}Q{1jEyNRVn*-Hy!XCu=6kdI^Z8#c zrYez0P~c@L0}qlxN8vHJECv~^KceNa32f$ zxIsAP;D~aCGg!p;cvQsFz$*h^HV=ux;2c)qJYI(jcmrm63m3V~;rs9k-h@jCa2Y>< zSMfu5O(&nwFFgaF)V;^#nBc8R0p9>Rdd+G(YpdyW+D&+_ld}8Rv+RGu zY|C<<2(F){a@~M&zftQnMAXeaDhS<3&8i!^EjqT=XzAV1zU4T2CcSt?o$XG(R9k<} zOw!*c+%G&6Uh~%}L2#Ft?6vzpg!?($2>2J%Vp`0Inch$AQ@R9ETW>q+9e%ui&$91! zr(TXlt>e@j>#p7ef3>DHn|i&sJMtqvOp@;6|3LMoV^%fCw3=eM&yqO(-&}Y8Ka`ek zH_UdM!`ou6{Q_9WB!zRE@|IM|msP1!-jNICTvjUQ4hm{EFC8k1tibd?fTCR5&ne2V z*spcx&@@a(tLvJ<1$1oDwmOd4G(@e{st+}GFAaNYtG142TdZ2PE|&V+?BC7iwfnja z-u{PcC#fCg&j`NkLBYF~+qQkZtHH~JY`I$Kd$R1#hA|k}l*-bE#09vYg|!d_O7dn- zQ7fgq`&+n^foMrq%Ia20+Lvz}l=fg$QnqudR4nF)%|SG~BNYmAUd>B4<&x677r8(p zQGV~&g1y2Qd&T`KdTaF=-?EV3bJoKMpOpD1c|QMOCoD9`_iKcC*a$ZtqfzEcv7}p! kje4H4S+@~OCB`VDC-hHWkWtR3RA49c8Ry5`hq2wi0Fxa(H~;_u delta 739 zcmZuvOHWfl6uyt)QEpoblmewZ1QoDSBn1Q%Y|EQzRyXn-4yyDxnYRX4fdM<4nzfU6k95Qf!qs>3y^$8}YJ1tS>6 z4FnKWnIJeZ~?%uq9*w1r12O6eSCsSP)& z9dmRZ^R#v$Dc+a%no6ALKhu=69ZyXQ%cu!h2fqaXsV_s(#h_Gn#|~1dEa_=!yJ{D zW+n}LVMEXP3VJ@FXMIR&Gm;?04NoSUPv+7)fwNAMvu|+SpBY$DRSIoIcVa1hbAG{m zEcdyoK0K5%?io9wO?`1JL+Qx&Z0}|)Z=}o*ve#T?6E4GfmZtffg$}>72h!oQviDM# z(R#Y+bmHxTs`(PRTv}UC=vp?DXZ&9nVe_rhX+Bkcm`|*4#9nj72J@Zmu5x4<55|L` zU~D30`t4iQM~>y#RAem}jm}2Kg7L`w+~JYERkSHwJ;rb(M}NeD?wftG72cPJ%*Dul>_$n*>lr-_T>P$@#n zB5O;4cYO;~2_$wr1yZ+2z>XI{g5TC4fvvI6_x&^DnUR0S_tf%3MH!QDy$gMQ@*CAx z7yiAnfHkz<^4oHIND_s&Cp&6GWfJ$%7!T@GdVDkxd~>h-@011g8Y2#n_DC8E_W)tj9wiNyy+hAZHRTcLmRg24Y$7M%x@XHtsgXxL_eM6Cc;3tA!38&4K}PpCrP2duReqf)v4gnr)Y4kr zrq;J!G%cgeyZfKyUyLdHAN}9hqk&)BY1IZHJga?kyLaG2g<;{Y*5SP!ZHo^kU@G3o ziaSYd5dB}Ew?f8Nzr(v$(=-gLURrS82liOZ*$5U3jhxAQR`Z#O33m;%U3Dk%rmMbc t@!g&oab5<$1d8|>&7xBeJ&v!VjIbDscsJh8htQ(piXU=1`rV%iu|HL^#J>Ol delta 571 zcmYk3IZFdU7=|;OV6NRwG%=goD<125#53OaUD$|)0k!ZOg%u6N*a<08FDTpjH!N-Z z0hay)LD0rR!N$Tj>j=&=@IB8v%gi@(8+mUub`>Sy!pJ||AIzhAuEYP41T0`)s5i!G zmAlE$y=Idcp#TLfMlvau8&>5W3e^~e1vheLM$P7$gscf!1X)yM&EObV6C4-+CIlzJ zDdcIv8E_WdLe<~RS|QpX+Qotna1PulxC`73?h&kmd%=Gj7~no|zsLvBQVcB(Ql5q^ ziH3zAf!E+iomR(4sXD!mL&u>Dj-C*UCt(w?Q;wY$c804oE9yDkU?yo^&;kyzSYyr& zEumh(ymDABl`7kMn?2;cWg+{~+hZquPYpRdbU9~#`1V38 XC}kFAvh#)QeShBnCTZ%cC+=fE-*SqV diff --git a/digitaleagentur/__pycache__/wsgi.cpython-38.pyc b/digitaleagentur/__pycache__/wsgi.cpython-38.pyc index eb6019fec0bf24fcad132a050838bc0b1281310c..f44f0f3bd2c43ca6ce074483c2af70c1cb10999c 100644 GIT binary patch delta 20 acmX@ka-4-bl$V!_0SE-^4sPVOVFCa!KLlj} delta 20 acmX@ka-4-bl$V!_0SF8Z?B2+2!vp{_h6KO> diff --git a/digitaleagentur/routing.py b/digitaleagentur/routing.py new file mode 100644 index 0000000..f67ce3e --- /dev/null +++ b/digitaleagentur/routing.py @@ -0,0 +1,12 @@ +from channels.auth import AuthMiddlewareStack +from channels.routing import ProtocolTypeRouter, URLRouter +import users.routing + +application = ProtocolTypeRouter({ + # Empty for now (http->django views is added by default) + 'websocket': AuthMiddlewareStack( + URLRouter( + users.routing.websocket_urlpatterns + ) + ), +}) \ No newline at end of file diff --git a/digitaleagentur/settings.py b/digitaleagentur/settings.py index 6403933..6ad9545 100644 --- a/digitaleagentur/settings.py +++ b/digitaleagentur/settings.py @@ -14,32 +14,35 @@ import os # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -############################################## PROD ##################################### -BASE_URL = "https://digitale-agentur.com/" +############################################## DEV ##################################### +BASE_URL = "https://dev01.digitale-agentur.com/" CRONAPIKEY = "gCddsaz6NOnE9QbXZM5LasdEk122D" MAILINFOKEY = "jka7sd8iukashdna78skduJAHDsu6dilaksdjba65a68iadbhjak" # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = False +DEBUG = True -# MAIL PROD +# MAIL DEV EMAIL_HOST = 'smtp.strato.de' EMAIL_PORT = 587 EMAIL_USE_TLS = True -EMAIL_HOST_USER = "noreply@digitale-agentur.com" -EMAIL_HOST_PASSWORD = "48c3n6YggZBuPyShtqOQ" -DEFAULT_FROM_EMAIL = "noreply@digitale-agentur.com" +EMAIL_HOST_USER = "support@dev01.digitale-agentur.com" +EMAIL_HOST_PASSWORD = "n2xd7emyKZFb6UREzvbintuUIG" +DEFAULT_FROM_EMAIL = "support@dev01.digitale-agentur.com" -# PROD +# DEV DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', - 'NAME' : 'digitaleagentur', - 'USER' : 'digitaleagentur', - 'PASSWORD' : 'H9hzbzyBqtUCnZlIwL1qSrzh', + 'NAME' : 'digitaleagentur_dev01', + 'USER' : 'digitaleagentur_dev01', + 'PASSWORD' : 't3TvtGAOkFHYXdJlUMIu9u3U', 'PORT' : 3306 } } -############################################## PROD ##################################### + +# REDIS +REDIS_URL = ("localhost", 6379) +############################################## DEV ##################################### @@ -55,7 +58,7 @@ X_FRAME_OPTIONS = 'SAMEORIGIN' # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = '_qv2t2lmsctjxpbb4rrp=op%_20_hxzonv^mvty1o85c)l$s^q' -ALLOWED_HOSTS = ['digitale-agentur.com', 'www.digitale-agentur.com', 'localhost', 'dev01.digitale-agentur.com'] +ALLOWED_HOSTS = ['digitale-agentur.com', 'www.digitale-agentur.com', 'localhost', 'dev01.digitale-agentur.com', '10.0.2.2'] # Application definition INSTALLED_APPS = [ @@ -72,7 +75,7 @@ INSTALLED_APPS = [ 'timemanagement.apps.TimemanagementConfig', 'news.apps.NewsConfig', 'crispy_forms', - 'colorful', + 'colorful', 'django_summernote', 'django.contrib.admin', 'mathfilters', @@ -85,6 +88,9 @@ INSTALLED_APPS = [ 'bootstrap_datepicker_plus', 'django_cleanup', 'django_user_agents', + 'rest_framework', + 'rest_framework.authtoken', + 'channels' ] MIDDLEWARE = [ @@ -116,10 +122,25 @@ TEMPLATES = [ }, ] -WSGI_APPLICATION = 'digitaleagentur.wsgi.application' - - +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework.authentication.TokenAuthentication', + ), + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.IsAuthenticated', + ], +} +#WSGI_APPLICATION = 'digitaleagentur.wsgi.application' +ASGI_APPLICATION = "digitaleagentur.routing.application" +CHANNEL_LAYERS = { + 'default': { + 'BACKEND': 'channels_redis.core.RedisChannelLayer', + 'CONFIG': { + "hosts": [REDIS_URL], + }, + }, +} # Password validation diff --git a/digitaleagentur/urls.py b/digitaleagentur/urls.py index 764d28c..4ed0e56 100644 --- a/digitaleagentur/urls.py +++ b/digitaleagentur/urls.py @@ -6,6 +6,7 @@ from django.conf.urls.static import static from users.views import AgencyCreateView, registerNewAgency from . import views from django.contrib.auth.decorators import login_required +from rest_framework.authtoken.views import obtain_auth_token ''' Main URLS @@ -43,7 +44,9 @@ urlpatterns = [ path('register/done', views.registerdone, name='register-done'), path('summernote/', include('django_summernote.urls')), path('notifications/', include('notificsys.urls'), name="notifications"), - path('tm/', include('timemanagement.urls'), name="timemanagement") + path('tm/', include('timemanagement.urls'), name="timemanagement"), + path('api/', include('api.urls', namespace='api')), + path('api-token-auth/', obtain_auth_token, name='api-token-auth'), ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file diff --git a/users/mainwebsocket.py b/users/mainwebsocket.py new file mode 100644 index 0000000..ee33275 --- /dev/null +++ b/users/mainwebsocket.py @@ -0,0 +1,47 @@ +import json +from channels.generic.websocket import WebsocketConsumer +from asgiref.sync import async_to_sync + +class UsersConsumer(WebsocketConsumer): + + ''' + + CONNECT A WEBSOCKET + + Die Clients werden in Channel-Layer pro Agentur gepackt, damit gesendete Websocket-Nachrichten + auch nur Clients innerhalb der Agentur treffen! + + ''' + + def connect(self): + loggeduser = self.scope["user"] + async_to_sync(self.channel_layer.group_add)( + "agency_" + str(loggeduser.profile.agency.pk), + self.channel_name + ) + self.accept() + + def disconnect(self, close_code): + pass + + ''' + def receive(self, text_data): + loggeduser = self.scope["user"] + async_to_sync(self.channel_layer.group_send)( + 'allusers', + { + 'type': 'chat_message', + 'message': 'von mainwebsocket.py' + } + ) + ''' + # WEBSOCKET-DATA-CONTENT + + # UPDATET STANDARD + def update_standard(self, event): + self.send("standard_update") + + # NEW AGENCY NEWS + def agency_newnews(self, event): + print(event) + self.send("agency_newnews") \ No newline at end of file diff --git a/users/routing.py b/users/routing.py new file mode 100644 index 0000000..b4a7cc9 --- /dev/null +++ b/users/routing.py @@ -0,0 +1,7 @@ +from django.urls import re_path + +from . import mainwebsocket + +websocket_urlpatterns = [ + re_path(r'', mainwebsocket.UsersConsumer), +] \ No newline at end of file diff --git a/users/signals.py b/users/signals.py index 9c7a33a..1077735 100644 --- a/users/signals.py +++ b/users/signals.py @@ -82,6 +82,9 @@ def checkDefaultAbsenceReasons(sender, user, request, **kwargs): new_ar_school = AbsenceReason(agency=user.profile.agency, name="Berufsschule", need_confirm=False, need_rep=False, is_holiday=False) new_ar_school.save() + + + @receiver(signal=user_logged_in, sender=User) def checkAllFreeDaysLoaded(sender, user, request, **kwargs): pass @@ -189,11 +192,14 @@ def adjust_group_notifications(instance, action, reverse, model, pk_set, using, ) +import channels.layers +from asgiref.sync import async_to_sync + # SIGNAL FOR STANDARDS POST SAVE @receiver(post_save, sender=Standards) def save_standard(sender, instance, **kwargs): - + print(kwargs) GLOBALSENDMAILS = True # NEW STANDARD AND DIRECT PUBLIC if(kwargs["created"]): @@ -229,7 +235,9 @@ def save_standard(sender, instance, **kwargs): if(user.has_perm("users.standardmanager")): newnotification = UserNotification(touser=user, notificationtext="Neuer unveröffentlichter Agenturstandard: " + instance.name, notificationtype="newstandard", elementid=instance.pk) newnotification.save() - + else: + channel_layer = channels.layers.get_channel_layer() + async_to_sync(channel_layer.group_send)("agency_" + str(instance.agency.pk), {'type' : 'update_standard'}) # SIGNAL FOR NEWS @@ -237,6 +245,11 @@ def save_standard(sender, instance, **kwargs): def save_news(sender, instance, **kwargs): GLOBALSENDMAILS = True if(kwargs["created"]): + + # Hier wird allen verbundenne Agenturmitgliedern die Info geschickt, dass es neue Agenturnews gibt + channel_layer = channels.layers.get_channel_layer() + async_to_sync(channel_layer.group_send)("agency_" + str(instance.agency.pk), {'type' : 'agency_newnews'}) + usersofagency = User.objects.filter(profile__agency__pk=instance.agency.pk) targeturl = settings.BASE_URL + "news/news/" + str(instance.pk) + "/single" @@ -265,9 +278,12 @@ def save_news(sender, instance, **kwargs): if(user.profile.news_push): newnotification = UserNotification(touser=user, notificationtext="Neue Agenturnews: " + instance.name, notificationtype="agencynews", elementid=instance.pk) newnotification.save() + + else: instance.agnotify = False - instance.save() + instance.save() + # SIGNALS FOR TASK diff --git a/users/templates/users/base.html b/users/templates/users/base.html index 1a14ad3..a41524e 100644 --- a/users/templates/users/base.html +++ b/users/templates/users/base.html @@ -14,7 +14,7 @@ - + @@ -40,23 +40,23 @@ - + - - + + - + - + - - - + + + @@ -399,15 +399,15 @@ - + - + - - + + - + @@ -423,7 +423,7 @@ - + @@ -573,8 +573,6 @@ function loadUnsendNotifications(){ $("#notificationcounter").html(""); if(i > 0){$("#notificationcounter").html(i);} - - } }); } @@ -633,9 +631,22 @@ $(window).click(function() { loadUnsendNotifications(); loadUnviewnNotifications(); }); + + + \ No newline at end of file diff --git a/users/templates/users/dashboard.html b/users/templates/users/dashboard.html index 6df32a9..85e0732 100644 --- a/users/templates/users/dashboard.html +++ b/users/templates/users/dashboard.html @@ -5,8 +5,7 @@ Letzter Login: {{ request.user.last_login }}
Agentur: {{ request.user.profile.agency.name }}
-
- +
{% if request.user.profile.agency.module_news %}
@@ -142,7 +141,6 @@ $(document).ready(function(){
- {% endblock content %} diff --git a/users/urls.py b/users/urls.py index 17a3bf6..7e83199 100644 --- a/users/urls.py +++ b/users/urls.py @@ -35,8 +35,7 @@ urlpatterns = [ path('impressum/', views.impressum, name="impressumda"), path('setuserparent/', views.setuserparent, name="users-setuserparent"), path('sendpassmail/', views.sendpassmail, name="users-sendpassmail"), - path('dacron/', views.cronactions, name="cronmain"), - + path('dacron/', views.cronactions, name="cronmain") ] diff --git a/users/views.py b/users/views.py index 737593a..67d4408 100644 --- a/users/views.py +++ b/users/views.py @@ -854,5 +854,5 @@ def cronactions(request, code): data.update({"status" : "failed"}) return JsonResponse(data) - - +def index(request): + return render(request, 'users/websocket.html', {}) \ No newline at end of file