Add ModuleDetail page and view + README.md handling on upload

This commit is contained in:
HugoNeveux 2020-04-28 14:09:22 +02:00
parent dfcecd47e0
commit 951c149a7f
11 changed files with 209 additions and 83 deletions

View File

@ -43,6 +43,7 @@ INSTALLED_APPS = [
'front',
'bootstrap_modal_forms',
'widget_tweaks',
'markdownx',
]
MIDDLEWARE = [

View File

@ -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')),
]

View 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),
),
]

View 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.'),
),
]

View File

@ -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

View File

@ -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 &raquo;</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 &raquo;</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 &raquo;</a></p>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</main>
{% endblock main %}

View 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 %}

View 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 &raquo;</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 &raquo;</a></p>
</div>
</div>
</div>
{% endfor %} -->
</div>
</main>
{% endblock main %}

View File

@ -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'),
]

View File

@ -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']:

View File

@ -10,6 +10,7 @@ django = "*"
django-widget-tweaks = "*"
django-bootstrap-modal-forms = "*"
python-magic = "*"
django-markdownx = "3.0.1"
[requires]
python_version = "3.8"