diff --git a/.vscode/launch.json b/.vscode/launch.json index 7ece932..4aaf173 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,7 +11,8 @@ "program": "${workspaceFolder}/certification_system/manage.py", "args": ["runserver"], "django": true, - "justMyCode": true + "justMyCode": true, + "cwd": "${workspaceFolder}/certification_system" } ] } diff --git a/certification_system/category/models.py b/certification_system/category/models.py index 71a8362..1d4851a 100644 --- a/certification_system/category/models.py +++ b/certification_system/category/models.py @@ -1,3 +1,13 @@ from django.db import models +from event.models import Event # Create your models here. +class Category(models.Model): + name = models.CharField(max_length=50) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + event=models.ForeignKey(Event, related_name="categories", on_delete=models.CASCADE) + + + def __str__(self): + return self.name diff --git a/certification_system/category/serializers.py b/certification_system/category/serializers.py new file mode 100644 index 0000000..3f3330a --- /dev/null +++ b/certification_system/category/serializers.py @@ -0,0 +1,8 @@ +from rest_framework import serializers +from .models import Category + +class CategorySerializer(serializers.ModelSerializer): + class Meta: + model = Category + read_only_fields = ('created_at', 'updated_at') + fields = '__all__' \ No newline at end of file diff --git a/certification_system/category/views.py b/certification_system/category/views.py index 91ea44a..518fd52 100644 --- a/certification_system/category/views.py +++ b/certification_system/category/views.py @@ -1,3 +1,13 @@ +from category.models import Category from django.shortcuts import render # Create your views here. +from rest_framework import viewsets +from rest_framework import permissions +from category.serializers import CategorySerializer + +class CategoryViewSet(viewsets.ModelViewSet): + queryset = Category.objects.all() + serializer_class = CategorySerializer + # permission_classes = [permissions.IsAuthenticated] + diff --git a/certification_system/certificate/models.py b/certification_system/certificate/models.py index 71a8362..fcafdb6 100644 --- a/certification_system/certificate/models.py +++ b/certification_system/certificate/models.py @@ -1,3 +1,25 @@ from django.db import models +from category.models import Category +from event.models import Event +import uuid +from certification_system.utils.images import get_file_path # Create your models here. +class Certificate(models.Model): + name = models.CharField(max_length=50) + email = models.EmailField() + active = models.BooleanField() + category = models.ForeignKey( + Category, related_name="certificates", on_delete=models.CASCADE + ) + event = models.ForeignKey( + Event, related_name="certificates", on_delete=models.CASCADE + ) + # save certificates to media/certificates folder + image = models.ImageField(upload_to=get_file_path, null=True, blank=True) + + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + return self.name diff --git a/certification_system/certificate/serializers.py b/certification_system/certificate/serializers.py new file mode 100644 index 0000000..fa1411e --- /dev/null +++ b/certification_system/certificate/serializers.py @@ -0,0 +1,10 @@ +# serializers for certificate +from rest_framework import serializers +from .models import Certificate + +class CertificateSerializer(serializers.ModelSerializer): + class Meta: + model = Certificate + # read only attributes + read_only_fields = ('created_at', 'updated_at') + fields = '__all__' \ No newline at end of file diff --git a/certification_system/certificate/views.py b/certification_system/certificate/views.py index 91ea44a..ec56063 100644 --- a/certification_system/certificate/views.py +++ b/certification_system/certificate/views.py @@ -1,3 +1,79 @@ -from django.shortcuts import render +import uuid +from certificate.models import Certificate +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import status +import csv +import io +from io import BytesIO +from django.core.files.base import ContentFile # Create your views here. +from rest_framework import viewsets +from rest_framework import permissions +from certificate.serializers import CertificateSerializer +from PIL import Image +from certification_system.utils.images import save_temporary_image, delete_temporary_image + + +def generate_certificate_dummy(template, data, mappings): + """ + Generate certificate from template and data + """ + file_path = save_temporary_image(template) + image = Image.open(file_path) + delete_temporary_image(file_path) + image = image.convert("L") + f = BytesIO() + try: + image.save(f, format="png") + return ContentFile(f.getvalue(), name=uuid.uuid4().hex + ".png") + finally: + f.close() + + +class CertificateViewSet(viewsets.ModelViewSet): + queryset = Certificate.objects.all().order_by("name") + serializer_class = CertificateSerializer + # permission_classes = [permissions.IsAuthenticated] + + +# route to generate bulk certificates using template image and csv file from the request +class BulkCertificateGenerator(APIView): + def post(self, request, format=None): + template_image = request.FILES["template_image"] + csv_file = request.FILES["csv_file"] + mapping = request.data["mapping"] + + file = csv_file.read().decode("utf-8") + reader = csv.DictReader(io.StringIO(file)) + + for person in reader: + data = { + "name": person["name"], + "email": person["email"], + "active": True, + "category": request.data["category"], + "event": request.data["event"], + "image": generate_certificate_dummy(template_image, person, mapping), + } + + certificate = CertificateSerializer(data=data) + try: + certificate.is_valid(raise_exception=True) + except Exception as e: + print(e) + return Response( + {"error": "Invalid data", "message": str(e)}, + status=status.HTTP_400_BAD_REQUEST, + ) + certificate.save() + + return Response( + data={ + "success": True, + "message": "Certificates generated successfully", + "data": [], + }, + status=status.HTTP_201_CREATED, + ) diff --git a/certification_system/certification_system/settings.py b/certification_system/certification_system/settings.py index 8a6afce..572ed0d 100644 --- a/certification_system/certification_system/settings.py +++ b/certification_system/certification_system/settings.py @@ -20,61 +20,62 @@ # See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-hk#rs6+=ymd$4biyom%e9@jw+t6bhpb@qwtji-wk=5&loivo+c' - +SECRET_KEY = "django-insecure-hk#rs6+=ymd$4biyom%e9@jw+t6bhpb@qwtji-wk=5&loivo+c" # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'rest_framework', - 'event' + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "rest_framework", + "event", + "category", + "certificate", ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'certification_system.urls' +ROOT_URLCONF = "certification_system.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] -WSGI_APPLICATION = 'certification_system.wsgi.application' +WSGI_APPLICATION = "certification_system.wsgi.application" # Database # https://docs.djangoproject.com/en/4.1/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db' / 'db.sqlite3', + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db" / "db.sqlite3", } } @@ -84,16 +85,16 @@ AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] @@ -101,9 +102,9 @@ # Internationalization # https://docs.djangoproject.com/en/4.1/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -113,13 +114,16 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.1/howto/static-files/ -STATIC_URL = 'static/' -STATIC_ROOT = '/app/static/' +STATIC_URL = "static/" +STATIC_ROOT = "/app/static/" +MEDIA_URL = "media/" +MEDIA_ROOT = BASE_DIR / "media/" + # Default primary key field type # https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" # Manual additions diff --git a/certification_system/certification_system/urls.py b/certification_system/certification_system/urls.py index c183b20..71ab801 100644 --- a/certification_system/certification_system/urls.py +++ b/certification_system/certification_system/urls.py @@ -14,21 +14,31 @@ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import include,path +from django.urls import include, path from rest_framework import routers -from event import views +from event.views import EventViewSet +from category.views import CategoryViewSet +from certificate.views import CertificateViewSet, BulkCertificateGenerator +from django.conf.urls.static import static +from django.conf import settings router = routers.DefaultRouter() -router.register(r'events', views.EventViewSet) +router.register(r"events", EventViewSet) +router.register(r"categories", CategoryViewSet) +router.register(r"certificates", CertificateViewSet) # router.register(r'groups', views.GroupViewSet) - urlpatterns = [ - path('', include(router.urls)), - path('admin/', admin.site.urls), - path('api-auth/', include('rest_framework.urls', namespace='rest_framework')) -] + path("", include(router.urls)), + path("admin/", admin.site.urls), + path("api-auth/", include("rest_framework.urls", namespace="rest_framework")), + path( + "generate-bulk-certificate/", + BulkCertificateGenerator.as_view(), + name="generate-bulk-certificate", + ), +] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) # urlpatterns = [ # path('admin/', admin.site.urls), # ] diff --git a/certification_system/event/models.py b/certification_system/event/models.py index 73215f4..ee744da 100644 --- a/certification_system/event/models.py +++ b/certification_system/event/models.py @@ -2,7 +2,7 @@ # Create your models here. class Event(models.Model): - name = models.CharField(max_length=50) + name = models.CharField(max_length=50,primary_key=True) description = models.TextField() start_date = models.DateField() end_date = models.DateField() diff --git a/certification_system/event/serializers.py b/certification_system/event/serializers.py index 698c471..f8819df 100644 --- a/certification_system/event/serializers.py +++ b/certification_system/event/serializers.py @@ -5,4 +5,6 @@ class EventSerializer(serializers.ModelSerializer): class Meta: model = Event + # read only attributes + read_only_fields = ('created_at', 'updated_at') fields = '__all__' \ No newline at end of file diff --git a/certification_system/utils/images.py b/certification_system/utils/images.py new file mode 100644 index 0000000..4a65a19 --- /dev/null +++ b/certification_system/utils/images.py @@ -0,0 +1,24 @@ +import uuid +import os +from django.core.files.storage import default_storage + + +def get_file_path(instance, filename): + ext = filename.split(".")[-1] + filename = "%s.%s" % (uuid.uuid4(), ext) + return os.path.join("certificates", filename) + + +def save_temporary_image(file_obj): + filename = str(uuid.uuid4()) + ".png" + with default_storage.open(filename, "wb+") as destination: + for chunk in file_obj.chunks(): + destination.write(chunk) + import os + + print(os.getcwd()) + return "media/" + filename + + +def delete_temporary_image(filename): + default_storage.delete(filename.split("/")[-1])