Skip to content

Commit

Permalink
Implement lending system
Browse files Browse the repository at this point in the history
  • Loading branch information
Arashfa0301 authored and jonasdeluna committed Oct 19, 2023
1 parent 6abb9ef commit d9db172
Show file tree
Hide file tree
Showing 16 changed files with 349 additions and 0 deletions.
5 changes: 5 additions & 0 deletions lego/api/v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@
from lego.apps.users.views.registration import UserRegistrationRequestViewSet
from lego.apps.users.views.user_delete import UserDeleteViewSet
from lego.apps.users.views.users import UsersViewSet
from lego.apps.lending.views import LendableObjectViewSet, LendingInstanceViewSet

from lego.utils.views import SiteMetaViewSet

router = routers.DefaultRouter()
Expand Down Expand Up @@ -141,6 +143,9 @@
basename="abakusgroup-memberships",
)
router.register(r"joblistings", JoblistingViewSet, basename="joblisting")
router.register(r"lendableobject", LendableObjectViewSet, basename="lendableobject")
router.register(r"lendinginstance", LendingInstanceViewSet, basename="lendinginstance")

router.register(
r"meeting-token", MeetingInvitationTokenViewSet, basename="meeting-token"
)
Expand Down
Empty file added lego/apps/lending/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions lego/apps/lending/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class LendingConfig(AppConfig):
name = "lego.apps.lending"
verbose_name = "Lending"
20 changes: 20 additions & 0 deletions lego/apps/lending/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from django_filters.rest_framework import FilterSet

from lego.apps.lending.models import LendableObject, LendingInstance


class LendableObjectFilterSet(FilterSet):
class Meta:
model = LendableObject
fields = ["title"]


class LendingInstanceFilterSet(FilterSet):
class Meta:
model = LendingInstance
fields = [
"user",
"lendable_object",
"start_date",
"pending",
]
25 changes: 25 additions & 0 deletions lego/apps/lending/managers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from lego.utils.managers import PersistentModelManager


class LendingInstanceManager(PersistentModelManager):
def create(self, *args, **kwargs):
from lego.apps.users.models import Membership, User
from lego.apps.lending.notifications import LendingInstanceNotification

lending_instance = super().create(*args, **kwargs)
abakus_groups = lending_instance.lendable_object.responsible_groups.all()
role = lending_instance.lendable_object.responsible_role

users_to_be_notified = Membership.objects.filter(
abakus_group__in=abakus_groups, role=role
).values_list("user", flat=True)

for user_id in users_to_be_notified:
user = User.objects.get(pk=user_id)
notification = LendingInstanceNotification(
lending_instance=lending_instance,
user_email=user,
)
notification.notify()

return lending_instance
60 changes: 60 additions & 0 deletions lego/apps/lending/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Generated by Django 4.0.10 on 2023-10-04 19:54

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone


class Migration(migrations.Migration):

initial = True

dependencies = [
('users', '0041_user_linkedin_id_alter_user_github_username'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='LendableObject',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(db_index=True, default=django.utils.timezone.now, editable=False)),
('updated_at', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
('deleted', models.BooleanField(db_index=True, default=False, editable=False)),
('title', models.CharField(max_length=128)),
('description', models.TextField(blank=True)),
('has_contract', models.BooleanField(default=False)),
('max_lending_period', models.DurationField(null=True)),
('responsible_role', models.CharField(choices=[('member', 'member'), ('leader', 'leader'), ('co-leader', 'co-leader'), ('treasurer', 'treasurer'), ('recruiting', 'recruiting'), ('development', 'development'), ('editor', 'editor'), ('retiree', 'retiree'), ('media_relations', 'media_relations'), ('active_retiree', 'active_retiree'), ('alumni', 'alumni'), ('webmaster', 'webmaster'), ('interest_group_admin', 'interest_group_admin'), ('alumni_admin', 'alumni_admin'), ('retiree_email', 'retiree_email'), ('company_admin', 'company_admin'), ('dugnad_admin', 'dugnad_admin'), ('trip_admin', 'trip_admin'), ('sponsor_admin', 'sponsor_admin'), ('social_admin', 'social_admin')], default='member', max_length=30)),
('created_by', models.ForeignKey(default=None, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created', to=settings.AUTH_USER_MODEL)),
('responsible_groups', models.ManyToManyField(to='users.abakusgroup')),
('updated_by', models.ForeignKey(default=None, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated', to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
'default_manager_name': 'objects',
},
),
migrations.CreateModel(
name='LendingInstance',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(db_index=True, default=django.utils.timezone.now, editable=False)),
('updated_at', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
('deleted', models.BooleanField(db_index=True, default=False, editable=False)),
('start_date', models.DateTimeField(null=True)),
('end_date', models.DateTimeField(null=True)),
('pending', models.BooleanField(default=True)),
('created_by', models.ForeignKey(default=None, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created', to=settings.AUTH_USER_MODEL)),
('lendable_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='lending.lendableobject')),
('updated_by', models.ForeignKey(default=None, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated', to=settings.AUTH_USER_MODEL)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
'default_manager_name': 'objects',
},
),
]
Empty file.
42 changes: 42 additions & 0 deletions lego/apps/lending/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from datetime import datetime, timedelta, timezone, tzinfo

from django.db import models

from lego.apps.users import constants
from lego.apps.lending.managers import LendingInstanceManager


from lego.utils.models import BasisModel


# Create your models here.


class LendableObject(BasisModel):
title = models.CharField(max_length=128, null=False, blank=False)
description = models.TextField(null=False, blank=True)
has_contract = models.BooleanField(default=False, null=False, blank=False)
max_lending_period = models.DurationField(null=True, blank=False)
responsible_groups = models.ManyToManyField("users.AbakusGroup")
responsible_role = models.CharField(
max_length=30, choices=constants.ROLES, default=constants.MEMBER
)
location = models.CharField(max_length=128, null=False, blank=False)

@property
def get_furthest_booking_date(self):
return timezone.now() + timedelta(days=14)


class LendingInstance(BasisModel):
user = models.ForeignKey("users.User", on_delete=models.CASCADE)
lendable_object = models.ForeignKey(LendableObject, on_delete=models.CASCADE)
start_date = models.DateTimeField(null=True)
end_date = models.DateTimeField(null=True)
pending = models.BooleanField(default=True)

objects = LendingInstanceManager() # type: ignore

@property
def active(self):
return timezone.now() < self.end_date and timezone.now() > self.start_date
40 changes: 40 additions & 0 deletions lego/apps/lending/notifications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from lego.apps.lending.models import LendingInstance
from lego.apps.notifications.notification import Notification
from lego.apps.users.models import User


class LendingInstanceNotification(Notification):
def __init__(self, lending_instance: LendingInstance, user: User):
self.lending_instance = lending_instance
self.user = user

# TODO: Might not work
super().__init__(user=lending_instance.user)

name = "lending_instance_creation"

def generate_mail(self):
return self._delay_mail(
to_email=self.user.email_address,
context={
"user": self.user.full_name,
"lendable_object": self.lending_instance.lendable_object.title,
"start_date": self.lending_instance.start_date,
"end_date": self.lending_instance.end_date,
},
subject="Utlån forespørsel",
plain_template="users/email/lending_instance.txt",
html_template="users/email/lending_instance.html",
)

def generate_push(self):
return self._delay_push(
template="users/push/lending_instance.txt",
context={
"user": self.user.full_name,
"lendable_object": self.lending_instance.lendable_object.title,
"start_date": self.lending_instance.start_date,
"end_date": self.lending_instance.end_date,
},
instance=self.lending_instance,
)
37 changes: 37 additions & 0 deletions lego/apps/lending/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from rest_framework import serializers


from lego.apps.lending.models import LendableObject, LendingInstance


class LendableObjectSerializer(serializers.ModelSerializer):
class Meta:
model = LendableObject
fields = "__all__"


class LendingInstanceSerializer(serializers.ModelSerializer):
class Meta:
model = LendingInstance
fields = "__all__"

def validate(self, data):
lendable_object_id = data["lendable_object"].id
lendable_object = LendableObject.objects.get(id=lendable_object_id)
user = self.request.user

if not user.abakus_groups.filter(
id__in=lendable_object.responsible_groups.all().values_list("id", flat=True)
).exists():
if (
data["end_date"] - data["start_date"]
> lendable_object.max_lending_period
):
raise serializers.ValidationError(
"Lending period exceeds maximum allowed duration"
)

# Add additional validation logic as per your use case
# ...

return data
3 changes: 3 additions & 0 deletions lego/apps/lending/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.
60 changes: 60 additions & 0 deletions lego/apps/lending/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from rest_framework import (
decorators,
exceptions,
mixins,
permissions,
renderers,
status,
viewsets,
)
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet

from lego.apps.lending.filters import LendableObjectFilterSet, LendingInstanceFilterSet
from lego.apps.lending.models import LendableObject, LendingInstance
from lego.apps.lending.serializers import (
LendableObjectSerializer,
LendingInstanceSerializer,
)
from lego.apps.permissions.api.views import AllowedPermissionsMixin


class LendableObjectViewSet(
AllowedPermissionsMixin,
mixins.CreateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet,
):
queryset = LendableObject.objects.all()
serializer_class = LendableObjectSerializer
filterset_class = LendableObjectFilterSet
http_method_names = ["get", "post", "patch", "delete", "head", "options"]
permission_classes = [
permissions.IsAuthenticated,
]


class LendingInstanceViewSet(
AllowedPermissionsMixin,
mixins.CreateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet,
):
queryset = LendingInstance.objects.all()
serializer_class = LendingInstanceSerializer
filterset_class = LendingInstanceFilterSet
http_method_names = ["get", "post", "patch", "delete", "head", "options"]
permission_classes = [
permissions.IsAuthenticated,
]

def create(self, request):
serializer = LendingInstanceSerializer(request, data=request.data)

if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(data=serializer.data, status=status.HTTP_201_CREATED)

return Response(data=serializer.errors, status=status.HTTP_400_BAD_REQUEST)
31 changes: 31 additions & 0 deletions lego/apps/users/templates/users/email/lending_instance.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{% extends "email/base.html" %}

{% block alert %}

<tr>
<td class="alert alert-warning">
Det er en ny forespørsel om å låne {{ lendable_object }}!
</td>
</tr>

{% endblock %}

{% block content %}

<tr>
<td class="content-block">
Bruker: {{ user }}!
</td>
</tr>



<tr>
<td class="content-block">
<i>Fra: {{ start_date }}, Til: {{ end_date }} </i>
</td>
</tr>



{% endblock %}
14 changes: 14 additions & 0 deletions lego/apps/users/templates/users/email/lending_instance.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{% extends "email/base.txt" %}

{% block content %}

Det er en ny forespørsel om å låne {{ lendable_object }}!

Bruker: {{ user }}!

Fra: {{ start_date }}, Til: {{ end_date }}




{% endblock %}
5 changes: 5 additions & 0 deletions lego/apps/users/templates/users/push/lending_instance.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Det er en ny forespørsel om å låne {{ lendable_object }}!

Bruker: {{ user }}!

Fra: {{ start_date }}, Til: {{ end_date }}
1 change: 1 addition & 0 deletions lego/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"lego.apps.tags",
"lego.apps.users",
"lego.apps.websockets",
"lego.apps.lending",
]

DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
Expand Down

0 comments on commit d9db172

Please sign in to comment.