I was trying to make a multiroom app with flask and flask socket-io integration but i think something is offset as if I try to join another room on another tab it sends the info to all rooms and other rooms show someone has joined in that room and after that if I send message it also don't work.
here's my code
from flask_socketio import SocketIO, join_room
from colorama import Fore, Style
app = Flask(__name__)
socketio = SocketIO(app)
#app.route("/")
def home():
return render_template("index.html")
#app.route("/chat")
def chat():
username = request.args.get("username")
room = request.args.get("room")
if username and room:
return render_template("chat.html", username=username, room=room)
else:
return redirect(url_for("home"))
#socketio.on('join_room')
def handle_join_room(data):
info = "[INFO] "+f"[ROOM: {data['room']}]: "+f"User {data['username']} has joined"
print(Fore.CYAN+info)
join_room(data['room'])
socketio.emit('join_room_ann', data)
Style.RESET_ALL
#socketio.on('send_mess')
def handle_mess(data):
info = f"[MESSAGE][ROOM: {data['room']}][USER:{data['username']}]: {data['message']}"
print(info)
if __name__ == "__main__":
socketio.run(app, debug=True)}```
and html side
client
[{% extends "common.html" %}
{% block index %}
<div class="log left">
<p>Username:</p>
<p class="cls">{{username}}</p>
<p>Room:</p>
<p class="cls">{{room}}</p>
</div>
<div id="messages" class='bottoml log'>
<p>in room:</p>
</div>
<form action="" id="inp_form">
<div class="log chat">
<div class="in">
<input type="text" id="inp_box" class="chat_in" placeholder="Type your text">
<button type="submit">Send</button>
</div>
</div>
</form>
{% endblock %}
{% block chat %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.2.0/socket.io.js"></script>
<script>
const socket = io.connect("http://127.0.0.1:5000");
socket.on('connect', function(){
socket.emit("join_room",{
username:"{{username}}",
room:"{{room}}"
});
let inp_box = document.getElementById('inp_box');
document.getElementById('inp_form').onsubmit = function (e){
e.preventDefault();
mes = inp_box.value.trim();
if (mes.length){
socket.emit('send_mess', {
username:"{{username}}",
room:"{{room}}",
message:mess
})
}
inp_box.value='';
inp_box.focus();
}
});
socket.on('join_room_ann', function(data){
console.log(data);
const newnode = document.createElement('div');
newnode.innerHTML = `${data.username}`;
newnode.className+="newn"
document.getElementById('messages').appendChild(newnode);
})
</script>
{% endblock %}]
If you want to send the join room announcement just to the room in question, you have to indicate that in the emit call:
socketio.emit('join_room_ann', data, room=data['room'])
Without the room argument, the emit goes to all connected clients, regardless of what room they're in.
Related
I have a Flask App which starts a long function after validation a form. I added a flash message before that function but the message appears after the function has finished:
# main_app.py
app = Flask(__name__)
class NameForm(FlaskForm):
name = StringField('Email Adress', validators=[DataRequired()], default='test#gmail.com')
# some more fields
submit = SubmitField('Submit')
def run_my_long_task(name):
# do some computing
# save results to a file
# return a pandas dataframe with the results for plotting with bokeh
#app.route('/', methods=['GET', 'POST'])
def index():
form = NameForm()
if form.validate_on_submit():
name = form.name.data
# I want to display a message before the run starts
flash('Analysis started, please wait.')
# Start a time consuming analysis
run_my_long_task(name)
return render_template('index.html', form=form)
Here is my index.html which contains a container with the flash message handling:
<div class="container">
{% for message in get_flashed_messages() %}
<div class="alert alert-warning">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ message }}
</div>
{% endfor %}
{% block page_content %}{% endblock %}
The message shows up successfully but after the run_my_long_task() has finished. How to show up this message while the job is running?
Edit: So in general I just want to inform the user about the start of the analysis. Not necessarily need to be a flash message.
Instead of using the flash function on the server-side, you might want to use some javascript to achieve this:(this is just an example, so it might seem very ugly)
<script>
document.querySelector('DOMContentLoaded', () => {
document.querySelector("#youform").addEventListener('submit', () => {
const messageRef = document.querySelector("#flash-message");
messageRef.innerHTML = 'the message';
messageRef.styles.display = 'block';
})
});
</script>
<div class="alert alert-warning" id="flash-message" style="display: hidden;">
<button type="button" class="close" data-dismiss="alert">×</button>
</div>
I came up using Bootstrap’s JavaScript modal plugin, which is enough for my needs
I am following by this tutorial on how to get live updates on django without refreshing the page.
The tutorial uses flasks render_template to get the html rendered which is then injected to a page section.
I am trying to do the same in Django, But django just directly renders it in the browser... I don't want that. I just want django to send the rendered html response to AJAX which could then inject that to a section on my live page.
Here is the code :
views.py
class ManageView(LoginRequiredMixin, View):
template_name = "dashboard/manage.html"
context = {}
def get(self, request, app_id, folder_id=None):
app = App.objects.get(pk=int(app_id))
self.context["app"] = app
if folder_id:
try:
self.context["folder"] = Folder.objects.get(id=folder_id)
except:
self.context["folder"] = app.folder
else:
self.context["folder"] = app.folder
return render(request, self.template_name, self.context)
def post(self, request, app_id, folder_id=None):
try:
files = request.FILES.getlist('files_to_upload')
folder_name = request.POST.get("folder")
master = request.POST.get("master")
if master:
master = Folder.objects.get(id=master)
if folder_name:
Folder.objects.create(name=folder_name, owner=request.user.customer, folder=master)
if files:
for file in files:
if file.size < settings.MAX_FILE_SIZE:
File.objects.create(folder=master, item=file, name=file.name, size=file.size)
app = App.objects.get(pk=int(app_id))
self.context["app"] = app
if folder_id:
try:
self.context["folder"] = Folder.objects.get(id=folder_id)
except:
self.context["folder"] = app.folder
else:
self.context["folder"] = app.folder
return render(request, 'dashboard/filesection.html', self.context)
except DatabaseError:
return render(request, "dashboard/index.html", self.context)
urls.py
urlpatterns = [ url(r'^manage/(?P<app_id>[0-9]+)/(?P<folder_id>.+)', test, name='browse'), ]
dashboard/manage.html
<div class="modal-body">
<form id="app-launch" enctype="multipart/form-data" method="post">
{% csrf_token %}
<div class="form-row">
<div class="input-group mb-3">
<div class="custom-file">
<input type="hidden" value="{{ folder.id }}" name="master">
<input type="hidden" value="{{ app.id }}" name="file_app_id">
<input type="file" class="custom-file-input" name="files_to_upload" id="file_upload" accept=".py,.js,.json,.txt,.css,.html,.pdf,.htm,.doc,.docx,.log,.ppt,.pptx" multiple>
<label class="custom-file-label" for="inputGroupFile02">Choose file</label>
</div>
<div class="input-group-append">
<button class="input-group-text btn btn-primary" id="">Upload</button>
<button class="input-group-text btn btn-primary fileButton" id="">Upload</button>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-danger" data-dismiss="modal">Cancel</button>
</div>
</div>
app.js AJAX calls
$(document).ready(function() {
$(document).on('click','fileButton', function(e) {
e.preventDefault()
// const axios = require('axios');
var formData = new FormData();
var ins = document.getElementById('file_upload').files.length;
for (var x = 0; x < ins; x++) {
formData.append("files_to_upload", document.getElementById('file_upload').files[x]);
}
const csrftoken = getCookie('csrftoken');
var app_id = $('input[name="file_app_id"]').val();
var folder_id = $('input[name="master"]').val();
formData.append('master', folder_id);
req = $.ajax({
type: 'POST',
url: `/manage/${app_id}/${folder_id}`,
data: formData,
processData: false,
contentType: false,
headers: {
"X-CSRFToken": csrftoken,
}
});
req.done(function (data) {
$('#refreshSection').html(data)
})
});
});
AJAX POST and everything works, it just that the django is refreshing and rendering that section template on the browser which i don't want.
[Solved]
Its was a mistake from my side. I missed e.preventDefault()
which is really dumb.
I am trying to use the Django templating language in my Django Channels 2.1.2 project to render out any unread chat messages in a Facebook-style notification popup.
The list of unread chatmessages (in their respective threads) are not displaying because I am having trouble with the correct syntax.
This is how the front end looks. When you click the message icon, the notification disappears.
I have a Notification model
class Notification(models.Model):
notification_user = models.ForeignKey(User, on_delete=models.CASCADE)
notification_chat = models.ForeignKey(ChatMessage, on_delete=models.CASCADE)
notification_read = models.BooleanField(default=False)
def __str__(self):
return f'{self.id}'
navbar.html
{% if user.is_authenticated %}
<li id="notification_li" class="nav-item">
<a class="nav-link" href="#" id="notificationLink">
<i class="fas fa-envelope"></i> Inbox</a>
{% for notifications in notification %}
<span id="notification_id">{{ notifications.notification_chat }}</span>
{% endfor %}
<div id="notificationContainer">
<div id="notificationTitle">Notifications</div>
<div id="notificationsBody" class="notifications">
{{ notification.notification_chatessage?? }}
</div>
<div id="notificationFooter">See All</div>
</div>
</li>
base.html
<script>
$(document).ready(function() {
$("#notificationLink").click(function() {
$("#notificationContainer").fadeToggle(300);
$("#notification_id").fadeOut("slow");
return false;
});
//Document Click hiding the popup
$(document).click(function() {
$("#notificationContainer").hide();
});
//Popup on click
$("#notificationContainer").click(function() {
return false;
});
});
</script>
context_processors.py
def notification(request):
if request.user.is_authenticated:
notification = Notification.objects.filter(notification_user=request.user)
return {'notification':notification}
return Notification.objects.none()
I have the context processor also added into settings
in the correct place. The notification_id should be sent using the message WebSocket and updated each time a new message is sent (I still haven't managed to do this successfully).
consumers.py
async def websocket_receive(self, event):
# when a message is received from the websocket
print("receive", event)
message_type = event.get('type', None) #check message type, act accordingly
if message_type == "notification_read":
# Update the notification read status flag in Notification model.
notification = Notification.object.get(id=notification_id)
notification.notification_read = True
notification.save() #commit to DB
print("notification read")
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.username
myResponse = {
'message': msg,
'username': username,
'notification': notification_id # send a unique identifier for the notification
}
...
thread.html
...
// below is the message I am receiving
socket.onmessage = function(e) {
var data = JSON.parse(event.data);
// Find the notification icon/button/whatever
// and show a red dot, add the notification_id to element as id or data attribute.
console.log("message", e)
var chatDataMsg = JSON.parse(e.data)
chatHolder.append('<li>' + chatDataMsg.message + ' from ' + chatDataMsg.username + '</li>')
}
In addition to helping me with this question, I would really appreciate any good learning resources.
For referencing the notification message, you should use {{notifications.notification_chat.message}}. Also, for showing all notifications, you will have to loop over all the notifications.
navbar.html
{% if user.is_authenticated %}
<li id="notification_li" class="nav-item">
<a class="nav-link" href="#" id="notificationLink">
<i class="fas fa-envelope"></i> Inbox</a>
{% for notifications in notification %}
<span id="inbox-{{notifications.id}}">{{ notifications.notification_chat.message }}</span>
{% endfor %}
<div id="notificationContainer">
<div id="notificationTitle">Notifications</div>
<div id="notificationsBody" class="notifications">
{% for notifications in notification %}
<span id="notification-{{notifications.id}}">{{ notifications.notification_chat.message }}</span>
{% endfor %}
</div>
<div id="notificationFooter">See All</div>
</div>
</li>
I also noticed that in your thread.html, you are not updating the notifications when you get a response from the server. you can use the ids to to prepend new notifications.
Good day, I have a simple web page with an email form in it. I'm trying to collect the data from it and populate a database without refreshing the template. Here is my code so far:
Form:
from flask_wtf import Form
class EmailForm(Form):
email = StringField('Email Address', [
DataRequired(message='Required field'),
Email(message='Please provide a valid email address')
])
submit = SubmitField('send')
Route:
#app.route('/', methods=('GET', 'POST'))
def index():
form = EmailForm(request.form)
if request.method == 'POST' and form.validate_on_submit():
try:
email = Email(form.data['email'])
db.session.add(email)
db.session.commit()
except IntegrityError as e:
app.logger.info(e)
return redirect(url_for('index'))
return render_template('index.html', form=form)
Ajax:
$(function() {
$('#email_submit').bind('click', function() {
$.getJSON('/', {
email: $('input[name="email"]').val()
});
return false;
});
});
Template:
<form name="collectEmail" id="collectForm" method="post" action="{{ url_for('index') }}">
{{ form.hidden_tag() }}
{{ form.csrf_token }}
{% if form.csrf_token.errors %}
<div class="warning">You have submitted an invalid CSRF token</div>
{% endif %}
<div class="input-group">
{{ form.email(class='form-control', placeholder='Your Email *', type='email')}}
<p class="help-block text-danger"></p>
<span class="input-group-btn">
{{ form.submit(class='btn btn-primary', id='email_submit', type='submit') }}
</span>
</div>
</form>
The database successfully populates; but, I would like to avoid refreshing the page after submitting the form.
You are not sending the request with AJAX, #email_submit is an input of type submit, not a button, so if you don't use preventDefault() you end up executing the default behaviour of that input.
You have 2 options there, one is using preventDefault() and the other is to switch that input to a button, so it won't submit the form before the javascript code runs.
New to flask here ... my problem is that I'm trying to check if a response is empty, and if so flash a message. With the below code, I can see that the redirect goes through and the subsequent GET responds with the correct html in dev tools, but the page is not loaded, it stays on the current page (which also happens to be layout.html, not sure if this is an issue, my intent is to simply reload this page to show the flashed message).
Relevant Flask:
#app.route('/')
def hello():
return render_template('layout.html')
#app.route('/query',methods=['POST'])
def query():
start=request.json['start']
end=request.json['end']
name=request.json['name']
sql="select some stuff"
data_list = []
stuff=cur.execute(sql)
for row in stuff:
data_list.append(row[0])
if not data_list:
flash('No balances for selected client/dates')
return redirect(url_for('hello'))
return json.dumps(data_list)
if __name__ == '__main__':
app.secret_key = 'secretkeyhere'
app.run(debug=True,host='127.0.0.1',port=8000)
Relevant portion of the html to avoid a wall of text
<body>
<div class="container">
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="flashed_message" role="alert">
<button type="button" class="close" data-dismiss="alert" aria- label="Close"><span aria-hidden="true">×</span></button>
{{message}}
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
And lastly.... AJAX:
$(function(){
$("[type=submit]").click(
function(){
// event.preventDefault()
$(".container").hide()
var startdate=$('#datetimepicker1').val()
var enddate=$('#datetimepicker2').val()
var name=$('#mhh').val()
$.ajax({
type: "POST",
url: $SCRIPT_ROOT + "/query",
contentType: "application/json; charset=utf-8",
dataType:"json",
success: function(response) {
console.log('worked!')
return {'start':response.start,'end':response.end,'name':response.name}
},
error:function(){
console.log('didn\'t work')
}
})
})
});
From what I can see of your code it looks like you're redirecting the AJAX call. Essentially your AJAX call will load your index, and not the browser itself (which is what you want).
Inside your AJAX, try changing your error function to
error: function() {
document.location.reload()
}
Then, alter your if not data_list block to:
if not data_list:
flash('No balances for selected client/dates')
return Response(status=500)