Skip to content

Commit 859742e

Browse files
authored
Merge 48d4811 into 6f13160
2 parents 6f13160 + 48d4811 commit 859742e

File tree

20 files changed

+364
-37
lines changed

20 files changed

+364
-37
lines changed

devops/lms.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,25 @@ services:
2929
- ./rabbitmq.cookie:/var/lib/rabbitmq/.erlang.cookie
3030
networks:
3131
- lms
32+
33+
mailer:
34+
image: lms:latest
35+
command: celery -A lms.utils worker
36+
volumes:
37+
- ../lms/utils/:/app_dir/lms/utils/
38+
environment:
39+
- CELERY_RABBITMQ_ERLANG_COOKIE=AAVyo5djdSMGIZXiwEQs3JeVaBx5l14z
40+
- CELERY_RABBITMQ_DEFAULT_USER=rabbit-user
41+
- CELERY_RABBITMQ_DEFAULT_PASS=YgKlCvnYVzpTa3T9adG3NrMoUNe4Z5aZ
42+
- CELERY_MAILER_VHOST=utils
43+
- CELERY_RABBITMQ_HOST=rabbitmq
44+
- CELERY_RABBITMQ_PORT=5672
45+
links:
46+
- rabbitmq
47+
depends_on:
48+
- rabbitmq
49+
networks:
50+
- lms
3251

3352
checks-sandbox:
3453
image: lms:latest

lms/lmsdb/bootstrap.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,18 @@ def _add_uuid_to_users_table(table: Model, _column: Field) -> None:
255255
user.save()
256256

257257

258+
def _add_mail_subscription_to_users_table(
259+
table: Model, _column: Field,
260+
) -> None:
261+
log.info(
262+
'Adding mail subscription for users, might take some extra time...',
263+
)
264+
with db_config.database.transaction():
265+
for user in table:
266+
user.mail_subscription = True
267+
user.save()
268+
269+
258270
def _api_keys_migration() -> bool:
259271
User = models.User
260272
_add_not_null_column(User, User.api_key, _add_api_keys_to_users_table)
@@ -316,6 +328,13 @@ def _uuid_migration() -> bool:
316328
return True
317329

318330

331+
def _mail_subscription() -> bool:
332+
User = models.User
333+
_add_not_null_column(
334+
User, User.mail_subscription, _add_mail_subscription_to_users_table,
335+
)
336+
337+
319338
def _assessment_migration() -> bool:
320339
Solution = models.Solution
321340
_add_not_null_column(Solution, Solution.assessment)
@@ -335,6 +354,7 @@ def main():
335354
_api_keys_migration()
336355
_last_course_viewed_migration()
337356
_uuid_migration()
357+
_mail_subscription()
338358

339359
if models.database.table_exists(models.UserCourse.__name__.lower()):
340360
_add_user_course_constaint()

lms/lmsdb/models.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ class User(UserMixin, BaseModel):
180180
api_key = CharField()
181181
last_course_viewed = ForeignKeyField(Course, null=True)
182182
uuid = UUIDField(default=uuid4, unique=True)
183+
mail_subscription = BooleanField(default=True)
183184

184185
def get_id(self):
185186
return str(self.uuid)
@@ -383,6 +384,41 @@ def on_notification_saved(
383384
instance.delete_instance()
384385

385386

387+
class MailMessage(BaseModel):
388+
user = ForeignKeyField(User, backref='mails')
389+
notification = ForeignKeyField(Notification, backref='mails')
390+
date = DateTimeField(default=datetime.now)
391+
392+
@classmethod
393+
def distincit_users(cls):
394+
return cls.select(cls.user).distinct()
395+
396+
@classmethod
397+
def by_user(cls, user: User) -> Iterable['MailMessage']:
398+
return (
399+
cls
400+
.select()
401+
.where(cls.user == user)
402+
.join(Notification)
403+
.where(Notification.viewed == False) # NOQA: E712
404+
)
405+
406+
@classmethod
407+
def user_messages_number(cls, user: User) -> int:
408+
return (
409+
cls
410+
.select(fn.Count(cls.id))
411+
.where(cls.user == user)
412+
.join(Notification)
413+
.where(Notification.viewed == False) # NOQA: E712
414+
.scalar()
415+
)
416+
417+
@classmethod
418+
def get_instances_number(cls):
419+
return cls.select(fn.Count(cls.id))
420+
421+
386422
class Exercise(BaseModel):
387423
subject = CharField()
388424
date = DateTimeField()

lms/lmsweb/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import typing
44

55
from flask import Flask
6+
from flask_apscheduler import APScheduler # type: ignore
67
from flask_babel import Babel # type: ignore
78
from flask_httpauth import HTTPBasicAuth
89
from flask_limiter import Limiter # type: ignore
@@ -48,6 +49,9 @@
4849

4950
webmail = Mail(webapp)
5051

52+
webscheduler = APScheduler(app=webapp)
53+
webscheduler.start()
54+
5155

5256
# Must import files after app's creation
5357
from lms.lmsdb import models # NOQA: F401, E402, I202

lms/lmsweb/config.py.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ LOCALE = 'en'
6464
LIMITS_PER_MINUTE = 5
6565
LIMITS_PER_HOUR = 50
6666

67+
# Scheduler
68+
SCHEDULER_API_ENABLED = True
69+
DEFAULT_DO_TASKS_EVERY_HOURS = 2
70+
6771
# Change password settings
6872
MAX_INVALID_PASSWORD_TRIES = 5
6973

lms/lmsweb/translations/he/LC_MESSAGES/messages.po

Lines changed: 61 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ msgstr ""
1818
"Content-Transfer-Encoding: 8bit\n"
1919
"Generated-By: Babel 2.9.1\n"
2020

21-
#: lmsdb/models.py:921
21+
#: lmsdb/models.py:951
2222
msgid "Fatal error"
2323
msgstr "כישלון חמור"
2424

@@ -75,6 +75,22 @@ msgstr "קישור לאיפוס הסיסמה נשלח בהצלחה"
7575
msgid "Reset password link is expired"
7676
msgstr "קישור איפוס הסיסמה פג תוקף"
7777

78+
#: lmsweb/views.py:376 templates/user.html:23
79+
msgid "Mail Subscription"
80+
msgstr "מנוי להודעות דוא\"ל"
81+
82+
#: lmsweb/views.py:378
83+
msgid "You've successfully subscribed to get mails for new notifications"
84+
msgstr "נרשמת בהצלחה לקבלת הודעות דוא\"ל עבור התראות חדשות"
85+
86+
#: lmsweb/views.py:383
87+
msgid "You've successfully unsubscribed to get mails for new notifications"
88+
msgstr "ביטלת בהצלחה את ההרשמה לקבלת הודעות דוא\"ל עבור התראות חדשות"
89+
90+
#: lmsweb/views.py:388
91+
msgid "Something went wrong..."
92+
msgstr "משהו השתבש..."
93+
7894
#: lmsweb/forms/change_password.py:20 lmsweb/forms/register.py:32
7995
#: lmsweb/forms/reset_password.py:25
8096
msgid "The passwords are not identical"
@@ -124,7 +140,7 @@ msgstr "שם המשתמש או הסיסמה שהוזנו לא תקינים"
124140
msgid "You have to confirm your registration with the link sent to your email"
125141
msgstr "עליך לאשר את מייל האימות"
126142

127-
#: models/users.py:50
143+
#: models/users.py:61
128144
#, python-format
129145
msgid "You are already registered to %(course_name)s course."
130146
msgstr "אתה כבר רשום לקורס %(course_name)s."
@@ -302,7 +318,7 @@ msgstr "חמ\"ל תרגילים"
302318
msgid "Name"
303319
msgstr "שם"
304320

305-
#: templates/status.html:13 templates/user.html:47
321+
#: templates/status.html:13 templates/user.html:53
306322
msgid "Checked"
307323
msgstr "נבדק/ו"
308324

@@ -328,7 +344,7 @@ msgstr "העלאת מחברות"
328344

329345
#: templates/upload.html:11
330346
msgid "Drag here the notebook file or click and choose it from your computer."
331-
msgstr "גררו לכאן את קובץ המחברת, או לחצו ובחרו אותה מהמחשב שלכם."
347+
msgstr "גררו לכאן את קובץ המחברת, או לחצו ובחרו אותה מהמחשב שלכם."
332348

333349
#: templates/upload.html:14
334350
msgid "Back to Exercises List"
@@ -350,63 +366,63 @@ msgstr "פרטי משתמש"
350366
msgid "Actions"
351367
msgstr "פעולות"
352368

353-
#: templates/user.html:21
369+
#: templates/user.html:27
354370
msgid "Join Courses"
355371
msgstr "הירשם לקורסים"
356372

357-
#: templates/user.html:27
373+
#: templates/user.html:33
358374
msgid "Exercises Submitted"
359375
msgstr "תרגילים שהוגשו"
360376

361-
#: templates/user.html:32
377+
#: templates/user.html:38
362378
msgid "Course name"
363379
msgstr "שם קורס"
364380

365-
#: templates/user.html:33
381+
#: templates/user.html:39
366382
msgid "Exercise name"
367383
msgstr "שם תרגיל"
368384

369-
#: templates/user.html:34
385+
#: templates/user.html:40
370386
msgid "Submission status"
371387
msgstr "מצב הגשה"
372388

373-
#: templates/user.html:35
389+
#: templates/user.html:41
374390
msgid "Submission"
375391
msgstr "הגשה"
376392

377-
#: templates/user.html:36
393+
#: templates/user.html:42
378394
msgid "Checker"
379395
msgstr "בודק"
380396

381-
#: templates/user.html:37 templates/view.html:25 templates/view.html:112
397+
#: templates/user.html:43 templates/view.html:25 templates/view.html:112
382398
msgid "Assessment"
383399
msgstr "הערה מילולית"
384400

385-
#: templates/user.html:47
401+
#: templates/user.html:53
386402
msgid "Submitted"
387403
msgstr "הוגש"
388404

389-
#: templates/user.html:47
405+
#: templates/user.html:53
390406
msgid "Not submitted"
391407
msgstr "לא הוגש"
392408

393-
#: templates/user.html:59
409+
#: templates/user.html:65
394410
msgid "Notes"
395411
msgstr "פתקיות"
396412

397-
#: templates/user.html:64 templates/user.html:66
413+
#: templates/user.html:70 templates/user.html:72
398414
msgid "New Note"
399415
msgstr "פתקית חדשה"
400416

401-
#: templates/user.html:70
417+
#: templates/user.html:76
402418
msgid "Related Exercise"
403419
msgstr "תרגיל משויך"
404420

405-
#: templates/user.html:79
421+
#: templates/user.html:85
406422
msgid "Privacy Level"
407423
msgstr "רמת פרטיות"
408424

409-
#: templates/user.html:85
425+
#: templates/user.html:91
410426
msgid "Add Note"
411427
msgstr "הוסף פתקית"
412428

@@ -478,12 +494,12 @@ msgstr "הערות בודק"
478494
msgid "Done Checking"
479495
msgstr "סיום בדיקה"
480496

481-
#: utils/mail.py:25
497+
#: utils/mail.py:30
482498
#, python-format
483499
msgid "Confirmation mail - %(site_name)s"
484500
msgstr "מייל אימות - %(site_name)s"
485501

486-
#: utils/mail.py:32
502+
#: utils/mail.py:37
487503
#, python-format
488504
msgid ""
489505
"Hello %(fullname)s,\n"
@@ -492,12 +508,12 @@ msgstr ""
492508
"שלום %(fullname)s,\n"
493509
"לינק האימות שלך למערכת הוא: %(link)s"
494510

495-
#: utils/mail.py:42
511+
#: utils/mail.py:47
496512
#, python-format
497513
msgid "Reset password mail - %(site_name)s"
498514
msgstr "מייל איפוס סיסמה - %(site_name)s"
499515

500-
#: utils/mail.py:49
516+
#: utils/mail.py:54
501517
#, python-format
502518
msgid ""
503519
"Hello %(fullname)s,\n"
@@ -506,12 +522,12 @@ msgstr ""
506522
"שלום %(fullname)s,\n"
507523
"לינק לצורך איפוס הסיסמה שלך הוא: %(link)s"
508524

509-
#: utils/mail.py:58
525+
#: utils/mail.py:63
510526
#, python-format
511527
msgid "Changing password - %(site_name)s"
512528
msgstr "שינוי סיסמה - %(site_name)s"
513529

514-
#: utils/mail.py:62
530+
#: utils/mail.py:67
515531
#, python-format
516532
msgid ""
517533
"Hello %(fullname)s. Your password in %(site_name)s site has been changed."
@@ -523,3 +539,23 @@ msgstr ""
523539
"אם אתה לא עשית את זה צור קשר עם הנהלת האתר.\n"
524540
"כתובת המייל: %(site_mail)s"
525541

542+
#: utils/mail.py:80
543+
#, python-format
544+
msgid "New notification - %(site_name)s"
545+
msgstr "התראה חדשה - %(site_name)s"
546+
547+
#: utils/mail.py:84
548+
#, python-format
549+
msgid ""
550+
"Hello %(fullname)s. You have %(num)d new notification:\n"
551+
"%(message)s"
552+
msgid_plural ""
553+
"Hello %(fullname)s. You have %(num)d new notifications:\n"
554+
"%(message)s"
555+
msgstr[0] ""
556+
"שלום %(fullname)s. יש לך %(num)d התראה חדשה:\n"
557+
"%(message)s"
558+
msgstr[1] ""
559+
"שלום %(fullname)s. יש לך %(num)d התראות חדשות:\n"
560+
"%(message)s"
561+

lms/lmsweb/views.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,25 @@ def read_all_notification():
370370
return jsonify({'success': success_state})
371371

372372

373+
@webapp.route('/mail/<subscription>', methods=['PATCH'])
374+
def mail_subscription(subscription: str):
375+
success_state = users.change_mail_subscription(current_user, subscription)
376+
title = _('Mail Subscription')
377+
if subscription == 'subscribe':
378+
body = _(
379+
"You've successfully subscribed to get mails "
380+
'for new notifications',
381+
)
382+
elif subscription == 'unsubscribe':
383+
body = _(
384+
"You've successfully unsubscribed to get mails "
385+
'for new notifications',
386+
)
387+
else:
388+
body = _('Something went wrong...')
389+
return jsonify({'success': success_state, 'title': title, 'body': body})
390+
391+
373392
@webapp.route('/share', methods=['POST'])
374393
@login_required
375394
def share():

lms/models/notifications.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import enum
22
from typing import Iterable, Optional
33

4-
from lms.lmsdb.models import Notification, User
4+
from lms.lmsdb.models import MailMessage, Notification, User
55

66

77
class NotificationKind(enum.Enum):
@@ -45,6 +45,8 @@ def send(
4545
related_id: Optional[int] = None,
4646
action_url: Optional[str] = None,
4747
) -> Notification:
48-
return Notification.send(
48+
notification = Notification.send(
4949
user, kind.value, message, related_id, action_url,
5050
)
51+
MailMessage.create(user=user, notification=notification)
52+
return notification

0 commit comments

Comments
 (0)