Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Notifications app for yaksh #709

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Empty file added notification/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions notification/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.contrib import admin

from .models import Subscription


admin.site.register(Subscription)
5 changes: 5 additions & 0 deletions notification/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class NotificationConfig(AppConfig):
name = 'notification'
26 changes: 26 additions & 0 deletions notification/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
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


class Subscription(models.Model):
user = models.ForeignKey(User,
related_name='subscription',
db_index=True,
on_delete=models.CASCADE)
subscribe = models.BooleanField(default=True)
target_ct = models.ForeignKey(ContentType,
blank=True,
null=True,
related_name='target_obj',
on_delete=models.CASCADE
)
target_id = models.PositiveIntegerField(null=True,
blank=True,
db_index=True)
target = GenericForeignKey('target_ct', 'target_id')
created = models.DateTimeField(auto_now_add=True, db_index=True)

class Meta:
ordering = ('-created', )
139 changes: 139 additions & 0 deletions notification/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
from textwrap import dedent
from collections import OrderedDict

from django.utils import timezone
from django.core.mail import send_mail
from django.template.loader import render_to_string
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User
from django.conf import settings

from celery import task
from celery import shared_task

from notifications_plugin.models import NotificationMessage, Notification

from yaksh.models import Course, Quiz, QuestionPaper, AnswerPaper, Post
from .models import Subscription


@task(name='course_deadline_task')
def course_deadline_task():
courses = Course.objects.filter(active=True, is_trial=False)
for course in courses:
if course.is_active_enrollment():
message = dedent("""
The deadline for the course {0} is {1}, please complete
the course if not completed before the deadline.
""".format(course.name, course.end_enroll_time)
)
creator = course.creator
student_ids = course.students.values_list('id', flat=True)
if student_ids:
notification_type = "warning"
nm = NotificationMessage.objects.add_single_message(
creator_id=creator.id, summary='Course Notification',
description=message, msg_type=notification_type
)
Notification.objects.add_bulk_user_notifications(
receiver_ids=student_ids, msg_id=nm.id
)


@task(name='quiz_deadline_task')
def quiz_deadline_task():
courses = Course.objects.filter(active=True, is_trial=False)
for course in courses:
student_ids = course.students.values_list('id', flat=True)
creator = course.creator
quizzes = course.get_quizzes()
if student_ids:
for quiz in quizzes:
if not quiz.is_expired():
message = dedent("""
The deadline for the quiz {0} of course {1} is
{2}, please complete the quiz if not completed
before the deadline.
""".format(
quiz.description, course.name, quiz.end_date_time
)
)
notification_type = 'warning'
nm = NotificationMessage.objects.add_single_message(
creator_id=creator.id, summary='Quiz Notification',
description=message, msg_type=notification_type
)
Notification.objects.add_bulk_user_notifications(
receiver_ids=student_ids, msg_id=nm.id
)


@task(name='course_quiz_deadline_mail_task')
def course_quiz_deadline_mail_task():
ct = ContentType.objects.get_for_model(Course)
subs = Subscription.objects.filter(target_ct=ct, subscribe=True)
if subs.exists():
for sub in subs:
course = sub.target
if course.is_active_enrollment():
user = sub.user
data = course.get_quizzes_digest(user)
subject = 'Quiz deadline notification for course {0}'.format(
course.name
)
msg_html = render_to_string('notification/email.html', {
'data': data
})
msg_plain = render_to_string('notification/email.txt', {
'data': data
})
send_mail(
subject, msg_plain,
settings.SENDER_EMAIL, [user.email],
html_message=msg_html
)


@shared_task
def notify_teachers(data):
teacher_ids = data.get("teacher_ids")
new_post_id = data.get("new_post_id")
course_id = data.get("course_id")
student_id = data.get("student_id")
course = Course.objects.get(id=course_id)
creator = course.creator
post = Post.objects.get(id=new_post_id)
student = User.objects.get(id=student_id)
teacher_ids = User.objects.filter(id__in=teacher_ids).values_list('id', flat=True)
message = '{0} has asked a question in forum on course {1}'.format(
student.get_full_name(), course.name
)
notification_type = 'success'
nm = NotificationMessage.objects.add_single_message(
creator_id=creator.id, summary='Forum Notification',
description=message, msg_type=notification_type
)
Notification.objects.add_bulk_user_notifications(
receiver_ids=teacher_ids, msg_id=nm.id
)


@shared_task
def notify_post_creator(data):
course_id = data.get("course_id")
post_id = data.get("post_id")
course = Course.objects.get(id=course_id)
course_creator = course.creator
post = Post.objects.get(id=post_id)
post_creator = post.creator
message = 'You received comment on your post {0} in course {1}'.format(
post.title, course.name
)
notification_type = 'success'
nm = NotificationMessage.objects.add_single_message(
creator_id=course_creator.id, summary='Forum Notification',
description=message, msg_type=notification_type
)
Notification.objects.add_single_notification(
receiver_id=post_creator.id, msg_id=nm.id
)
54 changes: 54 additions & 0 deletions notification/templates/notification/email.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
body {
margin: 0;
padding: 0;
}
table {
border-collapse: collapse;
width: 100%;
}

th, td {
padding: 8px;
text-align: left;
border-bottom: 1px solid #ddd;
}
</style>
</head>
<body>
{% with data.0 as data %}
<h2>Course:{{data.course.name}}</h2>
<p>The deadline for the course is <strong>{{data.course.end_enroll_time}}</strong></p>
<p>Below is the table of the quizzes for the course <strong>{{data.course.name}}</strong>. Please
make sure to <strong>complete</strong> these quizzes before the <strong>deadline</strong>.</p>
{% endwith %}
<table>
<thead>
<tr>
<th>Quiz</th>
<th>Deadline</th>
<th>Completed</th>
</tr>
</thead>
<tbody>
{% for quiz in data %}
<tr>
<td>{{quiz.quiz.description}}</td>
<td>{{quiz.quiz.end_date_time|date:'Y-M-D h:i A'}}</td>
<td>
{% if quiz.ap.exists %}
Complete
{% else %}
Incomplete
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
54 changes: 54 additions & 0 deletions notification/templates/notification/email.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
body {
margin: 0;
padding: 0;
}
table {
border-collapse: collapse;
width: 100%;
}

th, td {
padding: 8px;
text-align: left;
border-bottom: 1px solid #ddd;
}
</style>
</head>
<body>
{% with data.0 as data %}
<h2>Course:{{data.course.name}}</h2>
<p>The deadline for the course is <strong>{{data.course.end_enroll_time}}</strong></p>
<p>Below is the table of the quizzes for the course <strong>{{data.course.name}}</strong>. Please
make sure to <strong>complete</strong> these quizzes before the <strong>deadline</strong>.</p>
{% endwith %}
<table>
<thead>
<tr>
<th>Quiz</th>
<th>Deadline</th>
<th>Completed</th>
</tr>
</thead>
<tbody>
{% for quiz in data %}
<tr>
<td>{{quiz.quiz.description}}</td>
<td>{{quiz.quiz.end_date_time|date:'Y-M-D h:i A'}}</td>
<td>
{% if quiz.ap.exists %}
Complete
{% else %}
Incomplete
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
3 changes: 3 additions & 0 deletions notification/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.test import TestCase

# Create your tests here.
10 changes: 10 additions & 0 deletions notification/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.conf.urls import url

from . import views

app_name = 'notification'

urlpatterns = [
url(r'(?P<course_id>\d+)/$',
views.toggle_subscription_status, name="toggle_subscription_status"),
]
30 changes: 30 additions & 0 deletions notification/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.contenttypes.models import ContentType
from django.contrib import messages


from yaksh.models import Course
from .models import Subscription


def toggle_subscription_status(request, course_id):
user = request.user
course = get_object_or_404(Course, pk=course_id)
if not course.is_creator(user) and not course.is_teacher(user):
raise Http404('This course does not belong to you')

ct = ContentType.objects.get_for_model(course)
course_sub = Subscription.objects.get(
target_ct=ct, user=user, target_id=course.id
)

if course_sub.subscribe:
course_sub.subscribe = False
message = 'Unsubscribed from the course mail letter.'
else:
course_sub.subscribe = True
message = 'Subscribed to the course mail letter.'
course_sub.save()
messages.info(request, message)

return redirect('yaksh:course_modules', course_id)
28 changes: 27 additions & 1 deletion online_test/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from yaksh.pipeline.settings import AUTH_PIPELINE
import os
from decouple import config
from celery.schedules import crontab

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
Expand Down Expand Up @@ -51,7 +52,8 @@
'rest_framework',
'api',
'corsheaders',
'rest_framework.authtoken'
'rest_framework.authtoken',
'notification'
)

MIDDLEWARE = (
Expand Down Expand Up @@ -229,6 +231,30 @@
CELERY_BROKER_URL = 'redis://localhost'
CELERY_RESULT_BACKEND = 'django-db'

CELERY_BEAT_SCHEDULE = {
'send-course-deadline-notifications-twice-a-week': {
'task': 'course_deadline_task',
'schedule': crontab(
hour='08', minute=30, day_of_week='*/3', day_of_month='*',
month_of_year='*'
),
},
'send-quiz-deadline-notifications-twice-a-week': {
'task': 'quiz_deadline_task',
'schedule': crontab(
hour='09', minute=00, day_of_week='*/3', day_of_month='*',
month_of_year='*'
),
},
'send_course_quiz_deadline_mail': {
'task': 'course_quiz_deadline_mail_task',
'schedule': crontab(
hour='09', minute=31, day_of_week='*/3', day_of_month='*',
month_of_year='*'
)
}
}

REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
Expand Down
2 changes: 1 addition & 1 deletion online_test/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@
url(r'^', include('social_django.urls', namespace='social')),
url(r'^grades/', include(('grades.urls', 'grades'))),
url(r'^api/', include('api.urls', namespace='api')),

url(r'^subscribe/', include('notification.urls', namespace='notification'))
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Loading