Skip to content

Commit

Permalink
Add notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
tnaccarato committed Mar 17, 2024
1 parent 25aa638 commit 002d501
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 7 deletions.
31 changes: 31 additions & 0 deletions payapp/migrations/0006_notification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Generated by Django 5.0.2 on 2024-03-17 15:02

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('payapp', '0005_rename_transaction_transfer'),
]

operations = [
migrations.CreateModel(
name='Notification',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('notification_type', models.CharField(choices=[(1, 'Payment Sent'), (2, 'Request Sent'), (3, 'Request Declined'), (4, 'Request Accepted')], default='payment_sent', max_length=10)),
('message', models.CharField(max_length=255)),
('created_at', models.DateTimeField(auto_now_add=True)),
('read', models.BooleanField(default=False)),
('from_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_notification', to='payapp.account')),
('to_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_notification', to='payapp.account')),
],
options={
'verbose_name': 'Notification',
'verbose_name_plural': 'Notifications',
'db_table': 'notification',
},
),
]
60 changes: 59 additions & 1 deletion payapp/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ def execute(self, amount):
self.receiver.balance += amount
self.amount = converted_amount

# Save the sender, receiver and transfer
self.sender.save()
self.receiver.save()
self.save()
Expand Down Expand Up @@ -191,7 +192,7 @@ def accept_request(self, amount):
with transaction.atomic():
# Creates a transaction
transfer = Transfer(sender=self.receiver, receiver=self.sender,
amount=amount, type='request')
amount=amount, type='request')
transfer.save()
# Executes the transaction
transfer.execute(amount)
Expand Down Expand Up @@ -223,3 +224,60 @@ def cancel_request(self):
self.status = 'cancelled'
self.save()
return None


class Notification(models.Model):
"""
Notification model for storing notification information.
Attributes:
- from_user: ForeignKey to User model for the user
- to_user: ForeignKey to User model for the user
- NOTIFICATION_TYPE_CHOICES: Tuple of tuples to store notification type choices
- notification_type: IntegerField to store notification type
- message: CharField to store notification message
- created_at: DateTimeField to store notification creation date
- read: BooleanField to store whether the notification has been read
Methods:
- __str__: Returns the notification message
- mark_as_read: Marks the notification as read
"""

class Meta:
db_table = 'notification'
verbose_name = 'Notification'
verbose_name_plural = 'Notifications'

from_user = models.ForeignKey(Account, on_delete=models.CASCADE, related_name='sent_notification')
to_user = models.ForeignKey(Account, on_delete=models.CASCADE, related_name='received_notification')
NOTIFICATION_TYPE_CHOICES = (
('payment_sent', 'Payment Sent'),
('request_sent', 'Request Sent'),
('request_accepted', 'Request Accepted'),
('request_declined', 'Request Declined'),
)

notification_type = models.CharField(max_length=20, choices=NOTIFICATION_TYPE_CHOICES, default='payment_sent')
message = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
read = models.BooleanField(default=False)

def __str__(self):
"""
Returns the notification message.
:return: str: The notification message
"""
return self.message

@transaction.atomic
def mark_as_read(self):
"""
Marks the notification as read.
:return: None
"""
self.read = True
self.save()
return None
1 change: 1 addition & 0 deletions payapp/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@
path('decline_request/<int:request_id>/', views.decline_request, name='decline_request'),
path('cancel_request/<int:request_id>/', views.cancel_request, name='cancel_request'),
path('send_payment/', views.send_payment, name='send_payment'),
path('notifications/', views.notifications, name='notifications'),
]
71 changes: 70 additions & 1 deletion payapp/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@
from django.shortcuts import render, redirect, get_object_or_404
from payapp.custom_exceptions import InsufficientBalanceException
from payapp.forms import RequestForm, PaymentForm
from payapp.models import Transfer, Account, Request
from payapp.models import Transfer, Account, Request, Notification
from webapps2024 import settings
from django.db import transaction

currency_symbols = {
'USD': '$',
'GBP': '£',
'EUR': '€'
}

def login_required_message(function):
"""
Expand Down Expand Up @@ -127,6 +132,15 @@ def make_request(request):
# If the receiver is not the sender, save the request
if request_instance.receiver != request_instance.sender:
request_instance.save()
notification = Notification.objects.create(
to_user=request_instance.receiver,
from_user=request_instance.sender,
message=f"{request_instance.sender.user.username} has requested "
f"{currency_symbols.get(request_instance.sender.currency.upper())}"
f"{request_instance.amount}",
notification_type='request_sent',
created_at=request_instance.created_at
)
messages.success(request, "Request has been made")

# If the receiver is the sender, display an error message and return the form
Expand Down Expand Up @@ -165,6 +179,16 @@ def accept_request(request, request_id):
try:
req = get_object_or_404(Request, id=request_id)
req.accept_request(req.amount)
# Adds a notification to the sender's account
notification = Notification.objects.create(
to_user=req.sender,
from_user=req.receiver,
message=f"Your request for {currency_symbols.get(req.sender.currency.upper())}{req.amount} from "
f"{req.receiver.user.username} has been accepted.",
notification_type='request_accepted',
created_at=req.created_at
)
notification.save()
messages.success(request, "Request has been accepted")
return redirect('payapp:requests')

Expand Down Expand Up @@ -200,6 +224,16 @@ def decline_request(request, request_id):
try:
req = get_object_or_404(Request, id=request_id)
req.decline_request()
# Adds a notification to the sender's account
notification = Notification.objects.create(
to_user=req.sender,
from_user=req.receiver,
message=f"Your request for {currency_symbols.get(req.sender.currency.upper())}{req.amount} from "
f"{req.receiver.user.username} has been declined.",
notification_type='request_declined',
created_at=req.created_at
)
notification.save()
messages.success(request, "Request has been declined.")
return redirect('payapp:requests')
# If the request does not exist, display an error message and redirect to the requests page
Expand Down Expand Up @@ -258,9 +292,23 @@ def send_payment(request):
transaction_instance = form.save(commit=False)
transaction_instance.sender = Account.objects.get(user=request.user)
transaction_instance.receiver = Account.objects.get(user__username=request.POST['receiver'])
# Makes sure the sender is not the receiver
if transaction_instance.receiver != transaction_instance.sender:
transaction_instance.execute(transaction_instance.amount)
transaction_instance.save()
# Adds a notification to the receiver's account
notification = Notification.objects.create(
to_user=transaction_instance.receiver,
from_user=transaction_instance.sender,
message=f"You have received {currency_symbols.get(account.currency.upper())}"
f"{transaction_instance.amount} from "
f"{transaction_instance.sender.user.username}",
notification_type='payment_sent',
created_at=transaction_instance.created_at
)
print(notification.message)
notification.save()

messages.success(request, "Payment has been made")
return redirect('home')
else:
Expand Down Expand Up @@ -292,3 +340,24 @@ def send_payment(request):
else:
form = PaymentForm(user_currency=account.currency)
return render(request, 'payapp/send_payment.html', {'form': form})


@login_required_message
def notifications(request):
"""
View function to display the notifications of the logged-in user
:param request:
:return:
"""
user = request.user
account = Account.objects.get(user=user)
# Select notifications where the receiver is the logged-in user
notifications_list = (Notification.objects.filter(to_user=account, read=False))
if notifications_list.exists():
notifications_list = notifications_list.order_by('-created_at')
# Mark the notifications as read if the user views the notifications page
for notification in notifications_list:
notification.mark_as_read()
return render(request, 'payapp/notifications.html', {'notifications': notifications_list})

1 change: 0 additions & 1 deletion static/css/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ body {
background: none !important;
color: white !important;
border: none !important;
padding: 0 !important;
font: inherit !important;
cursor: pointer !important;
text-decoration: none !important;
Expand Down
24 changes: 20 additions & 4 deletions templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,25 @@

<!-- User controls -->
<ul class="navbar-nav ms-auto">
<form action="{% url 'register:logout' %}?next=/webapps2024/" method="post" class="form-inline">
{% csrf_token %}
<button class="btn btn-link link-like-button" type="submit">Logout</button>
</form>
<li class="nav-item">
<a class="nav-link" href="{% url 'payapp:notifications' %}">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
class="bi bi-bell" viewBox="0 0 16 16">
<path d="M8 16a2 2 0 0 0 2-2H6a2 2 0 0 0 2 2M8 1.918l-.797.161A4 4 0 0 0 4 6c0
.628-.134 2.197-.459 3.742-.16.767-.376 1.566-.663
2.258h10.244c-.287-.692-.502-1.49-.663-2.258C12.134 8.197 12 6.628 12 6a4 4 0 0
0-3.203-3.92zM14.22 12c.223.447.481.801.78 1H1c.299-.199.557-.553.78-1C2.68
10.2 3 6.88 3 6c0-2.42 1.72-4.44 4.005-4.901a1 1 0 1 1 1.99 0A5 5 0 0 1 13 6c0
.88.32 4.2 1.22 6"></path>
</svg>
</a>
</li>
<li class="nav-item">
<form action="{% url 'register:logout' %}?next=/webapps2024/" method="post" class="form-inline">
{% csrf_token %}
<button class="btn btn-link link-like-button nav-link" type="submit">Logout</button>
</form>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{% url 'register:login' %}">Login</a>
Expand All @@ -90,6 +105,7 @@
</li>
{% endif %}
</ul>

</div>
</div>
</nav>
Expand Down
44 changes: 44 additions & 0 deletions templates/payapp/notifications.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{% extends 'base.html' %}
{% load crispy_forms_filters %}
{% load crispy_forms_tags %}
<!DOCTYPE html>
<html>
<head>
<title>{% block title %} All Notifications{% endblock title %}</title>

</head>
<body>
{% block content %}
{% if notifications|length == 0 %}
<p>You have no unread notifications.</p>
{% else %}:
<h1>All Notifications</h1>
<div class="table-responsive">
<table class="table table-striped table-bordered table-hover table-sm">

<thead class="text-center">
<tr>
<th>Notification</th>
<th>Time</th>
</tr>
</thead>
<tbody>
{% for notification in notifications %}
<tr class="text-center">

<td><a {% if notification.notification_type == 'payment_sent' %}
href="{% url "payapp:transfers" %}"
{% else %}
href="{% url "payapp:requests" %}"
{% endif %}>{{ notification.message }} </a>
</td>
<td>{{ notification.created_at }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% endblock content %}
</body>
</html>
Binary file modified webapps.db
Binary file not shown.

0 comments on commit 002d501

Please sign in to comment.