diff --git a/onboarding/migrations/0008_files_for_page_of_package.py b/onboarding/migrations/0008_files_for_page_of_package.py new file mode 100644 index 00000000..9f0d330e --- /dev/null +++ b/onboarding/migrations/0008_files_for_page_of_package.py @@ -0,0 +1,27 @@ +# Generated by Django 3.1.10 on 2021-08-25 11:34 + +from django.db import migrations, models +import django.db.models.deletion +import onboarding.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('onboarding', '0007_model_listing_sessions_for_users'), + ] + + operations = [ + migrations.CreateModel( + name='PageFile', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('data_file', models.FileField(upload_to=onboarding.models.page_file_upload_to, validators=[onboarding.models.validate_file_extension])), + ('name', models.CharField(max_length=100)), + ('content_type', models.CharField(blank=True, max_length=200, null=True)), + ('size', models.DecimalField(decimal_places=2, max_digits=6)), + ('company', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='onboarding.company')), + ('page', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='onboarding.page')), + ], + ), + ] diff --git a/onboarding/models.py b/onboarding/models.py index 95ef04d3..4318066f 100644 --- a/onboarding/models.py +++ b/onboarding/models.py @@ -6,6 +6,7 @@ from django.contrib.auth.base_user import BaseUserManager from django.utils.translation import ugettext_lazy as _ from django.utils import timezone +from django.core.exceptions import ValidationError def upload_to(instance, filename): @@ -20,6 +21,39 @@ def upload_to(instance, filename): return f"users/{instance.pk}/{now:%Y%m%d%H%M%S}{milliseconds}{extension}" +def page_file_upload_to(instance, filename): + """ + :param instance: object of PageFile model + :param filename: name (with extension) of the file being uploaded + """ + company_id = instance.company + return f"company/{company_id}/{instance.page}/{filename}" + + +def validate_file_extension(value): + # valid_extensions = ['.doc', '.docx', '.pdf', '.jpg', '.xls', '.pptx'] + content_type = "" + try: + if content_type in value.file: + content_type = value.file.content_type + else: + content_type = value.content_type + + whitelist = ['application/msword', 'application/pdf', 'application/vnd.ms-excel', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'application/rtf', 'application/vnd.ms-powerpoint', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'text/plain', + 'image/png', 'image/bmp', 'image/gif', 'image/jpe', 'image/jpeg', 'image/jpeg', 'image/svg+xml', 'image/x-icon'] + except (AttributeError, KeyError): + content_type = os.path.splitext(value.name)[1] + whitelist = ['.doc', '.docx', '.pdf', '.xls', '.pptx', '.jpg', '.png', '.gif', '.txt'] + + if not content_type in whitelist: + raise ValidationError('Unsupported file extension.') + + class Company(models.Model): company_logo = models.ImageField(upload_to=upload_to, null=True, blank=True) @@ -174,6 +208,37 @@ def __str__(self): return self.title +class PageFile(models.Model): + """ + Stores files for respective Page (many-to-one). + data_file - the file which is to be storred + name - keeps original name of the file + content_type - content type of data_file guessed by file extension by DJango + company - Company that owns this file + size - number of kilobytes of uploaded file + """ + data_file = models.FileField(upload_to=page_file_upload_to, validators=[validate_file_extension]) + page = models.ForeignKey(Page, on_delete=models.CASCADE) + name = models.CharField(max_length=100) + content_type = models.CharField(max_length=200, null=True, blank=True) + company = models.ForeignKey(Company, on_delete=models.CASCADE) + # description = models.TextField(max_length=700, help_text='Enter a description about file', null=True, blank=True) + size = models.DecimalField(max_digits=6, decimal_places=2) + # updated_on = models.DateTimeField(auto_now=True) + + # class Meta: + # constraints = [ + # models.UniqueConstraint(fields=['data_file', 'company'], name='unique file for each company') + # ] + + def delete(self): + self.data_file.storage.delete(self.data_file.name) + super().delete() + + def __str__(self): + return f"file: {self.name}" + + class Section(models.Model): """ Owner - company where the HR/user who created the section is employed diff --git a/onboarding/serializers.py b/onboarding/serializers.py index d67ba1ef..eeee1ab0 100644 --- a/onboarding/serializers.py +++ b/onboarding/serializers.py @@ -1,6 +1,6 @@ from rest_framework import serializers from django.contrib.auth import get_user_model -from onboarding.models import ContactRequestDetail, Package, Page, Section, User, PackagesUsers +from onboarding.models import ContactRequestDetail, Package, Page, PageFile, Section, User, PackagesUsers from onboarding.models import Answer, Company, CompanyQuestionAndAnswer from . import mock_password @@ -291,6 +291,44 @@ class Meta: } +# FILES for PAGE +class PageFileSerializer(serializers.ModelSerializer): + class Meta: + model = PageFile + fields = '__all__' + read_only_fields = ('name', 'content_type', 'company', 'size') + + def validate(self, validated_data): + try: + validated_data['content_type'] = validated_data['data_file'].content_type + except (KeyError, AttributeError): + pass + validated_data['name'] = validated_data['data_file'].name + validated_data['size'] = validated_data['data_file'].size / 1024.0 + return validated_data + + +#class PageFileMetaDataSerializer(serializers.ModelSerializer): +# class Meta: +# model = PageFile +# fields = ( +# 'id', +# 'page', +# 'name', +# 'company', +# 'description', +# 'size' +# ) +# read_only_fields = ('company', 'size') + + +class PageDataFileSerializer(serializers.ModelSerializer): + class Meta: + model = PageFile + fields = ('data_file',) + read_only_fields = ('data_file',) # [f.name for f in PageFile._meta.get_fields()] + + # PACKAGE with PAGEs class PackagePagesSerializer(serializers.ModelSerializer): page_set = PageSerializer(many=True) diff --git a/onboarding/src/Components/FormsEdit/FormDescription.js b/onboarding/src/Components/FormsEdit/FormDescription.js index c982a4fb..08857a35 100644 --- a/onboarding/src/Components/FormsEdit/FormDescription.js +++ b/onboarding/src/Components/FormsEdit/FormDescription.js @@ -1,16 +1,24 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { useLocation } from "react-router-dom"; -import { savePageDetails } from "../hooks/FormsEdit"; +import { savePageDetails, getFilesForPage, removePageFile, addNewFiles } from "../hooks/FormsEdit"; import ModalWarning from "../ModalWarning"; import { isValidUrl } from "../utils"; import bookOpenedIcon from "../../static/icons/book-opened.svg"; +import trashIcon from "../../static/icons/trash.svg"; + const FormDescription = ({ formId, formData }) => { + const formFilesRef = useRef(); const location = useLocation(); const [formName, setFormName] = useState(""); const [link, setLink] = useState(""); const [description, setDescription] = useState(""); const [saveModal, setSaveModal ] = useState(<>>); + const [formFiles, updateFormFiles] = useState([]); + const [filesToSend, appendFileToSend] = useState([]); + const [filesToSendTable, updateFileToSendTable] = useState([]); + const [uploadingFilesProgress, updateUploadingProgress] = useState(true);// array of progresses of files or true if there is no files and list can be updated; + useEffect(() => { if(location.state && !formName) { @@ -22,10 +30,90 @@ const FormDescription = ({ formId, formData }) => { formData.title && setFormName(formData.title); formData.description && setDescription(formData.description); formData.link && setLink(formData.link); - }; - }; + } + } }, [formData]); + useEffect(() => { + if(uploadingFilesProgress === true){ + updateFileToSendTable([]);// remove list of un-uploaded files; + let abortCont = getFilesForPage(formId, arrayOfFilesToTable, arrayOfFilesToTable); + return () => abortCont.abort(); + } else if(updateUploadingProgress && Object.keys(updateUploadingProgress).length > 0){ + + let filesToSendNewTable = [], percentage; + Object.keys(updateUploadingProgress).forEach( (fileName) => { + percentage = progressCopy[fileName].loaded / progressCopy[fileName].total * 100.0; + percentage = parseFloat(percentage).toFixed(2); + filesToSendNewTable.push(