Update scripts for building and deployment

This commit is contained in:
Daniel Egger 2022-05-30 14:43:36 +02:00
parent a431f35743
commit 424af03ce1
38 changed files with 156 additions and 275 deletions

2
.gitignore vendored
View File

@ -283,3 +283,5 @@ cypress/screenshots
cypress/test-reports
/server/vbv_lernwelt/static/css/tailwind.css
/server/vbv_lernwelt/static/vue/
/server/vbv_lernwelt/templates/vue/index.html

View File

@ -28,7 +28,7 @@ cap.deploy_one_click_app(
namespace='vbv-lernwelt',
# check https://github.com/caprover/one-click-apps/blob/master/public/v4/apps/postgres.yml
app_variables={
'$$cap_postgres_version': '14.1',
'$$cap_postgres_version': '14.2',
'$$cap_pg_user': db_user,
'$$cap_pg_pass': db_pass,
'$$cap_pg_db': db_name,

View File

@ -1,5 +1,8 @@
#!/bin/bash
# create client
npm run build
# create and push new docker container
docker build -f compose/django/Dockerfile -t iterativ/vbv-lernwelt-django .
docker push iterativ/vbv-lernwelt-django

View File

@ -3,7 +3,7 @@
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"build": "vue-tsc --noEmit && vite build && cp ./dist/index.html ../server/vbv_lernwelt/templates/vue/index.html && cp -r ./dist/static/vue ../server/vbv_lernwelt/static/",
"preview": "vite preview --port 5050",
"test:unit": "vitest --environment jsdom",
"test:e2e": "start-server-and-test preview http://127.0.0.1:5050/ 'cypress open'",

View File

@ -1,7 +1,7 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import {createApp} from 'vue'
import {createPinia} from 'pinia'
import {setupI18n, loadLocaleMessages} from './i18n'
import {setupI18n} from './i18n'
import App from './App.vue'
import router from './router'
@ -11,7 +11,7 @@ const i18n = setupI18n()
const app = createApp(App)
// todo: define lang setup
await loadLocaleMessages(i18n, 'de')
// await loadLocaleMessages(i18n, 'de')
app.use(createPinia())
app.use(router)

View File

@ -36,8 +36,8 @@ const router = createRouter({
component: () => import('../views/ProfileView.vue'),
},
{
path: '/learningpath/:learningPathId',
component: () => import('../views/LearningPathOverview.vue'),
path: '/:pathMatch(.*)*',
component: () => import('../views/404View.vue'),
},
]
})

View File

@ -0,0 +1,11 @@
<template>
<main>
<h1>404 - Not Found as Vue view...</h1>
</main>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>

View File

@ -1,16 +0,0 @@
<template>
<h1>Lernpfad Overview</h1>
<LearningPath />
</template>
<script>
import LearningPath from "../components/LearningPath.vue";
export default {
name: 'LearningPathOverview',
components: {LearningPath}
}
</script>
<style scoped>
</style>

View File

@ -1,9 +1,8 @@
import path from 'path'
import {fileURLToPath, URL} from 'url'
import {defineConfig, loadEnv} from 'vite'
import vue from '@vitejs/plugin-vue'
import vueI18n from '@intlify/vite-plugin-vue-i18n'
// import vueI18n from '@intlify/vite-plugin-vue-i18n'
import alias from '@rollup/plugin-alias'
// https://vitejs.dev/config/
@ -12,17 +11,18 @@ export default ({mode}) => {
return defineConfig({
plugins: [
vue(),
vueI18n({
include: path.resolve(__dirname, './locales/**')
}),
// vueI18n({
// include: path.resolve(__dirname, './locales/**')
// }),
// won't work in vite's resolve.alias, so we'll make the alias here
alias({
entries: [
{
find: 'vue-i18n',
replacement: path.resolve(__dirname, './node_modules/vue-i18n/dist/vue-i18n.runtime.esm-bundler.js')
}
]
// TODO: why is that used?
// entries: [
// {
// find: 'vue-i18n',
// replacement: path.resolve(__dirname, './node_modules/vue-i18n/dist/vue-i18n.runtime.esm-bundler.js')
// }
// ]
})
],
resolve: {
@ -30,5 +30,8 @@ export default ({mode}) => {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
build: {
assetsDir: 'static/vue',
}
})
}

View File

@ -13,10 +13,7 @@ FROM node:16-bullseye-slim as client-builder
ARG APP_HOME=/app
WORKDIR ${APP_HOME}
COPY ./server/package.json ${APP_HOME}
RUN npm install && npm cache clean --force
COPY ./server ${APP_HOME}
RUN npm run build
# define an alias for the specfic python version used in this file.
FROM python:${PYTHON_VERSION} as python

View File

@ -4,9 +4,8 @@ set -o errexit
set -o pipefail
set -o nounset
python /app/manage.py collectstatic --noinput
python /app/manage.py migrate
python /app/manage.py createcachetable
python /app/manage.py migrate
/usr/local/bin/gunicorn config.asgi --bind 0.0.0.0:80 --chdir=/app -k uvicorn.workers.UvicornWorker

View File

@ -2,6 +2,8 @@
"name": "vbv_lernwelt_cypress",
"version": "1.0.0",
"scripts": {
"build": "npm install --prefix client && npm run build --prefix client && npm run build:tailwind",
"build:tailwind": "tailwindcss -i ./tailwind/input.css -o ./server/vbv_lernwelt/static/css/tailwind.css --minify",
"test": "echo \"Error: no test specified\" && exit 1",
"cypress:open": "cypress open",
"cypress:run": "cypress run",

View File

@ -57,7 +57,7 @@ if [ "$SKIP_SETUP" = false ]; then
python3 server/manage.py createcachetable --settings="$DJANGO_SETTINGS_MODULE"
python3 server/manage.py migrate --settings="$DJANGO_SETTINGS_MODULE"
python3 server/manage.py create_default_users --settings="$DJANGO_SETTINGS_MODULE"
python3 server/manage.py create_default_learningpath --settings="$DJANGO_SETTINGS_MODULE"
python3 server/manage.py create_default_learning_path --settings="$DJANGO_SETTINGS_MODULE"
# make django translations
(cd server && python3 manage.py compilemessages --settings="$DJANGO_SETTINGS_MODULE")

View File

@ -1,46 +0,0 @@
version: '3'
volumes:
production_postgres_data: {}
production_postgres_data_backups: {}
production_traefik: {}
services:
django:
build:
context: .
dockerfile: ./compose/production/django/Dockerfile
image: vbv_lernwelt_production_django
depends_on:
- postgres
- redis
env_file:
- env_secrets/production.env
command: /start
postgres:
build:
context: .
dockerfile: ./compose/production/postgres/Dockerfile
image: vbv_lernwelt_production_postgres
volumes:
- production_postgres_data:/var/lib/postgresql/data:Z
- production_postgres_data_backups:/backups:z
env_file:
- env_secrets/production.env
traefik:
build:
context: .
dockerfile: ./compose/production/traefik/Dockerfile
image: vbv_lernwelt_production_traefik
depends_on:
- django
volumes:
- production_traefik:/etc/traefik/acme:z
ports:
- "0.0.0.0:80:80"
- "0.0.0.0:443:443"
redis:
image: redis:6

View File

@ -1,4 +1,3 @@
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
@ -19,7 +18,7 @@ from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt
from vbv_lernwelt.core.views import (
rate_limit_exceeded_view,
permission_denied_view,
check_rate_limit, vue_home,
check_rate_limit, vue_home, cypress_reset_view,
)
from .wagtail_api import api_router
@ -65,6 +64,11 @@ urlpatterns += [
path("api/docs/", SpectacularSwaggerView.as_view(url_name="api-schema"), name="api-docs",),
path("", include(grapple_urls)),
]
if settings.APP_ENVIRONMENT != 'production':
urlpatterns += [
re_path(r'cypressreset/$', cypress_reset_view, name='cypress_reset_view'),
]
# fmt: on

View File

@ -1,5 +0,0 @@
python manage.py migrate
python manage.py createcachetable
python manage.py create_default_users
#python manage.py create_default_learingpath

View File

@ -4,17 +4,24 @@ from django.contrib.auth.models import Group
from vbv_lernwelt.core.models import User
def create_default_users(user_model=User, group_model=Group):
def create_default_users(user_model=User, group_model=Group, default_password=None):
admin_group, created = group_model.objects.get_or_create(name='admin_group')
content_creator_grop, created = group_model.objects.get_or_create(name='content_creator_grop')
student_group, created = group_model.objects.get_or_create(name='student_group')
admin_user, created = _get_or_create_user(user_model=user_model, username='admin', password='admin')
admin_password = default_password
if not admin_password:
admin_password = 'admin'
admin_user, created = _get_or_create_user(user_model=user_model, username='admin', password=admin_password)
admin_user.is_superuser = True
admin_user.is_staff = True
admin_user.groups.add(admin_group)
admin_user.save()
student_user, created = _get_or_create_user(user_model=user_model, username='student', password='student')
student_user_password = default_password
if not student_user_password:
student_user_password = 'student'
student_user, created = _get_or_create_user(user_model=user_model, username='student', password=student_user_password)
student_user.groups.add(student_group)
student_user.save()

View File

@ -1,8 +1,7 @@
from vbv_lernwelt.core.create_default_users import create_default_users
import djclick as click
from vbv_lernwelt.core.create_default_users import create_default_users
@click.command()
def command():

View File

@ -1,7 +1,7 @@
# pylint: disable=import-outside-toplevel
import djclick as click
from django.contrib.auth import get_user_model
from vbv_lernwelt.learnpath.tests.create_default_learning_path import create_default_learning_path, \
delete_default_learning_path
@click.command()
@ -9,17 +9,5 @@ from django.contrib.auth import get_user_model
def command(customer_language):
print("cypress reset data")
User = get_user_model()
users = [
"cypress@example.com",
]
for user in users:
User.objects.filter(username=user).delete()
user = User.objects.create(
username=user,
email=user,
)
user.set_password("test")
user.save()
delete_default_learning_path()
create_default_learning_path()

View File

@ -1,20 +1,16 @@
from django.conf import settings
from django.contrib.auth.models import Group
from django.db import migrations
from vbv_lernwelt.core.create_default_users import create_default_users
from vbv_lernwelt.core.models import User
def create_iterativ_users(apps, schema_editor):
for username in [
"info@iterativ.ch",
]:
user = User.objects.create(
username=username,
email=username,
is_superuser=True,
is_staff=True,
)
user.set_password("ACEEs0DCmNaPxdoNV8vhccuCTRl9b")
user.save()
def create_users(apps, schema_editor):
default_password = 'ACEEs0DCmNaPxdoNV8vhccuCTRl9b'
if settings.APP_ENVIRONMENT == 'development':
default_password = None
create_default_users(user_model=User, group_model=Group, default_password=default_password)
class Migration(migrations.Migration):
@ -23,5 +19,5 @@ class Migration(migrations.Migration):
]
operations = [
migrations.RunPython(create_iterativ_users),
migrations.RunPython(create_users),
]

View File

@ -1,10 +1,14 @@
# Create your views here.
import requests
from django.conf import settings
from django.http import JsonResponse, HttpResponse
from django.core.management import call_command
from django.http import JsonResponse, HttpResponse, HttpResponseRedirect
from django.shortcuts import render
from django.views.decorators.csrf import ensure_csrf_cookie
from ratelimit.decorators import ratelimit
from rest_framework import authentication
from rest_framework.decorators import api_view, authentication_classes, permission_classes
from rest_framework.permissions import IsAdminUser
from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt
@ -24,7 +28,7 @@ def vue_home(request):
)
# render index.html from `npm run build`
return render(request, 'index.html', {})
return render(request, 'vue/index.html', {})
def permission_denied_view(request, exception):
@ -51,3 +55,13 @@ def server_json_error(request, *args, **kwargs):
@django_view_authentication_exempt
def check_rate_limit(request):
return HttpResponse(content=b"Hello")
@api_view(['POST'])
@authentication_classes((authentication.SessionAuthentication,))
@permission_classes((IsAdminUser,))
def cypress_reset_view(request):
if settings.APP_ENVIRONMENT != 'production':
call_command('cypress_reset')
return HttpResponseRedirect('/admin/')

View File

@ -1,5 +1,3 @@
# pylint: disable=import-outside-toplevel
import djclick as click
from vbv_lernwelt.learnpath.tests.create_default_learning_path import create_default_learning_path

View File

@ -0,0 +1,8 @@
import djclick as click
from vbv_lernwelt.learnpath.tests.create_default_learning_path import delete_default_learning_path
@click.command()
def command():
delete_default_learning_path()

View File

@ -1,14 +0,0 @@
# pylint: disable=import-outside-toplevel
import djclick as click
from django.contrib.auth import get_user_model
from vbv_lernwelt.learnpath.tests.create_default_learning_path import create_default_learning_path, \
delete_default_learning_path
import djclick as click
@click.command()
def command():
delete_default_learning_path()

View File

@ -1,4 +1,4 @@
# Generated by Django 3.2.12 on 2022-05-04 15:52
# Generated by Django 3.2.12 on 2022-05-30 13:51
from django.db import migrations, models
import django.db.models.deletion
@ -51,6 +51,18 @@ class Migration(migrations.Migration):
},
bases=('wagtailcore.page',),
),
migrations.CreateModel(
name='LearningPackage',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('sort_order', models.IntegerField(blank=True, editable=False, null=True)),
('title', models.CharField(default='', max_length=256)),
],
options={
'ordering': ['sort_order'],
'abstract': False,
},
),
migrations.CreateModel(
name='LearningPath',
fields=[
@ -61,19 +73,6 @@ class Migration(migrations.Migration):
},
bases=('wagtailcore.page',),
),
migrations.CreateModel(
name='LearningSequence',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('sort_order', models.IntegerField(blank=True, editable=False, null=True)),
('title', models.CharField(default='', max_length=256)),
('category', models.CharField(choices=[('INCIRCLE', 'In Circle'), ('START', 'Start'), ('END', 'End')], default='INCIRCLE', max_length=16)),
('circle', modelcluster.fields.ParentalKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='learning_sequences', to='learnpath.circle')),
],
options={
'verbose_name': 'Learning Sequence',
},
),
migrations.CreateModel(
name='Topic',
fields=[
@ -93,17 +92,36 @@ class Migration(migrations.Migration):
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
('sort_order', models.IntegerField(blank=True, editable=False, null=True)),
('contents', wagtail.core.fields.StreamField([('web_based_training', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('video', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())]))], blank=True, null=True)),
('learning_sequence', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='learning_units', to='learnpath.learningsequence')),
('learning_package', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='learning_units', to='learnpath.learningpackage')),
],
options={
'verbose_name': 'Learning Unit',
},
bases=('wagtailcore.page', models.Model),
),
migrations.CreateModel(
name='LearningSequence',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('sort_order', models.IntegerField(blank=True, editable=False, null=True)),
('title', models.CharField(default='', max_length=256)),
('category', models.CharField(choices=[('INCIRCLE', 'In Circle'), ('START', 'Start'), ('END', 'End')], default='INCIRCLE', max_length=16)),
('circle', modelcluster.fields.ParentalKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='learning_sequences', to='learnpath.circle')),
],
options={
'verbose_name': 'Learning Sequence',
},
),
migrations.AddField(
model_name='learningpackage',
name='learning_sequence',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='learning_packages', to='learnpath.learningsequence'),
),
migrations.CreateModel(
name='FullfillmentCriteria',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('sort_order', models.IntegerField(blank=True, editable=False, null=True)),
('name', models.CharField(max_length=2048)),
('competence', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='learnpath.competence')),
],

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.12 on 2022-05-04 16:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('learnpath', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='fullfillmentcriteria',
name='sort_order',
field=models.IntegerField(blank=True, editable=False, null=True),
),
]

View File

@ -1,36 +0,0 @@
# Generated by Django 3.2.12 on 2022-05-12 12:56
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('learnpath', '0002_fullfillmentcriteria_sort_order'),
]
operations = [
migrations.RemoveField(
model_name='learningunit',
name='learning_sequence',
),
migrations.CreateModel(
name='LearningPackage',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('sort_order', models.IntegerField(blank=True, editable=False, null=True)),
('title', models.CharField(default='', max_length=256)),
('learning_sequence', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='learning_packages', to='learnpath.learningsequence')),
],
options={
'ordering': ['sort_order'],
'abstract': False,
},
),
migrations.AddField(
model_name='learningunit',
name='learning_package',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='learning_units', to='learnpath.learningpackage'),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 3.2.12 on 2022-05-12 12:56
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('learnpath', '0003_auto_20220512_1456'),
]
operations = [
migrations.AlterField(
model_name='learningpackage',
name='learning_sequence',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='learning_packages', to='learnpath.learningsequence'),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 3.2.12 on 2022-05-12 12:56
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('learnpath', '0004_alter_learningpackage_learning_sequence'),
]
operations = [
migrations.AlterField(
model_name='learningunit',
name='learning_package',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='learning_units', to='learnpath.learningpackage'),
),
]

View File

@ -2,7 +2,7 @@ import factory
import wagtail_factories
from vbv_lernwelt.learnpath.models_competences import Competence, FullfillmentCriteria, CompetencePage
from vbv_lernwelt.learnpath.tests.learningpath_factories import LearningPathFactory
from vbv_lernwelt.learnpath.tests.learning_path_factories import LearningPathFactory
class CompetencePageFactory(wagtail_factories.PageFactory):

View File

@ -1,8 +1,9 @@
import wagtail_factories
from django.conf import settings
from wagtail.core.models import Site
from vbv_lernwelt.learnpath.models import LearningPath, Topic, Circle, LearningSequence, LearningUnit
from vbv_lernwelt.learnpath.tests.learningpath_factories import LearningPathFactory, TopicFactory, CircleFactory, \
from vbv_lernwelt.learnpath.tests.learning_path_factories import LearningPathFactory, TopicFactory, CircleFactory, \
LearningSequenceFactory, LearningUnitFactory, VideoBlockFactory, WebBasedTrainingBlockFactory, \
LearningPackageFactory
@ -13,8 +14,9 @@ def create_default_learning_path():
if not site:
site = wagtail_factories.SiteFactory(is_default_site=True)
site.port = 8000
site.save()
if settings.APP_ENVIRONMENT == 'development':
site.port = 8000
site.save()
# create_default_competences()

View File

@ -1,5 +1,6 @@
import wagtail_factories
import factory
import wagtail_factories
from vbv_lernwelt.learnpath.models import LearningPath, Topic, Circle, LearningSequence, LearningUnit, LearningPackage
from vbv_lernwelt.learnpath.models_learning_unit_content import VideoBlock, WebBasedTrainingBlock

View File

@ -1,5 +1,3 @@
from django.test import TestCase
from rest_framework.test import APITestCase
from vbv_lernwelt.core.admin import User
@ -14,8 +12,8 @@ class TestRetrieveLearingPathContents(APITestCase):
def setUpClass(cls) -> None:
super(TestRetrieveLearingPathContents, cls).setUpClass()
create_locales_for_wagtail()
create_default_learning_path()
create_default_users()
create_default_learning_path()
def setUp(self) -> None:
qs = LearningPath.objects.filter(title="Versicherungsvermittler/in")

View File

@ -1,10 +1,6 @@
from django.conf import settings
from django.test import TestCase
from wagtail.core.models import Locale
from vbv_lernwelt.learnpath.models import LearningPath
from vbv_lernwelt.learnpath.tests.create_default_competences import create_default_competences
from vbv_lernwelt.learnpath.tests.create_default_learning_path import create_default_learning_path
class TestCreateDefaultCompetences(TestCase):

View File

View File

@ -0,0 +1,15 @@
{% extends "admin/index.html" %}
{% block content %}
<div id="content-main">
{% include "admin/app_list.html" with app_list=app_list show_changelinks=True %}
<div class="content">
<form action="/cypressreset/" method="post">
{% csrf_token %}
<button class="" name="">Testdaten zurück setzen</button>
</form>
</div>
</div>
{% endblock %}

View File

@ -8,29 +8,22 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="VBV Lernumgebung">
<meta name="author" content="Iterativ GmbH">
<link href="/static/css/tailwind.css" rel="stylesheet">
<link rel="icon" href="{% static 'images/favicons/favicon.ico' %}">
{% block css %}
<link href="/static/css/tailwind.css" rel="stylesheet">
<!-- Your stuff: Third-party CSS libraries go here -->
<!-- This file stores project-specific CSS -->
<link href="{% static 'css/project.min.css' %}" rel="stylesheet">
<link href="{% static 'css/output.css' %}" rel="stylesheet">
{% endblock %}
<!-- Le javascript
================================================== -->
{# Placed at the top of the document so pages load faster with defer #}
<script defer src="https://unpkg.com/htmx.org@1.6.1"></script>
{% block javascript %}
<!-- Your stuff: Third-party javascript libraries go here -->
<!-- place project specific Javascript in this file -->
<script defer src="{% static 'js/project.js' %}"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
{% endblock javascript %}
</head>