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

Fridges #492

Merged
merged 8 commits into from
Sep 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
Empty file.
Empty file.
Empty file added website/fridges/__init__.py
Empty file.
50 changes: 50 additions & 0 deletions website/fridges/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from django.contrib import admin
from django.contrib.admin import register
from guardian.admin import GuardedModelAdmin

from fridges.models import Fridge, GeneralOpeningHours, AccessLog, BlacklistEntry


class GeneralOpeningHoursInline(admin.TabularInline):
"""Inline for the GeneralOpeningHours model."""

model = GeneralOpeningHours
extra = 0


@register(Fridge)
class FridgeAdmin(GuardedModelAdmin):
"""Admin for the Fridge model."""

inlines = [GeneralOpeningHoursInline]
prepopulated_fields = {"slug": ("name",)}
list_display = ["name", "venue", "is_active", "last_opened", "last_opened_by"]


@register(AccessLog)
class AccessLogAdmin(admin.ModelAdmin):
"""Admin for the AccessLog model."""

list_display = ["user", "fridge", "timestamp"]
list_filter = ["fridge", "timestamp"]
search_fields = ["user__username", "user__first_name", "user__last_name"]
ordering = ["-timestamp"]

def has_add_permission(self, request):
"""Disable the add permission."""
return False

def has_change_permission(self, request, obj=None):
"""Disable the change permission."""
return False

def has_delete_permission(self, request, obj=None):
"""Disable the delete permission."""
return False


@register(BlacklistEntry)
class BlacklistEntryAdmin(admin.ModelAdmin):
"""Admin for the BlacklistEntry model."""

autocomplete_fields = ["user"]
Empty file added website/fridges/api/__init__.py
Empty file.
Empty file.
7 changes: 7 additions & 0 deletions website/fridges/api/v1/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.urls import path

from fridges.api.v1.views import FridgeUnlockAPIView

urlpatterns = [
path("unlock/", FridgeUnlockAPIView.as_view(), name="fridge_unlock"),
]
52 changes: 52 additions & 0 deletions website/fridges/api/v1/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from django.contrib.auth import get_user_model
from django.core.signing import SignatureExpired, BadSignature
from oauth2_provider.views.mixins import ClientProtectedResourceMixin
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView

from fridges.services import user_can_open_fridge, log_access
from users.services import get_user_from_identification_token

User = get_user_model()


class FridgeUnlockAPIView(ClientProtectedResourceMixin, APIView):
"""API view for testing whether a Fridge is allowed to be unlocked."""

def post(self, request, *args, **kwargs):
"""Process a request to unlock."""
fridge_candidates = request.auth.application.fridges.all()

if fridge_candidates.count() == 0:
return Response(
{"detail": "No fridges available"},
status=status.HTTP_401_UNAUTHORIZED,
)

user_token = request.data.get("user_token", None)
if user_token is None:
return Response(
{"detail": "Missing user_token"},
status=status.HTTP_400_BAD_REQUEST,
)

try:
user = get_user_from_identification_token(user_token)
except (User.DoesNotExist, BadSignature, SignatureExpired):
return Response(
{"detail": "Invalid user_token"},
status=status.HTTP_400_BAD_REQUEST,
)

response = []
for fridge in fridge_candidates:
user_can_open, how_long = user_can_open_fridge(user, fridge)
if user_can_open:
log_access(user, fridge)
response.append({"fridge": fridge.slug, "unlock_for": how_long})

return Response(
{"user": user.username, "unlock": response},
status=status.HTTP_200_OK,
)
8 changes: 8 additions & 0 deletions website/fridges/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django.apps import AppConfig


class FridgesConfig(AppConfig):
"""Configuration for the fridges app."""

default_auto_field = "django.db.models.BigAutoField"
name = "fridges"
128 changes: 128 additions & 0 deletions website/fridges/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Generated by Django 4.1.9 on 2023-06-29 17:17

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


class Migration(migrations.Migration):

initial = True

dependencies = [
migrations.swappable_dependency(settings.OAUTH2_PROVIDER_APPLICATION_MODEL),
("auth", "0012_alter_user_first_name_max_length"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("venues", "0005_venue_automatically_accept_first_reservation"),
]

operations = [
migrations.CreateModel(
name="Fridge",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("name", models.CharField(max_length=255)),
("slug", models.SlugField(max_length=255, unique=True)),
(
"is_active",
models.BooleanField(
default=True,
help_text="If the fridge is active, it will be shown on the website and can be opened by users within the set opening hours. People with 'open always' permissions can always open the fridge, regardless of whether it is active.",
),
),
(
"unlock_for_how_long",
models.DurationField(
default=datetime.timedelta(seconds=60),
help_text="How long to unlock the fridge for by default (HH:MM:SS).",
),
),
(
"oauth_client",
models.ForeignKey(
help_text="The OAuth2 client that may request opening the fridge.",
on_delete=django.db.models.deletion.PROTECT,
related_name="fridges",
to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL,
),
),
(
"venue",
models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to="venues.venue"
),
),
],
options={
"verbose_name": "fridge",
"verbose_name_plural": "fridges",
"ordering": ["name"],
"permissions": [("open_always", "Can always open fridges")],
},
),
migrations.CreateModel(
name="BlacklistEntry",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("fridge", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="fridges.fridge")),
("user", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
"verbose_name": "blacklist entry",
"verbose_name_plural": "blacklist entries",
},
),
migrations.CreateModel(
name="AccessLog",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("timestamp", models.DateTimeField(auto_now_add=True)),
("fridge", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="fridges.fridge")),
("user", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
"verbose_name": "access log",
"verbose_name_plural": "access logs",
"ordering": ["-timestamp"],
"get_latest_by": "timestamp",
},
),
migrations.CreateModel(
name="GeneralOpeningHours",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
(
"weekday",
models.IntegerField(
choices=[
(0, "Monday"),
(1, "Tuesday"),
(2, "Wednesday"),
(3, "Thursday"),
(4, "Friday"),
(5, "Saturday"),
(6, "Sunday"),
]
),
),
("start_time", models.TimeField()),
("end_time", models.TimeField()),
("fridge", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="fridges.fridge")),
(
"restrict_to_groups",
models.ManyToManyField(
blank=True,
help_text="Only allow members of these groups to open the fridge during these opening hours.",
to="auth.group",
),
),
],
options={
"verbose_name": "general opening hours",
"verbose_name_plural": "general opening hours",
"ordering": ["weekday", "start_time"],
"unique_together": {("weekday", "start_time", "end_time", "fridge")},
},
),
]
Empty file.
Loading