Reputation:
I'm working on a school project. Right now any user can ask a question.
In order to notify all the users when any users asks a question I've created a new app & notifying them through the simple 'view' whenever a question is asked. But it's just plain notifications yet.
How can I mark them read once a user opens the Notification tab? Just like on social networks!
Upvotes: 5
Views: 4245
Reputation: 51
First you need ManyToManyField for user module who is readed a post.
profile.py
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
subscribed = models.ManyToManyField(User, related_name='subscribed', blank=True)
readed = models.ManyToManyField("blogs.BlogPost", related_name='readed', blank=True)
updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return str(self.user.username)
class Meta:
ordering = ("-created",)
If you have a blog model like this:
class BlogPost(models.Model):
author = models.ForeignKey(Profile, on_delete=models.CASCADE)
post_title = models.CharField("Post Title", max_length=150, unique=True)
post_content = models.TextField("Content")
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.post_title
class Meta:
ordering = ("-created",)
You need to create a button function in blog posts view.
def mark_as_read_button(request):
if request.method == "POST":
my_profile = Profile.objects.get(user=request.user)
post = request.POST.get("post_pk")
obj = BlogPost.objects.get(pk=post)
if obj in my_profile.readed.all():
my_profile.readed.remove(obj)
else:
my_profile.readed.add(obj)
return redirect(request.META.get("HTTP_REFERER"))
return redirect("blogs:subscribed-blogs")
Then you can use is in the template file like this:
{% extends 'base.html' %}
{% block title %}Subscribed Blog Posts{% endblock %}
{% block content %}
{% for post in posts %}
<h5 class="card-title">{{ post.post_title }}</h5>
{% if post in readed %}
<form action="{% url "blogs:mark_as_read" %}" method="POST">
{% csrf_token %}
<input type="hidden" name="post_pk" value={{ post.pk }}>
<button type="submit" class="btn btn-danger btn-sm">Mark as unread</button>
</form>
{% else %}
<form action="{% url "blogs:mark_as_read" %}" method="POST">
{% csrf_token %}
<input type="hidden" name="post_pk" value={{ post.pk }}>
<button type="submit" class="btn btn-success btn-sm">Mark as read</button>
</form>
{% endif %}
<p class="card-text">{{ post.created }}</p>
<p class="card-body">{{ post.post_content }}</p>
<hr>
{% endfor %}
{% endblock %}
Good coding.
Upvotes: 1
Reputation: 4194
I suggest you to use ContentType
to make a dynamic notifications fo any models. This snippet below is an example how to implement the notification system;
1. in your models.py
from django.db import models
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
from django.utils.translation import ugettext_lazy as _
class ContentTypeToGetModel(object):
"""
requires fields:
- content_type: FK(ContentType)
- object_id: PositiveIntegerField()
"""
def get_related_object(self):
"""
return the related object of content_type.
eg: <Question: Holisticly grow synergistic best practices>
"""
# This should return an error: MultipleObjectsReturned
# return self.content_type.get_object_for_this_type()
# So, i handle it with this one:
model_class = self.content_type.model_class()
return model_class.objects.get(id=self.object_id)
@property
def _model_name(self):
"""
return lowercase of model name.
eg: `question`, `answer`
"""
return self.get_related_object()._meta.model_name
class Notification(models.Model, ContentTypeToGetModel):
# sender = models.ForeignKey(
# User, related_name='notification_sender')
receiver = models.ForeignKey(
User, related_name='notification_receiver')
content_type = models.ForeignKey(
ContentType, related_name='notifications', on_delete=models.CASCADE)
object_id = models.PositiveIntegerField(_('Object id'))
content_object = GenericForeignKey('content_type', 'object_id')
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
STATUS_CHOICES = (
('reply', _('a reply')),
('comment', _('a comment')),
('message', _('a message'))
)
status = models.CharField(
_('Status'), max_length=20,
choices=STATUS_CHOICES, default='comment')
is_read = models.BooleanField(
_('Is read?'), default=False)
def __str__(self):
title = _('%(receiver)s have a %(status)s in the %(model)s:%(id)s')
return title % {'receiver': self.receiver.username, 'status': self.status,
'model': self._model_name, 'id': self.object_id}
class Meta:
verbose_name_plural = _('notifications')
ordering = ['-created']
2. in your views.py
from django.views.generic import (ListView, DetailView)
from yourapp.models import Notification
class NotificationListView(ListView):
model = Notification
context_object_name = 'notifications'
paginate_by = 10
template_name = 'yourapp/notifications.html'
def get_queryset(self):
notifications = self.model.objects.filter(receiver=self.request.user)
# mark as reads if `user` is visit on this page.
notifications.update(is_read=True)
return notifications
3. in your yourapp/notifications.html
{% extends "base.html" %}
{% for notif in notifications %}
{{ notif }}
{# for specific is like below #}
{# `specific_model_name` eg: `comment`, `message`, `post` #}
{% if notif._model_name == 'specific_model_name' %}
{# do_stuff #}
{% endif %}
{% endfor %}
So, when I creat that notifications? eg: when the other user send a comment to
receiver
on this post.
from django.contrib.contenttypes.models import ContentType
def send_a_comment(request):
if request.method == 'POST':
form = SendCommentForm(request.POST)
if form.is_valid():
instance = form.save(commit=False)
#instance.sender = request.user
...
instance.save()
receiver = User.objects.filter(email=instance.email).first()
content_type = ContentType.objects.get(model='comment')
notif = Notification.objects.create(
receiver=receiver,
#sender=request.user,
content_type=content_type,
object_id=instance.id,
status='comment'
)
notif.save()
How about menu? Like this stackoverflow, facebook, instagram, or else?
you can handle it with templatetags, eg:
# yourapp/templatetags/notification_tags.py
from django import template
from yourapp.models import Notification
register = template.Library()
@register.filter
def has_unread_notif(user):
notifications = Notification.objects.filter(receiver=user, is_read=False)
if notifications.exists():
return True
return False
and the navs.html
menu:
{% load notification_tags %}
{% if request.user.is_authenticated %}
<ul class="authenticated-menu">
<li>
<a href="/notifications/">
{% if request.user|has_unread_notif %}
<i class="globe red active icon"></i>
{% else %}
<i class="globe icon"></i>
{% endif %}
</a>
</li>
</ul>
{% endif %}
Upvotes: 8