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.
Related
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.
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
My problem is that when the user creates a new record on the database in my webpage (Mysql) and the system redirects the user to the list of elements created, the new element won't show up until I click again the link option on a section. In other words I want django to show the new results when I redirect the user to the list, here's some fragments of code I'm using:
This is the link where the user clicks to show the list after creating a new object:
<div class="modal-footer">
<div class="col-lg-12 text-right">
Regresar a Listado
</div>
</div>
This is the list where I show the elements from the database:
{% for portal in portal_list %}
<section class="search-result-item">
<a class="image-link" href="/microsite/myportal/view/{{portal.portal_id}}">
<img class="image" src="{% static 'img/template-icon.png' %}">
</a>
<div class="search-result-item-body">
<div class="row">
<div class="col-sm-9">
<h4 class="search-result-item-heading">
No. de Plantilla : {{portal.portal_id}}
</h4>
<p class="description">
ID Terminos: {{portal.terms_id}}
</p>
</div>
<div class="col-sm-3 text-align-center">
<p class="value3 text-gray-dark mt-sm">
{{ randomNumber|random }} visitas
</p>
<p class="fs-mini text-muted">
Por Semana
</p>
<br>
Ver Detalles
</div>
</div>
</div>
</section>
{% endfor %}
Here's my function in views: (The object that comes from MySQL is called myportal_list)
from django.shortcuts import render
from django.http import HttpResponseRedirect
def myportal_list(request, device_serial):
logger.debug("views - myportal_create")
deviceObject = CaptivePortalService.CaptivePortalService().getNetwork(device_serial)
captive_portal_list=""
context = {'device_object': deviceObject}
myportal_list = my_portal.objects.all()
context['portal_list'] = myportal_list
number = []
for i in myportal_list:
number.append(randint(0, 25))
context['randomNumber'] = number
return render(request, URL_MYPORTAL_LIST, context)
And finally there's my list of options, that is actually the only way to show the new data from the database: (even if i press F5 the new data wont show)
<ul id="network-option" class="panel-collapse collapse ">
<li class="">Mi Portal</li>
<!-- <li class="">Configuraciones de Accceso</li> -->
<li class="">Configuraciones de Accceso</li>
<li class="">Promociones</li>
<li class="">Términos de Uso</li>
</ul>
The view function where I create a new record: (Im handling the response with AJAX to be able to handle messages if there's an error or if the form was submitted correctly, in this case the messages are listed in the modal footer that I posted first)
def myportal_create(request, device_serial):
if request.method == "GET":
logger.debug("views - myportal_create")
deviceObject = CaptivePortalService.CaptivePortalService().getNetwork(device_serial)
captive_portal_list=""
context = {'device_object': deviceObject}
promotion_list = promotions.objects.all()
context['promo_list'] = promotion_list
terms_list = use_terms.objects.all()
context['terms_list'] = terms_list
return render(request, URL_MYPORTAL_CREATE, context)
if request.method == "POST":
form = PortalForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect("/microsite/myportal/list/"+ device_serial)
else:
print(form.errors)
Also I have the correct configuration to use MySQL on settings so I don't think that being the problem.
Thanks for the time.
I am working with Django 1.11.5 and logging-in and validating users via Social Auth.
The user authentication, log-in and logoff are working as expected in 12/13 of my templates. All my templates extend my navbar and footer base.html template.
In 'base.html' I have the following code for the navbar:
{% if user.is_authenticated %}
<li class="nav-item">
<span class="nav-link" id="user-name">{{ request.user.username }}</span>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'logout' %}">Logoff</a>
</li>
{% else %}
<li class="nav-item log-in-link">
<a class="btn btn-primary nav-link log-in" href="{% url 'social:begin' 'google-oauth2' %}"><span>Log-In <i class="fa fa-google-plus" aria-hidden="true"></i></span></a>
</li>
{% endif %}
Now, this navbar code works properly in 12/13 of my templates. It is only for 1 template/view/URL where the user is not authenticated.
I have tried debugging by printing out the user name and information in the template giving me errors but it looks like the user is getting logged out when reaching that template via its URL and view.
I am totally lost.
Could someone point out things I can check/do to debug and locate the source of this logoff error? I can provide the relevant code if needed.
views.py code
def details_request(request, request_data_ID):
data_request_object = DatasetRequest.objects.get(pk=request_data_ID)
user_ID = data_request_object.user.username
has_attributes = False
request_data_attrs = []
if len(data_request_object.attr_names) > 0:
if len(data_request_object.attr_names['names']) > 0:
has_attributes = True
for idx, attr_names in enumerate(data_request_object.attr_names['names']):
request_data_attrs.append([attr_names,
data_request_object.attr_names['descriptions'][idx],
data_request_object.attr_names['types'][idx]])
data_request_detail_template = {
'dataset_request_title': data_request_object.dataset_title,
'dataset_request_description': data_request_object.dataset_description,
'dataset_votes': data_request_object.dataset_votes,
'dataset_date': data_request_object.created_date.strftime("%Y-%m-%d"),
'request_data_ID': request_data_ID,
'has_attributes': has_attributes,
'request_attrs': request_data_attrs,
'user': user_ID,
'is_completed': data_request_object.is_completed
}
data_comment_object = Comments.objects.filter(request_dataset_FK=data_request_object).order_by("-comment_votes")
if len(data_comment_object) > 0:
comment_list = []
for comment_object in data_comment_object:
if comment_object.isComment:
comment_list.append([comment_object.comment, comment_object.created_date.strftime("%Y-%m-%d"), comment_object.comment_votes, comment_object.pk, comment_object.user.username])
data_request_detail_template['comments'] = comment_list
return render(request, "detail_requests.html", data_request_detail_template)
You are specifying:
user_ID = data_request_object.user.username
and put it into context under user key.
In template you have {% if user.is_authenticated %} which means that you are trying to access missing attribute is_authenticated of user.username which always evaluates to False.
I'm new to flask admin and I need to move the delete button to the edit view.
Here is the AdminModelView class that my other views are inheriting from.
class AdminModelView(sqla.ModelView):
can_view_details = True
# column_extra_row_actions = [ViewRowAction()]
def is_accessible(self):
if not current_user.is_active or not current_user.is_authenticated:
return False
if current_user.can('administrator'):
return True
return False
def _handle_view(self, name, **kwargs):
"""
Override builtin _handle_view in order to redirect users when a view is not accessible.
"""
if not self.is_accessible():
if current_user.is_authenticated:
# permission denied
abort(403)
else:
# login
return redirect(url_for('auth.login', next=request.url))
print(self._edit_form_class)
def get_list_row_actions(self):
"""
Return list of row action objects, each is instance of
:class:`~flask_admin.model.template.BaseListRowAction`
"""
actions = []
if self.can_view_details:
if self.details_modal:
actions.append(template.ViewPopupRowAction())
else:
actions.append(template.ViewRowAction())
return actions + (self.column_extra_row_actions or [])
I've redefined get_list_row_actions to take the edit and delete buttons off of the list view. I'm wondering if there's a part of my AdminModelView class that I can change or if I need to change the template for the edit form.
My solution:
Wrote new endpoint on class AdminModelView(sqla.ModelView):
#expose('/delete', methods=('DELETE',))
def delete_view(self):
"""
Delete model
"""
id = request.args.get('id')
if id is None:
return jsonify({"success": False}), 404
model = self.get_one(id)
db.session.delete(model)
db.session.commit()
flash("{0} deleted".format(model))
return jsonify({"success": True}), 200
Used these macros to render a delete button and a modal to the edit view template.
{% if admin_view.can_delete and model %}
{{ add_modal_button(url='#', title='Delete', content='Delete', modal_window_id='delete_modal', btn_class='btn btn-danger') }}
{% endif %}
{% macro delete_modal() %}
<div class="modal fade" id="delete_modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
{# bootstrap version > 3.1.0 required for this to work #}
<div class="modal-content">
<div class="panel panel-danger">
<div class="panel-heading">Delete Record</div>
<div class="panel-body">
<p>You are about to delete this record. This action cannot be undone.</p>
<p>Would you like to proceed?</p>
</div>
<div class="panel-footer text-center">
<button type="button" class="btn btn-secondary" id="cancel_delete">Cancel</button>
<button type="button" class="btn btn-danger" id="confirm_delete">Delete</button>
</div>
</div>
</div>
</div>
</div>
{% endmacro %}
Hybrid Jinja2 and JavaScript. This should be refactored. {{model.id}} can be stored in the DOM and retrieved with jQuery before the AJAX request is made.
$("#cancel_delete").click(function() {
$("#delete_modal").modal("toggle")
});
$("#confirm_delete").click(function() {
$.ajax({
url: "../delete?id={{ model.id }}",
method: "DELETE"
}).done(function() {
window.location.replace("{{ return_url }}");
}).fail(function() {
$(".actions-nav").before(
'<div class="alert alert-danger alert-dismissable fade in">' +
'×' +
'<strong>Error:</strong> This object failed to delete.' +
'</div>'
)
})
})