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',
|
||||
'bootstrap_modal_forms',
|
||||
'widget_tweaks',
|
||||
'markdownx',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
@ -23,4 +23,5 @@ urlpatterns = [
|
||||
path('', include('front.urls'), name='front'),
|
||||
path('store/', include('store.urls'), name='store'),
|
||||
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.models.signals import pre_save
|
||||
from django.dispatch import receiver
|
||||
from django.utils import timezone
|
||||
import PDMI.settings as settings
|
||||
import os
|
||||
from django.contrib.auth.models import User
|
||||
from markdownx.models import MarkdownxField
|
||||
|
||||
|
||||
def upload_path(instance, filename):
|
||||
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)):
|
||||
# Delete file if already exists
|
||||
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)
|
||||
desc = models.TextField(max_length=2048, 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):
|
||||
return self.name
|
||||
@ -30,6 +34,7 @@ class Version(models.Model):
|
||||
bot_ver = models.CharField(max_length=15)
|
||||
metamodule = models.BooleanField(default=False)
|
||||
file = models.FileField(upload_to=upload_path)
|
||||
readme = MarkdownxField(default="No readme provided.")
|
||||
|
||||
def __str__(self):
|
||||
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
|
||||
|
||||
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('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('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.shortcuts import render
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils import timezone
|
||||
from bootstrap_modal_forms.generic import BSModalCreateView, BSModalLoginView
|
||||
from .forms import CustomUserCreationForm, CustomAuthenticationForm
|
||||
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 .response import response_mimetype, JsonResponse
|
||||
from .models import Module, Version, Dependency
|
||||
from PDMI import settings
|
||||
from packaging.specifiers import SpecifierSet
|
||||
from django.http import HttpResponse
|
||||
from django.utils import timezone
|
||||
from markdownx.utils import markdownify
|
||||
import os
|
||||
import zipfile
|
||||
import toml
|
||||
import shutil
|
||||
import magic
|
||||
|
||||
REQUIRED_FIELDS = ['name', 'description', 'version', 'bot_version']
|
||||
|
||||
class SignUpView(BSModalCreateView):
|
||||
form_class = CustomUserCreationForm
|
||||
@ -30,8 +37,33 @@ class CustomLoginView(BSModalLoginView):
|
||||
success_message = 'Success: You were successfully logged in.'
|
||||
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):
|
||||
form_class = FileFieldForm
|
||||
@ -59,6 +91,12 @@ class UploadView(LoginRequiredMixin, FormView):
|
||||
with open(os.path.join(extract_path, 'infos.toml'), 'r') as f:
|
||||
# Reading and parsing toml file
|
||||
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:
|
||||
if not required_field in module_info.keys():
|
||||
return JsonResponse({'error': f'Field {required_field}\
|
||||
@ -82,11 +120,12 @@ class UploadView(LoginRequiredMixin, FormView):
|
||||
version.file = file
|
||||
version.metamodule = module_info['metamodule']
|
||||
version.bot_ver = module_info['bot_version']
|
||||
version.readme = readme
|
||||
else:
|
||||
version = Version(module=module, ver=module_info['version'],
|
||||
bot_ver=module_info['bot_version'],
|
||||
metamodule=module_info['metamodule'],
|
||||
file=file)
|
||||
file=file, readme=readme)
|
||||
module.save()
|
||||
version.save()
|
||||
for dependency in module_info['dependencies']:
|
||||
|
Loading…
Reference in New Issue
Block a user