Commit e87555ba authored by Gladys Forte's avatar Gladys Forte

Merge pull request #62 in RMS/api-main-service from gladys-dev2 to RMSv2

* commit '5f689eb0':
  update 1
  notification v2
  notification
parents 419dd3b8 5f689eb0
# chat/consumers.py
from channels.generic.websocket import AsyncWebsocketConsumer
import json
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'chat_%s' % self.room_name
# Join room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
# Leave room group
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': message
}
)
# Receive message from room group
async def chat_message(self, event):
message = event['message']
# Send message to WebSocket
await self.send(text_data=json.dumps({
'message': message
}))
\ No newline at end of file
# chat/routing.py
from django.conf.urls import url
from app.applicationlayer.management.notification import consumers
websocket_urlpatterns = [
url(r'^ws/chat/(?P<room_name>[^/]+)/$', consumers.ChatConsumer),
]
\ No newline at end of file
from rest_framework import serializers
from app.entities import models
from django.conf import settings
class NotificationSerializer(serializers.ModelSerializer):
class Meta:
model = models.Notification
fields = '__all__'
read_only_fields = ['code']
\ No newline at end of file
from django.conf import settings
from websocket import create_connection
import json
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
REALTIMESERVER_IP = settings.REALTIMESERVER_IP
def send_broadcast_message(room_name, sender, message):
# ws = create_connection(f"ws://{REALTIMESERVER_IP}/ws/realtimeserver/{room_name}/")
ws = create_connection(f"ws://{REALTIMESERVER_IP}/ws/chat/{room_name}/")
data = {
'sender': sender,
'message': message
}
ws.send(json.dumps(data))
ws.close()
class CustomPagination(PageNumberPagination):
page_size = 10
max_page_size = 50
page_query_param = 'page'
page_size_query_param = 'page_size'
def get_paginated_response(self, data):
return Response({
'page_number': self.page.number,
'size_per_page': self.page.paginator.per_page,
'total_pages': self.page.paginator.num_pages,
'total': self.page.paginator.count,
'totalunseen': data['unseen'],
'code': data['code'],
'status': data['status'],
'message': data['message'],
'results': data['results']
})
\ No newline at end of file
from django.shortcuts import render
from django.utils.safestring import mark_safe
import json
from rest_framework import viewsets as meviewsets
from app.applicationlayer.management.notification import serializers
from app.entities import models
from app.applicationlayer import paginators
from datetime import datetime
from rest_framework.response import Response
from rest_framework import status
from django.conf import settings
from app.applicationlayer.management.notification.utils_notif import (
send_broadcast_message,
CustomPagination)
from app.applicationlayer.utils import (status_message_response)
from rest_framework.exceptions import ValidationError
from django.shortcuts import render, redirect, get_object_or_404
from rest_framework.decorators import action
from rest_framework.views import APIView
class NotificationsViewset(meviewsets.ModelViewSet):
lookup_field = 'account_no'
queryset = models.Notification.objects.all()
serializer_class = serializers.NotificationSerializer
pagination_class = CustomPagination
def list(self, request, *args, **kwargs):
try:
req = self.request
account_no = req.query_params.get('account_no')
app = req.query_params.get('app')
if account_no:
queryset = models.Notification.objects.filter(
account_no=account_no).order_by('-created')
queryset = self.filter_queryset(queryset)
unseen = models.Notification.objects.filter(
account_no=account_no, is_read=False).count()
if app:
queryset = models.Notification.objects.filter(
account_no=account_no, app=app).order_by('-created')
queryset = self.filter_queryset(queryset)
unseen = models.Notification.objects.filter(
account_no=account_no, app=app, is_read=False).count()
else:
queryset = models.Notification.objects.all().order_by('-created')
queryset = self.filter_queryset(queryset)
unseen = models.Notification.objects.filter(
is_read=False).count()
if not queryset:
message = status_message_response(
200, 'success', 'No records found', []
)
return Response(message)
serializer = self.get_serializer(queryset, many=True)
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
message = {
'unseen': unseen,
'code': 200,
'status': 'success',
'message': 'List of Notifications found',
'results': serializer.data
}
return self.get_paginated_response(message)
except Exception as e:
message = status_message_response(
500, 'failed',
'Request was not able to process' + str(e), [])
return Response(message,
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# comment
@action(methods=["PATCH"], detail=True)
def seen(self, request, account_no=None, **kwargs):
try:
account_no = self.kwargs['account_no']
print(account_no)
models.Notification.objects.filter(account_no=account_no,
id__in=request.data['ids']).update(
is_read=True,
modified=datetime.now())
message = status_message_response(
200, 'success',
'Status successfully updated',
"None"
)
return Response(message)
except Exception as e:
message = status_message_response(
500, 'failed',
'Request was not able to process' + str(e), []
)
return Response(message,
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@action(methods=["PATCH"], detail=True)
def seenall(self, request, account_no=None, **kwargs):
try:
account_no = self.kwargs['account_no']
print(account_no)
models.Notification.objects.filter(account_no=account_no).update(
is_read=True,
modified=datetime.now())
message = status_message_response(
200, 'success',
'Status successfully updated',
"None"
)
return Response(message)
except Exception as e:
message = status_message_response(
500, 'failed',
'Request was not able to process' + str(e), []
)
return Response(message,
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def create(self, request, *args, **kwargs):
serializer = serializers.NotificationSerializer(data=request.data)
try:
if serializer.is_valid():
serializer.save()
message = {
'code': 201,
'status': 'success',
'message': 'Notification created.',
'results': serializer.data
}
ROOM = serializer.data['account_no']
SENDER = serializer.data['sender_account_no']
send_broadcast_message(
ROOM,
SENDER,
'NEW NOTIFICATIONS'
)
return Response(message, status=status.HTTP_201_CREATED)
except ValidationError as e:
message = {
'code': 400,
'status': 'failed',
'message': str(e),
}
return Response(message, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
message = {
'code': 500,
'status': 'failed',
'message': 'Request was not able to process' + str(e.__class__)
}
return Response(message,
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def index(request):
return render(request, 'chat/index.html', {})
def room(request, room_name):
return render(request, 'chat/room.html', {
'room_name_json': mark_safe(json.dumps(room_name))
})
\ No newline at end of file
...@@ -5,6 +5,8 @@ from app.applicationlayer.management.company.views import CompanyViewSet ...@@ -5,6 +5,8 @@ from app.applicationlayer.management.company.views import CompanyViewSet
from app.applicationlayer.management.department.views import DepartmentViewSet from app.applicationlayer.management.department.views import DepartmentViewSet
from app.applicationlayer.management.module.views import ModuleViewSet from app.applicationlayer.management.module.views import ModuleViewSet
# from app.applicationlayer.management.user.views import UsersManagementViewSet # from app.applicationlayer.management.user.views import UsersManagementViewSet
from app.applicationlayer.management.notification.views import NotificationsViewset
router = routers.DefaultRouter() router = routers.DefaultRouter()
...@@ -13,6 +15,8 @@ router.register(r'companies', CompanyViewSet) ...@@ -13,6 +15,8 @@ router.register(r'companies', CompanyViewSet)
router.register(r'departments', DepartmentViewSet) router.register(r'departments', DepartmentViewSet)
router.register(r'modules', ModuleViewSet) router.register(r'modules', ModuleViewSet)
# router.register(r'users', UsersManagementViewSet) # router.register(r'users', UsersManagementViewSet)
router.register(r'notifications', NotificationsViewset)
urlpatterns = ( urlpatterns = (
path('', include(router.urls)), path('', include(router.urls)),
......
...@@ -29,6 +29,7 @@ class GenerateCode(Enum): ...@@ -29,6 +29,7 @@ class GenerateCode(Enum):
COMPANY = 'COMPANY' COMPANY = 'COMPANY'
DEPARTMENT = 'DEPARTMENT' DEPARTMENT = 'DEPARTMENT'
USER = 'USER' USER = 'USER'
NOTIFICATION = 'NOTIF'
''' '''
...@@ -66,3 +67,14 @@ class LogEntitiesEnum(Enum): ...@@ -66,3 +67,14 @@ class LogEntitiesEnum(Enum):
# STOCK = "Stock" # STOCK = "Stock"
# STOCK_ITEM = "Stock Item" # STOCK_ITEM = "Stock Item"
# REQUISITION = "Requisition Header" # REQUISITION = "Requisition Header"
'''
*********
NOTIFICATION ENUMS
*********
'''
class NotifTypeEnum(Enum):
REMINDER = "REMINDER"
ACTIVITY = "ACTIVITY"
TASK = "TASK"
\ No newline at end of file
# Generated by Django 2.2 on 2019-09-03 16:14
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('entities', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Notification',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('code', models.CharField(max_length=255, unique=True)),
('form_header_code', models.CharField(blank=True, max_length=255, null=True)),
('notif_type', models.CharField(choices=[('REMINDER', 'REMINDER'), ('ACTIVITY', 'ACTIVITY'), ('TASK', 'TASK')], default='TASK', max_length=20)),
('message', models.CharField(blank=True, max_length=255, null=True)),
('is_read', models.BooleanField(default=False, null=True)),
('created', models.DateTimeField(auto_now_add=True)),
('modified', models.DateTimeField(auto_now=True)),
('app_code', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='entities.Application', to_field='code')),
('receiver_account_no', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='receiver_account_no', to=settings.AUTH_USER_MODEL, to_field='code')),
('sender_account_no', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='sender_account_no', to=settings.AUTH_USER_MODEL, to_field='code')),
],
options={
'db_table': 'notifications',
},
),
]
# Generated by Django 2.2 on 2019-09-03 17:25
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('entities', '0002_notification'),
]
operations = [
migrations.RenameField(
model_name='notification',
old_name='receiver_account_no',
new_name='account_no',
),
migrations.RenameField(
model_name='notification',
old_name='app_code',
new_name='app',
),
]
...@@ -323,3 +323,40 @@ class EntityLog(AuditClass): ...@@ -323,3 +323,40 @@ class EntityLog(AuditClass):
class Meta: class Meta:
db_table = 'entity_logs' db_table = 'entity_logs'
"""
**********************
*** NOTIFICATION TABLES ***
**********************
"""
class Notification(models.Model):
code = models.CharField(unique=True, max_length=255) # primary key
form_header_code = models.CharField(max_length=255, null=True, blank=True)
# form_header_code = models.ForeignKey(ChangeRequestFormHeader, on_delete=models.DO_NOTHING,
# to_field='code')
app = models.ForeignKey(Application, on_delete=models.DO_NOTHING,
to_field='code')
notif_type = models.CharField(
choices=[(tag.value, tag.value) for tag in enums.NotifTypeEnum],
default=enums.NotifTypeEnum.TASK.value,
max_length=20
)
account_no = models.ForeignKey(User, on_delete=models.DO_NOTHING,
to_field='code',
related_name='receiver_account_no')
message = models.CharField(max_length=255, null=True, blank=True)
is_read = models.BooleanField(default=False, null=True)
sender_account_no = models.ForeignKey(User, on_delete=models.DO_NOTHING,
to_field='code',
related_name='sender_account_no')
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
class Meta:
db_table = 'notifications'
def save(self, *args, **kwargs):
super(Notification, self).save(*args, **kwargs)
code = number_generator(enums.GenerateCode.NOTIFICATION.value, self.id)
Notification.objects.filter(id=self.id).update(code=code)
\ No newline at end of file
<!-- chat/templates/chat/index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Chat Rooms</title>
</head>
<body>
What chat room would you like to enter?<br/>
<input id="room-name-input" type="text" size="100"/><br/>
<input id="room-name-submit" type="button" value="Enter"/>
<script>
document.querySelector('#room-name-input').focus();
document.querySelector('#room-name-input').onkeyup = function(e) {
if (e.keyCode === 13) { // enter, return
document.querySelector('#room-name-submit').click();
}
};
document.querySelector('#room-name-submit').onclick = function(e) {
var roomName = document.querySelector('#room-name-input').value;
window.location.pathname = '/chat/' + roomName + '/';
};
</script>
</body>
</html>
\ No newline at end of file
<!-- chat/templates/chat/room.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Chat Room</title>
</head>
<body>
<textarea id="chat-log" cols="100" rows="20"></textarea><br/>
<input id="chat-message-input" type="text" size="100"/><br/>
<input id="chat-message-submit" type="button" value="Send"/>
</body>
<script>
var roomName = {{ room_name_json }};
var chatSocket = new WebSocket(
'ws://' + window.location.host +
'/ws/chat/' + roomName + '/');
chatSocket.onmessage = function(e) {
var data = JSON.parse(e.data);
var message = data['message'];
document.querySelector('#chat-log').value += (message + '\n');
};
chatSocket.onclose = function(e) {
console.error('Chat socket closed unexpectedly');
};
document.querySelector('#chat-message-input').focus();
document.querySelector('#chat-message-input').onkeyup = function(e) {
if (e.keyCode === 13) { // enter, return
document.querySelector('#chat-message-submit').click();
}
};
document.querySelector('#chat-message-submit').onclick = function(e) {
var messageInputDom = document.querySelector('#chat-message-input');
var message = messageInputDom.value;
chatSocket.send(JSON.stringify({
'message': message
}));
messageInputDom.value = '';
};
</script>
</html>
\ No newline at end of file
# mysite/routing.py
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
import app.applicationlayer.management.notification.routing
application = ProtocolTypeRouter({
# (http->django views is added by default)
'websocket': AuthMiddlewareStack(
URLRouter(
app.applicationlayer.management.notification.routing.websocket_urlpatterns
)
),
})
\ No newline at end of file
...@@ -44,7 +44,9 @@ INSTALLED_APPS = [ ...@@ -44,7 +44,9 @@ INSTALLED_APPS = [
'rest_framework.authtoken', 'rest_framework.authtoken',
'app.accesslayer', 'app.accesslayer',
'app.entities' 'app.entities',
'channels',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
...@@ -80,6 +82,17 @@ TEMPLATES = [ ...@@ -80,6 +82,17 @@ TEMPLATES = [
WSGI_APPLICATION = 'config.wsgi.application' WSGI_APPLICATION = 'config.wsgi.application'
ASGI_APPLICATION = "config.routing.application"
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [('127.0.0.1', 6379)],
},
},
}
AUTH_USER_MODEL = 'entities.User' AUTH_USER_MODEL = 'entities.User'
# Password validation # Password validation
......
...@@ -27,3 +27,4 @@ SESSION_TIMEOUT = config['DEV']['SESSION_TIMEOUT'] ...@@ -27,3 +27,4 @@ SESSION_TIMEOUT = config['DEV']['SESSION_TIMEOUT']
FRONT_END_URL = config['DEV']['FRONT_END_URL'] FRONT_END_URL = config['DEV']['FRONT_END_URL']
AUTH_ACCESSS_TOKEN_TIMEOUT = config['DEV']['AUTH_ACCESSS_TOKEN_TIMEOUT'] AUTH_ACCESSS_TOKEN_TIMEOUT = config['DEV']['AUTH_ACCESSS_TOKEN_TIMEOUT']
USER_DEFAULT_PASSWORD = config['DEV']['USER_DEFAULT_PASSWORD'] USER_DEFAULT_PASSWORD = config['DEV']['USER_DEFAULT_PASSWORD']
REALTIMESERVER_IP = config['NOTIFICATION']['REALTIMESERVER_IP']
\ No newline at end of file
...@@ -25,3 +25,4 @@ SESSION_TIMEOUT = config['LOCAL']['SESSION_TIMEOUT'] ...@@ -25,3 +25,4 @@ SESSION_TIMEOUT = config['LOCAL']['SESSION_TIMEOUT']
FRONT_END_URL = config['LOCAL']['FRONT_END_URL'] FRONT_END_URL = config['LOCAL']['FRONT_END_URL']
AUTH_ACCESSS_TOKEN_TIMEOUT = config['LOCAL']['AUTH_ACCESSS_TOKEN_TIMEOUT'] AUTH_ACCESSS_TOKEN_TIMEOUT = config['LOCAL']['AUTH_ACCESSS_TOKEN_TIMEOUT']
USER_DEFAULT_PASSWORD = config['LOCAL']['USER_DEFAULT_PASSWORD'] USER_DEFAULT_PASSWORD = config['LOCAL']['USER_DEFAULT_PASSWORD']
REALTIMESERVER_IP = config['NOTIFICATION']['REALTIMESERVER_IP']
\ No newline at end of file
...@@ -27,3 +27,4 @@ SESSION_TIMEOUT = config['PRODUCTION']['SESSION_TIMEOUT'] ...@@ -27,3 +27,4 @@ SESSION_TIMEOUT = config['PRODUCTION']['SESSION_TIMEOUT']
FRONT_END_URL = config['PRODUCTION']['FRONT_END_URL'] FRONT_END_URL = config['PRODUCTION']['FRONT_END_URL']
AUTH_ACCESSS_TOKEN_TIMEOUT = config['PRODUCTION']['AUTH_ACCESSS_TOKEN_TIMEOUT'] AUTH_ACCESSS_TOKEN_TIMEOUT = config['PRODUCTION']['AUTH_ACCESSS_TOKEN_TIMEOUT']
USER_DEFAULT_PASSWORD = config['PRODUCTION']['USER_DEFAULT_PASSWORD'] USER_DEFAULT_PASSWORD = config['PRODUCTION']['USER_DEFAULT_PASSWORD']
REALTIMESERVER_IP = config['NOTIFICATION']['REALTIMESERVER_IP']
\ No newline at end of file
...@@ -27,3 +27,4 @@ SESSION_TIMEOUT = config['UAT']['SESSION_TIMEOUT'] ...@@ -27,3 +27,4 @@ SESSION_TIMEOUT = config['UAT']['SESSION_TIMEOUT']
FRONT_END_URL = config['UAT']['FRONT_END_URL'] FRONT_END_URL = config['UAT']['FRONT_END_URL']
AUTH_ACCESSS_TOKEN_TIMEOUT = config['UAT']['AUTH_ACCESSS_TOKEN_TIMEOUT'] AUTH_ACCESSS_TOKEN_TIMEOUT = config['UAT']['AUTH_ACCESSS_TOKEN_TIMEOUT']
USER_DEFAULT_PASSWORD = config['UAT']['USER_DEFAULT_PASSWORD'] USER_DEFAULT_PASSWORD = config['UAT']['USER_DEFAULT_PASSWORD']
REALTIMESERVER_IP = config['NOTIFICATION']['REALTIMESERVER_IP']
\ No newline at end of file
...@@ -15,6 +15,9 @@ Including another URLconf ...@@ -15,6 +15,9 @@ Including another URLconf
""" """
from django.contrib import admin from django.contrib import admin
from django.urls import path, include from django.urls import path, include
from django.conf.urls import url
from app.applicationlayer.management.notification import views as notifview
urlpatterns = [ urlpatterns = [
# path('admin/', admin.site.urls), # path('admin/', admin.site.urls),
...@@ -22,4 +25,7 @@ urlpatterns = [ ...@@ -22,4 +25,7 @@ urlpatterns = [
path('api/v1/auth/', include('app.accesslayer.urls')), path('api/v1/auth/', include('app.accesslayer.urls')),
path('api/v1/', include('app.applicationlayer.urls')), path('api/v1/', include('app.applicationlayer.urls')),
url(r'^chat/$', notifview.index, name='index'),
url(r'^chat/(?P<room_name>[^/]+)/$', notifview.room, name='room'),
] ]
...@@ -46,5 +46,8 @@ FRONT_END_URL = ...@@ -46,5 +46,8 @@ FRONT_END_URL =
AUTH_ACCESSS_TOKEN_TIMEOUT = 3600 AUTH_ACCESSS_TOKEN_TIMEOUT = 3600
USER_DEFAULT_PASSWORD = password USER_DEFAULT_PASSWORD = password
[NOTIFICATION]
REALTIMESERVER_IP = 127.0.0.1:8000
[SETTINGS] [SETTINGS]
CONFIG = config.settings.local CONFIG = config.settings.local
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment