Skip to content

Create a new template for activity page offline availability #1086

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

Open
wants to merge 1 commit into
base: staging
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions zubhub_backend/compose/celery/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ uritemplate>=3.0.1
urllib3>=1.25.11
vine>=1.3.0
watchdog>=0.10.2
weasyprint>=60.2
wcwidth>=0.2.5
whitenoise>=4.1.4
django-extensions>=1.0.0
2 changes: 2 additions & 0 deletions zubhub_backend/compose/web/dev/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ RUN apt-get update \
&& apt-get install -y libpq-dev \
# Translations dependencies
&& apt-get install -y gettext \
# dependencies of Weasyprint
&& apt install python3-pip python3-cffi python3-brotli libpango-1.0-0 libharfbuzz0b libpangoft2-1.0-0 -y\
# cleaning up unused files
&& apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \
&& rm -rf /var/lib/apt/lists/*
Expand Down
2 changes: 2 additions & 0 deletions zubhub_backend/compose/web/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ python-dateutil>=2.8.1
python3-openid>=3.2.0
pytz>=2020.4
PyYAML>=5.4
qrcode==7.4.2
requests>=2.23.0
requests-oauthlib>=1.3.0
s3transfer>=0.3.3
Expand All @@ -85,5 +86,6 @@ uritemplate>=3.0.1
urllib3>=1.25.11
vine>=1.3.0
watchdog>=0.10.2
weasyprint==52.4
wcwidth>=0.2.5
whitenoise>=4.1.4
3 changes: 2 additions & 1 deletion zubhub_backend/zubhub/activities/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
path('<uuid:pk>/delete/', ActivityDeleteAPIView.as_view(), name='delete'),
path('<uuid:pk>/toggle-save/', ToggleSaveAPIView.as_view(), name='save'),
path('<uuid:pk>/toggle-publish/', togglePublishActivityAPIView.as_view(), name='publish'),
path('<uuid:pk>/', ActivityDetailsAPIView.as_view(), name='detail_activity')
path('<uuid:pk>/', ActivityDetailsAPIView.as_view(), name='detail_activity'),
path('<uuid:pk>/pdf/', DownloadActivityPDF.as_view(), name='pdf'),
]
88 changes: 85 additions & 3 deletions zubhub_backend/zubhub/activities/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
import io
import base64
import qrcode
import requests
from django.http import HttpResponse
from django.template.loader import get_template
from weasyprint import HTML
from .models import *


Expand Down Expand Up @@ -34,21 +41,20 @@ def create_inspiring_examples(activity, inspiring_examples):


def create_activity_images(activity, images):

for image in images:
saved_image = Image.objects.create(**image['image'])
ActivityImage.objects.create(activity=activity, image=saved_image)


def update_image(image, image_data):
if(image_data is not None and image is not None):
if (image_data is not None and image is not None):
if image_data["file_url"] == image.file_url:
return image
else:
image.delete()
return Image.objects.create(**image_data)
else:
if(image):
if (image):
image.delete()
else:
return Image.objects.create(**image_data)
Expand All @@ -67,3 +73,79 @@ def update_making_steps(activity, making_steps):
def update_inspiring_examples(activity, inspiring_examples):
InspiringExample.objects.filter(activity=activity).delete()
create_inspiring_examples(activity, inspiring_examples)


def generate_qr_code(link):
"""
Generate a QR code for a given link and return it as a base64 string.

Args:
link (str): The link to encode in the QR code.

Returns:
str: The QR code as a base64 string.
"""
# Generate QR code
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(link)
qr.make(fit=True)

img = qr.make_image(fill_color="black", back_color="white")

buf = io.BytesIO()
img.save(buf, format="PNG")

img_bytes = buf.getvalue()

img_base64 = base64.b64encode(img_bytes).decode()

return img_base64


def generate_pdf(template_path, context):
"""
Generate a PDF file from a Jinja template.

Args:
template_path (str): The file path to the Jinja template.
context (dict): The context data for rendering the template.

Returns:
HttpResponse: A Django HTTP response with the generated PDF.
"""
template = get_template(template_path)

html = template.render(context)

pdf = HTML(string=html).write_pdf()

activity_id = context['activity_id']

response = HttpResponse(pdf, content_type="application/pdf")
response["Content-Disposition"] = f'attachment; filename="{activity_id}.pdf"'

return response


def download_file(file_url):
"""
Download a file from a given URL and save it to the local filesystem.

Args:
file_url (str): The URL of the file to download.

Returns:
bytes: The file data.
"""
response = requests.get(file_url, stream=True)
response.raise_for_status()
file_data = b""
for chunk in response.iter_content(chunk_size=4096):
if chunk:
file_data += chunk
return file_data
81 changes: 63 additions & 18 deletions zubhub_backend/zubhub/activities/views.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
from django.shortcuts import render
from django.utils.translation import ugettext_lazy as _
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.generics import (
ListAPIView, CreateAPIView, RetrieveAPIView, UpdateAPIView, DestroyAPIView)
from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly, AllowAny
from rest_framework.response import Response
from rest_framework.views import APIView
from .permissions import IsStaffOrModeratorOrEducator, IsOwner, IsStaffOrModerator
from django.shortcuts import get_object_or_404
from .models import *
from .serializers import *
from django.db import transaction
from django.core.exceptions import PermissionDenied
from django.contrib.auth.models import AnonymousUser

from .utils import generate_pdf, generate_qr_code, download_file
from django.conf import settings


class ActivityListAPIView(ListAPIView):
Expand All @@ -23,7 +21,7 @@ class ActivityListAPIView(ListAPIView):
def get_queryset(self):
all = Activity.objects.all()
return all


class UserActivitiesAPIView(ListAPIView):
"""
Expand All @@ -33,7 +31,7 @@ class UserActivitiesAPIView(ListAPIView):

serializer_class = ActivitySerializer
permission_classes = [IsAuthenticated, IsOwner]

def get_queryset(self):
return self.request.user.activities_created.all()

Expand All @@ -53,7 +51,7 @@ def get_object(self):
queryset = self.get_queryset()
pk = self.kwargs.get("pk")
obj = get_object_or_404(queryset, pk=pk)

if obj:
with transaction.atomic():
if isinstance(self.request.user, AnonymousUser):
Expand All @@ -65,7 +63,7 @@ def get_object(self):
obj.views_count += 1
obj.save()
return obj

else:
raise Exception()

Expand All @@ -77,7 +75,7 @@ class PublishedActivitiesAPIView(ListAPIView):

serializer_class = ActivitySerializer
permission_classes = [AllowAny]

def get_queryset(self):
limit = self.request.query_params.get('limit', 10000)

Expand All @@ -87,7 +85,7 @@ def get_queryset(self):
limit = 10

return Activity.objects.filter(publish= True)[:limit]

class UnPublishedActivitiesAPIView(ListAPIView):
"""
Fetch list of unpublished activities by authenticated staff member.
Expand All @@ -100,7 +98,7 @@ class UnPublishedActivitiesAPIView(ListAPIView):
permission_classes = [IsAuthenticated, IsStaffOrModerator]

def get_queryset(self):
return Activity.objects.filter(publish= False)
return Activity.objects.filter(publish= False)

class ActivityCreateAPIView(CreateAPIView):
"""
Expand Down Expand Up @@ -150,11 +148,11 @@ class ToggleSaveAPIView(RetrieveAPIView):
queryset = Activity.objects.all()
serializer_class = ActivitySerializer
permission_classes = [IsAuthenticated]

def get_object(self):
pk = self.kwargs.get("pk")
obj = get_object_or_404(self.get_queryset(), pk=pk)

if self.request.user in obj.saved_by.all():
obj.saved_by.remove(self.request.user)
obj.save()
Expand All @@ -173,12 +171,59 @@ class togglePublishActivityAPIView(RetrieveAPIView):
queryset = Activity.objects.all()
serializer_class = ActivitySerializer
permission_classes = [IsAuthenticated, IsStaffOrModerator]


def get_object(self):

pk = self.kwargs.get("pk")
obj = get_object_or_404(self.get_queryset(), pk=pk)
obj = get_object_or_404(self.get_queryset(), pk=pk)
obj.publish = not obj.publish
obj.save()
return obj



class DownloadActivityPDF(APIView):
"""
Download an activities.
Requires activities id.
Returns activities file.
"""
queryset = Activity.objects.all()
template_path = 'activities/activity_download.html'


def get_queryset(self):
return self.queryset

def get_object(self):
pk = self.kwargs.get("pk")
obj = get_object_or_404(self.get_queryset(), pk=pk)
return obj

def get(self, request, *args, **kwargs):
activity = self.get_object()
activity_images = ActivityImage.objects.filter(activity=activity)
activity_steps = ActivityMakingStep.objects.filter(activity=activity)
if settings.ENVIRONMENT == 'production':
qr_code = generate_qr_code(
link=f"https://zubhub.unstructured.studio/activities/{activity.id}"
)
else:
qr_code = generate_qr_code(
link=f"{settings.DEFAULT_BACKEND_PROTOCOL}//{settings.DEFAULT_BACKEND_DOMAIN}/activities/{activity.id}"
)
context = {
'activity': activity,
'activity_id': activity.id,
'activity_images': activity_images,
'activity_steps': activity_steps,
'activity_steps_images': [step.image.all() for step in activity_steps],
'activity_category': [category.name for category in activity.category.all()],
'creators': [creator for creator in activity.creators.all()],
'qr_code': qr_code
}
return generate_pdf(
template_path=self.template_path,
context=context
)
Loading
Loading