Add ModuleDetail page and view + README.md handling on upload
This commit is contained in:
parent
dfcecd47e0
commit
951c149a7f
@ -43,6 +43,7 @@ INSTALLED_APPS = [
|
|||||||
'front',
|
'front',
|
||||||
'bootstrap_modal_forms',
|
'bootstrap_modal_forms',
|
||||||
'widget_tweaks',
|
'widget_tweaks',
|
||||||
|
'markdownx',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
@ -23,4 +23,5 @@ urlpatterns = [
|
|||||||
path('', include('front.urls'), name='front'),
|
path('', include('front.urls'), name='front'),
|
||||||
path('store/', include('store.urls'), name='store'),
|
path('store/', include('store.urls'), name='store'),
|
||||||
path('doc/', include('doc.urls'), name='doc'),
|
path('doc/', include('doc.urls'), name='doc'),
|
||||||
|
path('markdownx/', include('markdownx.urls')),
|
||||||
]
|
]
|
||||||
|
30
PDMI/store/migrations/0008_auto_20200428_0821.py
Normal file
30
PDMI/store/migrations/0008_auto_20200428_0821.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Generated by Django 3.0 on 2020-04-28 08:21
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('store', '0007_remove_version_dependencies'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='module',
|
||||||
|
name='created_at',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='module',
|
||||||
|
name='updated_at',
|
||||||
|
field=models.DateTimeField(auto_now=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='module',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=255, unique=True),
|
||||||
|
),
|
||||||
|
]
|
19
PDMI/store/migrations/0009_version_readme.py
Normal file
19
PDMI/store/migrations/0009_version_readme.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 3.0 on 2020-04-28 11:00
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
import markdownx.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('store', '0008_auto_20200428_0821'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='version',
|
||||||
|
name='readme',
|
||||||
|
field=markdownx.models.MarkdownxField(default='No readme provided.'),
|
||||||
|
),
|
||||||
|
]
|
@ -1,14 +1,16 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.signals import pre_save
|
from django.db.models.signals import pre_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
from django.utils import timezone
|
||||||
import PDMI.settings as settings
|
import PDMI.settings as settings
|
||||||
import os
|
import os
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from markdownx.models import MarkdownxField
|
||||||
|
|
||||||
|
|
||||||
def upload_path(instance, filename):
|
def upload_path(instance, filename):
|
||||||
path = os.path.join(
|
path = os.path.join(
|
||||||
'modules', instance.module.name.lower(), instance.ver, filename)
|
'modules', instance.module.name.lower(), instance.ver, instance.module.name+'.zip')
|
||||||
if os.path.isfile(os.path.join(settings.MEDIA_ROOT, path)):
|
if os.path.isfile(os.path.join(settings.MEDIA_ROOT, path)):
|
||||||
# Delete file if already exists
|
# Delete file if already exists
|
||||||
os.remove(os.path.join(settings.MEDIA_ROOT, path))
|
os.remove(os.path.join(settings.MEDIA_ROOT, path))
|
||||||
@ -19,6 +21,8 @@ class Module(models.Model):
|
|||||||
name = models.CharField(max_length=255, unique=True)
|
name = models.CharField(max_length=255, unique=True)
|
||||||
desc = models.TextField(max_length=2048, null=True)
|
desc = models.TextField(max_length=2048, null=True)
|
||||||
creator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
|
creator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
@ -30,6 +34,7 @@ class Version(models.Model):
|
|||||||
bot_ver = models.CharField(max_length=15)
|
bot_ver = models.CharField(max_length=15)
|
||||||
metamodule = models.BooleanField(default=False)
|
metamodule = models.BooleanField(default=False)
|
||||||
file = models.FileField(upload_to=upload_path)
|
file = models.FileField(upload_to=upload_path)
|
||||||
|
readme = MarkdownxField(default="No readme provided.")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.ver
|
return self.ver
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
{% extends "store/base.html" %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
<main role="main">
|
|
||||||
<div class="jumbotron">
|
|
||||||
<div class="container">
|
|
||||||
<h1 class="display-3">PDMI Store</h1>
|
|
||||||
<!-- Search form -->
|
|
||||||
<form class="form-inline d-flex justify-content-center md-form form-sm mt-0">
|
|
||||||
<i class="fa fa-search" aria-hidden="true"></i>
|
|
||||||
<input class="form-control ml-3 w-75" type="text" placeholder="Search" aria-label="Search">
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
{% for i in '123'|make_list %}
|
|
||||||
<div class="col-md-4 my-3">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h5 class="card-title">Card title</h5>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<h6 class="card-subtitle mb-2 text-muted">Card subtitle - Description</h6>
|
|
||||||
<p class="card-text">Donec id elit non mi porta gravida at eget metus. Fusce dapibus,
|
|
||||||
tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo
|
|
||||||
sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui.
|
|
||||||
</p>
|
|
||||||
<p><a class="btn btn-secondary" href="#" role="button">View details »</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
{% for i in '123'|make_list %}
|
|
||||||
<div class="col-md-4 my-3">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h5 class="card-title">Card title</h5>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<h6 class="card-subtitle mb-2 text-muted">Card subtitle - Description</h6>
|
|
||||||
<p class="card-text">Donec id elit non mi porta gravida at eget metus. Fusce dapibus,
|
|
||||||
tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo
|
|
||||||
sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui.
|
|
||||||
</p>
|
|
||||||
<p><a class="btn btn-secondary" href="#" role="button">View details »</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
{% for i in '123'|make_list %}
|
|
||||||
<div class="col-md-4 my-3">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h5 class="card-title">Card title</h5>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<h6 class="card-subtitle mb-2 text-muted">Card subtitle - Description</h6>
|
|
||||||
<p class="card-text">Donec id elit non mi porta gravida at eget metus. Fusce dapibus,
|
|
||||||
tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo
|
|
||||||
sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui.
|
|
||||||
</p>
|
|
||||||
<p><a class="btn btn-secondary" href="#" role="button">View details »</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
{% endblock main %}
|
|
53
PDMI/store/templates/store/module_detail.html
Normal file
53
PDMI/store/templates/store/module_detail.html
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
{% extends "store/base.html" %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<main role="main">
|
||||||
|
<div class="jumbotron">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="display-3">{{ module.name|title }}</h1>
|
||||||
|
<p>{{ module.desc }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h3>Versions</h3>
|
||||||
|
<p>
|
||||||
|
{% for version in versions %}
|
||||||
|
{% if version.ver == view_version.ver %}
|
||||||
|
<h4><strong>{{ version.ver }}</strong></h4>
|
||||||
|
{% else %}
|
||||||
|
<h4>{{ version.ver }}</h4>
|
||||||
|
{% endif %}
|
||||||
|
<h5>Dependencies</h5>
|
||||||
|
{% for deps in dependencies %}
|
||||||
|
{% for dep in deps %}
|
||||||
|
{% if dep.version.ver == version.ver %}
|
||||||
|
{{ dep.dep_module }}
|
||||||
|
{{ dep.dep_version }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
{{ mdreadme|safe }}
|
||||||
|
</div>
|
||||||
|
<div class="col bg-grey">
|
||||||
|
<p>
|
||||||
|
<button type="button" name="button" class="btn btn-success">
|
||||||
|
Download <span class="fas fa-download"></span>
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Module by {{ module.creator }} <span class="fas fa-user border"></span>
|
||||||
|
</p>
|
||||||
|
<p>Created on {{ module.created_at }}</p>
|
||||||
|
<p>Last edit on {{ module.updated_at }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{% endblock main %}
|
52
PDMI/store/templates/store/module_list.html
Normal file
52
PDMI/store/templates/store/module_list.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{% extends "store/base.html" %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<main role="main">
|
||||||
|
<div class="jumbotron">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="display-3">PDMI Store</h1>
|
||||||
|
<!-- Search form -->
|
||||||
|
<form class="form-inline d-flex justify-content-center md-form form-sm mt-0">
|
||||||
|
<i class="fa fa-search" aria-hidden="true"></i>
|
||||||
|
<input class="form-control ml-3 w-75" type="text" placeholder="Search" aria-label="Search">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<div class="card-columns">
|
||||||
|
{% for module in object_list %}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="card-title">{{ module.name|title }}</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<!-- <h6 class="card-subtitle mb-2 text-muted">Card subtitle - Description</h6> -->
|
||||||
|
<p class="card-text">{{ module.desc }}
|
||||||
|
</p>
|
||||||
|
<p><a class="btn btn-secondary" href="{% url 'mod_detail' pk=module.id req_ver='latest' %}" role="button">View details »</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- {% for i in '123'|make_list %}
|
||||||
|
<div class="col-md-4 my-3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="card-title">Card title</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="card-subtitle mb-2 text-muted">Card subtitle - Description</h6>
|
||||||
|
<p class="card-text">Donec id elit non mi porta gravida at eget metus. Fusce dapibus,
|
||||||
|
tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo
|
||||||
|
sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui.
|
||||||
|
</p>
|
||||||
|
<p><a class="btn btn-secondary" href="#" role="button">View details »</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %} -->
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{% endblock main %}
|
@ -19,9 +19,11 @@ from django.contrib.auth.views import LoginView, LogoutView
|
|||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', TemplateView.as_view(template_name='store/index.html'), name='store_front_page'),
|
path('', views.ModuleListView.as_view(), name='store_front_page'),
|
||||||
path('login/', views.CustomLoginView.as_view(), name='login_modal'),
|
path('login/', views.CustomLoginView.as_view(), name='login_modal'),
|
||||||
path('signup/', views.SignUpView.as_view(), name='signup_modal'),
|
path('signup/', views.SignUpView.as_view(), name='signup_modal'),
|
||||||
#path('logout/', LogoutView.as_view(), name='logout'),
|
path('logout/', LogoutView.as_view(), name='logout'),
|
||||||
path('upload/', views.UploadView.as_view(), name='upload'),
|
path('upload/', views.UploadView.as_view(), name='upload'),
|
||||||
|
path('module/<str:pk>/', views.ModuleDetailView.as_view(), name='mod_detail'),
|
||||||
|
path('module/<str:pk>/<str:req_ver>/', views.ModuleDetailView.as_view(), name='mod_detail'),
|
||||||
]
|
]
|
||||||
|
@ -1,21 +1,28 @@
|
|||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render, get_object_or_404
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
from django.utils import timezone
|
||||||
from bootstrap_modal_forms.generic import BSModalCreateView, BSModalLoginView
|
from bootstrap_modal_forms.generic import BSModalCreateView, BSModalLoginView
|
||||||
from .forms import CustomUserCreationForm, CustomAuthenticationForm
|
from .forms import CustomUserCreationForm, CustomAuthenticationForm
|
||||||
from django.views.generic.edit import FormView
|
from django.views.generic.edit import FormView
|
||||||
|
from django.views.generic.list import ListView
|
||||||
|
from django.views.generic.detail import DetailView
|
||||||
|
from django.views import View
|
||||||
from .forms import FileFieldForm
|
from .forms import FileFieldForm
|
||||||
from .response import response_mimetype, JsonResponse
|
from .response import response_mimetype, JsonResponse
|
||||||
from .models import Module, Version, Dependency
|
from .models import Module, Version, Dependency
|
||||||
from PDMI import settings
|
from PDMI import settings
|
||||||
from packaging.specifiers import SpecifierSet
|
from packaging.specifiers import SpecifierSet
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
from django.utils import timezone
|
||||||
|
from markdownx.utils import markdownify
|
||||||
import os
|
import os
|
||||||
import zipfile
|
import zipfile
|
||||||
import toml
|
import toml
|
||||||
import shutil
|
import shutil
|
||||||
import magic
|
import magic
|
||||||
|
|
||||||
|
REQUIRED_FIELDS = ['name', 'description', 'version', 'bot_version']
|
||||||
|
|
||||||
class SignUpView(BSModalCreateView):
|
class SignUpView(BSModalCreateView):
|
||||||
form_class = CustomUserCreationForm
|
form_class = CustomUserCreationForm
|
||||||
@ -30,8 +37,33 @@ class CustomLoginView(BSModalLoginView):
|
|||||||
success_message = 'Success: You were successfully logged in.'
|
success_message = 'Success: You were successfully logged in.'
|
||||||
extra_content = dict(success_url=reverse_lazy('index'))
|
extra_content = dict(success_url=reverse_lazy('index'))
|
||||||
|
|
||||||
|
class ModuleListView(ListView):
|
||||||
|
model = Module
|
||||||
|
paginate_by = 100
|
||||||
|
|
||||||
REQUIRED_FIELDS = ['name', 'description', 'version', 'bot_version']
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context['now'] = timezone.now()
|
||||||
|
return context
|
||||||
|
|
||||||
|
class ModuleDetailView(View):
|
||||||
|
def get(self, request, pk, req_ver="latest"):
|
||||||
|
module = get_object_or_404(Module, id=pk)
|
||||||
|
versions = Version.objects.filter(module=module)
|
||||||
|
deps = []
|
||||||
|
for version in versions:
|
||||||
|
deps.append(Dependency.objects.filter(version=version))
|
||||||
|
if req_ver == "latest":
|
||||||
|
view_version = Version.objects.filter(module=module).order_by('-id')[0]
|
||||||
|
else:
|
||||||
|
view_version = Version.objects.get(module=module, ver=req_ver)
|
||||||
|
return render(request, 'store/module_detail.html', {
|
||||||
|
'module': module,
|
||||||
|
'versions': versions,
|
||||||
|
'dependencies': deps,
|
||||||
|
'view_version': view_version,
|
||||||
|
'mdreadme': markdownify(view_version.readme),
|
||||||
|
})
|
||||||
|
|
||||||
class UploadView(LoginRequiredMixin, FormView):
|
class UploadView(LoginRequiredMixin, FormView):
|
||||||
form_class = FileFieldForm
|
form_class = FileFieldForm
|
||||||
@ -59,6 +91,12 @@ class UploadView(LoginRequiredMixin, FormView):
|
|||||||
with open(os.path.join(extract_path, 'infos.toml'), 'r') as f:
|
with open(os.path.join(extract_path, 'infos.toml'), 'r') as f:
|
||||||
# Reading and parsing toml file
|
# Reading and parsing toml file
|
||||||
module_info = toml.loads(f.read())
|
module_info = toml.loads(f.read())
|
||||||
|
if os.path.isfile(os.path.join(extract_path, 'README.md')):
|
||||||
|
with open(os.path.join(extract_path, 'README.md'), 'r') as f:
|
||||||
|
# Reading README.md file
|
||||||
|
readme = f.read()
|
||||||
|
else:
|
||||||
|
readme = "No readme provided."
|
||||||
for required_field in REQUIRED_FIELDS:
|
for required_field in REQUIRED_FIELDS:
|
||||||
if not required_field in module_info.keys():
|
if not required_field in module_info.keys():
|
||||||
return JsonResponse({'error': f'Field {required_field}\
|
return JsonResponse({'error': f'Field {required_field}\
|
||||||
@ -82,11 +120,12 @@ class UploadView(LoginRequiredMixin, FormView):
|
|||||||
version.file = file
|
version.file = file
|
||||||
version.metamodule = module_info['metamodule']
|
version.metamodule = module_info['metamodule']
|
||||||
version.bot_ver = module_info['bot_version']
|
version.bot_ver = module_info['bot_version']
|
||||||
|
version.readme = readme
|
||||||
else:
|
else:
|
||||||
version = Version(module=module, ver=module_info['version'],
|
version = Version(module=module, ver=module_info['version'],
|
||||||
bot_ver=module_info['bot_version'],
|
bot_ver=module_info['bot_version'],
|
||||||
metamodule=module_info['metamodule'],
|
metamodule=module_info['metamodule'],
|
||||||
file=file)
|
file=file, readme=readme)
|
||||||
module.save()
|
module.save()
|
||||||
version.save()
|
version.save()
|
||||||
for dependency in module_info['dependencies']:
|
for dependency in module_info['dependencies']:
|
||||||
|
1
Pipfile
1
Pipfile
@ -10,6 +10,7 @@ django = "*"
|
|||||||
django-widget-tweaks = "*"
|
django-widget-tweaks = "*"
|
||||||
django-bootstrap-modal-forms = "*"
|
django-bootstrap-modal-forms = "*"
|
||||||
python-magic = "*"
|
python-magic = "*"
|
||||||
|
django-markdownx = "3.0.1"
|
||||||
|
|
||||||
[requires]
|
[requires]
|
||||||
python_version = "3.8"
|
python_version = "3.8"
|
||||||
|
Loading…
Reference in New Issue
Block a user