Skip to content
This repository has been archived by the owner on Feb 5, 2024. It is now read-only.

Commit

Permalink
Merge pull request #274 from SELab-2/development
Browse files Browse the repository at this point in the history
🚀 Release
  • Loading branch information
JasperJanin authored Apr 20, 2023
2 parents 930e7c7 + 7e56223 commit b8a02ef
Show file tree
Hide file tree
Showing 140 changed files with 6,246 additions and 686 deletions.
12 changes: 0 additions & 12 deletions .env.dev

This file was deleted.

6 changes: 5 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ on:
- pull_request

jobs:
test:
docker-runs:
runs-on: self-hosted
steps:
- uses: actions/checkout@v3
Expand All @@ -15,6 +15,10 @@ jobs:
run: docker exec DrTrottoir-be python manage.py test
- name: Test NEXT.js Jest
run: docker exec DrTrottoir-fe npm run jest:workflow
- name: Test NEXT.js Cypress component
run: docker exec DrTrottoir-fe npm run component:headless
- name: Test NEXT.js Cypress e2e
run: docker exec DrTrottoir-fe npm run e2e:headless
- name: Linter Python
run: docker exec DrTrottoir-be flake8 backend/ drtrottoir/
- name: Linter Javascript
Expand Down
7 changes: 6 additions & 1 deletion backend/backend/settings.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
from datetime import timedelta
from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
Expand Down Expand Up @@ -163,7 +164,6 @@
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'uploads'


# Default primary key field type
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field

Expand All @@ -172,3 +172,8 @@
API_DOCS_TITLE = 'DrTrottoir API'
API_DOCS_DESCRIPTION = 'This is an overview of all API endpoints on the DrTrottoir API, what you see depends on your ' \
'level of authentication, go to /admin to log in first. '

SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=int(os.environ.get("ACCES_TOKEN_LIFETIME", "1"), )),
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Generated by Django 4.1.7 on 2023-04-13 14:06

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


class Migration(migrations.Migration):

dependencies = [
('drtrottoir', '0015_alter_customuser_buildings'),
]

operations = [
migrations.AddField(
model_name='visit',
name='schedule',
field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.PROTECT, to='drtrottoir.schedule', verbose_name='schedule linked to visit'),
preserve_default=False,
),
migrations.AddField(
model_name='waste',
name='action',
field=models.CharField(default=0, max_length=256, verbose_name='Action required'),
preserve_default=False,
),
migrations.AlterField(
model_name='buildingintour',
name='building',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='drtrottoir.building', verbose_name='id of building'),
),
migrations.AlterField(
model_name='schedule',
name='student',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='id of user'),
),
migrations.AlterField(
model_name='visit',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='id of user'),
),
]
18 changes: 18 additions & 0 deletions backend/drtrottoir/migrations/0017_alter_photo_created_at.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.1.7 on 2023-04-18 14:37

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('drtrottoir', '0016_visit_schedule_waste_action_and_more'),
]

operations = [
migrations.AlterField(
model_name='photo',
name='created_at',
field=models.DateTimeField(null=True, verbose_name='time of creation'),
),
]
1 change: 0 additions & 1 deletion backend/drtrottoir/models/building.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ class Building(models.Model):
country = models.CharField(verbose_name="countryname", max_length=256)

region = models.ForeignKey(Region, verbose_name="region of building", on_delete=models.PROTECT)
# waste schedule

def __str__(self):
return self.nickname
2 changes: 1 addition & 1 deletion backend/drtrottoir/models/building_in_tour.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

class BuildingInTour(models.Model):
tour = models.ForeignKey(Tour, verbose_name="id of tour", on_delete=models.CASCADE)
building = models.ForeignKey(Building, verbose_name="id of building", on_delete=models.CASCADE)
building = models.ForeignKey(Building, verbose_name="id of building", on_delete=models.PROTECT)
order_index = models.IntegerField(verbose_name="order index of the building in the tour")

def __str__(self):
Expand Down
2 changes: 1 addition & 1 deletion backend/drtrottoir/models/custom_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def has_module_perms(self, app_label):

@property
def is_super(self):
# Is this a superuser, student or a developer
# Is this a superuser, admin or a developer
return self.role <= Roles.SUPERSTUDENT

@property
Expand Down
17 changes: 15 additions & 2 deletions backend/drtrottoir/models/photo.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from django.db import models
from .visit import Visit
from django.dispatch import receiver
from django.db.models.signals import pre_delete
from django.db.models.signals import pre_delete, post_save
from PIL import Image
from datetime import datetime

IMAGE_STATES = ((1, 'Arrival'), (2, 'Departure'), (3, 'Extra'))

Expand All @@ -11,7 +13,7 @@ class Photo(models.Model):
visit = models.ForeignKey(Visit, verbose_name="id of visit", on_delete=models.CASCADE)
state = models.IntegerField(verbose_name="type of photo", choices=IMAGE_STATES)
comment = models.TextField(verbose_name="comment on the photo")
created_at = models.DateTimeField(verbose_name="time of creation")
created_at = models.DateTimeField(verbose_name="time of creation", null=True)

def __str__(self):
return f"{self.visit}, {self.created_at}"
Expand All @@ -20,3 +22,14 @@ def __str__(self):
@receiver(pre_delete, sender=Photo)
def pre_delete(sender, instance: Photo, **kwargs):
instance.image.delete()


@receiver(post_save, sender=Photo)
def post_save_callback(sender, instance, created, *args, **kwargs):
if created:
try:
# exif tag 36867 is DateTimeOriginal
# https://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html
instance.created_at = Image.open(instance.image.path)._getexif()[36867]
except Exception: # When DateTimeOriginal is not found in the metadata store current time
instance.created_at = datetime.now()
2 changes: 1 addition & 1 deletion backend/drtrottoir/models/schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

class Schedule(models.Model):
date = models.DateField(verbose_name="date of the schedule")
student = models.ForeignKey(CustomUser, verbose_name="id of user", on_delete=models.CASCADE)
student = models.ForeignKey(CustomUser, verbose_name="id of user", on_delete=models.PROTECT)
tour = models.ForeignKey(Tour, verbose_name="id of the scheduled tour", on_delete=models.CASCADE)
comment = models.TextField(verbose_name="comment on the scheduled tour", blank=True)

Expand Down
4 changes: 3 additions & 1 deletion backend/drtrottoir/models/visit.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@

from .building_in_tour import BuildingInTour
from .custom_user import CustomUser
from .schedule import Schedule


class Visit(models.Model):
arrival = models.DateTimeField(verbose_name="time of arrival")
building_in_tour = models.ForeignKey(BuildingInTour,
verbose_name="id of building in tour",
on_delete=models.CASCADE)
user = models.ForeignKey(CustomUser, verbose_name="id of user", on_delete=models.CASCADE)
user = models.ForeignKey(CustomUser, verbose_name="id of user", on_delete=models.PROTECT)
comment = models.TextField(verbose_name="Comment on the visit", blank=True)
schedule = models.ForeignKey(Schedule, verbose_name="schedule linked to visit", on_delete=models.PROTECT)

def __str__(self):
return f"{self.user.first_name}, {self.building_in_tour.building.nickname}: {self.arrival}"
3 changes: 2 additions & 1 deletion backend/drtrottoir/models/waste.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
class Waste(models.Model):
date = models.DateField("Date of waste collection")
waste_type = models.CharField("Type of waste", max_length=256)
action = models.CharField("Action required", max_length=256)
building = models.ForeignKey(Building, verbose_name="building of waste collection", on_delete=models.CASCADE)

def __str__(self):
return f'{self.waste_type}, {self.building.nickname}, {self.date}'
return f'{self.action} {self.waste_type}, {self.building.nickname}, {self.date}'
20 changes: 18 additions & 2 deletions backend/drtrottoir/permissions/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
from .user_permissions import SuperPermission, SuperPermissionOrReadOnly, AdminPermissionOrReadOnly
from .user_permissions import (
SuperPermission,
SuperPermissionOrReadOnly,
AdminPermissionOrReadOnly,
SuperStudentPermissionOrReadOnly,
AnyonePostSuperEditPermission,
UserViewSetPermission,
PhotoPermission,
)

__all__ = [SuperPermission, SuperPermissionOrReadOnly, AdminPermissionOrReadOnly]
__all__ = [
SuperPermission,
SuperPermissionOrReadOnly,
AdminPermissionOrReadOnly,
SuperStudentPermissionOrReadOnly,
AnyonePostSuperEditPermission,
UserViewSetPermission,
PhotoPermission
]
46 changes: 46 additions & 0 deletions backend/drtrottoir/permissions/user_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,49 @@ def has_permission(self, request, view):

def has_object_permission(self, request, view, obj):
return request.user.role <= Roles.SUPERADMIN or request.method in SAFE_METHODS


class SuperStudentPermissionOrReadOnly(BasePermission):
"""
Including this permission allows everyone to do safe methods like GET.
But requires higher levels for non-safe methods.
"""

def has_permission(self, request, view):
return request.user.role <= Roles.SUPERSTUDENT or request.method in SAFE_METHODS

def has_object_permission(self, request, view, obj):
return request.user.role <= Roles.SUPERSTUDENT or request.method in SAFE_METHODS


class AnyonePostSuperEditPermission(BasePermission):
"""
Including this permission allows everyone to do safe methods like GET.
Anyone can post new entries, only the owner of the record and users with higher permissions can edit.
"""

def has_object_permission(self, request, view, obj):
return request.user.is_super or request.method in SAFE_METHODS or obj.user == request.user


class PhotoPermission(BasePermission):
"""
Including this permission allows everyone to do safe methods like GET.
Anyone can post new entries, only the owner of the record and users with higher permissions can edit.
"""

def has_object_permission(self, request, view, obj):
return request.user.is_super or request.method in SAFE_METHODS or obj.visit.user == request.user


class UserViewSetPermission(BasePermission):
"""
Including this permission allows only superadmins to GET index page.
Only Users that own the record and superadmins can view and edit that record.
"""

def has_permission(self, request, view):
return (request.user.role <= Roles.SUPERADMIN and request.method in SAFE_METHODS) or 'pk' in view.kwargs

def has_object_permission(self, request, view, obj):
return request.user.role <= Roles.SUPERADMIN or (obj == request.user and request.method in SAFE_METHODS)
2 changes: 1 addition & 1 deletion backend/drtrottoir/serializers/building_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class BuildingSerializer(serializers.HyperlinkedModelSerializer):
source='region.region_name',
read_only=True
)
owners = UserPartialSerializer(many=True)
owners = UserPartialSerializer(many=True, read_only=True)

class Meta:
model = Building
Expand Down
5 changes: 4 additions & 1 deletion backend/drtrottoir/serializers/tour_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ class TourSerializer(serializers.HyperlinkedModelSerializer):
"""
A serializer for tours and their associated location
"""
region_name = serializers.CharField(source='region.region_name', read_only=True)
region_name = serializers.CharField(
source='region.region_name',
read_only=True
)

class Meta:
model = Tour
Expand Down
16 changes: 14 additions & 2 deletions backend/drtrottoir/tests/factories/__init__.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
from .region_factory import RegionFactory
from .user_factory import DeveloperUserFactory
from .user_factory import (
DeveloperUserFactory,
SuperAdminUserFactory,
SuperStudentUserFactory,
OwnerUserFactory,
StudentUserFactory
)
from .visit_factory import VisitFactory
from .photo_factory import PhotoFactory
from .waste_factory import WasteFactory
from .tour_factory import TourFactory
from .building_factory import BuildingFactory
from .building_in_tour_factory import BuildingInTourFactory
from .schedule_factory import ScheduleFactory

__all__ = [
RegionFactory,
DeveloperUserFactory,
SuperAdminUserFactory,
SuperStudentUserFactory,
OwnerUserFactory,
StudentUserFactory,
BuildingFactory,
VisitFactory,
PhotoFactory,
TourFactory,
WasteFactory,
TourFactory,
BuildingInTourFactory
BuildingInTourFactory,
ScheduleFactory,
]
4 changes: 1 addition & 3 deletions backend/drtrottoir/tests/factories/photo_factory.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import random
from factory.django import DjangoModelFactory
import factory
from django.utils import timezone
from drtrottoir.models import Photo
from .visit_factory import VisitFactory


class PhotoFactory(DjangoModelFactory):
image = factory.Faker("image_url")
visit = factory.SubFactory(VisitFactory)
state = random.choice([0, 1, 2])
state = random.choice([1, 2, 3])
comment = factory.Faker("sentence")
created_at = factory.Faker("date_time", tzinfo=timezone.utc)

class Meta:
model = Photo
1 change: 1 addition & 0 deletions backend/drtrottoir/tests/factories/region_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ class RegionFactory(DjangoModelFactory):

class Meta:
model = Region
django_get_or_create = ('region_name',)
15 changes: 15 additions & 0 deletions backend/drtrottoir/tests/factories/schedule_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from factory.django import DjangoModelFactory
import factory
from drtrottoir.models import Schedule
from .user_factory import StudentUserFactory
from .tour_factory import TourFactory


class ScheduleFactory(DjangoModelFactory):
date = date = factory.Faker("date")
student = factory.SubFactory(StudentUserFactory)
tour = factory.SubFactory(TourFactory)
comment = factory.Faker("sentence")

class Meta:
model = Schedule
Loading

0 comments on commit b8a02ef

Please sign in to comment.