Commit 8f0896a3 authored by hynek's avatar hynek 🤤
Browse files

Merge branch 'feature/tests' into 'develop'

Feature/tests

See merge request !11
parents e3747bd4 508bbbbf
......@@ -13,7 +13,7 @@ indent_style = space
indent_size = 4
[*.py]
line_length = 120
line_length = 88
known_first_party = discuss_data
multi_line_output = 3
default_section = THIRDPARTY
......
[flake8]
ignore = E203, E266, E501, W503, F403, F401
max-line-length = 79
max-line-length = 88
max-complexity = 18
select = B,C,E,F,W,T4,B9
\ No newline at end of file
select = B,C,E,F,W,T4,B9
exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules
......@@ -10,11 +10,10 @@ include:
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_DRIVER: overlay2
CONTAINER_TEST_IMAGE: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG:$CI_COMMIT_SHA
CONTAINER_RELEASE_IMAGE: $CI_REGISTRY_IMAGE:latest
DS_PIP_DEPENDENCY_PATH: requirements/production.txt
DARIAH_STORAGE_TOKEN: $DH_TOKEN
stages:
- build
......@@ -40,17 +39,16 @@ build_develop:
- docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY
- docker build -t $CONTAINER_TEST_IMAGE -f compose/local/django/Dockerfile .
- docker push $CONTAINER_TEST_IMAGE
tests:
stage: test
image: tiangolo/docker-with-compose
script:
- docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY
- echo "Composing CI setup with $CONTAINER_TEST_IMAGE"
- docker-compose -f ci.yml build
# - docker-compose -f local.yml run --rm django pydocstyle
- docker-compose -f ci.yml run --rm django flake8
- docker-compose -f ci.yml run django coverage run -m pytest
- docker-compose -f ci.yml run django /bin/sh -c "./run_pytest"
- docker-compose -f ci.yml run --rm django coverage html
- docker-compose -f ci.yml run --rm django /bin/sh -c "cd docs && apk add make && make html"
- docker-compose -f ci.yml run django coverage report
......@@ -60,14 +58,15 @@ tests:
- htmlcov
- docs/_build
expire_in: 5 days
except:
allow_failure: true
except:
- master
code_quality:
stage: test
artifacts:
paths: [gl-code-quality-report.json]
except:
except:
- master
dependency_scanning:
......@@ -93,13 +92,13 @@ container_scanning:
create_release:
image: node:8
stage: release
script:
script:
- npm install
- npx semantic-release
only:
- master
release_image:
stage: deploy
script:
......
============================
Contributing to Discuss Data
============================
As an open source project, Discuss Data welcomes contributions of many forms.
Examples of contributions include:
* Code patches
* Documentation improvements
* Bug reports and patch reviews
Felix Herrmann, Stefan Hynek, Ubbo Veentjer
Felix Herrmann
Stefan Hynek
Ubbo Veentjer
......@@ -52,6 +52,12 @@ Running type checks with mypy:
$ mypy discuss_data
Django unit tests
^^^^^^^^^^^^
::
$ docker-compose -f local.yml run django python manage.py test
Test coverage
^^^^^^^^^^^^^
......@@ -60,13 +66,6 @@ To run the tests, check your test coverage, and generate an HTML coverage report
$ docker-compose -f local.yml run django coverage run -m pytest
$ docker-compose -f local.yml run django coverage report
Running tests with py.test
~~~~~~~~~~~~~~~~~~~~~~~~~~
::
$ pytest
Live reloading and Sass CSS compilation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
......
......@@ -4,6 +4,6 @@ set -o errexit
set -o pipefail
set -o nounset
python manage.py makemigrations
python manage.py migrate
python manage.py runserver_plus 0.0.0.0:8000
......@@ -81,11 +81,14 @@ THIRD_PARTY_APPS = [
LOCAL_APPS = [
# "discuss_data.users.apps.UsersConfig",
# Your stuff: custom apps go here
"discuss_data.ddusers.apps.DdusersConfig",
"discuss_data.dddatasets.apps.DddatasetsConfig",
"discuss_data.ddcomments.apps.DdcommentsConfig",
"discuss_data.core.apps.CoreConfig",
"discuss_data.ddpublications.apps.DdpublicationsConfig",
"discuss_data.ddusers", # discuss_data.ddusers.apps.DdusersConfig does not work
"discuss_data.dddatasets",
"discuss_data.ddcomments",
"discuss_data.core",
"discuss_data.ddpublications",
"discuss_data.dhrep",
"discuss_data.pages",
"discuss_data.utils",
]
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
......@@ -128,12 +131,8 @@ AUTH_PASSWORD_VALIDATORS = [
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"
},
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"
},
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
]
# MIDDLEWARE
......@@ -236,8 +235,7 @@ X_FRAME_OPTIONS = "DENY"
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
EMAIL_BACKEND = env(
"DJANGO_EMAIL_BACKEND",
default="django.core.mail.backends.smtp.EmailBackend",
"DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.smtp.EmailBackend",
)
# https://docs.djangoproject.com/en/2.2/ref/settings/#email-timeout
EMAIL_TIMEOUT = 5
......@@ -247,12 +245,7 @@ EMAIL_TIMEOUT = 5
# Django Admin URL.
ADMIN_URL = "admin/"
# https://docs.djangoproject.com/en/dev/ref/settings/#admins
ADMINS = [
(
"""Felix Herrmann, Stefan Hynek, Ubbo Veentjer""",
"info@discuss-data.net",
)
]
ADMINS = [("""Felix Herrmann, Stefan Hynek, Ubbo Veentjer""", "info@discuss-data.net",)]
# https://docs.djangoproject.com/en/dev/ref/settings/#managers
MANAGERS = ADMINS
......@@ -283,9 +276,7 @@ LOGGING = {
# django-allauth
# ------------------------------------------------------------------------------
ACCOUNT_ALLOW_REGISTRATION = env.bool(
"DJANGO_ACCOUNT_ALLOW_REGISTRATION", True
)
ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", True)
# https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_AUTHENTICATION_METHOD = "username"
# https://django-allauth.readthedocs.io/en/latest/configuration.html
......@@ -304,7 +295,6 @@ INSTALLED_APPS += ["compressor"]
STATICFILES_FINDERS += ["compressor.finders.CompressorFinder"]
# Your stuff...
# ------------------------------------------------------------------------------
INSTALLED_APPS += ["corsheaders"]
# Wagtail
......@@ -324,9 +314,9 @@ INSTALLED_APPS += [
# 'condensedinlinepanel',
"wagtailmenus",
"modelcluster",
"pages",
]
MIDDLEWARE += [
"wagtail.core.middleware.SiteMiddleware",
"wagtail.contrib.redirects.middleware.RedirectMiddleware",
......
......@@ -33,9 +33,7 @@ EMAIL_PORT = 1025
# WhiteNoise
# ------------------------------------------------------------------------------
# http://whitenoise.evans.io/en/latest/django.html#using-whitenoise-in-development
INSTALLED_APPS = [
"whitenoise.runserver_nostatic"
] + INSTALLED_APPS # noqa F405
INSTALLED_APPS = ["whitenoise.runserver_nostatic"] + INSTALLED_APPS # noqa F405
# django-debug-toolbar
......
......@@ -12,9 +12,7 @@ ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=["discuss-data.net"])
# ------------------------------------------------------------------------------
DATABASES["default"] = env.db("DATABASE_URL") # noqa F405
DATABASES["default"]["ATOMIC_REQUESTS"] = True # noqa F405
DATABASES["default"]["CONN_MAX_AGE"] = env.int( # noqa F405
"CONN_MAX_AGE", default=60
)
DATABASES["default"]["CONN_MAX_AGE"] = env.int("CONN_MAX_AGE", default=60) # noqa F405
# CACHES
# ------------------------------------------------------------------------------
......@@ -79,15 +77,12 @@ TEMPLATES[0]["OPTIONS"]["loaders"] = [ # noqa F405
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#default-from-email
DEFAULT_FROM_EMAIL = env(
"DJANGO_DEFAULT_FROM_EMAIL",
default="Discuss Data <noreply@discuss-data.net>",
"DJANGO_DEFAULT_FROM_EMAIL", default="Discuss Data <noreply@discuss-data.net>",
)
# https://docs.djangoproject.com/en/dev/ref/settings/#server-email
SERVER_EMAIL = env("DJANGO_SERVER_EMAIL", default=DEFAULT_FROM_EMAIL)
# https://docs.djangoproject.com/en/dev/ref/settings/#email-subject-prefix
EMAIL_SUBJECT_PREFIX = env(
"DJANGO_EMAIL_SUBJECT_PREFIX", default="[Discuss Data]"
)
EMAIL_SUBJECT_PREFIX = env("DJANGO_EMAIL_SUBJECT_PREFIX", default="[Discuss Data]")
# ADMIN
# ------------------------------------------------------------------------------
......@@ -103,9 +98,7 @@ EMAIL_BACKEND = "anymail.backends.mailgun.EmailBackend"
ANYMAIL = {
"MAILGUN_API_KEY": env("MAILGUN_API_KEY"),
"MAILGUN_SENDER_DOMAIN": env("MAILGUN_DOMAIN"),
"MAILGUN_API_URL": env(
"MAILGUN_API_URL", default="https://api.mailgun.net/v3"
),
"MAILGUN_API_URL": env("MAILGUN_API_URL", default="https://api.mailgun.net/v3"),
}
# django-compressor
......@@ -128,9 +121,7 @@ COMPRESS_URL = STATIC_URL # noqa F405
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"filters": {
"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}
},
"filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}},
"formatters": {
"verbose": {
"format": "%(levelname)s %(asctime)s %(module)s "
......
......@@ -49,3 +49,8 @@ EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
# Your stuff...
# ------------------------------------------------------------------------------
DARIAH_STORAGE_LOCATION = "https://cdstar.de.dariah.eu/test/dariah/"
DARIAH_PUBLISH_URL = "https://trep.de.dariah.eu/1.0/dhpublish/"
# put fresh DARIAH_STORAGE_TOKEN to .env for integration tests
DARIAH_STORAGE_TOKEN = env("DARIAH_STORAGE_TOKEN", default="SET-FOR-INTEGRATION-TEST")
......@@ -5,8 +5,8 @@ from django.contrib import admin
from django.views.generic import TemplateView
from django.views import defaults as default_views
from ddusers.views import dashboard_page
import dddatasets
from discuss_data.ddusers.views import dashboard_page
import discuss_data.dddatasets
urlpatterns = [
# remove next 4 lines and template files!
......@@ -15,9 +15,7 @@ urlpatterns = [
# "about/", TemplateView.as_view(template_name="pages/about.html"), name="about"
# ),
# Django Admin, use {% url 'admin:index' %}
path(
settings.ADMIN_URL + "doc/", include("django.contrib.admindocs.urls")
),
path(settings.ADMIN_URL + "doc/", include("django.contrib.admindocs.urls")),
path(settings.ADMIN_URL, admin.site.urls),
# User management
path("users/", include("discuss_data.ddusers.urls", namespace="ddusers")),
......@@ -25,10 +23,8 @@ urlpatterns = [
# next line to be removed
# path("accounts/", include("allauth.urls")),
# Your stuff: custom urls includes go here
path(
"dataset/",
include("discuss_data.dddatasets.urls", namespace="dddatasets"),
),
path("dataset/", include("discuss_data.dddatasets.urls", namespace="dddatasets"),),
path("dhrep/", include("discuss_data.dhrep.urls", namespace="dhrep")),
path("shib/", include("shibboleth.urls", namespace="shibboleth")),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
......@@ -56,6 +52,4 @@ if settings.DEBUG:
if "debug_toolbar" in settings.INSTALLED_APPS:
import debug_toolbar
urlpatterns = [
path("__debug__/", include(debug_toolbar.urls))
] + urlpatterns
urlpatterns = [path("__debug__/", include(debug_toolbar.urls))] + urlpatterns
import pytest
from django.conf import settings
from django.test import RequestFactory
from discuss_data.users.tests.factories import UserFactory
# pytest finds added options only in root conftest.py if not configured otherwise
def pytest_addoption(parser):
parser.addoption(
"--integration",
action="store_true",
default=False,
help="run integration tests",
)
@pytest.fixture(autouse=True)
def media_storage(settings, tmpdir):
settings.MEDIA_ROOT = tmpdir.strpath
@pytest.fixture
def user() -> settings.AUTH_USER_MODEL:
return UserFactory()
@pytest.fixture
def request_factory() -> RequestFactory:
return RequestFactory()
# list marker if requested with 'pytest --markers'
def pytest_configure(config):
config.addinivalue_line("markers", "integration: mark a test as integration test")
......@@ -28,7 +28,7 @@ class Migration(migrations.Migration):
validators=[_simple_domain_name_validator],
),
),
("name", models.CharField(max_length=50, verbose_name="display name")),
("name", models.CharField(max_length=50, verbose_name="display name"),),
],
options={
"ordering": ("domain",),
......
......@@ -12,10 +12,7 @@ def update_site_forward(apps, schema_editor):
Site = apps.get_model("sites", "Site")
Site.objects.update_or_create(
id=settings.SITE_ID,
defaults={
"domain": "discuss-data.net",
"name": "Discuss Data",
},
defaults={"domain": "discuss-data.net", "name": "Discuss Data",},
)
......@@ -23,7 +20,7 @@ def update_site_backward(apps, schema_editor):
"""Revert site domain and name to default."""
Site = apps.get_model("sites", "Site")
Site.objects.update_or_create(
id=settings.SITE_ID, defaults={"domain": "example.com", "name": "example.com"}
id=settings.SITE_ID, defaults={"domain": "example.com", "name": "example.com"},
)
......
......@@ -9,129 +9,303 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
("contenttypes", "0002_remove_content_type_name"),
]
operations = [
migrations.CreateModel(
name='AffiliationTagged',
name="AffiliationTagged",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, unique=True, verbose_name='Name')),
('slug', models.SlugField(max_length=100, unique=True, verbose_name='Slug')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"name",
models.CharField(max_length=100, unique=True, verbose_name="Name"),
),
(
"slug",
models.SlugField(max_length=100, unique=True, verbose_name="Slug"),
),
],
options={
'verbose_name': 'Affiliation Tag',
'verbose_name_plural': 'Affiliation Tags',
"verbose_name": "Affiliation Tag",
"verbose_name_plural": "Affiliation Tags",
},
),
migrations.CreateModel(
name='EnTagged',
name="EnTagged",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, unique=True, verbose_name='Name')),
('slug', models.SlugField(max_length=100, unique=True, verbose_name='Slug')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"name",
models.CharField(max_length=100, unique=True, verbose_name="Name"),
),
(
"slug",
models.SlugField(max_length=100, unique=True, verbose_name="Slug"),
),
],
options={
'verbose_name': 'English Tag',
'verbose_name_plural': 'English Tags',
"verbose_name": "English Tag",
"verbose_name_plural": "English Tags",
},
),
migrations.CreateModel(
name='Keyword',
name="Keyword",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=200)),
('description', models.TextField(blank=True)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("title", models.CharField(max_length=200)),
("description", models.TextField(blank=True)),
],
),
migrations.CreateModel(
name='KeywordTagged',
name="KeywordTagged",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, unique=True, verbose_name='Name')),
('slug', models.SlugField(max_length=100, unique=True, verbose_name='Slug')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"name",
models.CharField(max_length=100, unique=True, verbose_name="Name"),
),
(
"slug",
models.SlugField(max_length=100, unique=True, verbose_name="Slug"),
),
],
options={
'verbose_name': 'Keyword Tag',
'verbose_name_plural': 'Keyword Tags',
"verbose_name": "Keyword Tag",
"verbose_name_plural": "Keyword Tags",
},
),
migrations.CreateModel(
name='LanguageTagged',
name="LanguageTagged",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, unique=True, verbose_name='Name')),
('slug', models.SlugField(max_length=100, unique=True, verbose_name='Slug')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"name",
models.CharField(max_length=100, unique=True, verbose_name="Name"),
),
(
"slug",
models.SlugField(max_length=100, unique=True, verbose_name="Slug"),
),
],
options={
'verbose_name': 'Language Tag',
'verbose_name_plural': 'Language Tags',
"verbose_name": "Language Tag",
"verbose_name_plural": "Language Tags",
},
),
migrations.CreateModel(
name='Link',
name="Link",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('url', models.URLField()),
('doi', models.CharField(blank=True, max_length=200)),
('description', models.CharField(blank=True, max_length=400)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("url", models.URLField()),
("doi", models.CharField(blank=True, max_length=200)),
("description", models.CharField(blank=True, max_length=400)),
],
),
migrations.CreateModel(
name='Topic',
name="Topic",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=200)),
('description', models.TextField(blank=True)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("title", models.CharField(max_length=200)),
("description", models.TextField(blank=True)),
],
),
migrations.CreateModel(
name='LanguageTags',
name="LanguageTags",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('object_id', models.IntegerField(db_index=True, verbose_name='Object id')),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='core_languagetags_tagged_items', to='contenttypes.ContentType', verbose_name='Content type')),
('tag', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='languagetags_languagetagged', to='core.LanguageTagged')),
(
"id",
models.AutoField(</