I am trying to open a websocket connection from my angular 2+ app with my Django backend using Django Channels.
I went through this tutorial: https://www.youtube.com/watch?v=RVH05S1qab8
and managed to get everything working with the javascript portion written inline in a Django html template. But I am having issues simply opening a websocket connection when I migrated the front-end chat form to a separate angular 2 app.
Both font-end and backends are hosted locally.
Front End - Chat Form Template
<div *ngFor="let message of thread.messages">
<div>{{message.message}}</div>
</div>
<form #formData [formGroup]="form">
<div class="form-group">
<input formControlName="messageInput" #msgInput type="text" class="form-control" id="newMessage" placeholder="Type a message">
</div>
<button (click)="onSubmit($event)" type="submit" class="btn btn-primary">Send</button>
</form>
</div>
Front End - Chat Form Component
otherUser: User;
me: User;
threadId: number;
thread: Thread;
endpoint: string;
socket: WebSocket;
form = new FormGroup({
messageInput: new FormControl("")
});
get messageInput() {
return this.form.get('messageInput');
}
getThreadById(id: number) {
this._chatService.getThreadById(id).subscribe(thread => {
console.log("response: thread found", thread);
this.thread = thread;
},
error => {
console.log("error", error);
});
}
getEndpoint() {
let loc = window.location
let wsStart = "ws://"
if (loc.protocol == "https:") {
wsStart = 'wss://'
}
this.endpoint = wsStart + loc.host + loc.pathname;
return this.endpoint;
}
constructor(private _activatedRoute: ActivatedRoute) {
}
ngOnInit() {
this._activatedRoute.params.subscribe(params => { this.threadId = params['id']; });
if (this.threadId) {
this.getThreadById(this.threadId);
}
this.getEndpoint();
this.createSocket();
}
createSocket() {
this.socket = new WebSocket(this.endpoint);
console.log("endpoint ", this.endpoint);
this.socket.onopen = (e) => {
console.log("open", e);
}
this.socket.onmessage = (e) => {
console.log("message", e);
let chatDataMsg = JSON.parse(e.data);
this.thread.messages.push(chatDataMsg.message);
}
this.socket.onerror = (e) => {
console.log("error", e);
}
this.socket.onclose = (e) => {
console.log("close", e);
}
}
onSubmit($event) {
let msgText = this.messageInput.value;
let finalData = {
'message': msgText
};
let chatMessage: ChatMessage = new ChatMessage(this.thread, this.me, new Date(), msgText);
this.socket.send(JSON.stringify(finalData))
this.form.reset();
}
Django Backend Project Settings
ASGI_APPLICATION = "joole.routing.application"
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("localhost", 6379)],
#"hosts": [os.environ.get('REDIS_URL', 'redis://localhost:6379')]
},
},
}
ALLOWED_HOSTS = ['joole-api.herokuapp.com', '.herokuapp.com', '127.0.0.1']
Django Websocket Routing
from channels.routing import ProtocolTypeRouter, URLRouter
from django.conf.urls import url
from channels.auth import AuthMiddlewareStack
from channels.security.websocket import AllowedHostsOriginValidator, OriginValidator
from chat.consumers import ChatConsumer
application = ProtocolTypeRouter({
'websocket': AllowedHostsOriginValidator(
AuthMiddlewareStack(
URLRouter(
[
url(r"^messages/(?P<id>[\w.#+-]+)/$", ChatConsumer)
]
)
)
)
})
Django Project Urls
from django.urls import path, include
from django.conf import settings
urlpatterns = [
path('messages/', include('chat.urls')),
]
Django WebSocket Consumer
import asyncio
import json
from users.models import CustomUser, Employee
from channels.consumer import AsyncConsumer
from channels.db import database_sync_to_async
from .models import Thread, ChatMessage
class ChatConsumer(AsyncConsumer):
async def websocket_connect(self, event):
print("CONNECTED", event)
thread_id = self.scope['url_route']['kwargs']['id']
thread_obj = await self.get_thread(thread_id)
self.thread_obj = thread_obj
chat_room = f"thread_{thread_obj.id}"
self.chat_room = chat_room
await self.channel_layer.group_add(
chat_room,
self.channel_name
)
await self.send({
"type": 'websocket.accept'
})
async def websocket_receive(self, event):
print("receive", event)
front_text = event.get('text', None)
if front_text is not None:
loaded_dict_data = json.loads(front_text)
msg = loaded_dict_data.get('message')
user = self.scope['user']
username = 'default'
if user.is_authenticated:
username = user.email
myResponse = {
'message': msg,
'username': user.email
}
await self.create_chat_message(user, msg)
# broadcasts the message event to be sent
await self.channel_layer.group_send(
self.chat_room,
{
"type": "chat_message",
"text": json.dumps(myResponse)
}
)
async def chat_message(self, event):
# send the actual message event
print("message", event)
await self.send({
"type": "websocket.send",
"text": event["text"]
})
async def websocket_disconnect(self, event):
print("disconnected", event)
#database_sync_to_async
def get_thread(self, id):
return Thread.objects.get(id=id)
#database_sync_to_async
def create_chat_message(self, me, msg):
thread_obj = self.thread_obj
return ChatMessage.objects.create(thread=thread_obj, user=me, message=msg)
Django Chat Models
class Thread(models.Model):
first = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='chat_thread_first')
second = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='chat_thread_second')
updated = models.DateTimeField(auto_now=True)
timestamp = models.DateTimeField(auto_now_add=True)
#property
def room_group_name(self):
return f'chat_{self.id}'
def broadcast(self, msg=None):
if msg is not None:
broadcast_msg_to_chat(msg, group_name=self.room_group_name, user='admin')
return True
return False
class ChatMessage(models.Model):
thread = models.ForeignKey(Thread, null=True, blank=True, on_delete=models.SET_NULL, related_name="messages")
user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='sender', on_delete=models.CASCADE)
message = models.TextField()
timestamp = models.DateTimeField(auto_now_add=True)
Django Chat Urls
from django.urls import include, path
from rest_framework import routers
from .views import ThreadViewSet, ChatMessageViewSet
router = routers.DefaultRouter()
router.register('thread', ThreadViewSet)
router.register('chatMessage', ChatMessageViewSet)
urlpatterns = [
path('', include(router.urls)),
]
Expected Output
I was expecting the "CONNECTED" message with the event information printed on the console according to the websocket_connect method in the consumers class. Similarly a "open" message with the event fired in my browser's console from the Angular 2+ chat.component.ts.
Actual Output
I immediately get this warning message on the browser console:
WebSocket connection to 'ws://localhost:4200/sockjs-node/344/ux0z32ma/websocket' failed: WebSocket is closed before the connection is established.
After about 2 minutes of waiting...
Attached image shows what is automatically output on the console.
I may have missed something, because I cannot see the Django configuration, but as far as I understood, you wrote that you're running the frontend and backend server separately. You can see that the frontend is trying to establish the connection to localhost:4200. I belive that is the angular server which doesn't make sense, you should point your WebSocket to the Django app, so in my opinion you should modify the method below:
SERVER_URL = "localhost:8000"
getEndpoint() {
let wsStart = "ws://"
if (window.location.protocol == "https:") {
wsStart = 'wss://'
}
this.endpoint = wsStart + SERVER_URL + window.location.pathname;
return this.endpoint;
}
But if you're hosting both apps on Django and exposing that on port 4200 that doesn't apply, though I'm quite sure that's not your case.
Related
I'm using Django and React for a project that consumes Trello's API. I created functions in django views.py that will get user's boards, lists and cards. The problem is, to get a list, I need that the user select a board, because the list's endpoint requires a board id (and so on). I can show the user's boards on my react page and storage the board's id on a variable in Index.js (on pages folder), but I have no ideia how I can send the selected board id to views.py to consume Trello's api according to the user's selected board. Here's my code.
Django views.py:
from rest_framework.decorators import api_view
from rest_framework.response import Response
from dotenv import load_dotenv
from treegia.settings import TRELLO_URL
import os
import requests
load_dotenv()
TRELLO_KEY = os.getenv('TRELLO_KEY')
TRELLO_TOKEN = os.getenv('TRELLO_TOKEN')
#api_view(['GET'])
def get_boards(request):
if request.method == 'GET':
board_endpoint = TRELLO_URL+'members/me/boards'
jsonObj = {'fields':'name,id', 'key':TRELLO_KEY, 'token':TRELLO_TOKEN}
boards = requests.get(board_endpoint, json=jsonObj).json()
return Response(boards)
#api_view(['GET'])
def get_lists(request):
if request.method == 'GET':
list_endpoint = TRELLO_URL+ 'boards/' + id_board + '/lists'
jsonObj = {'fields':'name,id', 'id':id_board, 'key':TRELLO_KEY, 'token':TRELLO_TOKEN}
lists = requests.get(list_endpoint, json=jsonObj).json()
return Response(lists)
#api_view(['GET'])
def get_cards(request):
if request.method == 'GET':
card_endpoint = TRELLO_URL+ 'lists/' + id_list + '/cards'
jsonObj = {'fields':'name,id', 'id':id_list, 'key':TRELLO_KEY, 'token':TRELLO_TOKEN}
cards = requests.get(card_endpoint, json=jsonObj).json()
return Response(cards)
React Index.js:
import React from 'react';
import Navbar from '../components/Navbar';
import '../styles/index.css'
const Index = () => {
const [boards, setBoards] = React.useState([]);
const [boardId, setBoardId] = React.useState([]);
const [lists, setLists] = React.useState([]);
const [listId, setListId] = React.useState([]);
React.useEffect(() => {
getBoards();
}, []);
React.useEffect(() => {
getLists();
}, []);
async function getBoards() {
await fetch('http://localhost:8000/boards/')
.then(resp => resp.json())
.then(data => {
console.log(data);
if(data) setBoards(data);
})
}
async function getLists() {
await fetch('http://127.0.0.1:8000/lists/')
.then(resp => resp.json())
.then(data => {
console.log(data);
if(data) setLists(data);
})
}
return (
<div id='index-page'>
<Navbar />
<div className='boards'>
<h1>Your boards</h1>
<div className='boards-container'>
{boards.map(board => (
<button key={board.id} onClick={() => {
setBoardId(board.id)
}}>{board.name}</button>
))}
</div>
</div>
<div className='lists'>
<h1>Your lists</h1>
<div className='lists-container'>
{lists.map(list => (
<button key={list.id} onClick={() => {
setListId(list.id)
}}>{list.name}</button>
))}
</div>
</div>
{boardId ? <p>{boardId}</p> : ''}
{listId ? <p>{listId}</p> : ''}
</div>
);
}
export default Index;
Basically, when the user selects a board, I what to send the id to get_lists function. Is this possible to do?
In the view
def get_lists(request):
print(request.query_params['id_list']) # 'id_list being the variable you want to pass from the front end
this sets that we are expecting a parameter from the front end get request.
Remember to add a try catch arround it..cos if that query_param doesnt exist it will throw and erroe.
Non in the FE.. while making a request, set request parameter 'id_list = '
var url = new URL('http://127.0.0.1:8000/lists')
var params = {id_list:'3'} // or whatever the params
url.search = new URLSearchParams(params).toString();
fetch(url);
Essentially you add the query params to the request url like
http://127.0.0.1:8000/lists/?id_list=3&another_param=2..
test the backend using postman or smthn before integration..to make sure the correct url and stuff.
I had one flask table working fine for storing inputted emails. I wanted another page for storing inputted instagrams. So I basically doubled up most of everything of the email code for instagram code. I have a form but when I input something there on the webapp I get a 500 Internal Server Error. I think my problem is that the content-type maybe coming up as not a json? It's 'text/html'? I tried to change that and it didn't work. I feel like I'm missing a step in the initialization of the table or something and not just db.create_all()
app.py:
from flask import Flask, jsonify, request, send_from_directory
from flask_sqlalchemy import SQLAlchemy
import datetime
from flask_marshmallow import Marshmallow
from flask_cors import CORS
app = Flask(__name__, static_url_path='', static_folder='frontend/build')
CORS(app)
app.config['SQLALCHEMY_DATABASE_URI']='mysql://root:fakepass#localhost/pobblebonk'
ca#us-cdbr-east-04.cleardb.com/heroku_8caeb1ebf4606b6'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS']= False
db = SQLAlchemy(app)
ma = Marshmallow(app)
class Emails(db.Model):
id = db.Column(db.Integer, primary_key = True)
email = db.Column(db.String(120))
ighandle = db.Column(db.String(120))
def __init__(self, email):
self.email = email
class EmailsSchema(ma.Schema):
class Meta:
fields = ('id','email')
class IGHandles(db.Model):
__tablename__ = 'IGHandles'
num = db.Column(db.Integer, primary_key = True)
ighandle = db.Column(db.String(120))
def __init__(self, ighandle):
self.ighandle = ighandle
class IGHandlesSchema(ma.Schema):
class Meta:
fields = ('id','ighandle')
email_schema = EmailsSchema()
emails_schema = EmailsSchema(many=True)
ighandle_schema = IGHandlesSchema()
ighandles_schema = IGHandlesSchema(many=True)
#app.route("/", defaults={'path':''})
def serve(path):
return send_from_directory(app.static_folder, 'index.html')
#app.route('/get', methods = ['GET'])
def get_emails():
all_emails = Emails.query.all()
results = emails_schema.dump(all_emails)
return jsonify(results)
def get_ighandles():
all_ighandles = IGHandles.query.all()
results = ighandles_schema.dump(all_ighandles)
#app.route('/get/<id>', methods = ['GET'])
def post_emails(id):
results = Emails.query.get(id)
return email_schema.jsonify(results)
def post_ighandles(id):
results = IGHandles.query.get(id)
return ighandle_schema.jsonify(results)
#app.route('/add', methods = ['POST'])
def add_email2():
email = request.json['email']
print('add email python')
entry = Emails(email)
db.session.add(entry)
db.session.commit()
return email_schema.jsonify(entry)
#app.route('/add_ig', methods = ['POST'])
def add_ighandles():
ighandle = request.json['ighandle']
entry = IGHandles(ighandle)
db.session.add(entry)
db.session.commit()
if __name__ == "__main__":
app.run(debug=True)
APIService.js connects frontend to backend
export default class APIService {
static SubmitEmail(body) {
return fetch(`http://localhost:5000/add`, {
'method':'POST',
mode: 'cors',
headers: {
'Content-Type':'application/json'
},
body: JSON.stringify(body)
})
.then(resp => console.log("email here"))
.catch(error => console.log(error))
}
static SubmitIG(body) {
return fetch(`http://localhost:5000/add_ig`, {
'method':'POST',
mode: 'cors',
headers: {
'Content-Type':'application/json'
},
body: JSON.stringify(body)
})
.then(resp => console.log("ig here"))
.catch(error => console.log(error))
}
}
IgersForm.js...the form I use to input instagrams
import React, {useState, useEffect} from 'react'
import APIService from '../components/APIService'
function IgersForm() {
const[ighandle, setIGhandle] = useState('')
useEffect(() => {
setIGhandle()
},[])
const submitIG = () => {
APIService.SubmitIG({ighandle})
.then(setIGhandle(''))
.then(resp=> console.log(resp))
.then(resp=> console.log("hi igers"))
.then(resp=> console.log({ighandle}))
//.catch(error => console.log(error))
}
return (
<div>
<div className = "mb-3">
<label htmlFor = "ighandle" className = "form-label"></label>
<input type="text" className="form-control"
//value = {email|| ''}
value = {ighandle}
placeholder = "Please enter instagram handle"
onChange = {(e) => setIGhandle(e.target.value)}
/>
<button
onClick = {submitIG}
className = "btn btn-success mt-3"
>Submit</button>
</div>
</div>
)
}
export default IgersForm
and finally, my 500 error:
https://imgur.com/a/x8VIFQZ
I know how to broadcast but i can not target clients.
Here is my script:
import json
import trio
from quart import render_template, websocket, render_template_string
from quart_trio import QuartTrio
from quart_auth import current_user,login_required
from quart_auth import AuthUser, login_user, logout_user, AuthManager
import random
connections = set()
app = QuartTrio(__name__)
AuthManager(app)
app.secret_key = "secret key"
#app.route("/")
async def index():
clean_guy = await current_user.is_authenticated
if not clean_guy:
fake_ID = random.randrange(0, 9999) #quick dirty to test
login_user(AuthUser(fake_ID))
return await render_template_string("{{ current_user.__dict__ }}")
return await render_template_string("{{ current_user.__dict__ }}")
#app.websocket("/ws")
async def chat():
try:
connections.add(websocket._get_current_object())
async with trio.open_nursery() as nursery:
nursery.start_soon(heartbeat)
while True:
message = await websocket.receive()
await broadcast(message)
finally:
connections.remove(websocket._get_current_object())
async def broadcast(message):
for connection in connections:
await connection.send(json.dumps({"type": "message", "value": message}))
async def heartbeat():
while True:
await trio.sleep(1)
await websocket.send(json.dumps({"type": "heartbeat"}))
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
Here is my template:
<div>
<div>
<ul>
</ul>
</div>
<form>
<input type="text">
<button type="submit">Send</button>
</form>
</div>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function() {
const ws = new WebSocket(`ws://${window.location.host}/ws`);
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
if (data.type === "message") {
const ulDOM = document.querySelectorAll("ul")[0];
const liDOM = document.createElement("li");
liDOM.innerText = data.value;
ulDOM.appendChild(liDOM);
}
}
document.querySelectorAll("form")[0].onsubmit = function(event) {
event.preventDefault();
const inputDOM = document.querySelectorAll("input")[0];
ws.send(inputDOM.value);
inputDOM.value = "";
return false;
};
});
</script>
Also one problem:
if i use this in my script:
return await render_template("{{ current_user.__dict__ }}")
i am not able to display it with my jinja template even if i add {{ current_user.dict }} in my template.
I also noticed that:
with mozilla: i get something stable like
{'_auth_id': 9635, 'action': <Action.PASS: 2>}
with chrome: it changes on each refresh, it looks like
{'_auth_id': 529, 'action': <Action.WRITE: 3>}
I need to display the author, and the destination , and an input with a send button, how to fix the template ?
Is it also possible to send messages to targeted users with post via curl or websocat ? how to do that ?
Quart-Auth uses cookies to identify the user on each request/websocket-request so you can always get the identity of the user from the current_user if request is authenticated. Then for your need you will need to map websocket connections to each user (so you can target messages), hence the connections mapping should be a dictionary of connections, e.g.
import random
from collections import defaultdict
from quart import request, websocket
from quart_trio import QuartTrio
from quart_auth import (
AuthUser, current_user, login_required, login_user, logout_user, AuthManager
)
connections = defaultdict(set)
app = QuartTrio(__name__)
AuthManager(app)
app.secret_key = "secret key"
#app.route("/login", methods=["POST"])
async def login():
# Figure out who the user is,
user_id = random.randrange(0, 9999)
login_user(AuthUser(fake_ID))
return {}
#app.websocket("/ws")
#login_required
async def chat():
user_id = await current_user.auth_id
try:
connections[user_id].add(websocket._get_current_object())
while True:
data = await websocket.receive_json()
await broadcast(data["message"])
finally:
connections[user_id].remove(websocket._get_current_object())
#app.route('/broadcast', methods=['POST'])
#login_required
async def send_broadcast():
data = await request.get_json()
await broadcast(data["message"], data.get("target_id"))
return {}
async def broadcast(message, target = None):
if target is None:
for user_connections in connections.values():
for connection in user_connections:
await connection.send_json({"type": "message", "value": message})
else:
for connection in connections[target]:
await connection.send_json({"type": "message", "value": message})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
You can then send to the /broadcast either JSON that is just a message {"message": "something"} or a message with an id to target someone specifically {"message": "something for user 2", "target_id": 2}. Note as well the #login_required decorator ensures the route handler is only called for logged in users.
I am trying my hands on File Upload using Django REST and Angular.
Following is the angular directory structure:
app
|-----uploadcomponent
|-----uploadcomponent.module.ts
|-----uploadcomponent.html
|-----app.module.ts
|-----app.component.ts
|-----app.service.ts
uploadcomponent.htl:
<div>
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<input type="file" name="profile" enctype="multipart/form-data" accept=".xlsm,application/msexcel" (change)="onChange($event)" />
<button type="submit">Upload Template</button>
<button id="delete_button" class="delete_button" type="reset"><i class="fa fa-trash"></i></button>
</form>
</div>
uploadcomponent.ts:
import { FormBuilder, FormGroup, ReactiveFormsModule } from '#angular/forms';
import { Component, OnInit } from '#angular/core';
....
export class UploadComponent implements OnInit {
form: FormGroup;
constructor(private formBuilder: FormBuilder, private uploadService: AppService) {}
ngOnInit() {
this.form = this.formBuilder.group({
profile: ['']
});
}
onChange(event) {
if (event.target.files.length > 0) {
const file = event.target.files[0];
this.form.get('profile').setValue(file);
console.log(this.form.get('profile').value)
}
}
onSubmit() {
const formData = new FormData();
formData.append('file', this.form.get('profile').value);
this.uploadService.upload(formData).subscribe(
(res) => {
this.response = res;
console.log(res);
},
(err) => {
console.log(err);
});
}
}
app.service.ts:
upload(formData) {
const endpoint = this.service_url+'upload/';
return this.http.post(endpoint, formData, httpOptions);
}
Now in the backend I am using Django Rest Framework:
Following are the required files of code:
models.py:
from __future__ import unicode_literals
from django.db import models
from django.db import connection
from django_mysql.models import JSONField, Model
import uuid
import os
def change_filename(instance, filename):
extension = filename.split('.')[-1]
file_name = os.path.splitext(filename)[0]
uuid_name = uuid.uuid4()
return file_name+"_"+str(uuid_name)+"."+extension
class UploadTemplate (Model):
id = models.AutoField(primary_key=True)
file = models.FileField(blank=False, null=False, upload_to=change_filename)
def __str__(self):
return str(self.file.name)
views.py:
class UploadView(APIView):
serializer_class = UploadSerializer
parser_class = (FileUploadParser,)
def get_queryset(self):
queryset = UploadTemplate.objects.all()
return queryset
def post(self, request, *args, **kwargs):
file_serializer = UploadSerializer(data=request.data)
status = None
message = None
if file_serializer.is_valid():
file_serializer.save()
status = "Success"
message = "Success"
else:
status = "Failure"
message = "Failure!"
content = {'status': status, 'message': message}
return Response(content)
serializers.py:
from uploadtemplate.models import UploadTemplate
from rest_framework import serializers
class UploadSerializer(serializers.ModelSerializer):
class Meta:
model = UploadTemplate
fields = '__all__'
urls.py:
router.register(r'uploadtemplate', uploadtemplateviews.UploadTemplateView, base_name='UploadTemplate')
urlpatterns = [
....
url(r'upload/', uploadtemplateviews.UploadTemplateView.as_view()),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Also I have made required changes in settings.py.
Now when I upload an excel sheet (.xlsm extension) through UI, I get the following error:
JSON parse error - 'utf-8' codec can't decode byte 0xc9 in position 213: invalid continuation byte
Now, when I try to read the same excel file using pandas, I can see that few of the column values are NaN and I doubt this might be the reason for decoding.
Can someone please help me out on how to handle this in Angular/Rest so that my file uploads successfully.
Thanks!
Got the answer for this...
In views.py I had to change the parser to:
parser_classes = [MultiPartParser]
So my views.py now looks like:
class UploadView(APIView):
serializer_class = UploadSerializer
parser_classes = [MultiPartParser] <<<<< Changes are here
def get_queryset(self):
queryset = UploadTemplate.objects.all()
return queryset
def post(self, request, *args, **kwargs):
file_serializer = UploadSerializer(data=request.data)
status = None
message = None
if file_serializer.is_valid():
file_serializer.save()
status = "Success"
message = "Success"
else:
status = "Failure"
message = "Failure!"
content = {'status': status, 'message': message}
return Response(content)
Also, the http request was sending Content-Type as application/json by default.
Had to change it. So my apps.service.ts looked as following:
upload(formData) {
const endpoint = this.service_url+'upload/';
const httpOptions = headers: new HttpHeaders({ <<<< Changes are here
'Authorization': 'token xxxxxxx'})
};
return this.http.post(endpoint, formData, httpOptions);
}
Thank You!
URL: / client /[client_id]/ log / operator
Method: POST
Data:
{
"access_token": "TOKEN",
"limit": 100,
"sort": "asc",
"start_date": "2018-01-01T00: 00: 00Z"
}
OR
{
"access_token": "TOKEN",
"limit": 100,
"sort": "asc",
"offset": "500"
}
Success Response:
{
"result": "ok",
"logs": [
{
"date": "2018-01-01T00: 00: 00Z",
"text": "Log entry"
},
...
]
}
Log entry in the format: "message. User: name the name of the operator."
I need to create a request to receive logs of operator activity. Which returns a formatted log of all user actions.
I have an activity application in which there is an OperatorLog model.
from django.db import models
from users.models import User
from .constants import ACTIVITY_ACTIONS
class OperatorLogManager(models.Manager):
"""Manager for operator logs"""
def active(self):
"""Mark object as active"""
return self.filter(is_delete=False)
def get_by_user_id(self, id):
return self.active().filter(pk=id)
def get_by_client_id(self, client_uid):
return self.active().filter(client_uid=client_uid)
class OperatorLog(models.Model):
"""Model for logs"""
user = models.ForeignKey(User, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
is_deleted = models.BooleanField(default=False)
action_type = models.CharField(max_length=32,
сhoices=ACTIVITY_ACTIONS)
message = models.TextField(max_length=2048)
client_uid = models.CharField(max_length=50)
bitrix_id = models.PositiveIntegerField()
# Operator Manager
objects = OperatorLogManager()
def delete(self, **kwargs):
self.is_deleted = True
self.save()
You need a public api (activity / api.py) to register events in other applications,
There is also a file with a list of activity types (constants.py), it should be available in other applications via the import of api.py, you need to output the logs using the serializer (DRF).
constants.py (Sample version)
from model_utils import Choices
ACTIVITY_ACTIONS = Choices (
('ACTION_REQ_PHONE_VERIFY', 'Request phone verification'),
('VALIDATE_PHONE', 'Request phone validation'),
('VALIDATE_EMAIL', 'Email validation'),
('SET_INTERNAL_VERIFY', 'Set result of Internal verification'),
('SET_BANK_VERIFY', 'Set result of Bank verification'),
('GET_CLIENT_DATA', 'Request client data'),
('GET_SENSITIVE_INFO', 'Request sensitive info'),
)
Try the logging library, where you can access various type of log messages
like-
Error, Debug, Warning or just Information.
You can read more about here:
https://docs.python.org/3/library/logging.html