diff --git a/PDMI/store/forms.py b/PDMI/store/forms.py index 483e0e4..adfaff6 100644 --- a/PDMI/store/forms.py +++ b/PDMI/store/forms.py @@ -2,7 +2,7 @@ from django.contrib.auth.forms import UserCreationForm, AuthenticationForm from django.contrib.auth.models import User from bootstrap_modal_forms.mixins import PopRequestMixin, CreateUpdateAjaxMixin from django import forms -from .models import Module +from .models import Version class CustomUserCreationForm(PopRequestMixin, CreateUpdateAjaxMixin, UserCreationForm): @@ -17,3 +17,15 @@ class CustomAuthenticationForm(AuthenticationForm): class FileFieldForm(forms.Form): file = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True})) + +class FileFieldForm(forms.ModelForm): + class Meta: + model = Version + fields = ['file'] + widgets = { + 'file': forms.ClearableFileInput( + attrs = { + 'multiple': True + } + ) + } diff --git a/PDMI/store/migrations/0002_auto_20200425_2100.py b/PDMI/store/migrations/0002_auto_20200425_2100.py new file mode 100644 index 0000000..6227775 --- /dev/null +++ b/PDMI/store/migrations/0002_auto_20200425_2100.py @@ -0,0 +1,33 @@ +# Generated by Django 3.0 on 2020-04-25 21:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('store', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='module', + name='bot_ver', + field=models.CharField(max_length=10, null=True), + ), + migrations.AddField( + model_name='module', + name='metamodule', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='module', + name='ver', + field=models.CharField(max_length=10, null=True), + ), + migrations.AlterField( + model_name='module', + name='desc', + field=models.TextField(max_length=2048, null=True), + ), + ] diff --git a/PDMI/store/migrations/0003_module_dependencies.py b/PDMI/store/migrations/0003_module_dependencies.py new file mode 100644 index 0000000..d32daa2 --- /dev/null +++ b/PDMI/store/migrations/0003_module_dependencies.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0 on 2020-04-25 21:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('store', '0002_auto_20200425_2100'), + ] + + operations = [ + migrations.AddField( + model_name='module', + name='dependencies', + field=models.ManyToManyField(related_name='_module_dependencies_+', to='store.Module'), + ), + ] diff --git a/PDMI/store/migrations/0004_auto_20200425_2211.py b/PDMI/store/migrations/0004_auto_20200425_2211.py new file mode 100644 index 0000000..b12501d --- /dev/null +++ b/PDMI/store/migrations/0004_auto_20200425_2211.py @@ -0,0 +1,46 @@ +# Generated by Django 3.0 on 2020-04-25 22:11 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('store', '0003_module_dependencies'), + ] + + operations = [ + migrations.RemoveField( + model_name='module', + name='bot_ver', + ), + migrations.RemoveField( + model_name='module', + name='dependencies', + ), + migrations.RemoveField( + model_name='module', + name='file', + ), + migrations.RemoveField( + model_name='module', + name='metamodule', + ), + migrations.RemoveField( + model_name='module', + name='ver', + ), + migrations.CreateModel( + name='Version', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('ver', models.CharField(max_length=10, null=True)), + ('bot_ver', models.CharField(max_length=10, null=True)), + ('metamodule', models.BooleanField(default=False)), + ('file', models.FileField(upload_to='')), + ('dependencies', models.ManyToManyField(related_name='_version_dependencies_+', to='store.Version')), + ('module', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='store.Module')), + ], + ), + ] diff --git a/PDMI/store/migrations/0005_module_creator.py b/PDMI/store/migrations/0005_module_creator.py new file mode 100644 index 0000000..e65cf0f --- /dev/null +++ b/PDMI/store/migrations/0005_module_creator.py @@ -0,0 +1,21 @@ +# Generated by Django 3.0 on 2020-04-26 12:17 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('store', '0004_auto_20200425_2211'), + ] + + operations = [ + migrations.AddField( + model_name='module', + name='creator', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/PDMI/store/migrations/0006_auto_20200426_1450.py b/PDMI/store/migrations/0006_auto_20200426_1450.py new file mode 100644 index 0000000..01989f1 --- /dev/null +++ b/PDMI/store/migrations/0006_auto_20200426_1450.py @@ -0,0 +1,41 @@ +# Generated by Django 3.0 on 2020-04-26 14:50 + +from django.db import migrations, models +import django.db.models.deletion +import store.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('store', '0005_module_creator'), + ] + + operations = [ + migrations.AlterField( + model_name='version', + name='bot_ver', + field=models.CharField(default='~=0.1.0', max_length=15), + preserve_default=False, + ), + migrations.AlterField( + model_name='version', + name='file', + field=models.FileField(upload_to=store.models.upload_path), + ), + migrations.AlterField( + model_name='version', + name='ver', + field=models.CharField(default='0.1.0', max_length=15), + preserve_default=False, + ), + migrations.CreateModel( + name='Dependency', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('dep_module', models.CharField(max_length=255)), + ('dep_version', models.CharField(max_length=15)), + ('version', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='store.Version')), + ], + ), + ] diff --git a/PDMI/store/migrations/0007_remove_version_dependencies.py b/PDMI/store/migrations/0007_remove_version_dependencies.py new file mode 100644 index 0000000..1f6b400 --- /dev/null +++ b/PDMI/store/migrations/0007_remove_version_dependencies.py @@ -0,0 +1,17 @@ +# Generated by Django 3.0 on 2020-04-26 14:51 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('store', '0006_auto_20200426_1450'), + ] + + operations = [ + migrations.RemoveField( + model_name='version', + name='dependencies', + ), + ] diff --git a/PDMI/store/models.py b/PDMI/store/models.py index 6d5ee67..f6054ea 100644 --- a/PDMI/store/models.py +++ b/PDMI/store/models.py @@ -1,6 +1,40 @@ from django.db import models +from django.db.models.signals import pre_save +from django.dispatch import receiver +import PDMI.settings as settings +import os +from django.contrib.auth.models import User + + +def upload_path(instance, filename): + path = os.path.join( + 'modules', instance.module.name.lower(), instance.ver, filename) + if os.path.isfile(os.path.join(settings.MEDIA_ROOT, path)): + # Delete file if already exists + os.remove(os.path.join(settings.MEDIA_ROOT, path)) + return path + class Module(models.Model): - file = models.FileField() name = models.CharField(max_length=255) - desc = models.TextField(max_length=2048) + desc = models.TextField(max_length=2048, null=True) + creator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) + + def __str__(self): + return self.name + + +class Version(models.Model): + module = models.ForeignKey(Module, on_delete=models.CASCADE) + ver = models.CharField(max_length=15) + bot_ver = models.CharField(max_length=15) + metamodule = models.BooleanField(default=False) + file = models.FileField(upload_to=upload_path) + + def __str__(self): + return self.ver + +class Dependency(models.Model): + version = models.ForeignKey(Version, on_delete=models.CASCADE) + dep_module = models.CharField(max_length=255) + dep_version = models.CharField(max_length=15) diff --git a/PDMI/store/views.py b/PDMI/store/views.py index 4ff2ac9..f6173e9 100644 --- a/PDMI/store/views.py +++ b/PDMI/store/views.py @@ -5,9 +5,14 @@ from bootstrap_modal_forms.generic import BSModalCreateView, BSModalLoginView from .forms import CustomUserCreationForm, CustomAuthenticationForm from django.views.generic.edit import FormView from .forms import FileFieldForm -import os -import PDMI.settings as settings from .response import response_mimetype, JsonResponse +from .models import Module, Version, Dependency +from PDMI import settings +from packaging.specifiers import SpecifierSet +import os +import zipfile +import toml +import shutil class SignUpView(BSModalCreateView): @@ -33,10 +38,49 @@ class UploadView(LoginRequiredMixin, FormView): form = self.get_form(form_class) files = request.FILES.getlist('file') if form.is_valid(): - for f in files: - with open(os.path.join(settings.MEDIA_ROOT, f.name), 'wb+') as destination: - for chunk in f.chunks(): - destination.write(chunk) + for file in files: + zip_path = os.path.join(settings.MEDIA_ROOT, file.name) + extract_path = os.path.splitext(zip_path)[0] + with open(zip_path, "wb+") as f: # Writing archive + for chunk in file.chunks(): + f.write(chunk) + with zipfile.ZipFile(zip_path, 'r') as zip: # Unzip archive + zip.extractall(extract_path) + with open(os.path.join(extract_path, 'infos.toml'), 'r') as f: + # Reading and parsing toml file + module_info = toml.loads(f.read()) + shutil.rmtree(extract_path) + os.remove(zip_path) + if Module.objects.filter(name=module_info['name'].lower(), + creator=request.user): + # If module exists + module = Module.objects.get(name=module_info['name'].lower(), + creator=request.user) + else: + # Else, creating module + module = Module(name=module_info['name'].lower(), + desc=module_info['description'], + creator=request.user) + if Version.objects.filter(module=module, ver=module_info['version']).count() > 0: + # If version already exists, edit existing version + version = Version.objects.get( + module=module, ver=module_info['version']) + version.file = file + version.metamodule = module_info['metamodule'] + version.bot_ver = module_info['bot_version'] + else: + version = Version(module=module, ver=module_info['version'], + bot_ver=module_info['bot_version'], + metamodule=module_info['metamodule'], + file=file) + module.save() + version.save() + for dependency in module_info['dependencies']: + if not Dependency.objects.filter(version=version, dep_module=dependency, + dep_version=module_info['dependencies'][dependency]).count() > 0: + dep = Dependency(version=version, dep_module=dependency, + dep_version=module_info['dependencies'][dependency]) + dep.save() return JsonResponse({'form': True}) else: return JsonResponse({'form': False})