Skip to content
Snippets Groups Projects
Commit afb38e61 authored by Tom Teichler's avatar Tom Teichler :beers:
Browse files

Merge branch 'master' into '75-migrate-to-django-3-0'

# Conflicts:
#   poetry.lock
parents 09c5d8ec 77710f5c
No related branches found
No related tags found
1 merge request!61Resolve "Migrate to Django 3.0"
Pipeline #354 failed
Showing
with 247 additions and 83 deletions
stages:
- test
- build
- test
- deploy
variables:
GIT_SUBMODULE_STRATEGY: recursive
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
POSTGRESQL_USER: biscuit
POSTGRESQL_DB: biscuit
BISCUIT_http__allowed_hosts: "['*']"
BISCUIT_caching__memcached__address: memcached:11211
BISCUIT_database__host: db
test:
stage: test
cache:
key:
files:
- poetry.lock
paths:
- .cache/pip
build_wheel:
stage: build
image:
name: python:3.8-buster
before_script:
- apt-get -y update && apt-get -y install postgresql libpq5 libpq-dev libssl-dev sudo
- pip install poetry
- adduser --disabled-password --gecos "Test User" testuser
name: registry.edugit.org/teckids/docker-images/python-pimped:master
script:
- sudo -u testuser poetry install
- sudo -u testuser poetry run tox
- poetry build
artifacts:
paths:
- dist/
build_docker:
stage: build
......@@ -39,6 +50,47 @@ build_docker:
--cleanup
only:
- master
- tags
test_wheel:
stage: test
image:
name: registry.edugit.org/teckids/docker-images/python-pimped:master
services:
- selenium/hub
- selenium/node-chrome
- selenium/node-firefox
before_script:
- adduser --disabled-password --gecos "Test User" testuser
- mkdir -p screenshots && chown testuser screenshots
script:
- poetry export --without-hashes --dev -f requirements.txt | pip install -r /dev/stdin
- eatmydata pip install dist/*.whl
- python ./manage.py compilemessages
- eatmydata python ./manage.py yarn install
- python ./manage.py collectstatic --no-input --clear
- sudo -u testuser eatmydata env TEST_SCREENSHOT_PATH=./screenshots tox
- pip freeze | safety check --stdin --full-report
artifacts:
paths:
- screenshots/
test_docker:
stage: test
image:
name: registry.edugit.org/teckids/docker-images/python-pimped:master
services:
- name: postgres:12
alias: db
- name: memcached
alias: memcached
- name: registry.edugit.org/biscuit/biscuit-ng:${CI_COMMIT_REF_NAME}
alias: app
script:
- echo true
only:
- master
- tags
deploy_demo-master:
stage: deploy
......@@ -65,6 +117,7 @@ deploy_demo-master:
- grep -v "build:" docker-compose.yml | ssh root@demo-master.biscuit-sis.org
env BISCUIT_IMAGE_TAG=${CI_COMMIT_REF_NAME}
NGINX_HTTP_PORT=80
BISCUIT_maintenance__debug=true
docker-compose
-p biscuit-${CI_ENVIRONMENT_SLUG}
-f /dev/stdin
......@@ -75,13 +128,10 @@ deploy_demo-master:
pages:
stage: deploy
image:
name: python:3.8-buster
before_script:
- apt-get -y update && apt-get -y install make
- pip install poetry
name: registry.edugit.org/teckids/docker-images/python-pimped:master
script:
- poetry install
- poetry run make -C docs html BUILDDIR=../public/docs
- poetry export --without-hashes --dev -f requirements.txt | eatmydata pip install -r /dev/stdin
- make -C docs html BUILDDIR=../public/docs
artifacts:
paths:
- public/
......
......@@ -82,6 +82,26 @@ giving the user control over these decisions is not possible. Developers
need to decide what should resonably be followed.
The case on supporting non-free services
----------------------------------------
Defined by the `Free Software Definition`_, it is an essential freedom to
be allowed to use free software for any purpose, without limitation. Thus,
interoperability with non-free services shall not be ruled out, and the
BiscuIT project explicitly welcomes implementing support for
interoperability with non-free services.
However, to purposefullt foster free software and services, if
interoperability for a certain kind of non-free service is implemented, this
must be done in a generalised manner (i.e. using open protocols and
interfaces). For example, if implementing interoperability with some
cloud-hosted calendar provider can be implemented either through a
proprietary API, or through a standard iCalendar/Webcal interfaces, the
latter is to be preferred. Lacking such support, if a proprietary service
is connected through a proprietary, single-purpose interface, measures shall
be taken to also support alternative free services.
Text documents
--------------
......@@ -105,4 +125,5 @@ licence possible.
.. _Sane software manifesto: https://sane-software.globalcode.info/
.. _Accessibility Manifesto: http://accessibilitymanifesto.com/
.. _User Data Manifesto: https://userdatamanifesto.org/
.. _Free Software Definition: https://www.gnu.org/philosophy/free-sw.en.html
.. _reStructuredText: http://docutils.sourceforge.net/rst.html
......@@ -16,9 +16,10 @@ ENV BISCUIT_backup__location /var/lib/biscuit/backups
ENV POETRY_VERSION 1.0.0b3
# Install necessary Debian packages for build and runtime
RUN apt-get update && \
apt-get upgrade -y && \
apt-get install -y --no-install-recommends \
RUN apt-get -y update && \
apt-get -y install eatmydata && \
eatmydata apt-get -y upgrade && \
eatmydata apt-get install -y --no-install-recommends \
build-essential \
gettext \
libpq5 \
......@@ -30,35 +31,32 @@ RUN apt-get update && \
# Install core dependnecies
WORKDIR /usr/src/app
COPY poetry.lock pyproject.toml ./
RUN pip install "poetry==$POETRY_VERSION"; \
poetry export -f requirements.txt | pip install -r /dev/stdin; \
pip install gunicorn
RUN eatmydata pip install "poetry==$POETRY_VERSION"; \
poetry export -f requirements.txt | eatmydata pip install -r /dev/stdin; \
eatmydata pip install gunicorn
# Install core
COPY biscuit ./biscuit/
COPY LICENCE README.rst manage.py ./
RUN mkdir -p /var/lib/biscuit/media /var/lib/biscuit/static /var/lib/biscuit/backups; \
poetry build && pip install dist/*.whl
poetry build && eatmydata pip install dist/*.whl
# Build messages and assets
RUN python manage.py compilemessages; \
python manage.py yarn install; \
python manage.py collectstatic --no-input --clear
eatmydata python manage.py yarn install
# Clean up build dependencies
RUN apt-get remove --purge -y \
RUN eatmydata apt-get remove --purge -y \
build-essential \
gettext \
libpq-dev \
libssl-dev \
yarnpkg; \
apt-get autoremove --purge -y; \
eatmydata apt-get autoremove --purge -y; \
apt-get clean -y; \
pip uninstall -y poetry; \
eatmydata pip uninstall -y poetry; \
rm -f /var/lib/apt/lists/*_*; \
rm -rf /root/.cache; \
rm -rf biscuit/node_modules; \
rm -rf /usr/local/lib/node_modules
rm -rf /root/.cache
# Declare a persistent volume for all data
VOLUME /var/lib/biscuit
......
Subproject commit 9014661e518c6d9457723571d7b0ef1ac9be6c6b
Subproject commit 874b76b40b9b7e05d8934559587009cd545fdd40
Subproject commit d4da734afa405a6af2adbe607f553e2b759737d6
Subproject commit 296d95e813b94c756e378265dfaac68fbafe34c1
Subproject commit b03369df8214f062a18a70824c6a257cf4ab427d
Subproject commit 15b1e50d32f39ed59ed73357c4e107cc94761bac
......@@ -96,16 +96,6 @@ MENUS = {
}
]
},
{
'name': _('Support'),
'url': '#',
'submenu': [
{
'name': _('Get support'),
'url': 'contact_form'
}
]
}
],
'DATA_MANAGEMENT_MENU': [
],
......
# Generated by Django 2.2.8 on 2019-12-09 21:04
from django.contrib.auth import get_user_model
from django.db import migrations
def create_superuser(apps, schema_editor):
User = get_user_model()
if not User.objects.filter(is_superuser=True).exists():
User.objects.create_superuser(
username='admin',
email='root@example.com',
password='admin'
).save()
class Migration(migrations.Migration):
dependencies = [
('core', '0005_unlink_school'),
]
operations = [
migrations.RunPython(create_superuser)
]
# Generated by Django 2.2.8 on 2019-12-11 23:27
from django.db import migrations, models
def mark_current_term(apps, schema_editor):
db_alias = schema_editor.connection.alias
SchoolTerm = apps.get_model('core', 'SchoolTerm') # noqa
if not SchoolTerm.objects.filter(current=True).exists():
SchoolTerm.objects.using(db_alias).latest('date_start').update(current=True)
class Migration(migrations.Migration):
dependencies = [
('core', '0006_create_superuser'),
]
operations = [
migrations.RemoveField(
model_name='school',
name='current_term',
),
migrations.AddField(
model_name='schoolterm',
name='current',
field=models.NullBooleanField(default=None, unique=True),
),
]
......@@ -38,7 +38,9 @@ class School(models.Model):
logo = ImageCropField(verbose_name=_('School logo'), blank=True, null=True)
logo_cropping = ImageRatioField('logo', '600x600', size_warning=True)
current_term = models.ForeignKey('SchoolTerm', models.CASCADE, related_name='+')
@property
def current_term(self):
return SchoolTerm.objects.get(current=True)
class Meta:
ordering = ['name', 'name_official']
......@@ -57,6 +59,13 @@ class SchoolTerm(models.Model):
date_end = models.DateField(verbose_name=_(
'Effective end date of term'), null=True)
current = models.NullBooleanField(default=None, unique=True)
def save(self, *args, **kwargs):
if self.current is False:
self.current = None
super().save(*args, **kwargs)
class Person(models.Model, ExtensibleModel):
""" A model describing any person related to a school, including, but not
......
......@@ -34,7 +34,9 @@ DEBUG = _settings.get('maintenance.debug', False)
INTERNAL_IPS = _settings.get('maintenance.internal_ips', [])
DEBUG_TOOLBAR_CONFIG = {
'RENDER_PANELS': True,
'SHOW_COLLAPSED': True
'SHOW_COLLAPSED': True,
'JQUERY_URL': '',
'SHOW_TOOLBAR_CALLBACK': 'biscuit.core.util.core_helpers.dt_show_toolbar'
}
ALLOWED_HOSTS = _settings.get('http.allowed_hosts', [])
......@@ -65,7 +67,6 @@ INSTALLED_APPS = [
'menu_generator',
'phonenumber_field',
'debug_toolbar',
'contact_form',
'django_select2',
'hattori',
'django_otp.plugins.otp_totp',
......@@ -89,7 +90,6 @@ STATICFILES_FINDERS = [
MIDDLEWARE = [
# 'django.middleware.cache.UpdateCacheMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
......@@ -97,6 +97,7 @@ MIDDLEWARE = [
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware',
'django_otp.middleware.OTPMiddleware',
'impersonate.middleware.ImpersonateMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
......
{# -*- engine:django -*- #}
{% extends "core/base.html" %}
{% load bootstrap4 i18n %}
{% block page_title %}BiscuIT SIS support{% endblock %}
{% block content %}
<h1>{% blocktrans %}Get support{% endblocktrans %}</h1>
<form method="post">
{% csrf_token %}
{% bootstrap_form form %}
<button type="submit" class="btn btn-dark">
{% blocktrans %}Send{% endblocktrans %}
</button>
</form>
{% endblock %}
From: {{ name }} <{{ email }}>
{{ body }}
{# -*- engine:django -*- #}
{% extends "core/base.html" %}
{% load bootstrap4 i18n %}
{% block page_title %}BiscuIT School Information System (SIS){% endblock %}
{% block content %}
<div class="alert alert-success" role="alert">
{% blocktrans %}
The message was successfully submitted.
{% endblocktrans %}
</div>
{% endblock %}
[BiscuIT Support]
import os
import pytest
from django.conf import settings
from django.test.selenium import SeleniumTestCase, SeleniumTestCaseBase
from django.urls import reverse
SeleniumTestCaseBase.external_host = os.environ.get('TEST_HOST', '') or None
SeleniumTestCaseBase.browsers = list(filter(bool, os.environ.get('TEST_SELENIUM_BROWSERS', '').split(',')))
SeleniumTestCaseBase.selenium_hub = os.environ.get('TEST_SELENIUM_HUB', '') or None
class SeleniumTests(SeleniumTestCase):
serialized_rollback = True
@classmethod
def _screenshot(cls, filename):
screenshot_path = os.environ.get('TEST_SCREENSHOT_PATH', None)
if screenshot_path:
os.makedirs(os.path.join(screenshot_path, cls.browser), exist_ok=True)
return cls.selenium.save_screenshot(os.path.join(screenshot_path, cls.browser, filename))
else:
return False
@pytest.mark.django_db
def test_index(self):
self.selenium.get(self.live_server_url + '/')
assert 'BiscuIT' in self.selenium.title
self._screenshot('index.png')
@pytest.mark.django_db
def test_login_default_superuser(self):
username = 'admin'
password = 'admin'
# Navigate to configured login page
self.selenium.get(self.live_server_url + reverse(settings.LOGIN_URL))
self._screenshot('login_default_superuser_blank.png')
# Find login form input fields and enter defined credentials
self.selenium.find_element_by_xpath(
'//label[contains(text(), "Username")]/../input'
).send_keys(username)
self.selenium.find_element_by_xpath(
'//label[contains(text(), "Password")]/../input'
).send_keys(password)
self._screenshot('login_default_superuser_filled.png')
# Submit form by clicking django-two-factor-auth's Next button
self.selenium.find_element_by_xpath(
'//button[contains(text(), "Next")]'
).click()
self._screenshot('login_default_superuser_submitted.png')
# Should redirect away from login page and not put up an alert about wrong credentials
assert 'Please enter a correct username and password.' not in self.selenium.page_source
assert reverse(settings.LOGIN_URL) not in self.selenium.current_url
......@@ -34,7 +34,6 @@ urlpatterns = [
path('group/<int:id_>/edit', views.edit_group, name='edit_group_by_id'),
path('', views.index, name='index'),
path('maintenance-mode/', include('maintenance_mode.urls')),
path('contact/', include('contact_form.urls')),
path('impersonate/', include('impersonate.urls')),
path('__i18n__/', include('django.conf.urls.i18n')),
path('select2/', include('django_select2.urls')),
......
......@@ -2,9 +2,23 @@ from importlib import import_module
import pkgutil
from typing import Sequence
from django.conf import settings
from django.http import HttpRequest
def dt_show_toolbar(request: HttpRequest) -> bool:
from debug_toolbar.middleware import show_toolbar # noqa
if not settings.DEBUG:
return False
if show_toolbar(request):
return True
elif hasattr(request, 'user') and request.user.is_superuser:
return True
return False
def get_app_packages() -> Sequence[str]:
""" Find all packages within the biscuit.apps namespace. """
......
......@@ -19,6 +19,7 @@ services:
- BISCUIT_http__allowed_hosts="['*']"
- BISCUIT_caching__memcached__address=memcached:11211
- BISCUIT_database__host=db
- BISCUIT_maintenance__debug=${BISCUIT_maintenance__debug:-false}
depends_on:
- db
- memcached
......
......@@ -19,6 +19,7 @@ done
python manage.py flush --no-input
python manage.py migrate
python manage.py collectstatic --no-input --clear
if [[ -n "$@" ]]; then
exec "$@"
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment