Skip to content

Commit

Permalink
Feat(qr)/qr generator (#719)
Browse files Browse the repository at this point in the history
* added model for QR code, viewset and serializer

* finished create and destroy methods, and mades tests for these

* altered create viewset to take user from request

* fixed tests

* changed status code for deletion of qr code to 200 since frontend dosent accept 204 status code....

* format

* removed earlier unused method

* format

* Trigger Build

* changed exception to take all errors with making a blob

* format

* added azure key to settings file

* format

* changed name of exception for blbo not found

* altered ci.yaml file to add azure connecntion string as env

* fix ci file

* changed model to take content, and serializer not upload image to azure

* format
  • Loading branch information
MadsNyl authored Oct 27, 2023
1 parent f5bf798 commit 1033ecd
Show file tree
Hide file tree
Showing 16 changed files with 290 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ jobs:
- name: Checkout Code Repository
uses: actions/checkout@v2

- name: Set up .env file
run: |
touch .env
echo "AZURE_STORAGE_CONNECTION_STRING=${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}" >> .env
- name: Build the Stack
run: docker-compose build

Expand Down
11 changes: 8 additions & 3 deletions app/common/azure_file_handler.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import os
import re
import uuid

from django.conf import settings

from azure.storage.blob import BlobServiceClient, ContentSettings

from app.common.file_handler import FileHandler
Expand All @@ -17,7 +18,7 @@ def __init__(self, blob=None, url=None):
self.blobName = data[1]

def get_or_create_container(self, name="default"):
connection_string = os.environ.get("AZURE_STORAGE_CONNECTION_STRING")
connection_string = settings.AZURE_STORAGE_CONNECTION_STRING
blob_service_client = BlobServiceClient.from_connection_string(
connection_string
)
Expand Down Expand Up @@ -46,13 +47,17 @@ def uploadBlob(self):
container = self.get_or_create_container(containerName)

blob_name = f"{uuid.uuid4()}{self.getBlobName()}"

content_settings = ContentSettings(
content_type=self.blob.content_type if self.blob.content_type else None,
cache_control="public,max-age=2592000",
)

blob_client = container.get_blob_client(blob_name)
blob_client.upload_blob(data=self.blob, content_settings=content_settings)
blob_client.upload_blob(
data=self.blob.data if hasattr(self.blob, "data") else self.blob,
content_settings=content_settings,
)
if blob_client.url:
return blob_client.url
raise ValueError("Noe gikk galt under filopplastningen")
Expand Down
1 change: 1 addition & 0 deletions app/content/admin/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
admin.site.register(models.Page)
admin.site.register(models.ShortLink)
admin.site.register(models.Toddel)
admin.site.register(models.QRCode)


@admin.register(models.Strike)
Expand Down
1 change: 1 addition & 0 deletions app/content/factories/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
from app.content.factories.strike_factory import StrikeFactory
from app.content.factories.toddel_factory import ToddelFactory
from app.content.factories.priority_pool_factory import PriorityPoolFactory
from app.content.factories.qr_code_factory import QRCodeFactory
14 changes: 14 additions & 0 deletions app/content/factories/qr_code_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import factory
from factory.django import DjangoModelFactory

from app.content.factories.user_factory import UserFactory
from app.content.models import QRCode


class QRCodeFactory(DjangoModelFactory):
class Meta:
model = QRCode

name = factory.Sequence(lambda n: f"QRCode {n}")
user = factory.SubFactory(UserFactory)
image = "https://tihldestorage.blob.core.windows.net/imagepng/0331423a-11b3-4e6b-a505-f84e0991b696TestCode"
47 changes: 47 additions & 0 deletions app/content/migrations/0054_qrcode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Generated by Django 4.2.5 on 2023-10-08 20:45

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


class Migration(migrations.Migration):

dependencies = [
("content", "0053_event_contact_person"),
]

operations = [
migrations.CreateModel(
name="QRCode",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
("image", models.URLField(blank=True, max_length=600, null=True)),
("image_alt", models.CharField(blank=True, max_length=200, null=True)),
("name", models.CharField(max_length=50)),
("url", models.URLField(max_length=600)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="qr_codes",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"verbose_name": "qr_code",
"verbose_name_plural": "qr_codes",
},
),
]
23 changes: 23 additions & 0 deletions app/content/migrations/0055_remove_qrcode_url_qrcode_content.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 4.2.5 on 2023-10-21 13:57

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("content", "0054_qrcode"),
]

operations = [
migrations.RemoveField(
model_name="qrcode",
name="url",
),
migrations.AddField(
model_name="qrcode",
name="content",
field=models.CharField(default=None, max_length=600),
preserve_default=False,
),
]
1 change: 1 addition & 0 deletions app/content/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
get_strike_description,
get_strike_strike_size,
)
from app.content.models.qr_code import QRCode
22 changes: 22 additions & 0 deletions app/content/models/qr_code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from django.db import models

from app.common.enums import Groups
from app.common.permissions import BasePermissionModel
from app.content.models import User
from app.util.models import BaseModel, OptionalImage


class QRCode(BaseModel, OptionalImage, BasePermissionModel):
write_access = (Groups.TIHLDE,)
read_access = (Groups.TIHLDE,)

name = models.CharField(max_length=50)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="qr_codes")
content = models.CharField(max_length=600)

class Meta:
verbose_name = "qr_code"
verbose_name_plural = "qr_codes"

def __str__(self):
return f"{self.name} - {self.user.user_id}"
19 changes: 19 additions & 0 deletions app/content/serializers/qr_code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from rest_framework import serializers

from app.common.serializers import BaseModelSerializer
from app.content.models import QRCode


class QRCodeSerializer(serializers.ModelSerializer):
class Meta:
model = QRCode
fields = ("id", "name", "created_at", "updated_at", "content")


class QRCodeCreateSerializer(BaseModelSerializer):
class Meta:
model = QRCode
fields = (
"name",
"content",
)
2 changes: 2 additions & 0 deletions app/content/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
EventViewSet,
NewsViewSet,
PageViewSet,
QRCodeViewSet,
RegistrationViewSet,
ShortLinkViewSet,
StrikeViewSet,
Expand All @@ -25,6 +26,7 @@
router.register("events", EventViewSet, basename="event")
router.register("categories", CategoryViewSet)
router.register("short-links", ShortLinkViewSet, basename="short-link")
router.register("qr-codes", QRCodeViewSet, basename="qr-code")
router.register("users", UserViewSet, basename="user")
router.register(
r"events/(?P<event_id>\d+)/registrations",
Expand Down
1 change: 1 addition & 0 deletions app/content/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
from app.content.views.upload import upload
from app.content.views.strike import StrikeViewSet
from app.content.views.toddel import ToddelViewSet
from app.content.views.qr_code import QRCodeViewSet
42 changes: 42 additions & 0 deletions app/content/views/qr_code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from django.shortcuts import get_object_or_404
from rest_framework import status
from rest_framework.response import Response

from app.common.permissions import BasicViewPermission
from app.common.viewsets import BaseViewSet
from app.content.models import QRCode, User
from app.content.serializers.qr_code import (
QRCodeCreateSerializer,
QRCodeSerializer,
)


class QRCodeViewSet(BaseViewSet):
serializer_class = QRCodeSerializer
queryset = QRCode.objects.all()
permission_classes = [BasicViewPermission]

def get_queryset(self):
if hasattr(self, "action") and self.action == "retrieve":
return super().get_queryset()
user = get_object_or_404(User, user_id=self.request.id)
return super().get_queryset().filter(user=user)

def create(self, request, *args, **kwargs):
user = get_object_or_404(User, user_id=request.id)
data = request.data

serializer = QRCodeCreateSerializer(data=data, context={"request": request})

if serializer.is_valid():
qr_code = super().perform_create(serializer, user=user)
serializer = QRCodeSerializer(qr_code, context={"request": request})
return Response(serializer.data, status=status.HTTP_201_CREATED)

return Response(
{"detail": serializer.errors}, status=status.HTTP_400_BAD_REQUEST
)

def destroy(self, request, *args, **kwargs):
super().destroy(request, *args, **kwargs)
return Response({"detail": "QR-koden ble slettet"}, status=status.HTTP_200_OK)
1 change: 1 addition & 0 deletions app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
)

AZURE_BLOB_STORAGE_NAME = "tihldestorage.blob.core.windows.net"
AZURE_STORAGE_CONNECTION_STRING = os.environ.get("AZURE_STORAGE_CONNECTION_STRING")

# Application definition
sentry_sdk.init(
Expand Down
6 changes: 6 additions & 0 deletions app/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
NewsFactory,
PageFactory,
ParentPageFactory,
QRCodeFactory,
PriorityPoolFactory,
RegistrationFactory,
ShortLinkFactory,
Expand Down Expand Up @@ -70,6 +71,11 @@ def user():
return UserFactory()


@pytest.fixture()
def qr_code():
return QRCodeFactory()


@pytest.fixture
def token(user):
return Token.objects.get(user_id=user.user_id)
Expand Down
97 changes: 97 additions & 0 deletions app/tests/content/test_qr_code_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
from rest_framework import status

import pytest

API_QR_CODE_BASE_URL = "/qr-codes/"


def get_data():
return {"name": "Test QR Code", "content": "https://tihlde.org"}


@pytest.mark.django_db
def test_list_qr_codes_as_anonymous_user(default_client):
"""
An anonymous user should not be able to list QR Codes.
"""

response = default_client.get(API_QR_CODE_BASE_URL)

assert response.status_code == status.HTTP_403_FORBIDDEN


@pytest.mark.django_db
def test_list_qr_codes_as_member(member, api_client):
"""
A member of TIHLDE should be able to list QR Codes.
"""

client = api_client(user=member)
response = client.get(API_QR_CODE_BASE_URL)

assert response.status_code == status.HTTP_200_OK


@pytest.mark.django_db
def test_create_qr_code_as_member(member, api_client):
"""
A member of TIHLDE should be able to create a QR Code.
"""

data = get_data()

client = api_client(user=member)
response = client.post(API_QR_CODE_BASE_URL, data=data)

assert response.status_code == status.HTTP_201_CREATED


@pytest.mark.django_db
def test_create_qr_code_as_anonymous_user(default_client):
"""
An anonymous user should not be able to create a QR Code.
"""

data = get_data()

response = default_client.post(API_QR_CODE_BASE_URL, data=data)

assert response.status_code == status.HTTP_403_FORBIDDEN


@pytest.mark.django_db
def test_delete_qr_code_with_invalid_blob_as_member(member, api_client, qr_code):
"""
A member of TIHLDE should be able to delete a QR Code.
"""

qr_code.user = member
qr_code.save()

client = api_client(user=member)
response = client.delete(f"{API_QR_CODE_BASE_URL}{qr_code.id}/")

assert response.status_code == status.HTTP_200_OK


@pytest.mark.django_db
def test_delete_qr_code_with_invalid_blob_as_anonymous_user(default_client, qr_code):
"""
An anonymous user should not be able to delete a QR Code.
"""

response = default_client.delete(f"{API_QR_CODE_BASE_URL}{qr_code.id}/")

assert response.status_code == status.HTTP_403_FORBIDDEN


@pytest.mark.django_db
def test_delete_other_users_qr_code(member, api_client, qr_code):
"""
A member of TIHLDE should not be able to delete another users QR Code.
"""

client = api_client(user=member)
response = client.delete(f"{API_QR_CODE_BASE_URL}{qr_code.id}/")

assert response.status_code == status.HTTP_404_NOT_FOUND

0 comments on commit 1033ecd

Please sign in to comment.