From 1f0d3e278c68428cf7e75a15ead28b860b779700 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Thu, 8 Feb 2024 09:53:20 +0200 Subject: [PATCH 001/240] Update .gitignore, README, add Django app & Docker configurations Included the addition of Django files, Docker configurations, updated .gitignore and README files, along with requirements.txt for package management. This makes the project application ready, Dockerized, and catered towards a Python/Django environment --- .gitignore | 173 ++++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 15 ++++ Makefile | 64 ++++++++++++++++ app/app/__init__.py | 0 app/app/asgi.py | 16 ++++ app/app/settings.py | 134 ++++++++++++++++++++++++++++++++++ app/app/urls.py | 22 ++++++ app/app/wsgi.py | 16 ++++ app/manage.py | 22 ++++++ docker-compose.yml | 27 +++++++ requirements.txt | 2 + 11 files changed, 491 insertions(+) create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 app/app/__init__.py create mode 100644 app/app/asgi.py create mode 100644 app/app/settings.py create mode 100644 app/app/urls.py create mode 100644 app/app/wsgi.py create mode 100755 app/manage.py create mode 100644 docker-compose.yml create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore index b24d71e2..616ab0e1 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,176 @@ Thumbs.db *.mov *.wmv +### Django template +*.log +*.pot +*.pyc +__pycache__/ +local_settings.py +db.sqlite3 +db.sqlite3-journal +media + +# If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/ +# in your Git repository. Update and uncomment the following line accordingly. +# /staticfiles/ + +### macOS template +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + + + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..4f2c35a5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.12.0-slim + +WORKDIR /app + +RUN ls + +COPY . /app + +RUN pip install --no-cache-dir -r requirements.txt + +EXPOSE 8000 + +ENV NAME TMS + +CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..39c6d4ac --- /dev/null +++ b/Makefile @@ -0,0 +1,64 @@ +list: + clear + @echo "Available commands:" + @echo "up - Start the project" + @echo "upd - Start the project in background" + @echo "build - Build the project" + @echo "stop - Stop the project" + @echo "down - Stop and remove the project" + @echo "restart - Restart the project" + @echo "makemigrations - Create new migrations based on the changes you have made to your models" + @echo "migrate - Apply migrations to your database" + @echo "shell - Run the Python shell" + @echo "logs - Show logs" + @echo "createsuperuser - Create a superuser" + +up: + clear + @docker-compose up + +upd: + clear + @docker-compose up -d + +build: + clear + @docker-compose build + +stop: + clear + @docker-compose stop + +down: + clear + @docker-compose down + +restart: + clear + @docker-compose restart + +makemigrations: + clear + @docker-compose run --rm web python manage.py makemigrations + +migrate: + clear + @docker-compose run --rm web python manage.py migrate + +shell: + clear + @docker-compose run --rm web python manage.py shell + +logs: + clear + @docker-compose logs -tf + +createsuperuser: + clear + @docker-compose run --rm web python manage.py createsuperuser + +docker-stop-all: + clear + docker stop `docker ps -q` + docker ps + diff --git a/app/app/__init__.py b/app/app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/app/asgi.py b/app/app/asgi.py new file mode 100644 index 00000000..de171914 --- /dev/null +++ b/app/app/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for app project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') + +application = get_asgi_application() diff --git a/app/app/settings.py b/app/app/settings.py new file mode 100644 index 00000000..dd8a60b6 --- /dev/null +++ b/app/app/settings.py @@ -0,0 +1,134 @@ +""" +Django settings for app project. + +Generated by 'django-admin startproject' using Django 5.0.1. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.0/ref/settings/ +""" +import os +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-w!h85bp^$e8gm%c23r!0%9i7yzd=6w$s&ic+6!%306&kj8@k*5' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'app.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'app.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.0/ref/settings/#databases + +# DATABASES = { +# 'default': { +# 'ENGINE': 'django.db.backends.sqlite3', +# 'NAME': BASE_DIR / 'db.sqlite3', +# } +# } + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'HOST': os.environ.get('DJANGO_DB_HOST'), + 'PORT': os.environ.get('DJANGO_DB_PORT'), + 'NAME': os.environ.get('DJANGO_DB_NAME'), + 'USER': os.environ.get('DJANGO_DB_USER'), + 'PASSWORD': os.environ.get('DJANGO_DB_PASSWORD'), + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators + +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', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.0/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.0/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/app/app/urls.py b/app/app/urls.py new file mode 100644 index 00000000..ded1f113 --- /dev/null +++ b/app/app/urls.py @@ -0,0 +1,22 @@ +""" +URL configuration for app project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path + +urlpatterns = [ + path('admin/', admin.site.urls), +] diff --git a/app/app/wsgi.py b/app/app/wsgi.py new file mode 100644 index 00000000..eb514abf --- /dev/null +++ b/app/app/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for app project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') + +application = get_wsgi_application() diff --git a/app/manage.py b/app/manage.py new file mode 100755 index 00000000..49313893 --- /dev/null +++ b/app/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..62000e9c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,27 @@ +version: '3' + +services: + db: + image: postgres:12 + environment: + - POSTGRES_DB=wsu_db + - POSTGRES_USER=wsu + - POSTGRES_PASSWORD=wsu + volumes: + - .:/app + web: + build: . + command: python manage.py runserver 0.0.0.0:8000 + volumes: + - ./app:/app + ports: + - "8000:8000" + depends_on: + - db + environment: + - DJANGO_DB_HOST=db + - DJANGO_DB_PORT=5432 + - DJANGO_DB_NAME=tms_db + - DJANGO_DB_USER=tms + - DJANGO_DB_PASSWORD=tms + - DATABASE_URL=postgresql://wsu:wsu@db/wsu_db \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..0c056e6c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +django>=3.2 +psycopg2-binary From 093cbba4f08184e21a0a0d585972693fa8f5742f Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Thu, 8 Feb 2024 10:45:47 +0200 Subject: [PATCH 002/240] Update Makefile commands, docker-compose and dependencies This commit refactors commands in the Makefile and modifies some configurations in the docker-compose file. The changes include the addition of a new command to the Makefile, adjustment of environment variables for the database and addition of a new dependency to the project. This ensures smoother operations, optimizes Docker setup, and maintains effective tracking of project dependencies --- Makefile | 10 ++++++---- app/app/settings.py | 13 ------------- docker-compose.yml | 10 +++++++--- requirements.txt | 1 + 4 files changed, 14 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index 39c6d4ac..31602d6f 100644 --- a/Makefile +++ b/Makefile @@ -7,11 +7,13 @@ list: @echo "stop - Stop the project" @echo "down - Stop and remove the project" @echo "restart - Restart the project" - @echo "makemigrations - Create new migrations based on the changes you have made to your models" + @echo "make-migrations - Create new migrations based on the changes you have made to your models" @echo "migrate - Apply migrations to your database" @echo "shell - Run the Python shell" @echo "logs - Show logs" - @echo "createsuperuser - Create a superuser" + @echo "create-super-user - Create a superuser" + @echo "docker-stop-all - Stop all running containers" + up: clear @@ -37,7 +39,7 @@ restart: clear @docker-compose restart -makemigrations: +make-migrations: clear @docker-compose run --rm web python manage.py makemigrations @@ -53,7 +55,7 @@ logs: clear @docker-compose logs -tf -createsuperuser: +create-super-user: clear @docker-compose run --rm web python manage.py createsuperuser diff --git a/app/app/settings.py b/app/app/settings.py index dd8a60b6..27bd7b85 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -15,7 +15,6 @@ # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent - # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ @@ -27,7 +26,6 @@ ALLOWED_HOSTS = [] - # Application definition INSTALLED_APPS = [ @@ -69,17 +67,9 @@ WSGI_APPLICATION = 'app.wsgi.application' - # Database # https://docs.djangoproject.com/en/5.0/ref/settings/#databases -# DATABASES = { -# 'default': { -# 'ENGINE': 'django.db.backends.sqlite3', -# 'NAME': BASE_DIR / 'db.sqlite3', -# } -# } - DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', @@ -91,7 +81,6 @@ } } - # Password validation # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators @@ -110,7 +99,6 @@ }, ] - # Internationalization # https://docs.djangoproject.com/en/5.0/topics/i18n/ @@ -122,7 +110,6 @@ USE_TZ = True - # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.0/howto/static-files/ diff --git a/docker-compose.yml b/docker-compose.yml index 62000e9c..622a56d8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,14 +3,18 @@ version: '3' services: db: image: postgres:12 + container_name: nwu-terminology-db environment: - POSTGRES_DB=wsu_db - POSTGRES_USER=wsu - POSTGRES_PASSWORD=wsu volumes: - .:/app + ports: + - "5432:5432" web: build: . + container_name: nwu-terminology-web command: python manage.py runserver 0.0.0.0:8000 volumes: - ./app:/app @@ -21,7 +25,7 @@ services: environment: - DJANGO_DB_HOST=db - DJANGO_DB_PORT=5432 - - DJANGO_DB_NAME=tms_db - - DJANGO_DB_USER=tms - - DJANGO_DB_PASSWORD=tms + - DJANGO_DB_NAME=wsu_db + - DJANGO_DB_USER=wsu + - DJANGO_DB_PASSWORD=wsu - DATABASE_URL=postgresql://wsu:wsu@db/wsu_db \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 0c056e6c..49401d2f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ django>=3.2 psycopg2-binary +ruff From 90dcbb6dd5646b0eac62a58d27218d1a4619cb55 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Thu, 8 Feb 2024 11:08:39 +0200 Subject: [PATCH 003/240] Update README and clean up .gitignore The README.md file was extensively changed to reflect the updated project requirements, installation guide, and usage of docker-compose and Makefile. In addition, unnecessary default lines in .gitignore have been removed for clarity and simplicity, leaving only the relevant ignored files and directories. These changes are important to provide clear and updated instructions to facilitate the setup and launch of the project --- .gitignore | 9 --------- README.md | 44 +++++++++++++++----------------------------- 2 files changed, 15 insertions(+), 38 deletions(-) diff --git a/.gitignore b/.gitignore index 616ab0e1..b4f751c9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,3 @@ -# These are some examples of commonly ignored file patterns. -# You should customize this list as applicable to your project. -# Learn more about .gitignore: -# https://www.atlassian.com/git/tutorials/saving-changes/gitignore - # Node artifact files node_modules/ dist/ @@ -58,10 +53,6 @@ db.sqlite3 db.sqlite3-journal media -# If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/ -# in your Git repository. Update and uncomment the following line accordingly. -# /staticfiles/ - ### macOS template # General .DS_Store diff --git a/README.md b/README.md index 16eff969..0bc71fa1 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,31 @@ -**Edit a file, create a new file, and clone from Bitbucket in under 2 minutes** +**NWU Terminology** -When you're done, you can delete the content in this README and update the file with details for others getting started with your repository. - -*We recommend that you open this README in another tab as you perform the tasks below. You can [watch our video](https://youtu.be/0ocf7u76WSo) for a full demo of all the steps in this tutorial. Open the video in a new tab to avoid leaving Bitbucket.* +About the project: --- -## Edit a file +## Requirements -You’ll start by editing this README file to learn how to edit a file in Bitbucket. +- Docker +- Docker-compose +- Makefile reader installed on device -1. Click **Source** on the left side. -2. Click the README.md link from the list of files. -3. Click the **Edit** button. -4. Delete the following text: *Delete this line to make a change to the README from Bitbucket.* -5. After making your change, click **Commit** and then **Commit** again in the dialog. The commit page will open and you’ll see the change you just made. -6. Go back to the **Source** page. +## Installations guide ---- +### Using Docker-compose -## Create a file +1. docker-compose up --build +2. docker-compose down -Next, you’ll add a new file to this repository. +### Using Makefile -1. Click the **New file** button at the top of the **Source** page. -2. Give the file a filename of **contributors.txt**. -3. Enter your name in the empty file space. -4. Click **Commit** and then **Commit** again in the dialog. -5. Go back to the **Source** page. +1. Clone the repository +2. Run `make build` to build the docker image +3. Run `make run` to run the docker container +4. Run `make stop` to stop the docker container -Before you move on, go ahead and explore the repository. You've already seen the **Source** page, but check out the **Commits**, **Branches**, and **Settings** pages. ---- -## Clone a repository -Use these steps to clone from SourceTree, our client for using the repository command-line free. Cloning allows you to work on your files locally. If you don't yet have SourceTree, [download and install first](https://www.sourcetreeapp.com/). If you prefer to clone from the command line, see [Clone a repository](https://confluence.atlassian.com/x/4whODQ). -1. You’ll see the clone button under the **Source** heading. Click that button. -2. Now click **Check out in SourceTree**. You may need to create a SourceTree account or log in. -3. When you see the **Clone New** dialog in SourceTree, update the destination path and name if you’d like to and then click **Clone**. -4. Open the directory you just created to see your repository’s files. -Now that you're more familiar with your Bitbucket repository, go ahead and add a new file locally. You can [push your change back to Bitbucket with SourceTree](https://confluence.atlassian.com/x/iqyBMg), or you can [add, commit,](https://confluence.atlassian.com/x/8QhODQ) and [push from the command line](https://confluence.atlassian.com/x/NQ0zDQ). \ No newline at end of file From fc65c10272b8b8a1175552e790fb9cbc5fa4f8a1 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Fri, 9 Feb 2024 14:23:29 +0200 Subject: [PATCH 004/240] Update Docker configuration and Django version Updated the Docker configuration, changing the PostgreSQL database and container names, and updated Django to version 5.0.2. These changes align with new project requirements and make sure the application is using the latest frameworks --- docker-compose.yml | 18 +++++++++--------- requirements.txt | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 622a56d8..15ba0eda 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,18 +3,18 @@ version: '3' services: db: image: postgres:12 - container_name: nwu-terminology-db + container_name: sadilar-terminology-db environment: - - POSTGRES_DB=wsu_db - - POSTGRES_USER=wsu - - POSTGRES_PASSWORD=wsu + - POSTGRES_DB=term_db + - POSTGRES_USER=sadilar + - POSTGRES_PASSWORD=sadilar volumes: - .:/app ports: - "5432:5432" web: build: . - container_name: nwu-terminology-web + container_name: sadilar-terminology-web command: python manage.py runserver 0.0.0.0:8000 volumes: - ./app:/app @@ -25,7 +25,7 @@ services: environment: - DJANGO_DB_HOST=db - DJANGO_DB_PORT=5432 - - DJANGO_DB_NAME=wsu_db - - DJANGO_DB_USER=wsu - - DJANGO_DB_PASSWORD=wsu - - DATABASE_URL=postgresql://wsu:wsu@db/wsu_db \ No newline at end of file + - DJANGO_DB_NAME=term_db + - DJANGO_DB_USER=sadilar + - DJANGO_DB_PASSWORD=sadilar + - DATABASE_URL=postgresql://sadilar:sadilar@db/term_db \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 49401d2f..7570ac99 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -django>=3.2 +django>=5.0.2 psycopg2-binary ruff From 4fff4dc9d0d4a50091aa5fb691ff31a454725bbf Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Fri, 9 Feb 2024 17:21:56 +0200 Subject: [PATCH 005/240] Update README and Dockerfile, fix Django version Updated the project name in the README file and revamped the Dockerfile for better environment settings. Also, fixed the Django version in requirements.txt from open-ended to a fixed version for better version control and to avoid unexpected behavior from future versions --- Dockerfile | 22 ++++++++++++++-------- README.md | 2 +- requirements.txt | 2 +- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4f2c35a5..3b2a8e46 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,21 @@ -FROM python:3.12.0-slim +FROM python:3.11 -WORKDIR /app - -RUN ls +# Set environment variables +ENV PYTHONDONTWRITEBYTECODE 1 +ENV PYTHONUNBUFFERED 1 -COPY . /app +# Set work directory +WORKDIR /app -RUN pip install --no-cache-dir -r requirements.txt +# Install dependencies +COPY requirements.txt /app/ -EXPOSE 8000 +# Install dependencies +RUN pip install --upgrade pip +RUN pip install -r requirements.txt -ENV NAME TMS +# Copy project +COPY . /app/ +# Run the application CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] \ No newline at end of file diff --git a/README.md b/README.md index 0bc71fa1..ab4adc6f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -**NWU Terminology** +**Sadilar Terminology** About the project: diff --git a/requirements.txt b/requirements.txt index 7570ac99..a8766947 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -django>=5.0.2 +django==5.0.2 psycopg2-binary ruff From 5a5d888dd3ef83f6b0b3b6d0bc4830311dedea8f Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Mon, 12 Feb 2024 09:27:58 +0200 Subject: [PATCH 006/240] Update Dockerfile Python version and simplify .gitignore The Dockerfile has been updated to use Python version 3.12 and the .gitignore file has been simplified by removing unneeded ignore lines. The simplification of the .gitignore file makes it easier for developers to understand what files or folders are ignored in the repository --- .gitignore | 180 +---------------------------------------------------- Dockerfile | 2 +- 2 files changed, 3 insertions(+), 179 deletions(-) diff --git a/.gitignore b/.gitignore index b4f751c9..bf9603d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,50 +1,13 @@ -# Node artifact files -node_modules/ -dist/ - -# Compiled Java class files -*.class - -# Compiled Python bytecode -*.py[cod] - # Log files *.log -# Package files -*.jar - -# Maven -target/ -dist/ - # JetBrains IDE .idea/ -# Unit test reports -TEST*.xml - -# Generated by MacOS -.DS_Store - # Generated by Windows Thumbs.db -# Applications -*.app -*.exe -*.war - -# Large media files -*.mp4 -*.tiff -*.avi -*.flv -*.mov -*.wmv - -### Django template -*.log +# Django template *.pot *.pyc __pycache__/ @@ -53,130 +16,12 @@ db.sqlite3 db.sqlite3-journal media -### macOS template +# macOS template # General .DS_Store .AppleDouble .LSOverride -# Icon must end with two \r -Icon - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -### Python template -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py -.pdm.toml - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - # Environments .env .venv @@ -186,29 +31,8 @@ ENV/ env.bak/ venv.bak/ -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ -# pytype static type analyzer -.pytype/ -# Cython debug symbols -cython_debug/ diff --git a/Dockerfile b/Dockerfile index 3b2a8e46..47668496 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.11 +FROM python:3.12 # Set environment variables ENV PYTHONDONTWRITEBYTECODE 1 From 432668af4981b6eb8bfbfa9c8b7307738e7e5a16 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Mon, 12 Feb 2024 13:47:34 +0200 Subject: [PATCH 007/240] Create 'users' Django app and introduce CustomUser Implemented a new 'users' Django app featuring a CustomUser model that extends from the AbstractUser. --- app/app/settings.py | 3 ++ app/users/__init__.py | 0 app/users/admin.py | 4 +++ app/users/apps.py | 6 ++++ app/users/migrations/0001_initial.py | 44 ++++++++++++++++++++++++++++ app/users/migrations/__init__.py | 0 app/users/models.py | 7 +++++ app/users/tests.py | 3 ++ app/users/views.py | 3 ++ 9 files changed, 70 insertions(+) create mode 100644 app/users/__init__.py create mode 100644 app/users/admin.py create mode 100644 app/users/apps.py create mode 100644 app/users/migrations/0001_initial.py create mode 100644 app/users/migrations/__init__.py create mode 100644 app/users/models.py create mode 100644 app/users/tests.py create mode 100644 app/users/views.py diff --git a/app/app/settings.py b/app/app/settings.py index 27bd7b85..ffa6f461 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -35,8 +35,11 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'users' ] +AUTH_USER_MODEL = "users.CustomUser" + MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', diff --git a/app/users/__init__.py b/app/users/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/users/admin.py b/app/users/admin.py new file mode 100644 index 00000000..a011d19b --- /dev/null +++ b/app/users/admin.py @@ -0,0 +1,4 @@ +from django.contrib import admin + + +# Register your models here. diff --git a/app/users/apps.py b/app/users/apps.py new file mode 100644 index 00000000..72b14010 --- /dev/null +++ b/app/users/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UsersConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'users' diff --git a/app/users/migrations/0001_initial.py b/app/users/migrations/0001_initial.py new file mode 100644 index 00000000..5c2f715a --- /dev/null +++ b/app/users/migrations/0001_initial.py @@ -0,0 +1,44 @@ +# Generated by Django 5.0.2 on 2024-02-12 10:47 + +import django.contrib.auth.models +import django.contrib.auth.validators +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='CustomUser', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + ] diff --git a/app/users/migrations/__init__.py b/app/users/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/users/models.py b/app/users/models.py new file mode 100644 index 00000000..a4e9a409 --- /dev/null +++ b/app/users/models.py @@ -0,0 +1,7 @@ +from django.contrib.auth.models import AbstractUser + + +class CustomUser(AbstractUser): + pass + + diff --git a/app/users/tests.py b/app/users/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/app/users/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/app/users/views.py b/app/users/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/app/users/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From 7004bf4169f044eeb86532250d0675707db62681 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Mon, 12 Feb 2024 17:00:14 +0200 Subject: [PATCH 008/240] Add graphviz and schema generation capability Updated Dockerfile and requirements.txt to include packages necessary for generating database schema diagrams --- Dockerfile | 3 +++ Makefile | 5 +++++ app/app/settings.py | 1 + app/schema/schema.png | Bin 0 -> 109589 bytes app/users/admin.py | 5 +++++ app/users/models.py | 3 ++- requirements.txt | 2 ++ 7 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 app/schema/schema.png diff --git a/Dockerfile b/Dockerfile index 47668496..4969b6bc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,6 +10,9 @@ WORKDIR /app # Install dependencies COPY requirements.txt /app/ +RUN apt-get update +RUN apt-get install graphviz graphviz-dev -y + # Install dependencies RUN pip install --upgrade pip RUN pip install -r requirements.txt diff --git a/Makefile b/Makefile index 31602d6f..1948e186 100644 --- a/Makefile +++ b/Makefile @@ -64,3 +64,8 @@ docker-stop-all: docker stop `docker ps -q` docker ps +create-schema: + clear + @docker-compose run --rm web python manage.py graph_models -a -o schema/schema.png + + diff --git a/app/app/settings.py b/app/app/settings.py index ffa6f461..37f29344 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -35,6 +35,7 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'django_extensions', 'users' ] diff --git a/app/schema/schema.png b/app/schema/schema.png new file mode 100644 index 0000000000000000000000000000000000000000..4750cce033fc3f23b824edee8a5b455d731f9a68 GIT binary patch literal 109589 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´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 literal 0 HcmV?d00001 diff --git a/app/users/admin.py b/app/users/admin.py index a011d19b..12f58121 100644 --- a/app/users/admin.py +++ b/app/users/admin.py @@ -1,4 +1,9 @@ from django.contrib import admin +from .models import CustomUser +from django.contrib.auth.admin import UserAdmin +# admin.site.register(CustomUser,UserAdmin) +admin.site.register(CustomUser,UserAdmin) # Register your models here. + diff --git a/app/users/models.py b/app/users/models.py index a4e9a409..05b954ed 100644 --- a/app/users/models.py +++ b/app/users/models.py @@ -4,4 +4,5 @@ class CustomUser(AbstractUser): pass - + def __str__(self): + return self.username diff --git a/requirements.txt b/requirements.txt index a8766947..828accc2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ django==5.0.2 psycopg2-binary ruff +pygraphviz +django-extensions From 4d9f203b03e55a481ee45112b0a2563e75446230 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Thu, 22 Feb 2024 14:43:23 +0200 Subject: [PATCH 009/240] Add new Django app 'general' with models, tests, admins, and migrations This commit adds a new Django application named 'general'. It includes new models - 'ContributingCentre', 'Institution', 'Language', and 'Subject'. --- Makefile | 3 ++ app/app/settings.py | 3 +- app/general/__init__.py | 0 app/general/admin.py | 17 ++++++ app/general/apps.py | 6 +++ app/general/migrations/0001_initial.py | 52 +++++++++++++++++++ app/general/migrations/__init__.py | 0 app/general/models.py | 44 ++++++++++++++++ app/general/tests/__init__.py | 0 .../tests/tests_contributing_centre.py | 51 ++++++++++++++++++ app/general/tests/tests_institution.py | 46 ++++++++++++++++ app/general/tests/tests_language.py | 32 ++++++++++++ app/general/tests/tests_subject.py | 32 ++++++++++++ app/general/views.py | 3 ++ app/users/admin.py | 24 +++++++-- app/users/migrations/0001_initial.py | 7 ++- app/users/models.py | 11 ++-- app/users/tests.py | 2 +- 18 files changed, 323 insertions(+), 10 deletions(-) create mode 100644 app/general/__init__.py create mode 100644 app/general/admin.py create mode 100644 app/general/apps.py create mode 100644 app/general/migrations/0001_initial.py create mode 100644 app/general/migrations/__init__.py create mode 100644 app/general/models.py create mode 100644 app/general/tests/__init__.py create mode 100644 app/general/tests/tests_contributing_centre.py create mode 100644 app/general/tests/tests_institution.py create mode 100644 app/general/tests/tests_language.py create mode 100644 app/general/tests/tests_subject.py create mode 100644 app/general/views.py diff --git a/Makefile b/Makefile index 1948e186..6c7bde9f 100644 --- a/Makefile +++ b/Makefile @@ -68,4 +68,7 @@ create-schema: clear @docker-compose run --rm web python manage.py graph_models -a -o schema/schema.png +test: + clear + @docker-compose run --rm web python manage.py test diff --git a/app/app/settings.py b/app/app/settings.py index 37f29344..8060ce77 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -36,7 +36,8 @@ 'django.contrib.messages', 'django.contrib.staticfiles', 'django_extensions', - 'users' + 'users', + 'general' ] AUTH_USER_MODEL = "users.CustomUser" diff --git a/app/general/__init__.py b/app/general/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/general/admin.py b/app/general/admin.py new file mode 100644 index 00000000..f294d5ca --- /dev/null +++ b/app/general/admin.py @@ -0,0 +1,17 @@ +from django.contrib import admin + +from .models import ContributingCentre, Institution, Language, Subject + + +class SubjectInline(admin.TabularInline): + model = Subject.contributing_centre.through + + +class ContributingCentreAdmin(admin.ModelAdmin): + inlines = [SubjectInline] + + +admin.site.register(ContributingCentre, ContributingCentreAdmin) +admin.site.register(Institution) +admin.site.register(Language) +admin.site.register(Subject) diff --git a/app/general/apps.py b/app/general/apps.py new file mode 100644 index 00000000..d7dbe115 --- /dev/null +++ b/app/general/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class GeneralConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'general' diff --git a/app/general/migrations/0001_initial.py b/app/general/migrations/0001_initial.py new file mode 100644 index 00000000..c3bf636b --- /dev/null +++ b/app/general/migrations/0001_initial.py @@ -0,0 +1,52 @@ +# Generated by Django 5.0.2 on 2024-02-22 11:11 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='ContributingCentre', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200, unique=True)), + ('url', models.URLField()), + ('logo', models.FileField(blank=True, upload_to='logos/')), + ], + ), + migrations.CreateModel( + name='Language', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, unique=True)), + ('iso_code', models.CharField(help_text='Enter the ISO code for the language', max_length=10, unique=True)), + ], + ), + migrations.CreateModel( + name='Institution', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200, unique=True)), + ('abbreviation', models.CharField(max_length=200)), + ('url', models.URLField()), + ('email', models.EmailField(max_length=200)), + ('logo', models.FileField(blank=True, upload_to='logos/')), + ('contributing_centre', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, to='general.contributingcentre')), + ], + ), + migrations.CreateModel( + name='Subject', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, unique=True)), + ('contributing_centre', models.ManyToManyField(blank=True, related_name='subjects', to='general.contributingcentre')), + ], + ), + ] diff --git a/app/general/migrations/__init__.py b/app/general/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/general/models.py b/app/general/models.py new file mode 100644 index 00000000..ff6625eb --- /dev/null +++ b/app/general/models.py @@ -0,0 +1,44 @@ +from django.db import models + + +class ContributingCentre(models.Model): + name = models.CharField(max_length=200, unique=True) + url = models.URLField(max_length=200) + logo = models.FileField(upload_to='logos/', blank=True) + + def __str__(self): + return self.name + + +class Institution(models.Model): + name = models.CharField(max_length=200, unique=True) + abbreviation = models.CharField(max_length=200) + url = models.URLField(max_length=200) + email = models.EmailField(max_length=200) + logo = models.FileField(upload_to='logos/', blank=True) + contributing_centre = models.ForeignKey( + ContributingCentre, on_delete=models.CASCADE, blank=True + ) + + def __str__(self): + return self.name + + +class Language(models.Model): + name = models.CharField(max_length=100, unique=True) + iso_code = models.CharField( + max_length=10, unique=True, help_text='Enter the ISO code for the language' + ) + + def __str__(self): + return self.name + + +class Subject(models.Model): + name = models.CharField(max_length=100, unique=True) + contributing_centre = models.ManyToManyField( + 'ContributingCentre', related_name='subjects', blank=True + ) + + def __str__(self): + return self.name \ No newline at end of file diff --git a/app/general/tests/__init__.py b/app/general/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/general/tests/tests_contributing_centre.py b/app/general/tests/tests_contributing_centre.py new file mode 100644 index 00000000..641233d9 --- /dev/null +++ b/app/general/tests/tests_contributing_centre.py @@ -0,0 +1,51 @@ +import unittest + +from django.db.utils import IntegrityError +from django.test import TestCase + +from general.models import ContributingCentre, Subject + +# from subject.models import Subject + + +class TestContributingCentre(TestCase): + def setUp(self): + self.subject1 = Subject.objects.create(name='Maths') + self.subject2 = Subject.objects.create(name='Science') + + self.contrib_centre1 = ContributingCentre.objects.create( + name='Centre1', url='http://example.com' + ) + self.contrib_centre2 = ContributingCentre.objects.create( + name='Centre2', url='http://test.com', logo='http://test.com/logo.png' + ) + + self.contrib_centre1.subjects.add(self.subject1, self.subject2) + self.contrib_centre2.subjects.add(self.subject1) + + def test_centre_creation(self): + self.assertEqual(ContributingCentre.objects.count(), 2) + + def test_centre_name_unique(self): + with self.assertRaises(IntegrityError): + ContributingCentre.objects.create(name='Centre1', url='http://example.com') + + def test_centre_url_field(self): + self.assertEqual(self.contrib_centre1.url, 'http://example.com') + self.assertEqual(self.contrib_centre2.url, 'http://test.com') + + def test_centre_name_str(self): + self.assertEqual(str(self.contrib_centre1), 'Centre1') + self.assertEqual(str(self.contrib_centre2), 'Centre2') + + def test_centre_logo_field(self): + self.assertEqual(self.contrib_centre1.logo, '') + self.assertEqual(self.contrib_centre2.logo, 'http://test.com/logo.png') + + def test_centre_subject_field(self): + self.assertEqual(list(self.contrib_centre1.subjects.all()), [self.subject1, self.subject2]) + self.assertEqual(list(self.contrib_centre2.subjects.all()), [self.subject1]) + + +if __name__ == '__main__': + unittest.main() diff --git a/app/general/tests/tests_institution.py b/app/general/tests/tests_institution.py new file mode 100644 index 00000000..5db2be3b --- /dev/null +++ b/app/general/tests/tests_institution.py @@ -0,0 +1,46 @@ +import unittest + +from django.test import TestCase + +from general.models import ContributingCentre, Institution + + +class TestInstitution(TestCase): + def setUp(self): + self.contributing_centre = ContributingCentre.objects.create( + name='Test Centre', + # add additional fields here as required by the ContributingCentre model + ) + self.institution = Institution.objects.create( + name='Test University', + abbreviation='tu', + url='http://www.testuni.com', + email='info@testuni.dev', + logo='testuni.png', + contributing_centre=self.contributing_centre, + ) + + def test_institution_creation(self): + self.assertIsInstance(self.institution, Institution) + + def test_institution_name(self): + self.assertEqual(self.institution.name, 'Test University') + + def test_institution_abbreviation(self): + self.assertEqual(self.institution.abbreviation, 'tu') + + def test_institution_url(self): + self.assertEqual(self.institution.url, 'http://www.testuni.com') + + def test_institution_email(self): + self.assertEqual(self.institution.email, 'info@testuni.dev') + + def test_institution_logo(self): + self.assertEqual(self.institution.logo, 'testuni.png') + + def test_institution_contributing_centre(self): + self.assertEqual(self.institution.contributing_centre, self.contributing_centre) + + +if __name__ == '__main__': + unittest.main() diff --git a/app/general/tests/tests_language.py b/app/general/tests/tests_language.py new file mode 100644 index 00000000..1f5883cc --- /dev/null +++ b/app/general/tests/tests_language.py @@ -0,0 +1,32 @@ +import unittest + +from django.test import TestCase + +from general.models import ContributingCentre, Subject + + +class TestSubject(TestCase): + def setUp(self): + self.subject = Subject.objects.create(name='Maths') + self.subject2 = Subject.objects.create(name='Science') + + def test_subject_creation(self): + self.assertEqual(self.subject.name, 'Maths') + self.assertEqual(self.subject.__str__(), 'Maths') + + def test_subject_name_uniqueness(self): + duplicate_subject = Subject(name='Maths') + self.assertRaises(Exception, duplicate_subject.save) + + def test_contributing_centre_relationship(self): + contributing_centre = ContributingCentre(name='Test Centre') + contributing_centre.save() + self.subject.contributing_centre.add(contributing_centre) + self.assertTrue(contributing_centre in self.subject.contributing_centre.all()) + + def test_blank_contributing_centre(self): + self.assertEqual(self.subject.contributing_centre.count(), 0) + + +if __name__ == '__main__': + unittest.main() diff --git a/app/general/tests/tests_subject.py b/app/general/tests/tests_subject.py new file mode 100644 index 00000000..1f5883cc --- /dev/null +++ b/app/general/tests/tests_subject.py @@ -0,0 +1,32 @@ +import unittest + +from django.test import TestCase + +from general.models import ContributingCentre, Subject + + +class TestSubject(TestCase): + def setUp(self): + self.subject = Subject.objects.create(name='Maths') + self.subject2 = Subject.objects.create(name='Science') + + def test_subject_creation(self): + self.assertEqual(self.subject.name, 'Maths') + self.assertEqual(self.subject.__str__(), 'Maths') + + def test_subject_name_uniqueness(self): + duplicate_subject = Subject(name='Maths') + self.assertRaises(Exception, duplicate_subject.save) + + def test_contributing_centre_relationship(self): + contributing_centre = ContributingCentre(name='Test Centre') + contributing_centre.save() + self.subject.contributing_centre.add(contributing_centre) + self.assertTrue(contributing_centre in self.subject.contributing_centre.all()) + + def test_blank_contributing_centre(self): + self.assertEqual(self.subject.contributing_centre.count(), 0) + + +if __name__ == '__main__': + unittest.main() diff --git a/app/general/views.py b/app/general/views.py new file mode 100644 index 00000000..fd0e0449 --- /dev/null +++ b/app/general/views.py @@ -0,0 +1,3 @@ +# from django.shortcuts import render + +# Create your views here. diff --git a/app/users/admin.py b/app/users/admin.py index 12f58121..5e81635c 100644 --- a/app/users/admin.py +++ b/app/users/admin.py @@ -1,9 +1,25 @@ from django.contrib import admin -from .models import CustomUser from django.contrib.auth.admin import UserAdmin +from .models import CustomUser + + +class CustomUserAdmin(UserAdmin): + model = CustomUser + list_display = [ + 'username', + 'email', + 'first_name', + 'last_name', + 'is_staff', + 'is_active', + ] + list_filter = [ + 'username', + 'email', + ] + fieldsets = UserAdmin.fieldsets + ((None, {'fields': ('institution', 'languages', 'subject')}),) + add_fieldsets = UserAdmin.add_fieldsets -# admin.site.register(CustomUser,UserAdmin) -admin.site.register(CustomUser,UserAdmin) -# Register your models here. +admin.site.register(CustomUser, CustomUserAdmin) \ No newline at end of file diff --git a/app/users/migrations/0001_initial.py b/app/users/migrations/0001_initial.py index 5c2f715a..ed126114 100644 --- a/app/users/migrations/0001_initial.py +++ b/app/users/migrations/0001_initial.py @@ -1,7 +1,8 @@ -# Generated by Django 5.0.2 on 2024-02-12 10:47 +# Generated by Django 5.0.2 on 2024-02-22 11:11 import django.contrib.auth.models import django.contrib.auth.validators +import django.db.models.deletion import django.utils.timezone from django.db import migrations, models @@ -12,6 +13,7 @@ class Migration(migrations.Migration): dependencies = [ ('auth', '0012_alter_user_first_name_max_length'), + ('general', '0001_initial'), ] operations = [ @@ -30,6 +32,9 @@ class Migration(migrations.Migration): ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('institution', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='general.institution')), + ('languages', models.ManyToManyField(to='general.language')), + ('subject', models.ManyToManyField(to='general.subject')), ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), ], options={ diff --git a/app/users/models.py b/app/users/models.py index 05b954ed..8cd3c88a 100644 --- a/app/users/models.py +++ b/app/users/models.py @@ -1,8 +1,13 @@ from django.contrib.auth.models import AbstractUser +from django.db import models + +from general.models import Institution, Language, Subject class CustomUser(AbstractUser): - pass + institution = models.ForeignKey(Institution, on_delete=models.CASCADE, null=True, blank=True) + languages = models.ManyToManyField(Language) + subject = models.ManyToManyField(Subject) - def __str__(self): - return self.username + def __str__(self): + return self.username diff --git a/app/users/tests.py b/app/users/tests.py index 7ce503c2..a79ca8be 100644 --- a/app/users/tests.py +++ b/app/users/tests.py @@ -1,3 +1,3 @@ -from django.test import TestCase +# from django.test import TestCase # Create your tests here. From cf25651791c29fe208ea557f1a708796150428da Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Fri, 23 Feb 2024 13:13:17 +0200 Subject: [PATCH 010/240] Add Ruff - Refactor codebase for consistency and style improvements In this commit, some changes were made to enhance the code quality and readability. --- Dockerfile | 4 +- Makefile | 12 +++ app/.editorconfig | 9 ++ app/app/asgi.py | 2 +- app/app/settings.py | 86 +++++++++---------- app/app/urls.py | 2 +- app/app/wsgi.py | 2 +- app/general/admin.py | 4 +- app/general/apps.py | 4 +- app/general/migrations/0001_initial.py | 2 +- app/general/models.py | 10 +-- .../tests/tests_contributing_centre.py | 60 ++++++------- app/general/tests/tests_institution.py | 58 ++++++------- app/general/tests/tests_language.py | 36 ++++---- app/general/tests/tests_subject.py | 36 ++++---- app/manage.py | 4 +- app/pyproject.toml | 10 +++ app/users/admin.py | 32 +++---- app/users/apps.py | 4 +- app/users/migrations/0001_initial.py | 2 +- app/users/models.py | 10 +-- app/users/views.py | 2 +- 22 files changed, 211 insertions(+), 180 deletions(-) create mode 100644 app/.editorconfig create mode 100644 app/pyproject.toml diff --git a/Dockerfile b/Dockerfile index 4969b6bc..568e5033 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ RUN pip install --upgrade pip RUN pip install -r requirements.txt # Copy project -COPY . /app/ +COPY ./app /app/ # Run the application -CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] \ No newline at end of file +CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] diff --git a/Makefile b/Makefile index 6c7bde9f..8ed15cf6 100644 --- a/Makefile +++ b/Makefile @@ -72,3 +72,15 @@ test: clear @docker-compose run --rm web python manage.py test +ruff-check: + clear + @docker-compose run --rm web ruff check . + +ruff-format: + clear + @docker-compose run --rm web ruff format . + +ruff-fix: + clear + @docker-compose run --rm web ruff check --fix . + diff --git a/app/.editorconfig b/app/.editorconfig new file mode 100644 index 00000000..0385e343 --- /dev/null +++ b/app/.editorconfig @@ -0,0 +1,9 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true diff --git a/app/app/asgi.py b/app/app/asgi.py index de171914..fd207eb3 100644 --- a/app/app/asgi.py +++ b/app/app/asgi.py @@ -11,6 +11,6 @@ from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") application = get_asgi_application() diff --git a/app/app/settings.py b/app/app/settings.py index 8060ce77..ae0abd60 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -19,7 +19,7 @@ # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-w!h85bp^$e8gm%c23r!0%9i7yzd=6w$s&ic+6!%306&kj8@k*5' +SECRET_KEY = "django-insecure-w!h85bp^$e8gm%c23r!0%9i7yzd=6w$s&ic+6!%306&kj8@k*5" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -29,60 +29,60 @@ # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django_extensions', - 'users', - 'general' + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django_extensions", + "users", + "general", ] AUTH_USER_MODEL = "users.CustomUser" MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'app.urls' +ROOT_URLCONF = "app.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] -WSGI_APPLICATION = 'app.wsgi.application' +WSGI_APPLICATION = "app.wsgi.application" # Database # https://docs.djangoproject.com/en/5.0/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'HOST': os.environ.get('DJANGO_DB_HOST'), - 'PORT': os.environ.get('DJANGO_DB_PORT'), - 'NAME': os.environ.get('DJANGO_DB_NAME'), - 'USER': os.environ.get('DJANGO_DB_USER'), - 'PASSWORD': os.environ.get('DJANGO_DB_PASSWORD'), + "default": { + "ENGINE": "django.db.backends.postgresql", + "HOST": os.environ.get("DJANGO_DB_HOST"), + "PORT": os.environ.get("DJANGO_DB_PORT"), + "NAME": os.environ.get("DJANGO_DB_NAME"), + "USER": os.environ.get("DJANGO_DB_USER"), + "PASSWORD": os.environ.get("DJANGO_DB_PASSWORD"), } } @@ -91,25 +91,25 @@ AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] # Internationalization # https://docs.djangoproject.com/en/5.0/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -118,9 +118,9 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.0/howto/static-files/ -STATIC_URL = 'static/' +STATIC_URL = "static/" # Default primary key field type # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" diff --git a/app/app/urls.py b/app/app/urls.py index ded1f113..b065827a 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -18,5 +18,5 @@ from django.urls import path urlpatterns = [ - path('admin/', admin.site.urls), + path("admin/", admin.site.urls), ] diff --git a/app/app/wsgi.py b/app/app/wsgi.py index eb514abf..1a4e28fe 100644 --- a/app/app/wsgi.py +++ b/app/app/wsgi.py @@ -11,6 +11,6 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") application = get_wsgi_application() diff --git a/app/general/admin.py b/app/general/admin.py index f294d5ca..433d8b0a 100644 --- a/app/general/admin.py +++ b/app/general/admin.py @@ -4,11 +4,11 @@ class SubjectInline(admin.TabularInline): - model = Subject.contributing_centre.through + model = Subject.contributing_centre.through class ContributingCentreAdmin(admin.ModelAdmin): - inlines = [SubjectInline] + inlines = [SubjectInline] admin.site.register(ContributingCentre, ContributingCentreAdmin) diff --git a/app/general/apps.py b/app/general/apps.py index d7dbe115..5a41e701 100644 --- a/app/general/apps.py +++ b/app/general/apps.py @@ -2,5 +2,5 @@ class GeneralConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'general' + default_auto_field = "django.db.models.BigAutoField" + name = "general" diff --git a/app/general/migrations/0001_initial.py b/app/general/migrations/0001_initial.py index c3bf636b..93967b8b 100644 --- a/app/general/migrations/0001_initial.py +++ b/app/general/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.2 on 2024-02-22 11:11 +# Generated by Django 5.0.2 on 2024-02-23 11:05 import django.db.models.deletion from django.db import migrations, models diff --git a/app/general/models.py b/app/general/models.py index ff6625eb..83c683da 100644 --- a/app/general/models.py +++ b/app/general/models.py @@ -4,7 +4,7 @@ class ContributingCentre(models.Model): name = models.CharField(max_length=200, unique=True) url = models.URLField(max_length=200) - logo = models.FileField(upload_to='logos/', blank=True) + logo = models.FileField(upload_to="logos/", blank=True) def __str__(self): return self.name @@ -15,7 +15,7 @@ class Institution(models.Model): abbreviation = models.CharField(max_length=200) url = models.URLField(max_length=200) email = models.EmailField(max_length=200) - logo = models.FileField(upload_to='logos/', blank=True) + logo = models.FileField(upload_to="logos/", blank=True) contributing_centre = models.ForeignKey( ContributingCentre, on_delete=models.CASCADE, blank=True ) @@ -27,7 +27,7 @@ def __str__(self): class Language(models.Model): name = models.CharField(max_length=100, unique=True) iso_code = models.CharField( - max_length=10, unique=True, help_text='Enter the ISO code for the language' + max_length=10, unique=True, help_text="Enter the ISO code for the language" ) def __str__(self): @@ -37,8 +37,8 @@ def __str__(self): class Subject(models.Model): name = models.CharField(max_length=100, unique=True) contributing_centre = models.ManyToManyField( - 'ContributingCentre', related_name='subjects', blank=True + "ContributingCentre", related_name="subjects", blank=True ) def __str__(self): - return self.name \ No newline at end of file + return self.name diff --git a/app/general/tests/tests_contributing_centre.py b/app/general/tests/tests_contributing_centre.py index 641233d9..a50cd4dd 100644 --- a/app/general/tests/tests_contributing_centre.py +++ b/app/general/tests/tests_contributing_centre.py @@ -9,43 +9,43 @@ class TestContributingCentre(TestCase): - def setUp(self): - self.subject1 = Subject.objects.create(name='Maths') - self.subject2 = Subject.objects.create(name='Science') + def setUp(self): + self.subject1 = Subject.objects.create(name="Maths") + self.subject2 = Subject.objects.create(name="Science") - self.contrib_centre1 = ContributingCentre.objects.create( - name='Centre1', url='http://example.com' - ) - self.contrib_centre2 = ContributingCentre.objects.create( - name='Centre2', url='http://test.com', logo='http://test.com/logo.png' - ) + self.contrib_centre1 = ContributingCentre.objects.create( + name="Centre1", url="http://example.com" + ) + self.contrib_centre2 = ContributingCentre.objects.create( + name="Centre2", url="http://test.com", logo="http://test.com/logo.png" + ) - self.contrib_centre1.subjects.add(self.subject1, self.subject2) - self.contrib_centre2.subjects.add(self.subject1) + self.contrib_centre1.subjects.add(self.subject1, self.subject2) + self.contrib_centre2.subjects.add(self.subject1) - def test_centre_creation(self): - self.assertEqual(ContributingCentre.objects.count(), 2) + def test_centre_creation(self): + self.assertEqual(ContributingCentre.objects.count(), 2) - def test_centre_name_unique(self): - with self.assertRaises(IntegrityError): - ContributingCentre.objects.create(name='Centre1', url='http://example.com') + def test_centre_name_unique(self): + with self.assertRaises(IntegrityError): + ContributingCentre.objects.create(name="Centre1", url="http://example.com") - def test_centre_url_field(self): - self.assertEqual(self.contrib_centre1.url, 'http://example.com') - self.assertEqual(self.contrib_centre2.url, 'http://test.com') + def test_centre_url_field(self): + self.assertEqual(self.contrib_centre1.url, "http://example.com") + self.assertEqual(self.contrib_centre2.url, "http://test.com") - def test_centre_name_str(self): - self.assertEqual(str(self.contrib_centre1), 'Centre1') - self.assertEqual(str(self.contrib_centre2), 'Centre2') + def test_centre_name_str(self): + self.assertEqual(str(self.contrib_centre1), "Centre1") + self.assertEqual(str(self.contrib_centre2), "Centre2") - def test_centre_logo_field(self): - self.assertEqual(self.contrib_centre1.logo, '') - self.assertEqual(self.contrib_centre2.logo, 'http://test.com/logo.png') + def test_centre_logo_field(self): + self.assertEqual(self.contrib_centre1.logo, "") + self.assertEqual(self.contrib_centre2.logo, "http://test.com/logo.png") - def test_centre_subject_field(self): - self.assertEqual(list(self.contrib_centre1.subjects.all()), [self.subject1, self.subject2]) - self.assertEqual(list(self.contrib_centre2.subjects.all()), [self.subject1]) + def test_centre_subject_field(self): + self.assertEqual(list(self.contrib_centre1.subjects.all()), [self.subject1, self.subject2]) + self.assertEqual(list(self.contrib_centre2.subjects.all()), [self.subject1]) -if __name__ == '__main__': - unittest.main() +if __name__ == "__main__": + unittest.main() diff --git a/app/general/tests/tests_institution.py b/app/general/tests/tests_institution.py index 5db2be3b..4271514f 100644 --- a/app/general/tests/tests_institution.py +++ b/app/general/tests/tests_institution.py @@ -6,41 +6,41 @@ class TestInstitution(TestCase): - def setUp(self): - self.contributing_centre = ContributingCentre.objects.create( - name='Test Centre', - # add additional fields here as required by the ContributingCentre model - ) - self.institution = Institution.objects.create( - name='Test University', - abbreviation='tu', - url='http://www.testuni.com', - email='info@testuni.dev', - logo='testuni.png', - contributing_centre=self.contributing_centre, - ) + def setUp(self): + self.contributing_centre = ContributingCentre.objects.create( + name="Test Centre", + # add additional fields here as required by the ContributingCentre model + ) + self.institution = Institution.objects.create( + name="Test University", + abbreviation="tu", + url="http://www.testuni.com", + email="info@testuni.dev", + logo="testuni.png", + contributing_centre=self.contributing_centre, + ) - def test_institution_creation(self): - self.assertIsInstance(self.institution, Institution) + def test_institution_creation(self): + self.assertIsInstance(self.institution, Institution) - def test_institution_name(self): - self.assertEqual(self.institution.name, 'Test University') + def test_institution_name(self): + self.assertEqual(self.institution.name, "Test University") - def test_institution_abbreviation(self): - self.assertEqual(self.institution.abbreviation, 'tu') + def test_institution_abbreviation(self): + self.assertEqual(self.institution.abbreviation, "tu") - def test_institution_url(self): - self.assertEqual(self.institution.url, 'http://www.testuni.com') + def test_institution_url(self): + self.assertEqual(self.institution.url, "http://www.testuni.com") - def test_institution_email(self): - self.assertEqual(self.institution.email, 'info@testuni.dev') + def test_institution_email(self): + self.assertEqual(self.institution.email, "info@testuni.dev") - def test_institution_logo(self): - self.assertEqual(self.institution.logo, 'testuni.png') + def test_institution_logo(self): + self.assertEqual(self.institution.logo, "testuni.png") - def test_institution_contributing_centre(self): - self.assertEqual(self.institution.contributing_centre, self.contributing_centre) + def test_institution_contributing_centre(self): + self.assertEqual(self.institution.contributing_centre, self.contributing_centre) -if __name__ == '__main__': - unittest.main() +if __name__ == "__main__": + unittest.main() diff --git a/app/general/tests/tests_language.py b/app/general/tests/tests_language.py index 1f5883cc..70331e70 100644 --- a/app/general/tests/tests_language.py +++ b/app/general/tests/tests_language.py @@ -6,27 +6,27 @@ class TestSubject(TestCase): - def setUp(self): - self.subject = Subject.objects.create(name='Maths') - self.subject2 = Subject.objects.create(name='Science') + def setUp(self): + self.subject = Subject.objects.create(name="Maths") + self.subject2 = Subject.objects.create(name="Science") - def test_subject_creation(self): - self.assertEqual(self.subject.name, 'Maths') - self.assertEqual(self.subject.__str__(), 'Maths') + def test_subject_creation(self): + self.assertEqual(self.subject.name, "Maths") + self.assertEqual(self.subject.__str__(), "Maths") - def test_subject_name_uniqueness(self): - duplicate_subject = Subject(name='Maths') - self.assertRaises(Exception, duplicate_subject.save) + def test_subject_name_uniqueness(self): + duplicate_subject = Subject(name="Maths") + self.assertRaises(Exception, duplicate_subject.save) - def test_contributing_centre_relationship(self): - contributing_centre = ContributingCentre(name='Test Centre') - contributing_centre.save() - self.subject.contributing_centre.add(contributing_centre) - self.assertTrue(contributing_centre in self.subject.contributing_centre.all()) + def test_contributing_centre_relationship(self): + contributing_centre = ContributingCentre(name="Test Centre") + contributing_centre.save() + self.subject.contributing_centre.add(contributing_centre) + self.assertTrue(contributing_centre in self.subject.contributing_centre.all()) - def test_blank_contributing_centre(self): - self.assertEqual(self.subject.contributing_centre.count(), 0) + def test_blank_contributing_centre(self): + self.assertEqual(self.subject.contributing_centre.count(), 0) -if __name__ == '__main__': - unittest.main() +if __name__ == "__main__": + unittest.main() diff --git a/app/general/tests/tests_subject.py b/app/general/tests/tests_subject.py index 1f5883cc..70331e70 100644 --- a/app/general/tests/tests_subject.py +++ b/app/general/tests/tests_subject.py @@ -6,27 +6,27 @@ class TestSubject(TestCase): - def setUp(self): - self.subject = Subject.objects.create(name='Maths') - self.subject2 = Subject.objects.create(name='Science') + def setUp(self): + self.subject = Subject.objects.create(name="Maths") + self.subject2 = Subject.objects.create(name="Science") - def test_subject_creation(self): - self.assertEqual(self.subject.name, 'Maths') - self.assertEqual(self.subject.__str__(), 'Maths') + def test_subject_creation(self): + self.assertEqual(self.subject.name, "Maths") + self.assertEqual(self.subject.__str__(), "Maths") - def test_subject_name_uniqueness(self): - duplicate_subject = Subject(name='Maths') - self.assertRaises(Exception, duplicate_subject.save) + def test_subject_name_uniqueness(self): + duplicate_subject = Subject(name="Maths") + self.assertRaises(Exception, duplicate_subject.save) - def test_contributing_centre_relationship(self): - contributing_centre = ContributingCentre(name='Test Centre') - contributing_centre.save() - self.subject.contributing_centre.add(contributing_centre) - self.assertTrue(contributing_centre in self.subject.contributing_centre.all()) + def test_contributing_centre_relationship(self): + contributing_centre = ContributingCentre(name="Test Centre") + contributing_centre.save() + self.subject.contributing_centre.add(contributing_centre) + self.assertTrue(contributing_centre in self.subject.contributing_centre.all()) - def test_blank_contributing_centre(self): - self.assertEqual(self.subject.contributing_centre.count(), 0) + def test_blank_contributing_centre(self): + self.assertEqual(self.subject.contributing_centre.count(), 0) -if __name__ == '__main__': - unittest.main() +if __name__ == "__main__": + unittest.main() diff --git a/app/manage.py b/app/manage.py index 49313893..1a64b14a 100755 --- a/app/manage.py +++ b/app/manage.py @@ -6,7 +6,7 @@ def main(): """Run administrative tasks.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -18,5 +18,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/app/pyproject.toml b/app/pyproject.toml new file mode 100644 index 00000000..5be1384c --- /dev/null +++ b/app/pyproject.toml @@ -0,0 +1,10 @@ +[tool.ruff] +line-length = 100 +exclude = ["migrations", ".venv"] + +[tool.ruff.lint] +select = [ + "DJ", + "I", + "W" +] diff --git a/app/users/admin.py b/app/users/admin.py index 5e81635c..364391db 100644 --- a/app/users/admin.py +++ b/app/users/admin.py @@ -5,21 +5,21 @@ class CustomUserAdmin(UserAdmin): - model = CustomUser - list_display = [ - 'username', - 'email', - 'first_name', - 'last_name', - 'is_staff', - 'is_active', - ] - list_filter = [ - 'username', - 'email', - ] - fieldsets = UserAdmin.fieldsets + ((None, {'fields': ('institution', 'languages', 'subject')}),) - add_fieldsets = UserAdmin.add_fieldsets + model = CustomUser + list_display = [ + "username", + "email", + "first_name", + "last_name", + "is_staff", + "is_active", + ] + list_filter = [ + "username", + "email", + ] + fieldsets = UserAdmin.fieldsets + ((None, {"fields": ("institution", "languages", "subject")}),) + add_fieldsets = UserAdmin.add_fieldsets -admin.site.register(CustomUser, CustomUserAdmin) \ No newline at end of file +admin.site.register(CustomUser, CustomUserAdmin) diff --git a/app/users/apps.py b/app/users/apps.py index 72b14010..88f7b179 100644 --- a/app/users/apps.py +++ b/app/users/apps.py @@ -2,5 +2,5 @@ class UsersConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'users' + default_auto_field = "django.db.models.BigAutoField" + name = "users" diff --git a/app/users/migrations/0001_initial.py b/app/users/migrations/0001_initial.py index ed126114..1fc6e464 100644 --- a/app/users/migrations/0001_initial.py +++ b/app/users/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.2 on 2024-02-22 11:11 +# Generated by Django 5.0.2 on 2024-02-23 11:05 import django.contrib.auth.models import django.contrib.auth.validators diff --git a/app/users/models.py b/app/users/models.py index 8cd3c88a..88a6d09e 100644 --- a/app/users/models.py +++ b/app/users/models.py @@ -5,9 +5,9 @@ class CustomUser(AbstractUser): - institution = models.ForeignKey(Institution, on_delete=models.CASCADE, null=True, blank=True) - languages = models.ManyToManyField(Language) - subject = models.ManyToManyField(Subject) + institution = models.ForeignKey(Institution, on_delete=models.CASCADE, null=True, blank=True) + languages = models.ManyToManyField(Language) + subject = models.ManyToManyField(Subject) - def __str__(self): - return self.username + def __str__(self): + return self.username diff --git a/app/users/views.py b/app/users/views.py index 91ea44a2..fd0e0449 100644 --- a/app/users/views.py +++ b/app/users/views.py @@ -1,3 +1,3 @@ -from django.shortcuts import render +# from django.shortcuts import render # Create your views here. From bf68c1e4b01f408ef29bcff57639ba00d1916508 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Mon, 26 Feb 2024 14:43:36 +0200 Subject: [PATCH 011/240] Add new institutions and languages and implement fixture loading In this commit, two JSON fixtures were created for institutions and languages in the general app. --- Makefile | 4 + app/fixtures/institution.json | 302 ++++++++++++++++++++++++++++++++++ app/fixtures/language.json | 26 +++ 3 files changed, 332 insertions(+) create mode 100644 app/fixtures/institution.json create mode 100644 app/fixtures/language.json diff --git a/Makefile b/Makefile index 8ed15cf6..39f01523 100644 --- a/Makefile +++ b/Makefile @@ -84,3 +84,7 @@ ruff-fix: clear @docker-compose run --rm web ruff check --fix . +load-fixtures: + clear + @docker-compose run --rm web python manage.py loaddata fixtures/institution.json + @docker-compose run --rm web python manage.py loaddata fixtures/language.json diff --git a/app/fixtures/institution.json b/app/fixtures/institution.json new file mode 100644 index 00000000..24baaa7f --- /dev/null +++ b/app/fixtures/institution.json @@ -0,0 +1,302 @@ +[ + { + "model": "general.Institution", + "pk": 1, + "fields": { + "name": "University of South Africa (UNISA)", + "abbreviation": "UNISA", + "url": "https://example.dev", + "email": "email@example.dev", + "logo": "logo", + "contributing_centre_id": "1" + } + }, + { + "model": "general.Institution", + "pk": 2, + "fields": { + "name": "North-West University", + "abbreviation": "NWU", + "url": "https://example.dev", + "email": "email@example.dev", + "logo": "logo", + "contributing_centre_id": "1" + } + }, + { + "model": "general.Institution", + "pk": 3, + "fields": { + "name": "University of Pretoria", + "abbreviation": "UP", + "url": "https://example.dev", + "email": "email@example.dev", + "logo": "logo", + "contributing_centre_id": "1" + } + }, + { + "model": "general.Institution", + "pk": 4, + "fields": { + "name": "University of Johannesburg", + "abbreviation": "UJ", + "url": "https://example.dev", + "email": "email@example.dev", + "logo": "logo", + "contributing_centre_id": "1" + } + }, + { + "model": "general.Institution", + "pk": 5, + "fields": { + "name": "University of KwaZulu-Natal", + "abbreviation": "UKZN", + "url": "https://example.dev", + "email": "email@example.dev", + "logo": "logo", + "contributing_centre_id": "1" + } + }, + { + "model": "general.Institution", + "pk": 5, + "fields": { + "name": "University of the Free State", + "abbreviation": "UFS", + "url": "https://example.dev", + "email": "email@example.dev", + "logo": "logo", + "contributing_centre_id": "1" + } + }, + { + "model": "general.Institution", + "pk": 6, + "fields": { + "name": "Cape Peninsula University of Technology", + "abbreviation": "CPUT", + "url": "https://example.dev", + "email": "email@example.dev", + "logo": "logo", + "contributing_centre_id": "1" + } + }, + { + "model": "general.Institution", + "pk": 7, + "fields": { + "name": "University of the Witwatersrand", + "abbreviation": "WITS", + "url": "https://example.dev", + "email": "email@example.dev", + "logo": "logo", + "contributing_centre_id": "1" + } + }, + { + "model": "general.Institution", + "pk": 8, + "fields": { + "name": "University of Stellenbosch", + "abbreviation": "US", + "url": "https://example.dev", + "email": "email@example.dev", + "logo": "logo", + "contributing_centre_id": "1" + } + }, + { + "model": "general.Institution", + "pk": 9, + "fields": { + "name": "University of Cape Town", + "abbreviation": "UCT", + "url": "https://example.dev", + "email": "email@example.dev", + "logo": "logo", + "contributing_centre_id": "1" + } + }, + { + "model": "general.Institution", + "pk": 10, + "fields": { + "name": "Nelson Mandela Metropolitan University", + "abbreviation": "NMU", + "url": "https://example.dev", + "email": "email@example.dev", + "logo": "logo", + "contributing_centre_id": "1" + } + }, + { + "model": "general.Institution", + "pk": 11, + "fields": { + "name": "Walter Sisulu University", + "abbreviation": "WSU", + "url": "https://example.dev", + "email": "email@example.dev", + "logo": "logo", + "contributing_centre_id": "1" + } + }, + { + "model": "general.Institution", + "pk": 12, + "fields": { + "name": "Durban University of Technology", + "abbreviation": "DUT", + "url": "https://example.dev", + "email": "email@example.dev", + "logo": "logo", + "contributing_centre_id": "1" + } + }, + { + "model": "general.Institution", + "pk": 13, + "fields": { + "name": "University of Limpopo", + "abbreviation": "UL", + "url": "https://example.dev", + "email": "email@example.dev", + "logo": "logo", + "contributing_centre_id": "1" + } + }, + { + "model": "general.Institution", + "pk": 14, + "fields": { + "name": "Vaal University of Technology", + "abbreviation": "VUT", + "url": "https://example.dev", + "email": "email@example.dev", + "logo": "logo", + "contributing_centre_id": "1" + } + }, + { + "model": "general.Institution", + "pk": 15, + "fields": { + "name": "University of Zululand", + "abbreviation": "UniZulu", + "url": "https://example.dev", + "email": "email@example.dev", + "logo": "logo", + "contributing_centre_id": "1" + } + }, + { + "model": "general.Institution", + "pk": 16, + "fields": { + "name": "University of the Western Cape", + "abbreviation": "UWC", + "url": "https://example.dev", + "email": "email@example.dev", + "logo": "logo", + "contributing_centre_id": "1" + } + }, + { + "model": "general.Institution", + "pk": 17, + "fields": { + "name": "Central University of Technology", + "abbreviation": "CUT", + "url": "https://example.dev", + "email": "email@example.dev", + "logo": "logo", + "contributing_centre_id": "1" + } + }, + { + "model": "general.Institution", + "pk": 18, + "fields": { + "name": "University of Fort Hare", + "abbreviation": "UFH", + "url": "https://example.dev", + "email": "email@example.dev", + "logo": "logo", + "contributing_centre_id": "1" + } + }, + { + "model": "general.Institution", + "pk": 19, + "fields": { + "name": "Rhodes University", + "abbreviation": "RU", + "url": "https://example.dev", + "email": "email@example.dev", + "logo": "logo", + "contributing_centre_id": "1" + } + }, + { + "model": "general.Institution", + "pk": 20, + "fields": { + "name": "University of Venda", + "abbreviation": "Venda", + "url": "https://example.dev", + "email": "email@example.dev", + "logo": "logo", + "contributing_centre_id": "1" + } + }, + { + "model": "general.Institution", + "pk": 21, + "fields": { + "name": "Mangosuthu University of Technology", + "abbreviation": "MUT", + "url": "https://example.dev", + "email": "email@example.dev", + "logo": "logo", + "contributing_centre_id": "1" + } + }, + { + "model": "general.Institution", + "pk": 22, + "fields": { + "name": "Sefako Makgatho Health Sciences University", + "abbreviation": "FMHSU", + "url": "https://example.dev", + "email": "email@example.dev", + "logo": "logo", + "contributing_centre_id": "1" + } + }, + { + "model": "general.Institution", + "pk": 23, + "fields": { + "name": "University of Mpumalanga", + "abbreviation": "UMP", + "url": "https://example.dev", + "email": "email@example.dev", + "logo": "logo", + "contributing_centre_id": "1" + } + }, + { + "model": "general.Institution", + "pk": 24, + "fields": { + "name": "Sol Plaatje University", + "abbreviation": "SPU", + "url": "https://example.dev", + "email": "email@example.dev", + "logo": "logo", + "contributing_centre_id": "1" + } + } +] diff --git a/app/fixtures/language.json b/app/fixtures/language.json new file mode 100644 index 00000000..57dc9139 --- /dev/null +++ b/app/fixtures/language.json @@ -0,0 +1,26 @@ +[ + { + "model": "general.Language", + "pk": 1, + "fields": { + "name": "English", + "iso_code": "en" + } + }, + { + "model": "general.Language", + "pk": 2, + "fields": { + "name": "Afrikaans", + "iso_code": "af" + } + }, + { + "model": "general.Language", + "pk": 3, + "fields": { + "name": "Zulu", + "iso_code": "zu" + } + } +] From 673f9b0e3730b7c4d4a90ae12e6f55f8316ff45e Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Tue, 27 Feb 2024 10:38:22 +0200 Subject: [PATCH 012/240] Add subjects and contributing centres fixtures This commit introduces JSON fixture files for 'Subject' and 'ContributingCentre' in the general app. --- Makefile | 2 ++ app/fixtures/contributing-centre.json | 20 ++++++++++++++++++++ app/fixtures/subjects.json | 16 ++++++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 app/fixtures/contributing-centre.json create mode 100644 app/fixtures/subjects.json diff --git a/Makefile b/Makefile index 39f01523..181487a4 100644 --- a/Makefile +++ b/Makefile @@ -88,3 +88,5 @@ load-fixtures: clear @docker-compose run --rm web python manage.py loaddata fixtures/institution.json @docker-compose run --rm web python manage.py loaddata fixtures/language.json + @docker-compose run --rm web python manage.py loaddata fixtures/subjects.json + @docker-compose run --rm web python manage.py loaddata fixtures/contributing-centre.json diff --git a/app/fixtures/contributing-centre.json b/app/fixtures/contributing-centre.json new file mode 100644 index 00000000..60807651 --- /dev/null +++ b/app/fixtures/contributing-centre.json @@ -0,0 +1,20 @@ +[ + { + "model": "general.ContributingCentre", + "pk": 1, + "fields": { + "name": "Study Centre", + "url": "https://example.dev", + "logo": "logo.png" + } + }, + { + "model": "general.ContributingCentre", + "pk": 2, + "fields": { + "name": "Research Centre", + "url": "https://example.dev", + "logo": "logo.png" + } + } +] diff --git a/app/fixtures/subjects.json b/app/fixtures/subjects.json new file mode 100644 index 00000000..d2c2af7b --- /dev/null +++ b/app/fixtures/subjects.json @@ -0,0 +1,16 @@ +[ + { + "model": "general.Subject", + "pk": 1, + "fields": { + "name": "Science" + } + }, + { + "model": "general.Subject", + "pk": 2, + "fields": { + "name": "Maths" + } + } +] From a941dc5304d8afd3f283544672743d0143709f50 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Tue, 27 Feb 2024 14:20:17 +0200 Subject: [PATCH 013/240] Add pre-commit hooks for linter and formatter A new .pre-commit-config.yaml file was added, specifying hooks for a linter and a formatter from the ruff-pre-commit repository. This will ensure automatic and consistent checks during development. --- .pre-commit-config.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..24895d5a --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,9 @@ +repos: +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.2.2 + hooks: + # Run the linter. + - id: ruff + # Run the formatter. + - id: ruff-format From 316ed2f1cabc13be44e27138d484322feec8e29e Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Wed, 28 Feb 2024 11:42:27 +0200 Subject: [PATCH 014/240] Updated Precomit linters In addition, a makefile target was created to clear the console and run pre-commit on all files. --- .pre-commit-config.yaml | 18 +++++++++++++++++- Makefile | 4 ++++ docker-compose.yml | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 24895d5a..3b30aa47 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,6 @@ repos: -- repo: https://github.com/astral-sh/ruff-pre-commit + # Linting and formatting for Python. + - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. rev: v0.2.2 hooks: @@ -7,3 +8,18 @@ repos: - id: ruff # Run the formatter. - id: ruff-format + + # Automatically sort python imports + - repo: https://github.com/PyCQA/isort + rev: 5.13.2 + hooks: + - id: isort + args: [ --profile, black ] + + # Lint: YAML + - repo: https://github.com/adrienverge/yamllint + rev: v1.35.1 + hooks: + - id: yamllint + args: ["-d {extends: relaxed, rules: {line-length: disable}}", "-s"] + files: \.(yaml|yml)$ diff --git a/Makefile b/Makefile index 181487a4..830b1074 100644 --- a/Makefile +++ b/Makefile @@ -90,3 +90,7 @@ load-fixtures: @docker-compose run --rm web python manage.py loaddata fixtures/language.json @docker-compose run --rm web python manage.py loaddata fixtures/subjects.json @docker-compose run --rm web python manage.py loaddata fixtures/contributing-centre.json + +pre-commit-all: + clear + pre-commit run --all-files diff --git a/docker-compose.yml b/docker-compose.yml index 15ba0eda..3d60cf86 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,4 +28,4 @@ services: - DJANGO_DB_NAME=term_db - DJANGO_DB_USER=sadilar - DJANGO_DB_PASSWORD=sadilar - - DATABASE_URL=postgresql://sadilar:sadilar@db/term_db \ No newline at end of file + - DATABASE_URL=postgresql://sadilar:sadilar@db/term_db From 8c87e73f6d26316a52a990fe643952647aaf3b06 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Fri, 8 Mar 2024 09:30:53 +0200 Subject: [PATCH 015/240] Add fixtures loading and extend language and subject data This update includes two major changes. First, the makefile was updated to include a new target "load-fixtures". --- Makefile | 1 + app/fixtures/language.json | 72 +++ app/fixtures/subjects.json | 1043 +++++++++++++++++++++++++++++++++++- 3 files changed, 1102 insertions(+), 14 deletions(-) diff --git a/Makefile b/Makefile index 181487a4..429d6cfb 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,7 @@ list: @echo "logs - Show logs" @echo "create-super-user - Create a superuser" @echo "docker-stop-all - Stop all running containers" + @echo "load-fixtures - Load fixtures" up: diff --git a/app/fixtures/language.json b/app/fixtures/language.json index 57dc9139..0e428ac7 100644 --- a/app/fixtures/language.json +++ b/app/fixtures/language.json @@ -22,5 +22,77 @@ "name": "Zulu", "iso_code": "zu" } + }, + { + "model": "general.Language", + "pk": 4, + "fields": { + "name": "Sepedi", + "iso_code": "nso" + } + }, + { + "model": "general.Language", + "pk": 5, + "fields": { + "name": "Tswana", + "iso_code": "tn" + } + }, + { + "model": "general.Language", + "pk": 6, + "fields": { + "name": "Sesotho", + "iso_code": "sot" + } + }, + { + "model": "general.Language", + "pk": 7, + "fields": { + "name": "Tsonga", + "iso_code": "ts" + } + }, + { + "model": "general.Language", + "pk": 8, + "fields": { + "name": "Swati", + "iso_code": "swz" + } + }, + { + "model": "general.Language", + "pk": 8, + "fields": { + "name": "Swati", + "iso_code": "swz" + } + }, + { + "model": "general.Language", + "pk": 9, + "fields": { + "name": "Venda", + "iso_code": "ve" + } + }, + { + "model": "general.Language", + "pk": 10, + "fields": { + "name": "Ndebele", + "iso_code": "nr" + } + }, + { + "model": "general.Language", + "pk": 11, + "fields": { + "name": "SA Sign Language", + "iso_code": "sfs" + } } ] diff --git a/app/fixtures/subjects.json b/app/fixtures/subjects.json index d2c2af7b..f6f1a22e 100644 --- a/app/fixtures/subjects.json +++ b/app/fixtures/subjects.json @@ -1,16 +1,1031 @@ [ - { - "model": "general.Subject", - "pk": 1, - "fields": { - "name": "Science" + { + "model": "general.Subject", + "pk": 1, + "fields": { + "name": "Accounting data processing" + } + }, + { + "model": "general.Subject", + "pk": 2, + "fields": { + "name": "African politics" + } + }, + { + "model": "general.Subject", + "pk": 3, + "fields": { + "name": "Agricultural business and management" + } + }, + { + "model": "general.Subject", + "pk": 4, + "fields": { + "name": "Agricultural management" + } + }, + { + "model": "general.Subject", + "pk": 5, + "fields": { + "name": "Agricultural science" + } + }, + { + "model": "general.Subject", + "pk": 6, + "fields": { + "name": "Agronomy" + } + }, + { + "model": "general.Subject", + "pk": 7, + "fields": { + "name": "Anaesthesiology" + } + }, + { + "model": "general.Subject", + "pk": 8, + "fields": { + "name": "Anatomy" + } + }, + { + "model": "general.Subject", + "pk": 9, + "fields": { + "name": "Ancient history" + } + }, + { + "model": "general.Subject", + "pk": 10, + "fields": { + "name": "Ancient near eastern studies" + } + }, + { + "model": "general.Subject", + "pk": 11, + "fields": { + "name": "Animal health" + } + }, + { + "model": "general.Subject", + "pk": 12, + "fields": { + "name": "Anthropology" + } + }, + { + "model": "general.Subject", + "pk": 13, + "fields": { + "name": "Applied information science" + } + }, + { + "model": "general.Subject", + "pk": 14, + "fields": { + "name": "Applied mathematics" + } + }, + { + "model": "general.Subject", + "pk": 15, + "fields": { + "name": "Archaeology" + } + }, + { + "model": "general.Subject", + "pk": 16, + "fields": { + "name": "Archives and records management" + } + }, + { + "model": "general.Subject", + "pk": 17, + "fields": { + "name": "Art history" + } + }, + { + "model": "general.Subject", + "pk": 18, + "fields": { + "name": "Astronomy" + } + }, + { + "model": "general.Subject", + "pk": 19, + "fields": { + "name": "Auditing" + } + }, + { + "model": "general.Subject", + "pk": 20, + "fields": { + "name": "Banking and finance" + } + }, + { + "model": "general.Subject", + "pk": 21, + "fields": { + "name": "Biblical archaeology" + } + }, + { + "model": "general.Subject", + "pk": 22, + "fields": { + "name": "Biochemistry" + } + }, + { + "model": "general.Subject", + "pk": 23, + "fields": { + "name": "Biology" + } + }, + { + "model": "general.Subject", + "pk": 24, + "fields": { + "name": "Biomedical science" + } + }, + { + "model": "general.Subject", + "pk": 25, + "fields": { + "name": "Biotechnology" + } + }, + { + "model": "general.Subject", + "pk": 26, + "fields": { + "name": "Botany" + } + }, + { + "model": "general.Subject", + "pk": 27, + "fields": { + "name": "Business management" + } + }, + { + "model": "general.Subject", + "pk": 28, + "fields": { + "name": "Chemical engineering" + } + }, + { + "model": "general.Subject", + "pk": 29, + "fields": { + "name": "Chemistry" + } + }, + { + "model": "general.Subject", + "pk": 30, + "fields": { + "name": "Christian leadership" + } + }, + { + "model": "general.Subject", + "pk": 31, + "fields": { + "name": "Christian spirituality" + } + }, + { + "model": "general.Subject", + "pk": 32, + "fields": { + "name": "Church history" + } + }, + { + "model": "general.Subject", + "pk": 33, + "fields": { + "name": "Civil engineering" + } + }, + { + "model": "general.Subject", + "pk": 34, + "fields": { + "name": "Classical culture" + } + }, + { + "model": "general.Subject", + "pk": 35, + "fields": { + "name": "Classical studies" + } + }, + { + "model": "general.Subject", + "pk": 36, + "fields": { + "name": "Communication science" + } + }, + { + "model": "general.Subject", + "pk": 37, + "fields": { + "name": "Communication studies" + } + }, + { + "model": "general.Subject", + "pk": 38, + "fields": { + "name": "Community health" + } + }, + { + "model": "general.Subject", + "pk": 39, + "fields": { + "name": "Composition studies" + } + }, + { + "model": "general.Subject", + "pk": 40, + "fields": { + "name": "Computer science" + } + }, + { + "model": "general.Subject", + "pk": 41, + "fields": { + "name": "Consumer science" + } + }, + { + "model": "general.Subject", + "pk": 42, + "fields": { + "name": "Corrections management" + } + }, + { + "model": "general.Subject", + "pk": 43, + "fields": { + "name": "Credit management" + } + }, + { + "model": "general.Subject", + "pk": 44, + "fields": { + "name": "Criminology" + } + }, + { + "model": "general.Subject", + "pk": 45, + "fields": { + "name": "Curriculum and instructional studies" + } + }, + { + "model": "general.Subject", + "pk": 46, + "fields": { + "name": "Decoloniality" + } + }, + { + "model": "general.Subject", + "pk": 47, + "fields": { + "name": "Development studies" + } + }, + { + "model": "general.Subject", + "pk": 48, + "fields": { + "name": "Early childhood education" + } + }, + { + "model": "general.Subject", + "pk": 49, + "fields": { + "name": "Economics" + } + }, + { + "model": "general.Subject", + "pk": 50, + "fields": { + "name": "Educational foundations" + } + }, + { + "model": "general.Subject", + "pk": 51, + "fields": { + "name": "Educational leadership and managment" + } + }, + { + "model": "general.Subject", + "pk": 52, + "fields": { + "name": "Electrical engineering" + } + }, + { + "model": "general.Subject", + "pk": 53, + "fields": { + "name": "Entrepreneurship" + } + }, + { + "model": "general.Subject", + "pk": 54, + "fields": { + "name": "Environmental sciences" + } + }, + { + "model": "general.Subject", + "pk": 55, + "fields": { + "name": "Epidemiology" + } + }, + { + "model": "general.Subject", + "pk": 56, + "fields": { + "name": "Financial accounting" + } + }, + { + "model": "general.Subject", + "pk": 57, + "fields": { + "name": "Financial governance" + } + }, + { + "model": "general.Subject", + "pk": 58, + "fields": { + "name": "Financial intelligence" + } + }, + { + "model": "general.Subject", + "pk": 59, + "fields": { + "name": "Food service management" + } + }, + { + "model": "general.Subject", + "pk": 60, + "fields": { + "name": "Forensic auditing" + } + }, + { + "model": "general.Subject", + "pk": 61, + "fields": { + "name": "Forensic investigation" + } + }, + { + "model": "general.Subject", + "pk": 62, + "fields": { + "name": "Gender studies" + } + }, + { + "model": "general.Subject", + "pk": 63, + "fields": { + "name": "Genetics" + } + }, + { + "model": "general.Subject", + "pk": 64, + "fields": { + "name": "Geography" + } + }, + { + "model": "general.Subject", + "pk": 65, + "fields": { + "name": "Health sciences education" + } + }, + { + "model": "general.Subject", + "pk": 66, + "fields": { + "name": "Health services management" + } + }, + { + "model": "general.Subject", + "pk": 67, + "fields": { + "name": "Health studies" + } + }, + { + "model": "general.Subject", + "pk": 68, + "fields": { + "name": "History" + } + }, + { + "model": "general.Subject", + "pk": 69, + "fields": { + "name": "Horticulture" + } + }, + { + "model": "general.Subject", + "pk": 70, + "fields": { + "name": "Hospitality management" + } + }, + { + "model": "general.Subject", + "pk": 71, + "fields": { + "name": "Human resource management" + } + }, + { + "model": "general.Subject", + "pk": 72, + "fields": { + "name": "Inclusive education" + } + }, + { + "model": "general.Subject", + "pk": 73, + "fields": { + "name": "Industrial and organisational psychology" + } + }, + { + "model": "general.Subject", + "pk": 74, + "fields": { + "name": "Information resource management" + } + }, + { + "model": "general.Subject", + "pk": 75, + "fields": { + "name": "Information science" + } + }, + { + "model": "general.Subject", + "pk": 76, + "fields": { + "name": "Information systems" + } + }, + { + "model": "general.Subject", + "pk": 77, + "fields": { + "name": "Information technology" + } + }, + { + "model": "general.Subject", + "pk": 78, + "fields": { + "name": "Insurance" + } + }, + { + "model": "general.Subject", + "pk": 79, + "fields": { + "name": "Internal auditing" + } + }, + { + "model": "general.Subject", + "pk": 80, + "fields": { + "name": "International politics" + } + }, + { + "model": "general.Subject", + "pk": 81, + "fields": { + "name": "Investment" + } + }, + { + "model": "general.Subject", + "pk": 82, + "fields": { + "name": "Islamic studies" + } + }, + { + "model": "general.Subject", + "pk": 83, + "fields": { + "name": "Language education" + } + }, + { + "model": "general.Subject", + "pk": 84, + "fields": { + "name": "Law" + } + }, + { + "model": "general.Subject", + "pk": 85, + "fields": { + "name": "Life and consumer sciences" + } + }, + { + "model": "general.Subject", + "pk": 86, + "fields": { + "name": "Linguistics" + } + }, + { + "model": "general.Subject", + "pk": 87, + "fields": { + "name": "Management accounting" + } + }, + { + "model": "general.Subject", + "pk": 88, + "fields": { + "name": "Marketing and retail management" + } + }, + { + "model": "general.Subject", + "pk": 89, + "fields": { + "name": "Mathematics" + } + }, + { + "model": "general.Subject", + "pk": 90, + "fields": { + "name": "Mathematics education" + } + }, + { + "model": "general.Subject", + "pk": 91, + "fields": { + "name": "Mechanical and industrial engineering" + } + }, + { + "model": "general.Subject", + "pk": 92, + "fields": { + "name": "Microbiology" + } + }, + { + "model": "general.Subject", + "pk": 93, + "fields": { + "name": "Midwifery" + } + }, + { + "model": "general.Subject", + "pk": 94, + "fields": { + "name": "Mine engineering" + } + }, + { + "model": "general.Subject", + "pk": 95, + "fields": { + "name": "Ministry" + } + }, + { + "model": "general.Subject", + "pk": 96, + "fields": { + "name": "Missiology" + } + }, + { + "model": "general.Subject", + "pk": 97, + "fields": { + "name": "Music in history and society" + } + }, + { + "model": "general.Subject", + "pk": 98, + "fields": { + "name": "Nature conservation" + } + }, + { + "model": "general.Subject", + "pk": 99, + "fields": { + "name": "Neurology" + } + }, + { + "model": "general.Subject", + "pk": 100, + "fields": { + "name": "New Testament" + } + }, + { + "model": "general.Subject", + "pk": 101, + "fields": { + "name": "Nursing" + } + }, + { + "model": "general.Subject", + "pk": 102, + "fields": { + "name": "Nutrition" + } + }, + { + "model": "general.Subject", + "pk": 103, + "fields": { + "name": "Obstetrics" + } + }, + { + "model": "general.Subject", + "pk": 104, + "fields": { + "name": "Occupational therapy" + } + }, + { + "model": "general.Subject", + "pk": 105, + "fields": { + "name": "Oenology" + } + }, + { + "model": "general.Subject", + "pk": 106, + "fields": { + "name": "Old Testament" + } + }, + { + "model": "general.Subject", + "pk": 107, + "fields": { + "name": "Operations management" + } + }, + { + "model": "general.Subject", + "pk": 108, + "fields": { + "name": "Orthodontics" + } + }, + { + "model": "general.Subject", + "pk": 109, + "fields": { + "name": "Paediatrics" + } + }, + { + "model": "general.Subject", + "pk": 110, + "fields": { + "name": "Pathology" + } + }, + { + "model": "general.Subject", + "pk": 111, + "fields": { + "name": "Pharmacology" + } + }, + { + "model": "general.Subject", + "pk": 112, + "fields": { + "name": "Philosophy" + } + }, + { + "model": "general.Subject", + "pk": 113, + "fields": { + "name": "Physics" + } + }, + { + "model": "general.Subject", + "pk": 114, + "fields": { + "name": "Physiology" + } + }, + { + "model": "general.Subject", + "pk": 115, + "fields": { + "name": "Policing" + } + }, + { + "model": "general.Subject", + "pk": 116, + "fields": { + "name": "Politics" + } + }, + { + "model": "general.Subject", + "pk": 117, + "fields": { + "name": "Practical theology" + } + }, + { + "model": "general.Subject", + "pk": 118, + "fields": { + "name": "Private law" + } + }, + { + "model": "general.Subject", + "pk": 119, + "fields": { + "name": "Psychology" + } + }, + { + "model": "general.Subject", + "pk": 120, + "fields": { + "name": "Psychiatry" + } + }, + { + "model": "general.Subject", + "pk": 121, + "fields": { + "name": "Public administration" + } + }, + { + "model": "general.Subject", + "pk": 122, + "fields": { + "name": "Public health" + } + }, + { + "model": "general.Subject", + "pk": 123, + "fields": { + "name": "Public relations" + } + }, + { + "model": "general.Subject", + "pk": 124, + "fields": { + "name": "Radiology" + } + }, + { + "model": "general.Subject", + "pk": 125, + "fields": { + "name": "Religious studies" + } + }, + { + "model": "general.Subject", + "pk": 126, + "fields": { + "name": "Risk management" + } + }, + { + "model": "general.Subject", + "pk": 127, + "fields": { + "name": "Safety management" + } + }, + { + "model": "general.Subject", + "pk": 128, + "fields": { + "name": "Science and technology education" + } + }, + { + "model": "general.Subject", + "pk": 129, + "fields": { + "name": "Security management" + } + }, + { + "model": "general.Subject", + "pk": 130, + "fields": { + "name": "Social auxiliary work" + } + }, + { + "model": "general.Subject", + "pk": 131, + "fields": { + "name": "Social sciences" + } + }, + { + "model": "general.Subject", + "pk": 132, + "fields": { + "name": "Social work" + } + }, + { + "model": "general.Subject", + "pk": 133, + "fields": { + "name": "Sociology" + } + }, + { + "model": "general.Subject", + "pk": 134, + "fields": { + "name": "Statistics" + } + }, + { + "model": "general.Subject", + "pk": 135, + "fields": { + "name": "Supply chain management" + } + }, + { + "model": "general.Subject", + "pk": 136, + "fields": { + "name": "Systematic theology" + } + }, + { + "model": "general.Subject", + "pk": 137, + "fields": { + "name": "Taxation" + } + }, + { + "model": "general.Subject", + "pk": 138, + "fields": { + "name": "Theological ethics" + } + }, + { + "model": "general.Subject", + "pk": 139, + "fields": { + "name": "Theology" + } + }, + { + "model": "general.Subject", + "pk": 140, + "fields": { + "name": "Theory of literature" + } + }, + { + "model": "general.Subject", + "pk": 141, + "fields": { + "name": "Tourism management" + } + }, + { + "model": "general.Subject", + "pk": 142, + "fields": { + "name": "Transport economics and logistics" + } + }, + { + "model": "general.Subject", + "pk": 143, + "fields": { + "name": "Urology" + } + }, + { + "model": "general.Subject", + "pk": 144, + "fields": { + "name": "Veterinary publich health" + } + }, + { + "model": "general.Subject", + "pk": 145, + "fields": { + "name": "Viticulture" + } + }, + { + "model": "general.Subject", + "pk": 146, + "fields": { + "name": "Visual arts" + } + }, + { + "model": "general.Subject", + "pk": 147, + "fields": { + "name": "Zoology" + } } - }, - { - "model": "general.Subject", - "pk": 2, - "fields": { - "name": "Maths" - } - } -] +] \ No newline at end of file From 3c784246481f5eb1c49b2a64406778bf97764392 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Fri, 8 Mar 2024 11:18:09 +0200 Subject: [PATCH 016/240] Update Makefile and adjust language data This commit adds a new command to the Makefile to facilitate loading fixtures. --- Makefile | 2 +- app/fixtures/language.json | 56 +++++++++++++++++++------------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/Makefile b/Makefile index 429d6cfb..1cdad3dc 100644 --- a/Makefile +++ b/Makefile @@ -87,7 +87,7 @@ ruff-fix: load-fixtures: clear + @docker-compose run --rm web python manage.py loaddata fixtures/contributing-centre.json @docker-compose run --rm web python manage.py loaddata fixtures/institution.json @docker-compose run --rm web python manage.py loaddata fixtures/language.json @docker-compose run --rm web python manage.py loaddata fixtures/subjects.json - @docker-compose run --rm web python manage.py loaddata fixtures/contributing-centre.json diff --git a/app/fixtures/language.json b/app/fixtures/language.json index 0e428ac7..f4839cf6 100644 --- a/app/fixtures/language.json +++ b/app/fixtures/language.json @@ -3,31 +3,31 @@ "model": "general.Language", "pk": 1, "fields": { - "name": "English", - "iso_code": "en" + "name": "Afrikaans", + "iso_code": "af" } }, { "model": "general.Language", "pk": 2, "fields": { - "name": "Afrikaans", - "iso_code": "af" + "name": "English", + "iso_code": "en" } }, { "model": "general.Language", "pk": 3, "fields": { - "name": "Zulu", - "iso_code": "zu" + "name": "Ndebele", + "iso_code": "nr" } }, - { + { "model": "general.Language", "pk": 4, "fields": { - "name": "Sepedi", + "name": "Pedi", "iso_code": "nso" } }, @@ -35,45 +35,45 @@ "model": "general.Language", "pk": 5, "fields": { - "name": "Tswana", - "iso_code": "tn" + "name": "Sotho", + "iso_code": "sot" } }, - { + { "model": "general.Language", "pk": 6, "fields": { - "name": "Sesotho", - "iso_code": "sot" + "name": "South African Sign Language", + "iso_code": "sfs" } }, - { + { "model": "general.Language", "pk": 7, "fields": { - "name": "Tsonga", - "iso_code": "ts" + "name": "Swati", + "iso_code": "swz" } }, { "model": "general.Language", "pk": 8, "fields": { - "name": "Swati", - "iso_code": "swz" + "name": "Tsonga", + "iso_code": "ts" } }, { "model": "general.Language", - "pk": 8, + "pk": 9, "fields": { - "name": "Swati", - "iso_code": "swz" + "name": "Tswana", + "iso_code": "tn" } }, { "model": "general.Language", - "pk": 9, + "pk": 10, "fields": { "name": "Venda", "iso_code": "ve" @@ -81,18 +81,18 @@ }, { "model": "general.Language", - "pk": 10, + "pk": 11, "fields": { - "name": "Ndebele", - "iso_code": "nr" + "name": "Xhosa", + "iso_code": "xh" } }, { "model": "general.Language", - "pk": 11, + "pk": 12, "fields": { - "name": "SA Sign Language", - "iso_code": "sfs" + "name": "Zulu", + "iso_code": "zu" } } ] From d799ca7a690702865c62aa501ef4aa92ee558d9a Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Fri, 8 Mar 2024 12:05:41 +0200 Subject: [PATCH 017/240] Update URLs for institutions in fixture file This commit involves updating the placeholder URLs for each of the institutions in 'institution.json' --- app/fixtures/institution.json | 50 +++++++++++++++++------------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/app/fixtures/institution.json b/app/fixtures/institution.json index 24baaa7f..bd32a2c6 100644 --- a/app/fixtures/institution.json +++ b/app/fixtures/institution.json @@ -5,7 +5,7 @@ "fields": { "name": "University of South Africa (UNISA)", "abbreviation": "UNISA", - "url": "https://example.dev", + "url": "https://www.unisa.ac.za/", "email": "email@example.dev", "logo": "logo", "contributing_centre_id": "1" @@ -17,7 +17,7 @@ "fields": { "name": "North-West University", "abbreviation": "NWU", - "url": "https://example.dev", + "url": "https://www.nwu.ac.za/", "email": "email@example.dev", "logo": "logo", "contributing_centre_id": "1" @@ -29,7 +29,7 @@ "fields": { "name": "University of Pretoria", "abbreviation": "UP", - "url": "https://example.dev", + "url": "https://www.up.ac.za/", "email": "email@example.dev", "logo": "logo", "contributing_centre_id": "1" @@ -41,7 +41,7 @@ "fields": { "name": "University of Johannesburg", "abbreviation": "UJ", - "url": "https://example.dev", + "url": "https://www.uj.ac.za/", "email": "email@example.dev", "logo": "logo", "contributing_centre_id": "1" @@ -53,7 +53,7 @@ "fields": { "name": "University of KwaZulu-Natal", "abbreviation": "UKZN", - "url": "https://example.dev", + "url": "https://www.ukzn.ac.za/", "email": "email@example.dev", "logo": "logo", "contributing_centre_id": "1" @@ -65,7 +65,7 @@ "fields": { "name": "University of the Free State", "abbreviation": "UFS", - "url": "https://example.dev", + "url": "https://www.ufs.ac.za/", "email": "email@example.dev", "logo": "logo", "contributing_centre_id": "1" @@ -77,7 +77,7 @@ "fields": { "name": "Cape Peninsula University of Technology", "abbreviation": "CPUT", - "url": "https://example.dev", + "url": "https://www.cput.ac.za/", "email": "email@example.dev", "logo": "logo", "contributing_centre_id": "1" @@ -89,7 +89,7 @@ "fields": { "name": "University of the Witwatersrand", "abbreviation": "WITS", - "url": "https://example.dev", + "url": "https://www.wits.ac.za/", "email": "email@example.dev", "logo": "logo", "contributing_centre_id": "1" @@ -101,7 +101,7 @@ "fields": { "name": "University of Stellenbosch", "abbreviation": "US", - "url": "https://example.dev", + "url": "http://www.sun.ac.za/", "email": "email@example.dev", "logo": "logo", "contributing_centre_id": "1" @@ -113,7 +113,7 @@ "fields": { "name": "University of Cape Town", "abbreviation": "UCT", - "url": "https://example.dev", + "url": "https://uct.ac.za/", "email": "email@example.dev", "logo": "logo", "contributing_centre_id": "1" @@ -125,7 +125,7 @@ "fields": { "name": "Nelson Mandela Metropolitan University", "abbreviation": "NMU", - "url": "https://example.dev", + "url": "https://www.mandela.ac.za/", "email": "email@example.dev", "logo": "logo", "contributing_centre_id": "1" @@ -137,7 +137,7 @@ "fields": { "name": "Walter Sisulu University", "abbreviation": "WSU", - "url": "https://example.dev", + "url": "https://www.wsu.ac.za/", "email": "email@example.dev", "logo": "logo", "contributing_centre_id": "1" @@ -149,7 +149,7 @@ "fields": { "name": "Durban University of Technology", "abbreviation": "DUT", - "url": "https://example.dev", + "url": "https://www.dut.ac.za/", "email": "email@example.dev", "logo": "logo", "contributing_centre_id": "1" @@ -161,7 +161,7 @@ "fields": { "name": "University of Limpopo", "abbreviation": "UL", - "url": "https://example.dev", + "url": "https://www.ul.ac.za/", "email": "email@example.dev", "logo": "logo", "contributing_centre_id": "1" @@ -173,7 +173,7 @@ "fields": { "name": "Vaal University of Technology", "abbreviation": "VUT", - "url": "https://example.dev", + "url": "https://www.vut.ac.za/", "email": "email@example.dev", "logo": "logo", "contributing_centre_id": "1" @@ -185,7 +185,7 @@ "fields": { "name": "University of Zululand", "abbreviation": "UniZulu", - "url": "https://example.dev", + "url": "https://www.unizulu.ac.za/", "email": "email@example.dev", "logo": "logo", "contributing_centre_id": "1" @@ -197,7 +197,7 @@ "fields": { "name": "University of the Western Cape", "abbreviation": "UWC", - "url": "https://example.dev", + "url": "https://www.uwc.ac.za/", "email": "email@example.dev", "logo": "logo", "contributing_centre_id": "1" @@ -209,7 +209,7 @@ "fields": { "name": "Central University of Technology", "abbreviation": "CUT", - "url": "https://example.dev", + "url": "https://www.cut.ac.za/", "email": "email@example.dev", "logo": "logo", "contributing_centre_id": "1" @@ -221,7 +221,7 @@ "fields": { "name": "University of Fort Hare", "abbreviation": "UFH", - "url": "https://example.dev", + "url": "https://www.ufh.ac.za/", "email": "email@example.dev", "logo": "logo", "contributing_centre_id": "1" @@ -233,7 +233,7 @@ "fields": { "name": "Rhodes University", "abbreviation": "RU", - "url": "https://example.dev", + "url": "https://www.ru.ac.za/", "email": "email@example.dev", "logo": "logo", "contributing_centre_id": "1" @@ -245,7 +245,7 @@ "fields": { "name": "University of Venda", "abbreviation": "Venda", - "url": "https://example.dev", + "url": "https://www.univen.ac.za/", "email": "email@example.dev", "logo": "logo", "contributing_centre_id": "1" @@ -257,7 +257,7 @@ "fields": { "name": "Mangosuthu University of Technology", "abbreviation": "MUT", - "url": "https://example.dev", + "url": "https://www.mut.ac.za/", "email": "email@example.dev", "logo": "logo", "contributing_centre_id": "1" @@ -269,7 +269,7 @@ "fields": { "name": "Sefako Makgatho Health Sciences University", "abbreviation": "FMHSU", - "url": "https://example.dev", + "url": "https://www.smu.ac.za/", "email": "email@example.dev", "logo": "logo", "contributing_centre_id": "1" @@ -281,7 +281,7 @@ "fields": { "name": "University of Mpumalanga", "abbreviation": "UMP", - "url": "https://example.dev", + "url": "https://www.ump.ac.za/", "email": "email@example.dev", "logo": "logo", "contributing_centre_id": "1" @@ -293,7 +293,7 @@ "fields": { "name": "Sol Plaatje University", "abbreviation": "SPU", - "url": "https://example.dev", + "url": "https://www.spu.ac.za/", "email": "email@example.dev", "logo": "logo", "contributing_centre_id": "1" From 59bc4b95f7c77868c93b848e3680455addf9d5cb Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Fri, 15 Mar 2024 09:35:21 +0200 Subject: [PATCH 018/240] Rename 'ContributingCentre' to 'Project' and update references The 'ContributingCentre' model has been renamed to 'Project', and all references in the code, files, and tests have been updated accordingly. --- Makefile | 2 +- app/app/settings.py | 1 + app/app/urls.py | 1 + app/fixtures/contributing-centre.json | 20 ---- app/fixtures/institution.json | 89 +++++++----------- app/fixtures/projects.json | 26 +++++ app/general/admin.py | 19 ++-- app/general/migrations/0001_initial.py | 25 ++--- app/general/models.py | 11 +-- .../tests/tests_contributing_centre.py | 51 ---------- app/general/tests/tests_institution.py | 6 +- app/general/tests/tests_language.py | 4 +- app/general/tests/tests_projects.py | 59 ++++++++++++ app/general/tests/tests_subject.py | 4 +- app/logos/photo_2024-03-14_07.57.40.jpeg | Bin 0 -> 43262 bytes app/manage.py | 1 + app/schema/schema.png | Bin 109589 -> 176928 bytes app/users/migrations/0001_initial.py | 2 +- 18 files changed, 156 insertions(+), 165 deletions(-) delete mode 100644 app/fixtures/contributing-centre.json create mode 100644 app/fixtures/projects.json delete mode 100644 app/general/tests/tests_contributing_centre.py create mode 100644 app/general/tests/tests_projects.py create mode 100644 app/logos/photo_2024-03-14_07.57.40.jpeg diff --git a/Makefile b/Makefile index ec3ef0ca..e1ee8622 100644 --- a/Makefile +++ b/Makefile @@ -87,7 +87,7 @@ ruff-fix: load-fixtures: clear - @docker-compose run --rm web python manage.py loaddata fixtures/contributing-centre.json + @docker-compose run --rm web python manage.py loaddata fixtures/projects.json @docker-compose run --rm web python manage.py loaddata fixtures/institution.json @docker-compose run --rm web python manage.py loaddata fixtures/language.json @docker-compose run --rm web python manage.py loaddata fixtures/subjects.json diff --git a/app/app/settings.py b/app/app/settings.py index ae0abd60..7ddaf0ee 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -9,6 +9,7 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/5.0/ref/settings/ """ + import os from pathlib import Path diff --git a/app/app/urls.py b/app/app/urls.py index b065827a..b4dd8616 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -14,6 +14,7 @@ 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ + from django.contrib import admin from django.urls import path diff --git a/app/fixtures/contributing-centre.json b/app/fixtures/contributing-centre.json deleted file mode 100644 index 60807651..00000000 --- a/app/fixtures/contributing-centre.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "model": "general.ContributingCentre", - "pk": 1, - "fields": { - "name": "Study Centre", - "url": "https://example.dev", - "logo": "logo.png" - } - }, - { - "model": "general.ContributingCentre", - "pk": 2, - "fields": { - "name": "Research Centre", - "url": "https://example.dev", - "logo": "logo.png" - } - } -] diff --git a/app/fixtures/institution.json b/app/fixtures/institution.json index bd32a2c6..1e18b243 100644 --- a/app/fixtures/institution.json +++ b/app/fixtures/institution.json @@ -7,8 +7,7 @@ "abbreviation": "UNISA", "url": "https://www.unisa.ac.za/", "email": "email@example.dev", - "logo": "logo", - "contributing_centre_id": "1" + "logo": "logo" } }, { @@ -19,8 +18,7 @@ "abbreviation": "NWU", "url": "https://www.nwu.ac.za/", "email": "email@example.dev", - "logo": "logo", - "contributing_centre_id": "1" + "logo": "logo" } }, { @@ -31,8 +29,7 @@ "abbreviation": "UP", "url": "https://www.up.ac.za/", "email": "email@example.dev", - "logo": "logo", - "contributing_centre_id": "1" + "logo": "logo" } }, { @@ -43,8 +40,7 @@ "abbreviation": "UJ", "url": "https://www.uj.ac.za/", "email": "email@example.dev", - "logo": "logo", - "contributing_centre_id": "1" + "logo": "logo" } }, { @@ -55,8 +51,7 @@ "abbreviation": "UKZN", "url": "https://www.ukzn.ac.za/", "email": "email@example.dev", - "logo": "logo", - "contributing_centre_id": "1" + "logo": "logo" } }, { @@ -67,8 +62,7 @@ "abbreviation": "UFS", "url": "https://www.ufs.ac.za/", "email": "email@example.dev", - "logo": "logo", - "contributing_centre_id": "1" + "logo": "logo" } }, { @@ -79,8 +73,7 @@ "abbreviation": "CPUT", "url": "https://www.cput.ac.za/", "email": "email@example.dev", - "logo": "logo", - "contributing_centre_id": "1" + "logo": "logo" } }, { @@ -91,8 +84,7 @@ "abbreviation": "WITS", "url": "https://www.wits.ac.za/", "email": "email@example.dev", - "logo": "logo", - "contributing_centre_id": "1" + "logo": "logo" } }, { @@ -103,8 +95,7 @@ "abbreviation": "US", "url": "http://www.sun.ac.za/", "email": "email@example.dev", - "logo": "logo", - "contributing_centre_id": "1" + "logo": "logo" } }, { @@ -115,8 +106,7 @@ "abbreviation": "UCT", "url": "https://uct.ac.za/", "email": "email@example.dev", - "logo": "logo", - "contributing_centre_id": "1" + "logo": "logo" } }, { @@ -127,8 +117,7 @@ "abbreviation": "NMU", "url": "https://www.mandela.ac.za/", "email": "email@example.dev", - "logo": "logo", - "contributing_centre_id": "1" + "logo": "logo" } }, { @@ -139,8 +128,7 @@ "abbreviation": "WSU", "url": "https://www.wsu.ac.za/", "email": "email@example.dev", - "logo": "logo", - "contributing_centre_id": "1" + "logo": "logo" } }, { @@ -151,8 +139,7 @@ "abbreviation": "DUT", "url": "https://www.dut.ac.za/", "email": "email@example.dev", - "logo": "logo", - "contributing_centre_id": "1" + "logo": "logo" } }, { @@ -163,11 +150,10 @@ "abbreviation": "UL", "url": "https://www.ul.ac.za/", "email": "email@example.dev", - "logo": "logo", - "contributing_centre_id": "1" + "logo": "logo" } }, - { + { "model": "general.Institution", "pk": 14, "fields": { @@ -175,11 +161,10 @@ "abbreviation": "VUT", "url": "https://www.vut.ac.za/", "email": "email@example.dev", - "logo": "logo", - "contributing_centre_id": "1" + "logo": "logo" } }, - { + { "model": "general.Institution", "pk": 15, "fields": { @@ -187,11 +172,10 @@ "abbreviation": "UniZulu", "url": "https://www.unizulu.ac.za/", "email": "email@example.dev", - "logo": "logo", - "contributing_centre_id": "1" + "logo": "logo" } }, - { + { "model": "general.Institution", "pk": 16, "fields": { @@ -199,8 +183,7 @@ "abbreviation": "UWC", "url": "https://www.uwc.ac.za/", "email": "email@example.dev", - "logo": "logo", - "contributing_centre_id": "1" + "logo": "logo" } }, { @@ -211,11 +194,10 @@ "abbreviation": "CUT", "url": "https://www.cut.ac.za/", "email": "email@example.dev", - "logo": "logo", - "contributing_centre_id": "1" + "logo": "logo" } }, - { + { "model": "general.Institution", "pk": 18, "fields": { @@ -223,8 +205,7 @@ "abbreviation": "UFH", "url": "https://www.ufh.ac.za/", "email": "email@example.dev", - "logo": "logo", - "contributing_centre_id": "1" + "logo": "logo" } }, { @@ -235,8 +216,7 @@ "abbreviation": "RU", "url": "https://www.ru.ac.za/", "email": "email@example.dev", - "logo": "logo", - "contributing_centre_id": "1" + "logo": "logo" } }, { @@ -247,11 +227,10 @@ "abbreviation": "Venda", "url": "https://www.univen.ac.za/", "email": "email@example.dev", - "logo": "logo", - "contributing_centre_id": "1" + "logo": "logo" } }, - { + { "model": "general.Institution", "pk": 21, "fields": { @@ -259,8 +238,7 @@ "abbreviation": "MUT", "url": "https://www.mut.ac.za/", "email": "email@example.dev", - "logo": "logo", - "contributing_centre_id": "1" + "logo": "logo" } }, { @@ -271,11 +249,10 @@ "abbreviation": "FMHSU", "url": "https://www.smu.ac.za/", "email": "email@example.dev", - "logo": "logo", - "contributing_centre_id": "1" + "logo": "logo" } }, - { + { "model": "general.Institution", "pk": 23, "fields": { @@ -283,11 +260,10 @@ "abbreviation": "UMP", "url": "https://www.ump.ac.za/", "email": "email@example.dev", - "logo": "logo", - "contributing_centre_id": "1" + "logo": "logo" } }, - { + { "model": "general.Institution", "pk": 24, "fields": { @@ -295,8 +271,7 @@ "abbreviation": "SPU", "url": "https://www.spu.ac.za/", "email": "email@example.dev", - "logo": "logo", - "contributing_centre_id": "1" + "logo": "logo" } } ] diff --git a/app/fixtures/projects.json b/app/fixtures/projects.json new file mode 100644 index 00000000..a17afef2 --- /dev/null +++ b/app/fixtures/projects.json @@ -0,0 +1,26 @@ +[ + { + "model": "general.Project", + "pk": 1, + "fields": { + "name": "Study Centre", + "url": "https://example.dev", + "logo": "logo.png", + "start_date": "2020-01-01", + "end_date": "2020-01-01", + "institution": 1 + } + }, + { + "model": "general.Project", + "pk": 2, + "fields": { + "name": "Research Centre", + "url": "https://example.dev", + "logo": "logo.png", + "start_date": "2020-01-01", + "end_date": "2020-01-01", + "institution": 1 + } + } +] diff --git a/app/general/admin.py b/app/general/admin.py index 433d8b0a..bea29513 100644 --- a/app/general/admin.py +++ b/app/general/admin.py @@ -1,17 +1,18 @@ from django.contrib import admin -from .models import ContributingCentre, Institution, Language, Subject +from .models import Institution, Language, Project, Subject +# class SubjectInline(admin.TabularInline): +# # model = Subject.projects.through +# pass +# +# +# class ProjectsAdmin(admin.ModelAdmin): +# inlines = [SubjectInline] -class SubjectInline(admin.TabularInline): - model = Subject.contributing_centre.through - -class ContributingCentreAdmin(admin.ModelAdmin): - inlines = [SubjectInline] - - -admin.site.register(ContributingCentre, ContributingCentreAdmin) +# admin.site.register(Projects, ProjectsAdmin) +admin.site.register(Project) admin.site.register(Institution) admin.site.register(Language) admin.site.register(Subject) diff --git a/app/general/migrations/0001_initial.py b/app/general/migrations/0001_initial.py index 93967b8b..3c82b724 100644 --- a/app/general/migrations/0001_initial.py +++ b/app/general/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.2 on 2024-02-23 11:05 +# Generated by Django 5.0.2 on 2024-03-14 10:10 import django.db.models.deletion from django.db import migrations, models @@ -13,11 +13,13 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='ContributingCentre', + name='Institution', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=200, unique=True)), + ('abbreviation', models.CharField(max_length=200)), ('url', models.URLField()), + ('email', models.EmailField(max_length=200)), ('logo', models.FileField(blank=True, upload_to='logos/')), ], ), @@ -30,23 +32,22 @@ class Migration(migrations.Migration): ], ), migrations.CreateModel( - name='Institution', + name='Subject', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200, unique=True)), - ('abbreviation', models.CharField(max_length=200)), - ('url', models.URLField()), - ('email', models.EmailField(max_length=200)), - ('logo', models.FileField(blank=True, upload_to='logos/')), - ('contributing_centre', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, to='general.contributingcentre')), + ('name', models.CharField(max_length=100, unique=True)), ], ), migrations.CreateModel( - name='Subject', + name='Project', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100, unique=True)), - ('contributing_centre', models.ManyToManyField(blank=True, related_name='subjects', to='general.contributingcentre')), + ('name', models.CharField(max_length=200, unique=True)), + ('url', models.URLField()), + ('logo', models.FileField(blank=True, upload_to='logos/')), + ('start_date', models.DateField(blank=True)), + ('end_date', models.DateField(blank=True)), + ('institution', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, to='general.institution')), ], ), ] diff --git a/app/general/models.py b/app/general/models.py index 83c683da..1e7640c6 100644 --- a/app/general/models.py +++ b/app/general/models.py @@ -1,10 +1,13 @@ from django.db import models -class ContributingCentre(models.Model): +class Project(models.Model): name = models.CharField(max_length=200, unique=True) url = models.URLField(max_length=200) logo = models.FileField(upload_to="logos/", blank=True) + start_date = models.DateField(blank=True) + end_date = models.DateField(blank=True) + institution = models.ForeignKey("Institution", on_delete=models.CASCADE, blank=True) def __str__(self): return self.name @@ -16,9 +19,6 @@ class Institution(models.Model): url = models.URLField(max_length=200) email = models.EmailField(max_length=200) logo = models.FileField(upload_to="logos/", blank=True) - contributing_centre = models.ForeignKey( - ContributingCentre, on_delete=models.CASCADE, blank=True - ) def __str__(self): return self.name @@ -36,9 +36,6 @@ def __str__(self): class Subject(models.Model): name = models.CharField(max_length=100, unique=True) - contributing_centre = models.ManyToManyField( - "ContributingCentre", related_name="subjects", blank=True - ) def __str__(self): return self.name diff --git a/app/general/tests/tests_contributing_centre.py b/app/general/tests/tests_contributing_centre.py deleted file mode 100644 index a50cd4dd..00000000 --- a/app/general/tests/tests_contributing_centre.py +++ /dev/null @@ -1,51 +0,0 @@ -import unittest - -from django.db.utils import IntegrityError -from django.test import TestCase - -from general.models import ContributingCentre, Subject - -# from subject.models import Subject - - -class TestContributingCentre(TestCase): - def setUp(self): - self.subject1 = Subject.objects.create(name="Maths") - self.subject2 = Subject.objects.create(name="Science") - - self.contrib_centre1 = ContributingCentre.objects.create( - name="Centre1", url="http://example.com" - ) - self.contrib_centre2 = ContributingCentre.objects.create( - name="Centre2", url="http://test.com", logo="http://test.com/logo.png" - ) - - self.contrib_centre1.subjects.add(self.subject1, self.subject2) - self.contrib_centre2.subjects.add(self.subject1) - - def test_centre_creation(self): - self.assertEqual(ContributingCentre.objects.count(), 2) - - def test_centre_name_unique(self): - with self.assertRaises(IntegrityError): - ContributingCentre.objects.create(name="Centre1", url="http://example.com") - - def test_centre_url_field(self): - self.assertEqual(self.contrib_centre1.url, "http://example.com") - self.assertEqual(self.contrib_centre2.url, "http://test.com") - - def test_centre_name_str(self): - self.assertEqual(str(self.contrib_centre1), "Centre1") - self.assertEqual(str(self.contrib_centre2), "Centre2") - - def test_centre_logo_field(self): - self.assertEqual(self.contrib_centre1.logo, "") - self.assertEqual(self.contrib_centre2.logo, "http://test.com/logo.png") - - def test_centre_subject_field(self): - self.assertEqual(list(self.contrib_centre1.subjects.all()), [self.subject1, self.subject2]) - self.assertEqual(list(self.contrib_centre2.subjects.all()), [self.subject1]) - - -if __name__ == "__main__": - unittest.main() diff --git a/app/general/tests/tests_institution.py b/app/general/tests/tests_institution.py index 4271514f..b623b51f 100644 --- a/app/general/tests/tests_institution.py +++ b/app/general/tests/tests_institution.py @@ -2,14 +2,14 @@ from django.test import TestCase -from general.models import ContributingCentre, Institution +from general.models import Institution, Project class TestInstitution(TestCase): def setUp(self): - self.contributing_centre = ContributingCentre.objects.create( + self.contributing_centre = Project.objects.create( name="Test Centre", - # add additional fields here as required by the ContributingCentre model + # add additional fields here as required by the ProjectsAdmin model ) self.institution = Institution.objects.create( name="Test University", diff --git a/app/general/tests/tests_language.py b/app/general/tests/tests_language.py index 70331e70..d6752d41 100644 --- a/app/general/tests/tests_language.py +++ b/app/general/tests/tests_language.py @@ -2,7 +2,7 @@ from django.test import TestCase -from general.models import ContributingCentre, Subject +from general.models import Projects, Subject class TestSubject(TestCase): @@ -19,7 +19,7 @@ def test_subject_name_uniqueness(self): self.assertRaises(Exception, duplicate_subject.save) def test_contributing_centre_relationship(self): - contributing_centre = ContributingCentre(name="Test Centre") + contributing_centre = Project(name="Test Centre") contributing_centre.save() self.subject.contributing_centre.add(contributing_centre) self.assertTrue(contributing_centre in self.subject.contributing_centre.all()) diff --git a/app/general/tests/tests_projects.py b/app/general/tests/tests_projects.py new file mode 100644 index 00000000..0b27705d --- /dev/null +++ b/app/general/tests/tests_projects.py @@ -0,0 +1,59 @@ +import unittest + +from django.db.utils import IntegrityError +from django.test import TestCase + +from general.models import Project, Subject + +# from subject.models import Subject + + +class TestProjects(TestCase): + def setUp(self): + self.subject1 = Subject.objects.create(name="Maths") + self.subject2 = Subject.objects.create(name="Science") + + self.project1 = Project.objects.create(name="Centre1", url="http://example.com") + self.project2 = Project.objects.create( + name="Centre2", url="http://test.com", logo="http://test.com/logo.png" + ) + + # self.project1.subjects.add(self.subject1, self.subject2) + # self.project2.subjects.add(self.subject1) + + def test_centre_creation(self): + self.assertEqual(Project.objects.count(), 2) + + def test_centre_name_unique(self): + with self.assertRaises(IntegrityError): + Project.objects.create(name="Centre1", url="http://example.com") + + def test_centre_url_field(self): + self.assertEqual(self.project1.url, "http://example.com") + self.assertEqual(self.project2.url, "http://test.com") + + def test_centre_name_str(self): + self.assertEqual(str(self.project1), "Centre1") + self.assertEqual(str(self.project2), "Centre2") + + def test_centre_logo_field(self): + self.assertEqual(self.project1.logo, "") + self.assertEqual(self.project2.logo, "http://test.com/logo.png") + + def test_centre_subject_field(self): + self.assertEqual(list(self.project1.subjects.all()), [self.subject1, self.subject2]) + self.assertEqual(list(self.project2.subjects.all()), [self.subject1]) + + def test_start_date(self): + self.project1.start_date = "2021-01-01" + self.project1.save() + self.assertEqual(self.project1.start_date, "2021-01-01") + + def test_end_date(self): + self.project1.end_date = "2021-01-01" + self.project1.save() + self.assertEqual(self.project1.end_date, "2021-01-01") + + +if __name__ == "__main__": + unittest.main() diff --git a/app/general/tests/tests_subject.py b/app/general/tests/tests_subject.py index 70331e70..d8c368ae 100644 --- a/app/general/tests/tests_subject.py +++ b/app/general/tests/tests_subject.py @@ -2,7 +2,7 @@ from django.test import TestCase -from general.models import ContributingCentre, Subject +from general.models import Project, Subject class TestSubject(TestCase): @@ -19,7 +19,7 @@ def test_subject_name_uniqueness(self): self.assertRaises(Exception, duplicate_subject.save) def test_contributing_centre_relationship(self): - contributing_centre = ContributingCentre(name="Test Centre") + contributing_centre = Project(name="Test Centre") contributing_centre.save() self.subject.contributing_centre.add(contributing_centre) self.assertTrue(contributing_centre in self.subject.contributing_centre.all()) diff --git a/app/logos/photo_2024-03-14_07.57.40.jpeg b/app/logos/photo_2024-03-14_07.57.40.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..cbbbea213f3d27b475e31e94bf2ae6776eb61b95 GIT binary patch literal 43262 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 literal 0 HcmV?d00001 diff --git a/app/manage.py b/app/manage.py index 1a64b14a..923e331a 100755 --- a/app/manage.py +++ b/app/manage.py @@ -1,5 +1,6 @@ #!/usr/bin/env python """Django's command-line utility for administrative tasks.""" + import os import sys diff --git a/app/schema/schema.png b/app/schema/schema.png index 4750cce033fc3f23b824edee8a5b455d731f9a68..44dc754861cd1f7247f23357ef0869b4304ea322 100644 GIT binary patch literal 176928 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 ^1!aYO0w;*)?4g|k}(nFDcp<-s-gGht`ZBU`9 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 literal 109589 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´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 diff --git a/app/users/migrations/0001_initial.py b/app/users/migrations/0001_initial.py index 1fc6e464..df008b48 100644 --- a/app/users/migrations/0001_initial.py +++ b/app/users/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.2 on 2024-02-23 11:05 +# Generated by Django 5.0.2 on 2024-03-14 10:10 import django.contrib.auth.models import django.contrib.auth.validators From 9f598739fda7c78cdab79569b0358b2b0c998726 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Mon, 18 Mar 2024 15:49:15 +0200 Subject: [PATCH 019/240] Refactor code to support Institution-Project relationship Modified Institution and Project models to facilitate many-to-many relationship. --- Makefile | 2 +- app/fixtures/institution.json | 14 +++---- app/fixtures/projects.json | 4 +- app/general/admin.py | 10 ----- app/general/migrations/0001_initial.py | 15 +++++--- app/general/models.py | 11 ++++-- app/general/tests/tests_institution.py | 13 +++++-- app/general/tests/tests_language.py | 11 +----- app/general/tests/tests_projects.py | 45 +++++++++++++---------- app/general/tests/tests_subject.py | 11 +----- app/logos/photo_2024-03-14_07.57.40.jpeg | Bin 43262 -> 0 bytes app/users/admin.py | 2 + app/users/migrations/0001_initial.py | 2 +- 13 files changed, 67 insertions(+), 73 deletions(-) delete mode 100644 app/logos/photo_2024-03-14_07.57.40.jpeg diff --git a/Makefile b/Makefile index e1ee8622..abd67139 100644 --- a/Makefile +++ b/Makefile @@ -87,8 +87,8 @@ ruff-fix: load-fixtures: clear - @docker-compose run --rm web python manage.py loaddata fixtures/projects.json @docker-compose run --rm web python manage.py loaddata fixtures/institution.json + @docker-compose run --rm web python manage.py loaddata fixtures/projects.json @docker-compose run --rm web python manage.py loaddata fixtures/language.json @docker-compose run --rm web python manage.py loaddata fixtures/subjects.json diff --git a/app/fixtures/institution.json b/app/fixtures/institution.json index 1e18b243..8fa0cc12 100644 --- a/app/fixtures/institution.json +++ b/app/fixtures/institution.json @@ -153,7 +153,7 @@ "logo": "logo" } }, - { + { "model": "general.Institution", "pk": 14, "fields": { @@ -164,7 +164,7 @@ "logo": "logo" } }, - { + { "model": "general.Institution", "pk": 15, "fields": { @@ -175,7 +175,7 @@ "logo": "logo" } }, - { + { "model": "general.Institution", "pk": 16, "fields": { @@ -197,7 +197,7 @@ "logo": "logo" } }, - { + { "model": "general.Institution", "pk": 18, "fields": { @@ -230,7 +230,7 @@ "logo": "logo" } }, - { + { "model": "general.Institution", "pk": 21, "fields": { @@ -252,7 +252,7 @@ "logo": "logo" } }, - { + { "model": "general.Institution", "pk": 23, "fields": { @@ -263,7 +263,7 @@ "logo": "logo" } }, - { + { "model": "general.Institution", "pk": 24, "fields": { diff --git a/app/fixtures/projects.json b/app/fixtures/projects.json index a17afef2..a6de4fbb 100644 --- a/app/fixtures/projects.json +++ b/app/fixtures/projects.json @@ -8,7 +8,7 @@ "logo": "logo.png", "start_date": "2020-01-01", "end_date": "2020-01-01", - "institution": 1 + "Institution": 1 } }, { @@ -20,7 +20,7 @@ "logo": "logo.png", "start_date": "2020-01-01", "end_date": "2020-01-01", - "institution": 1 + "Institution": 2 } } ] diff --git a/app/general/admin.py b/app/general/admin.py index bea29513..8ea6d4fd 100644 --- a/app/general/admin.py +++ b/app/general/admin.py @@ -2,16 +2,6 @@ from .models import Institution, Language, Project, Subject -# class SubjectInline(admin.TabularInline): -# # model = Subject.projects.through -# pass -# -# -# class ProjectsAdmin(admin.ModelAdmin): -# inlines = [SubjectInline] - - -# admin.site.register(Projects, ProjectsAdmin) admin.site.register(Project) admin.site.register(Institution) admin.site.register(Language) diff --git a/app/general/migrations/0001_initial.py b/app/general/migrations/0001_initial.py index 3c82b724..41f78096 100644 --- a/app/general/migrations/0001_initial.py +++ b/app/general/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.2 on 2024-03-14 10:10 +# Generated by Django 5.0.2 on 2024-03-18 13:47 import django.db.models.deletion from django.db import migrations, models @@ -42,12 +42,17 @@ class Migration(migrations.Migration): name='Project', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200, unique=True)), + ('name', models.CharField(max_length=200)), ('url', models.URLField()), ('logo', models.FileField(blank=True, upload_to='logos/')), - ('start_date', models.DateField(blank=True)), - ('end_date', models.DateField(blank=True)), - ('institution', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, to='general.institution')), + ('start_date', models.DateField(blank=True, null=True)), + ('end_date', models.DateField(blank=True, null=True)), + ('Institution', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, to='general.institution', verbose_name='institution')), ], ), + migrations.AddField( + model_name='institution', + name='projects', + field=models.ManyToManyField(blank=True, to='general.project'), + ), ] diff --git a/app/general/models.py b/app/general/models.py index 1e7640c6..5535b424 100644 --- a/app/general/models.py +++ b/app/general/models.py @@ -2,12 +2,14 @@ class Project(models.Model): - name = models.CharField(max_length=200, unique=True) + name = models.CharField(max_length=200) url = models.URLField(max_length=200) logo = models.FileField(upload_to="logos/", blank=True) - start_date = models.DateField(blank=True) - end_date = models.DateField(blank=True) - institution = models.ForeignKey("Institution", on_delete=models.CASCADE, blank=True) + start_date = models.DateField(blank=True, null=True) + end_date = models.DateField(blank=True, null=True) + Institution = models.ForeignKey( + "Institution", on_delete=models.CASCADE, blank=True, verbose_name=("institution") + ) def __str__(self): return self.name @@ -19,6 +21,7 @@ class Institution(models.Model): url = models.URLField(max_length=200) email = models.EmailField(max_length=200) logo = models.FileField(upload_to="logos/", blank=True) + projects = models.ManyToManyField(Project, blank=True) def __str__(self): return self.name diff --git a/app/general/tests/tests_institution.py b/app/general/tests/tests_institution.py index b623b51f..062a2c08 100644 --- a/app/general/tests/tests_institution.py +++ b/app/general/tests/tests_institution.py @@ -7,17 +7,22 @@ class TestInstitution(TestCase): def setUp(self): - self.contributing_centre = Project.objects.create( + self.project = Project.objects.create( name="Test Centre", + url="http://example.com", + start_date="2021-01-01", + end_date="2021-01-01", + institution=1, # add additional fields here as required by the ProjectsAdmin model ) + self.institution = Institution.objects.create( name="Test University", abbreviation="tu", url="http://www.testuni.com", email="info@testuni.dev", logo="testuni.png", - contributing_centre=self.contributing_centre, + project=self.project, ) def test_institution_creation(self): @@ -38,8 +43,8 @@ def test_institution_email(self): def test_institution_logo(self): self.assertEqual(self.institution.logo, "testuni.png") - def test_institution_contributing_centre(self): - self.assertEqual(self.institution.contributing_centre, self.contributing_centre) + def test_project(self): + self.assertEqual(self.institution.projects, self.project) if __name__ == "__main__": diff --git a/app/general/tests/tests_language.py b/app/general/tests/tests_language.py index d6752d41..2efe19bc 100644 --- a/app/general/tests/tests_language.py +++ b/app/general/tests/tests_language.py @@ -2,7 +2,7 @@ from django.test import TestCase -from general.models import Projects, Subject +from general.models import Subject class TestSubject(TestCase): @@ -18,15 +18,6 @@ def test_subject_name_uniqueness(self): duplicate_subject = Subject(name="Maths") self.assertRaises(Exception, duplicate_subject.save) - def test_contributing_centre_relationship(self): - contributing_centre = Project(name="Test Centre") - contributing_centre.save() - self.subject.contributing_centre.add(contributing_centre) - self.assertTrue(contributing_centre in self.subject.contributing_centre.all()) - - def test_blank_contributing_centre(self): - self.assertEqual(self.subject.contributing_centre.count(), 0) - if __name__ == "__main__": unittest.main() diff --git a/app/general/tests/tests_projects.py b/app/general/tests/tests_projects.py index 0b27705d..a0c4ee29 100644 --- a/app/general/tests/tests_projects.py +++ b/app/general/tests/tests_projects.py @@ -3,47 +3,54 @@ from django.db.utils import IntegrityError from django.test import TestCase -from general.models import Project, Subject - -# from subject.models import Subject +from general.models import Institution, Project class TestProjects(TestCase): def setUp(self): - self.subject1 = Subject.objects.create(name="Maths") - self.subject2 = Subject.objects.create(name="Science") + self.institution = Institution.objects.create( + name="Institution1", + abbreviation="Inst1", + url="http://institution1.com", + email="institution1@example.com", + ) - self.project1 = Project.objects.create(name="Centre1", url="http://example.com") - self.project2 = Project.objects.create( - name="Centre2", url="http://test.com", logo="http://test.com/logo.png" + self.project1 = Project.objects.create( + name="Centre1", + url="http://example.com", + start_date="2021-01-01", + end_date="2021-01-01", + institution=self.institution, ) - # self.project1.subjects.add(self.subject1, self.subject2) - # self.project2.subjects.add(self.subject1) + self.project2 = Project.objects.create( + name="Centre2", + url="http://test.com", + logo="http://test.com/logo.png", + start_date="2021-01-01", + end_date="2021-01-01", + institution=self.institution, + ) - def test_centre_creation(self): + def test_project_creation(self): self.assertEqual(Project.objects.count(), 2) - def test_centre_name_unique(self): + def test_project_name_unique(self): with self.assertRaises(IntegrityError): Project.objects.create(name="Centre1", url="http://example.com") - def test_centre_url_field(self): + def test_project_url_field(self): self.assertEqual(self.project1.url, "http://example.com") self.assertEqual(self.project2.url, "http://test.com") - def test_centre_name_str(self): + def test_project_name_str(self): self.assertEqual(str(self.project1), "Centre1") self.assertEqual(str(self.project2), "Centre2") - def test_centre_logo_field(self): + def test_project_logo_field(self): self.assertEqual(self.project1.logo, "") self.assertEqual(self.project2.logo, "http://test.com/logo.png") - def test_centre_subject_field(self): - self.assertEqual(list(self.project1.subjects.all()), [self.subject1, self.subject2]) - self.assertEqual(list(self.project2.subjects.all()), [self.subject1]) - def test_start_date(self): self.project1.start_date = "2021-01-01" self.project1.save() diff --git a/app/general/tests/tests_subject.py b/app/general/tests/tests_subject.py index d8c368ae..2efe19bc 100644 --- a/app/general/tests/tests_subject.py +++ b/app/general/tests/tests_subject.py @@ -2,7 +2,7 @@ from django.test import TestCase -from general.models import Project, Subject +from general.models import Subject class TestSubject(TestCase): @@ -18,15 +18,6 @@ def test_subject_name_uniqueness(self): duplicate_subject = Subject(name="Maths") self.assertRaises(Exception, duplicate_subject.save) - def test_contributing_centre_relationship(self): - contributing_centre = Project(name="Test Centre") - contributing_centre.save() - self.subject.contributing_centre.add(contributing_centre) - self.assertTrue(contributing_centre in self.subject.contributing_centre.all()) - - def test_blank_contributing_centre(self): - self.assertEqual(self.subject.contributing_centre.count(), 0) - if __name__ == "__main__": unittest.main() diff --git a/app/logos/photo_2024-03-14_07.57.40.jpeg b/app/logos/photo_2024-03-14_07.57.40.jpeg deleted file mode 100644 index cbbbea213f3d27b475e31e94bf2ae6776eb61b95..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43262 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 diff --git a/app/users/admin.py b/app/users/admin.py index 364391db..a5d010c8 100644 --- a/app/users/admin.py +++ b/app/users/admin.py @@ -14,10 +14,12 @@ class CustomUserAdmin(UserAdmin): "is_staff", "is_active", ] + list_filter = [ "username", "email", ] + fieldsets = UserAdmin.fieldsets + ((None, {"fields": ("institution", "languages", "subject")}),) add_fieldsets = UserAdmin.add_fieldsets diff --git a/app/users/migrations/0001_initial.py b/app/users/migrations/0001_initial.py index df008b48..4bdf2076 100644 --- a/app/users/migrations/0001_initial.py +++ b/app/users/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.2 on 2024-03-14 10:10 +# Generated by Django 5.0.2 on 2024-03-18 13:47 import django.contrib.auth.models import django.contrib.auth.validators From 9128b372db6470eb60257dea738c4afe107534f9 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Tue, 19 Mar 2024 14:03:01 +0200 Subject: [PATCH 020/240] Update code for inline ProjectAdmin in admin panel Implemented ProjectAdminInline, a new admin interface to manage projects from the Institution detail page. --- app/general/admin.py | 12 +++++++++++- app/general/migrations/0001_initial.py | 7 +------ app/general/models.py | 2 +- app/general/tests/tests_projects.py | 1 + app/general/tests/tests_subject.py | 12 ++++++------ app/schema/schema.png | Bin 176928 -> 176717 bytes app/users/migrations/0001_initial.py | 2 +- 7 files changed, 21 insertions(+), 15 deletions(-) diff --git a/app/general/admin.py b/app/general/admin.py index 8ea6d4fd..fb656f28 100644 --- a/app/general/admin.py +++ b/app/general/admin.py @@ -2,7 +2,17 @@ from .models import Institution, Language, Project, Subject + +class ProjectAdminInline(admin.TabularInline): + model = Project + extra = 0 + + +class ProjectAdmin(admin.ModelAdmin): + inlines = [ProjectAdminInline] + + admin.site.register(Project) -admin.site.register(Institution) +admin.site.register(Institution, ProjectAdmin) admin.site.register(Language) admin.site.register(Subject) diff --git a/app/general/migrations/0001_initial.py b/app/general/migrations/0001_initial.py index 41f78096..0be9c218 100644 --- a/app/general/migrations/0001_initial.py +++ b/app/general/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.2 on 2024-03-18 13:47 +# Generated by Django 5.0.2 on 2024-03-19 08:51 import django.db.models.deletion from django.db import migrations, models @@ -50,9 +50,4 @@ class Migration(migrations.Migration): ('Institution', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, to='general.institution', verbose_name='institution')), ], ), - migrations.AddField( - model_name='institution', - name='projects', - field=models.ManyToManyField(blank=True, to='general.project'), - ), ] diff --git a/app/general/models.py b/app/general/models.py index 5535b424..45f24935 100644 --- a/app/general/models.py +++ b/app/general/models.py @@ -21,7 +21,7 @@ class Institution(models.Model): url = models.URLField(max_length=200) email = models.EmailField(max_length=200) logo = models.FileField(upload_to="logos/", blank=True) - projects = models.ManyToManyField(Project, blank=True) + # projects = models.ManyToManyField(Project, blank=True) def __str__(self): return self.name diff --git a/app/general/tests/tests_projects.py b/app/general/tests/tests_projects.py index a0c4ee29..2b7af952 100644 --- a/app/general/tests/tests_projects.py +++ b/app/general/tests/tests_projects.py @@ -18,6 +18,7 @@ def setUp(self): self.project1 = Project.objects.create( name="Centre1", url="http://example.com", + logo="http://test.com/logo.png", start_date="2021-01-01", end_date="2021-01-01", institution=self.institution, diff --git a/app/general/tests/tests_subject.py b/app/general/tests/tests_subject.py index 2efe19bc..ddef00d5 100644 --- a/app/general/tests/tests_subject.py +++ b/app/general/tests/tests_subject.py @@ -7,16 +7,16 @@ class TestSubject(TestCase): def setUp(self): - self.subject = Subject.objects.create(name="Maths") + self.subject1 = Subject.objects.create(name="Mathematics") self.subject2 = Subject.objects.create(name="Science") def test_subject_creation(self): - self.assertEqual(self.subject.name, "Maths") - self.assertEqual(self.subject.__str__(), "Maths") + self.assertEqual(str(self.subject1), "Mathematics") + self.assertEqual(str(self.subject2), "Science") - def test_subject_name_uniqueness(self): - duplicate_subject = Subject(name="Maths") - self.assertRaises(Exception, duplicate_subject.save) + # def test_subject_name_uniqueness(self): + # with self.assertRaises(Exception): + # Subject.objects.create(name='Mathematics') if __name__ == "__main__": diff --git a/app/schema/schema.png b/app/schema/schema.png index 44dc754861cd1f7247f23357ef0869b4304ea322..c3c8a6aeb14c3fc4eab72f5c0f04c5ba3bb6ce78 100644 GIT binary patch delta 145932 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

k$_?~78p6lkyQ?d>+d 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˟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℘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 Date: Tue, 19 Mar 2024 18:05:25 +0200 Subject: [PATCH 021/240] Refactor models and update related tests The code refactor includes the removal of a many-to-many relationship in the Institution model and enhancements in the ProjectAdmin model. Corresponding updates have been made in related test cases. deleted images --- app/general/models.py | 1 - app/general/tests/tests_institution.py | 16 +---- app/general/tests/tests_language.py | 1 + app/general/tests/tests_projects.py | 86 +++++++++++--------------- app/general/tests/tests_subject.py | 6 +- 5 files changed, 43 insertions(+), 67 deletions(-) diff --git a/app/general/models.py b/app/general/models.py index 45f24935..822cb0ff 100644 --- a/app/general/models.py +++ b/app/general/models.py @@ -21,7 +21,6 @@ class Institution(models.Model): url = models.URLField(max_length=200) email = models.EmailField(max_length=200) logo = models.FileField(upload_to="logos/", blank=True) - # projects = models.ManyToManyField(Project, blank=True) def __str__(self): return self.name diff --git a/app/general/tests/tests_institution.py b/app/general/tests/tests_institution.py index 062a2c08..bb2363b6 100644 --- a/app/general/tests/tests_institution.py +++ b/app/general/tests/tests_institution.py @@ -7,26 +7,17 @@ class TestInstitution(TestCase): def setUp(self): - self.project = Project.objects.create( - name="Test Centre", - url="http://example.com", - start_date="2021-01-01", - end_date="2021-01-01", - institution=1, - # add additional fields here as required by the ProjectsAdmin model - ) - self.institution = Institution.objects.create( name="Test University", abbreviation="tu", url="http://www.testuni.com", email="info@testuni.dev", logo="testuni.png", - project=self.project, ) def test_institution_creation(self): - self.assertIsInstance(self.institution, Institution) + self.assertTrue(isinstance(self.institution, Institution)) + self.assertEqual(self.institution.__str__(), "Test University") def test_institution_name(self): self.assertEqual(self.institution.name, "Test University") @@ -43,9 +34,6 @@ def test_institution_email(self): def test_institution_logo(self): self.assertEqual(self.institution.logo, "testuni.png") - def test_project(self): - self.assertEqual(self.institution.projects, self.project) - if __name__ == "__main__": unittest.main() diff --git a/app/general/tests/tests_language.py b/app/general/tests/tests_language.py index 2efe19bc..36a9882b 100644 --- a/app/general/tests/tests_language.py +++ b/app/general/tests/tests_language.py @@ -7,6 +7,7 @@ class TestSubject(TestCase): def setUp(self): + # pass self.subject = Subject.objects.create(name="Maths") self.subject2 = Subject.objects.create(name="Science") diff --git a/app/general/tests/tests_projects.py b/app/general/tests/tests_projects.py index 2b7af952..e89b2122 100644 --- a/app/general/tests/tests_projects.py +++ b/app/general/tests/tests_projects.py @@ -1,6 +1,6 @@ import unittest +from datetime import datetime -from django.db.utils import IntegrityError from django.test import TestCase from general.models import Institution, Project @@ -8,59 +8,47 @@ class TestProjects(TestCase): def setUp(self): - self.institution = Institution.objects.create( - name="Institution1", - abbreviation="Inst1", - url="http://institution1.com", - email="institution1@example.com", - ) - - self.project1 = Project.objects.create( - name="Centre1", - url="http://example.com", - logo="http://test.com/logo.png", - start_date="2021-01-01", - end_date="2021-01-01", - institution=self.institution, - ) - - self.project2 = Project.objects.create( - name="Centre2", + self.institution = Institution.objects.create(name="Test Institution") + self.project = Project.objects.create( + name="Test Project", url="http://test.com", logo="http://test.com/logo.png", - start_date="2021-01-01", - end_date="2021-01-01", - institution=self.institution, + start_date="2023-01-01", + end_date="2023-12-31", + Institution=self.institution, ) def test_project_creation(self): - self.assertEqual(Project.objects.count(), 2) - - def test_project_name_unique(self): - with self.assertRaises(IntegrityError): - Project.objects.create(name="Centre1", url="http://example.com") - - def test_project_url_field(self): - self.assertEqual(self.project1.url, "http://example.com") - self.assertEqual(self.project2.url, "http://test.com") - - def test_project_name_str(self): - self.assertEqual(str(self.project1), "Centre1") - self.assertEqual(str(self.project2), "Centre2") - - def test_project_logo_field(self): - self.assertEqual(self.project1.logo, "") - self.assertEqual(self.project2.logo, "http://test.com/logo.png") - - def test_start_date(self): - self.project1.start_date = "2021-01-01" - self.project1.save() - self.assertEqual(self.project1.start_date, "2021-01-01") - - def test_end_date(self): - self.project1.end_date = "2021-01-01" - self.project1.save() - self.assertEqual(self.project1.end_date, "2021-01-01") + self.assertTrue(isinstance(self.project, Project)) + self.assertEqual(self.project.__str__(), "Test Project") + self.assertEqual(self.project.name, "Test Project") + self.assertEqual(self.project.url, "http://test.com") + self.assertEqual(self.project.logo, "http://test.com/logo.png") + self.assertEqual(self.project.start_date, "2023-01-01") + self.assertEqual(self.project.end_date, "2023-12-31") + self.assertEqual(self.project.Institution, self.institution) + + def test_project_name(self): + self.assertEqual(self.project.name, "Test Project") + + def test_project_url(self): + self.assertEqual(self.project.url, "http://test.com") + + def test_project_start_date(self): + date_format = "%Y-%m-%d" + end_date = datetime.strptime(self.project.start_date, date_format) + self.assertEqual(end_date.strftime(date_format), "2023-01-01") + + def test_project_end_date(self): + date_format = "%Y-%m-%d" + end_date = datetime.strptime(self.project.end_date, date_format) + self.assertEqual(end_date.strftime(date_format), "2023-12-31") + + def test_project_institution(self): + self.assertEqual(self.project.Institution.name, "Test Institution") + + def test_str(self): + self.assertEqual(str(self.project), "Test Project") if __name__ == "__main__": diff --git a/app/general/tests/tests_subject.py b/app/general/tests/tests_subject.py index ddef00d5..7c623485 100644 --- a/app/general/tests/tests_subject.py +++ b/app/general/tests/tests_subject.py @@ -14,9 +14,9 @@ def test_subject_creation(self): self.assertEqual(str(self.subject1), "Mathematics") self.assertEqual(str(self.subject2), "Science") - # def test_subject_name_uniqueness(self): - # with self.assertRaises(Exception): - # Subject.objects.create(name='Mathematics') + def test_subject_name_uniqueness(self): + with self.assertRaises(Exception): + Subject.objects.create(name="Mathematics") if __name__ == "__main__": From cd605a5136b0f36835ca42d301b9f0ca5d2562e7 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Wed, 20 Mar 2024 13:11:00 +0200 Subject: [PATCH 022/240] Add Project.subjects --- app/general/migrations/0001_initial.py | 3 ++- app/general/models.py | 1 + app/general/tests/tests_language.py | 1 - app/general/tests/tests_projects.py | 8 +++++++- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/general/migrations/0001_initial.py b/app/general/migrations/0001_initial.py index 0be9c218..988c857c 100644 --- a/app/general/migrations/0001_initial.py +++ b/app/general/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.2 on 2024-03-19 08:51 +# Generated by Django 5.0.2 on 2024-03-20 08:58 import django.db.models.deletion from django.db import migrations, models @@ -48,6 +48,7 @@ class Migration(migrations.Migration): ('start_date', models.DateField(blank=True, null=True)), ('end_date', models.DateField(blank=True, null=True)), ('Institution', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, to='general.institution', verbose_name='institution')), + ('subjects', models.ManyToManyField(to='general.subject', blank=True)), ], ), ] diff --git a/app/general/models.py b/app/general/models.py index 822cb0ff..59118b89 100644 --- a/app/general/models.py +++ b/app/general/models.py @@ -10,6 +10,7 @@ class Project(models.Model): Institution = models.ForeignKey( "Institution", on_delete=models.CASCADE, blank=True, verbose_name=("institution") ) + subjects = models.ManyToManyField("Subject", blank=True) def __str__(self): return self.name diff --git a/app/general/tests/tests_language.py b/app/general/tests/tests_language.py index 36a9882b..2efe19bc 100644 --- a/app/general/tests/tests_language.py +++ b/app/general/tests/tests_language.py @@ -7,7 +7,6 @@ class TestSubject(TestCase): def setUp(self): - # pass self.subject = Subject.objects.create(name="Maths") self.subject2 = Subject.objects.create(name="Science") diff --git a/app/general/tests/tests_projects.py b/app/general/tests/tests_projects.py index e89b2122..abf0b5bb 100644 --- a/app/general/tests/tests_projects.py +++ b/app/general/tests/tests_projects.py @@ -3,12 +3,13 @@ from django.test import TestCase -from general.models import Institution, Project +from general.models import Institution, Project, Subject class TestProjects(TestCase): def setUp(self): self.institution = Institution.objects.create(name="Test Institution") + self.subject = Subject.objects.create(name="Test Subject") self.project = Project.objects.create( name="Test Project", url="http://test.com", @@ -17,6 +18,7 @@ def setUp(self): end_date="2023-12-31", Institution=self.institution, ) + self.project.subjects.add(self.subject) def test_project_creation(self): self.assertTrue(isinstance(self.project, Project)) @@ -27,6 +29,7 @@ def test_project_creation(self): self.assertEqual(self.project.start_date, "2023-01-01") self.assertEqual(self.project.end_date, "2023-12-31") self.assertEqual(self.project.Institution, self.institution) + self.assertIn(self.subject, self.project.subjects.all()) def test_project_name(self): self.assertEqual(self.project.name, "Test Project") @@ -47,6 +50,9 @@ def test_project_end_date(self): def test_project_institution(self): self.assertEqual(self.project.Institution.name, "Test Institution") + def test_project_subject(self): + self.assertTrue(self.project.subjects.filter(name="Test Subject").exists()) + def test_str(self): self.assertEqual(str(self.project), "Test Project") From 76ef9d8b48cefb832420d78a37e32772ef481685 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Wed, 3 Apr 2024 14:35:00 +0200 Subject: [PATCH 023/240] make: +dev-quick-install --- Makefile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Makefile b/Makefile index abd67139..40a2be82 100644 --- a/Makefile +++ b/Makefile @@ -95,3 +95,10 @@ load-fixtures: pre-commit-install: clear pre-commit install + +dev-quick-install: + clear + @make migrate + @make load-fixtures + echo "Creating superuser" + @make create-super-user From d31a1299986d9c5943a06408c5b0ab07ed2d5689 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Wed, 3 Apr 2024 14:37:15 +0200 Subject: [PATCH 024/240] Rename Project.Institution -> Project.institution --- app/fixtures/projects.json | 4 ++-- app/general/migrations/0001_initial.py | 4 ++-- app/general/models.py | 4 ++-- app/general/tests/tests_projects.py | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/fixtures/projects.json b/app/fixtures/projects.json index a6de4fbb..6c85eb2b 100644 --- a/app/fixtures/projects.json +++ b/app/fixtures/projects.json @@ -8,7 +8,7 @@ "logo": "logo.png", "start_date": "2020-01-01", "end_date": "2020-01-01", - "Institution": 1 + "institution": 1 } }, { @@ -20,7 +20,7 @@ "logo": "logo.png", "start_date": "2020-01-01", "end_date": "2020-01-01", - "Institution": 2 + "institution": 2 } } ] diff --git a/app/general/migrations/0001_initial.py b/app/general/migrations/0001_initial.py index 988c857c..43eb1dbd 100644 --- a/app/general/migrations/0001_initial.py +++ b/app/general/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.2 on 2024-03-20 08:58 +# Generated by Django 5.0.2 on 2024-04-03 09:12 import django.db.models.deletion from django.db import migrations, models @@ -47,7 +47,7 @@ class Migration(migrations.Migration): ('logo', models.FileField(blank=True, upload_to='logos/')), ('start_date', models.DateField(blank=True, null=True)), ('end_date', models.DateField(blank=True, null=True)), - ('Institution', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, to='general.institution', verbose_name='institution')), + ('institution', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, to='general.institution', verbose_name='institution')), ('subjects', models.ManyToManyField(to='general.subject', blank=True)), ], ), diff --git a/app/general/models.py b/app/general/models.py index 59118b89..1475f235 100644 --- a/app/general/models.py +++ b/app/general/models.py @@ -7,8 +7,8 @@ class Project(models.Model): logo = models.FileField(upload_to="logos/", blank=True) start_date = models.DateField(blank=True, null=True) end_date = models.DateField(blank=True, null=True) - Institution = models.ForeignKey( - "Institution", on_delete=models.CASCADE, blank=True, verbose_name=("institution") + institution = models.ForeignKey( + "Institution", on_delete=models.CASCADE, blank=True, verbose_name="institution" ) subjects = models.ManyToManyField("Subject", blank=True) diff --git a/app/general/tests/tests_projects.py b/app/general/tests/tests_projects.py index abf0b5bb..3baf88bb 100644 --- a/app/general/tests/tests_projects.py +++ b/app/general/tests/tests_projects.py @@ -16,7 +16,7 @@ def setUp(self): logo="http://test.com/logo.png", start_date="2023-01-01", end_date="2023-12-31", - Institution=self.institution, + institution=self.institution, ) self.project.subjects.add(self.subject) @@ -28,7 +28,7 @@ def test_project_creation(self): self.assertEqual(self.project.logo, "http://test.com/logo.png") self.assertEqual(self.project.start_date, "2023-01-01") self.assertEqual(self.project.end_date, "2023-12-31") - self.assertEqual(self.project.Institution, self.institution) + self.assertEqual(self.project.institution, self.institution) self.assertIn(self.subject, self.project.subjects.all()) def test_project_name(self): @@ -48,7 +48,7 @@ def test_project_end_date(self): self.assertEqual(end_date.strftime(date_format), "2023-12-31") def test_project_institution(self): - self.assertEqual(self.project.Institution.name, "Test Institution") + self.assertEqual(self.project.institution.name, "Test Institution") def test_project_subject(self): self.assertTrue(self.project.subjects.filter(name="Test Subject").exists()) From 31eb366fc1d7fdd6abf3059e0af5a34d81f4bee8 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Wed, 3 Apr 2024 14:53:30 +0200 Subject: [PATCH 025/240] Remove whitespace at the end of .gitignore --- .gitignore | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.gitignore b/.gitignore index bf9603d7..47866af8 100644 --- a/.gitignore +++ b/.gitignore @@ -30,9 +30,3 @@ venv/ ENV/ env.bak/ venv.bak/ - - - - - - From d9c75ca3665fc09b48f2fc1efb5466414660bdeb Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Wed, 3 Apr 2024 14:54:14 +0200 Subject: [PATCH 026/240] Increase some field max_lengths --- app/general/migrations/0001_initial.py | 6 +++--- app/general/models.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/general/migrations/0001_initial.py b/app/general/migrations/0001_initial.py index 43eb1dbd..66665c47 100644 --- a/app/general/migrations/0001_initial.py +++ b/app/general/migrations/0001_initial.py @@ -27,15 +27,15 @@ class Migration(migrations.Migration): name='Language', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100, unique=True)), - ('iso_code', models.CharField(help_text='Enter the ISO code for the language', max_length=10, unique=True)), + ('name', models.CharField(max_length=150, unique=True)), + ('iso_code', models.CharField(help_text='Enter the ISO code for the language', max_length=50, unique=True)), ], ), migrations.CreateModel( name='Subject', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100, unique=True)), + ('name', models.CharField(max_length=150, unique=True)), ], ), migrations.CreateModel( diff --git a/app/general/models.py b/app/general/models.py index 1475f235..fb5ccfa6 100644 --- a/app/general/models.py +++ b/app/general/models.py @@ -28,9 +28,9 @@ def __str__(self): class Language(models.Model): - name = models.CharField(max_length=100, unique=True) + name = models.CharField(max_length=150, unique=True) iso_code = models.CharField( - max_length=10, unique=True, help_text="Enter the ISO code for the language" + max_length=50, unique=True, help_text="Enter the ISO code for the language" ) def __str__(self): @@ -38,7 +38,7 @@ def __str__(self): class Subject(models.Model): - name = models.CharField(max_length=100, unique=True) + name = models.CharField(max_length=150, unique=True) def __str__(self): return self.name From 983eb22ffa762ac0f461138180eb3e7b15b16dfc Mon Sep 17 00:00:00 2001 From: OMosimege Date: Wed, 6 Mar 2024 07:16:03 +0200 Subject: [PATCH 027/240] Infrastructure for templates and static files --- Makefile | 4 ++++ app/app/settings.py | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index abd67139..6438c48b 100644 --- a/Makefile +++ b/Makefile @@ -48,6 +48,10 @@ migrate: clear @docker-compose run --rm web python manage.py migrate +collectstatic: + clear + @docker-compose run --rm web python manage.py collectstatic --noinput + shell: clear @docker-compose run --rm web python manage.py shell diff --git a/app/app/settings.py b/app/app/settings.py index 7ddaf0ee..d9124599 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -58,7 +58,10 @@ TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [], + "DIRS": [ + BASE_DIR, + "templates", + ], "APP_DIRS": True, "OPTIONS": { "context_processors": [ @@ -120,6 +123,9 @@ # https://docs.djangoproject.com/en/5.0/howto/static-files/ STATIC_URL = "static/" +STATICFILES_DIRS = [ + BASE_DIR / "static", +] # Default primary key field type # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field From da36766cd237605a3975324267d71c3515cab2ba Mon Sep 17 00:00:00 2001 From: OMosimege Date: Thu, 4 Apr 2024 14:51:12 +0200 Subject: [PATCH 028/240] Frontend styling with basic front page --- app/app/urls.py | 3 + app/app/views.py | 8 +++ app/general/views.py | 3 - app/static/css/styles.css | 119 ++++++++++++++++++++++++++++++++++++ app/static/img/sadilar.png | Bin 0 -> 44306 bytes app/templates/app/home.html | 48 +++++++++++++++ app/templates/base.html | 62 +++++++++++++++++++ 7 files changed, 240 insertions(+), 3 deletions(-) create mode 100644 app/app/views.py delete mode 100644 app/general/views.py create mode 100755 app/static/css/styles.css create mode 100644 app/static/img/sadilar.png create mode 100644 app/templates/app/home.html create mode 100644 app/templates/base.html diff --git a/app/app/urls.py b/app/app/urls.py index b4dd8616..fdb3661b 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -18,6 +18,9 @@ from django.contrib import admin from django.urls import path +from . import views + urlpatterns = [ path("admin/", admin.site.urls), + path("", views.home, name="home"), ] diff --git a/app/app/views.py b/app/app/views.py new file mode 100644 index 00000000..e9e360ce --- /dev/null +++ b/app/app/views.py @@ -0,0 +1,8 @@ +from django.shortcuts import render + + +def home(request): + template = "app/home.html" + context = {} + + return render(request, template_name=template, context=context) diff --git a/app/general/views.py b/app/general/views.py deleted file mode 100644 index fd0e0449..00000000 --- a/app/general/views.py +++ /dev/null @@ -1,3 +0,0 @@ -# from django.shortcuts import render - -# Create your views here. diff --git a/app/static/css/styles.css b/app/static/css/styles.css new file mode 100755 index 00000000..ec6de953 --- /dev/null +++ b/app/static/css/styles.css @@ -0,0 +1,119 @@ +/* Remember to sync colours with admin CSS. */ +:root { + --primary: #1a2f69; + --primary-light: #8288bc; + --primary-red: #d11f26; + --primary-fg: #ffffff; + --body-quiet-color: #485d95; + --text-color: #000000; +} + +body { + margin: 0; + padding: 0; +} + +.header-container { + display: inline-block; +} + +.nav-menu { + float: left; + padding: 5px; + margin: 10px 10px 10px 10px; +} + +.nav-logo { + margin: 3px 10px 10px 10px; + float: left; +} + +.nav { + background: var(--primary-fg); + width: 100%; +} + +.nav > li > a{ + margin-top: 15px; + color: var(--text-color); +} + +.nav > li > .active{ + margin-top: 15px; + background-color: var(--primary) !important; + color: var(--primary-fg); +} + +.nav > li > a:hover{ + margin-top: 15px; + background-color: var(--body-quiet-color); + color: var(--primary-fg); +} + +.nav > li > a:active{ + margin-top: 15px; + background-color: var(--primary); + color: var(--primary); +} + +.btn-primary{ + margin-top:20px; + outline-color: var(--primary); + background-color: var(--primary); + border-color: var(--primary); +} + +.btn-outline-primary{ + margin-top:20px; + background: #2D4481; + outline-color: var(--primary); +} + +.btn-primary:hover{ + margin-top:20px; + background: var(--body-quiet-color); + outline-color: var(--primary); +} + +.app-footer .footer-title { + color: var(--primary); +} + +.card{ + border-color: var(--primary-red); + margin-right: 50px; +} + +.fa-search{ + margin-right: 30px; +} + +.footer { + color: white; + background: var(--primary); + width: 100%; +} + +.footer > a { + color: var(--primary-fg); +} + +.section{ + margin-left: 10px; + margin-right: 10px; + margin-bottom: 10px; +} + +.input-group{ + margin: 10px 10px 10px 10px; +} + +.form-group{ + margin-bottom: 10px; +} + +.main-logo { + width: 140px; + height: 75px; + margin: 10px 10px 10px 10px; +} diff --git a/app/static/img/sadilar.png b/app/static/img/sadilar.png new file mode 100644 index 0000000000000000000000000000000000000000..d8be30d02c0ff794f84736694ac4d1c594637413 GIT binary patch literal 44306 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

?ThKE$cQ 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�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 literal 0 HcmV?d00001 diff --git a/app/templates/app/home.html b/app/templates/app/home.html new file mode 100644 index 00000000..ae659cce --- /dev/null +++ b/app/templates/app/home.html @@ -0,0 +1,48 @@ +{% extends "base.html" %} +{% load static %} + +{% block content %} + +

+ +{% endblock content %} diff --git a/app/templates/base.html b/app/templates/base.html new file mode 100644 index 00000000..f4c71d84 --- /dev/null +++ b/app/templates/base.html @@ -0,0 +1,62 @@ +{% load static %} + + + + + SADiLaR + + + + + + + + + + + +{% block app_header %} +
+
+ + +
+
+{% endblock app_header %} + + +
+ {% block content %} + + {% endblock content %} +
+ + + + + + + + + From 40e99a0e6be2eac66a36c23ceccae04139cb703b Mon Sep 17 00:00:00 2001 From: OMosimege Date: Fri, 5 Apr 2024 08:38:55 +0200 Subject: [PATCH 029/240] Styling for Django admin backend The base_site.html template is replaced, and relevant CSS is overriden to leave the built-in CSS unchanged. --- app/app/urls.py | 3 ++ app/static/css/admin.css | 63 ++++++++++++++++++++++++++++++ app/templates/admin/base_site.html | 25 ++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 app/static/css/admin.css create mode 100644 app/templates/admin/base_site.html diff --git a/app/app/urls.py b/app/app/urls.py index fdb3661b..e41c9e18 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -20,6 +20,9 @@ from . import views +admin.site.index_title = "SADiLaR Administration" +admin.site.site_title = "SADiLaR Site Admin Portal" + urlpatterns = [ path("admin/", admin.site.urls), path("", views.home, name="home"), diff --git a/app/static/css/admin.css b/app/static/css/admin.css new file mode 100644 index 00000000..0669673e --- /dev/null +++ b/app/static/css/admin.css @@ -0,0 +1,63 @@ +/* + Django admin styles + extended from https://github.com/django/django/blob/main/django/contrib/admin/static/admin/css/base.css + Remember to sync colours with front-end CSS. +*/ + +/* VARIABLE DEFINITIONS */ +html[data-theme="light"], +:root { + --primary: #1a2f69; + --primary-light: #8288bc; + + --header-color: var(--primary-light); + --header-branding-color: var(--primary-light); + --header-bg: var(--primary); + --header-link-color: #000000; + + --breadcrumbs-bg: var(--primary-light); + + --link-fg: var(--primary); + --link-selected-fg: var(--primary); + + --message-success-bg: #9fadd1; + --message-warning-bg: var(--primary-light); + + --selected-row: var(--primary-light); + + --button-bg: var(--primary); + --button-hover-bg: #485D95; + --default-button-bg:#485D95; + --default-button-hover-bg: var(--primary); +} + +/* LINKS */ +a.section:link, a.section:visited { + color: #ffffff; +} + +/* HEADER */ +#header { + padding: 3px 15px 0 10px; + background: #ffffff; + color: #000; +} + +#header a:link, #header a:visited, #logout-form button { + color: var(--link-fg); +} + +.theme-toggle svg.theme-icon-when-auto, .theme-toggle svg.theme-icon-when-dark, .theme-toggle svg.theme-icon-when-light { + fill: var(--link-fg); + color: #fff; +} + +.header-title { + margin: 10px; +} + +.main-logo { + margin-left: 1px; + width: auto; + height: 70px; +} diff --git a/app/templates/admin/base_site.html b/app/templates/admin/base_site.html new file mode 100644 index 00000000..38b7f2a8 --- /dev/null +++ b/app/templates/admin/base_site.html @@ -0,0 +1,25 @@ + + +{% extends "admin/base.html" %} + +{% load static %} + +{% block title %}{% if subtitle %}{{ subtitle }} | {% endif %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %} + +{% block extrastyle %} + +{% endblock %} + +{% block branding %} +

+ +

+{% if user.is_anonymous %} + {% include "admin/color_theme_toggle.html" %} +{% endif %} +{% endblock %} + From 65fc0cd9d3e04d9219133d3daad07705b43ca414 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Mon, 8 Apr 2024 08:18:46 +0200 Subject: [PATCH 030/240] Updated Language in the models Updated schema changes Changed language to plural updated models and tests --- Makefile | 8 +++++++- app/general/migrations/0001_initial.py | 5 ++--- app/general/models.py | 1 + app/general/tests/tests_projects.py | 7 ++++++- app/users/migrations/0001_initial.py | 2 -- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 62a57a83..06e52de3 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,13 @@ list: @echo "create-super-user - Create a superuser" @echo "docker-stop-all - Stop all running containers" @echo "load-fixtures - Load fixtures" - + @echo "create-schema - Create a schema" + @echo "test - Run tests" + @echo "ruff-check - Run ruff check" + @echo "ruff-format - Run ruff format" + @echo "ruff-fix - Run ruff check --fix" + @echo "pre-commit-install - Install pre-commit" + @echo "dev-quick-install - Run all the necessary commands to start the project" up: clear diff --git a/app/general/migrations/0001_initial.py b/app/general/migrations/0001_initial.py index 66665c47..3facdcfc 100644 --- a/app/general/migrations/0001_initial.py +++ b/app/general/migrations/0001_initial.py @@ -1,5 +1,3 @@ -# Generated by Django 5.0.2 on 2024-04-03 09:12 - import django.db.models.deletion from django.db import migrations, models @@ -48,7 +46,8 @@ class Migration(migrations.Migration): ('start_date', models.DateField(blank=True, null=True)), ('end_date', models.DateField(blank=True, null=True)), ('institution', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, to='general.institution', verbose_name='institution')), - ('subjects', models.ManyToManyField(to='general.subject', blank=True)), + ('languages', models.ManyToManyField(blank=True, to='general.language')), + ('subjects', models.ManyToManyField(blank=True, to='general.subject')), ], ), ] diff --git a/app/general/models.py b/app/general/models.py index fb5ccfa6..624dcda9 100644 --- a/app/general/models.py +++ b/app/general/models.py @@ -11,6 +11,7 @@ class Project(models.Model): "Institution", on_delete=models.CASCADE, blank=True, verbose_name="institution" ) subjects = models.ManyToManyField("Subject", blank=True) + languages = models.ManyToManyField("Language", blank=True) def __str__(self): return self.name diff --git a/app/general/tests/tests_projects.py b/app/general/tests/tests_projects.py index 3baf88bb..e593ed90 100644 --- a/app/general/tests/tests_projects.py +++ b/app/general/tests/tests_projects.py @@ -3,13 +3,14 @@ from django.test import TestCase -from general.models import Institution, Project, Subject +from general.models import Institution, Language, Project, Subject class TestProjects(TestCase): def setUp(self): self.institution = Institution.objects.create(name="Test Institution") self.subject = Subject.objects.create(name="Test Subject") + self.language = Language.objects.create(name="Test Language", iso_code="TL") self.project = Project.objects.create( name="Test Project", url="http://test.com", @@ -19,6 +20,7 @@ def setUp(self): institution=self.institution, ) self.project.subjects.add(self.subject) + self.project.languages.add(self.language) def test_project_creation(self): self.assertTrue(isinstance(self.project, Project)) @@ -53,6 +55,9 @@ def test_project_institution(self): def test_project_subject(self): self.assertTrue(self.project.subjects.filter(name="Test Subject").exists()) + def test_project_language(self): + self.assertTrue(self.project.languages.filter(name="Test Language").exists()) + def test_str(self): self.assertEqual(str(self.project), "Test Project") diff --git a/app/users/migrations/0001_initial.py b/app/users/migrations/0001_initial.py index 6d24d17c..4e27c840 100644 --- a/app/users/migrations/0001_initial.py +++ b/app/users/migrations/0001_initial.py @@ -1,5 +1,3 @@ -# Generated by Django 5.0.2 on 2024-03-19 08:51 - import django.contrib.auth.models import django.contrib.auth.validators import django.db.models.deletion From 9f9e0178eac831247b11e847467864b120cbb16c Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Thu, 11 Apr 2024 11:16:16 +0200 Subject: [PATCH 031/240] +Basic health endpoint --- app/app/urls.py | 1 + app/app/views.py | 8 ++++++++ app/general/tests/test_views.py | 8 ++++++++ doc/urls.md | 5 +++++ 4 files changed, 22 insertions(+) create mode 100644 app/general/tests/test_views.py create mode 100644 doc/urls.md diff --git a/app/app/urls.py b/app/app/urls.py index e41c9e18..6171d332 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -25,5 +25,6 @@ urlpatterns = [ path("admin/", admin.site.urls), + path("_health/", views.health, name="health"), path("", views.home, name="home"), ] diff --git a/app/app/views.py b/app/app/views.py index e9e360ce..4d8eea18 100644 --- a/app/app/views.py +++ b/app/app/views.py @@ -1,6 +1,14 @@ +from django.http import HttpResponse from django.shortcuts import render +def health(request): + """A very basic (minimal dependency) health endpoint.""" + # If we want/need a health check for DB, cache, files, etc. that should + # probably be separate. + return HttpResponse("OK", content_type="text/plain") + + def home(request): template = "app/home.html" context = {} diff --git a/app/general/tests/test_views.py b/app/general/tests/test_views.py new file mode 100644 index 00000000..f3f0d32f --- /dev/null +++ b/app/general/tests/test_views.py @@ -0,0 +1,8 @@ +from django.test import Client, TestCase + + +class TestViews(TestCase): + + def test_health(self): + response = Client().get("/_health/") + self.assertIn(b"OK", response.content) diff --git a/doc/urls.md b/doc/urls.md new file mode 100644 index 00000000..f3caedc1 --- /dev/null +++ b/doc/urls.md @@ -0,0 +1,5 @@ +URL naming +========== + +If a URL is not really for public consumption, prefix it with an undrescore, +e.g. "/_health/". From 67cb88ffee1e4e216b888f2e985e7af7d667b5ad Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Thu, 11 Apr 2024 15:12:39 +0200 Subject: [PATCH 032/240] +CODEOWNERS --- CODEOWNERS | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 CODEOWNERS diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 00000000..ef815f52 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,2 @@ +* @friedelwolff +.github @friedelwolff @jrb-s2c-github From 464a1d458f4b07c79b4735346807493e43831c0c Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Fri, 12 Apr 2024 11:00:23 +0200 Subject: [PATCH 033/240] Remove dev-dependencies from base requirements --- Dockerfile | 3 +-- app/app/settings.py | 5 ++++- requirements.txt | 3 --- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 568e5033..74667d8b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,8 +10,7 @@ WORKDIR /app # Install dependencies COPY requirements.txt /app/ -RUN apt-get update -RUN apt-get install graphviz graphviz-dev -y +RUN apt-get update && apt-get -y upgrade # Install dependencies RUN pip install --upgrade pip diff --git a/app/app/settings.py b/app/app/settings.py index d9124599..1f709e54 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -36,10 +36,13 @@ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", - "django_extensions", "users", "general", ] +if DEBUG: + INSTALLED_APPS += [ + "django_extensions", + ] AUTH_USER_MODEL = "users.CustomUser" diff --git a/requirements.txt b/requirements.txt index 828accc2..17e58b21 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,2 @@ django==5.0.2 psycopg2-binary -ruff -pygraphviz -django-extensions From 9804c273941187426ca8882c211e25eebd25c322 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Fri, 12 Apr 2024 10:46:29 +0200 Subject: [PATCH 034/240] institutions - fields changed to optional --- app/general/migrations/0001_initial.py | 4 ++-- app/general/models.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/general/migrations/0001_initial.py b/app/general/migrations/0001_initial.py index 3facdcfc..bb69dc54 100644 --- a/app/general/migrations/0001_initial.py +++ b/app/general/migrations/0001_initial.py @@ -16,8 +16,8 @@ class Migration(migrations.Migration): ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=200, unique=True)), ('abbreviation', models.CharField(max_length=200)), - ('url', models.URLField()), - ('email', models.EmailField(max_length=200)), + ('url', models.URLField(blank=True)), + ('email', models.EmailField(blank=True, max_length=200)), ('logo', models.FileField(blank=True, upload_to='logos/')), ], ), diff --git a/app/general/models.py b/app/general/models.py index 624dcda9..103787d7 100644 --- a/app/general/models.py +++ b/app/general/models.py @@ -20,8 +20,8 @@ def __str__(self): class Institution(models.Model): name = models.CharField(max_length=200, unique=True) abbreviation = models.CharField(max_length=200) - url = models.URLField(max_length=200) - email = models.EmailField(max_length=200) + url = models.URLField(max_length=200, blank=True) + email = models.EmailField(max_length=200, blank=True) logo = models.FileField(upload_to="logos/", blank=True) def __str__(self): From 0e4117e23bbd7d1843e262ef49b56244c81a4d61 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Fri, 12 Apr 2024 11:06:34 +0200 Subject: [PATCH 035/240] Get more settings from environment variables --- app/app/settings.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/app/app/settings.py b/app/app/settings.py index 1f709e54..3ff2d73f 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -19,13 +19,14 @@ # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = "django-insecure-w!h85bp^$e8gm%c23r!0%9i7yzd=6w$s&ic+6!%306&kj8@k*5" +SECRET_KEY = os.environ["SECRET_KEY"] # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = bool(os.getenv("DEBUG", "")) -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "").split() +USE_X_FORWARDED_HOST = bool(os.getenv("USE_X_FORWARDED_HOST", "")) +CSRF_TRUSTED_ORIGINS = os.getenv("CSRF_TRUSTED_ORIGINS", "").split() # Application definition @@ -85,11 +86,11 @@ DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", - "HOST": os.environ.get("DJANGO_DB_HOST"), - "PORT": os.environ.get("DJANGO_DB_PORT"), - "NAME": os.environ.get("DJANGO_DB_NAME"), - "USER": os.environ.get("DJANGO_DB_USER"), - "PASSWORD": os.environ.get("DJANGO_DB_PASSWORD"), + "HOST": os.environ.get("DB_HOST"), + "PORT": os.environ.get("DB_PORT"), + "NAME": os.environ.get("DB_NAME"), + "USER": os.environ.get("DB_USER"), + "PASSWORD": os.environ.get("DB_PASSWORD"), } } From bd0afeccfa8fcf5271a112024f808b62864608f6 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Fri, 12 Apr 2024 11:10:43 +0200 Subject: [PATCH 036/240] +Entrypoint launching gunicorn for Dockerfile --- Dockerfile | 3 ++- entrypoint.sh | 11 +++++++++++ requirements.txt | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 entrypoint.sh diff --git a/Dockerfile b/Dockerfile index 74667d8b..7cc53694 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,6 +18,7 @@ RUN pip install -r requirements.txt # Copy project COPY ./app /app/ +COPY ./entrypoint.sh / # Run the application -CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] +ENTRYPOINT ["bash", "/entrypoint.sh"] diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 00000000..ce0b1ff3 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -e +set -o pipefail +set -x + +python manage.py check --deploy --database default +python manage.py migrate --no-input +python manage.py collectstatic --no-input + +gunicorn app.wsgi:application --bind 0.0.0.0:8000 diff --git a/requirements.txt b/requirements.txt index 17e58b21..df8a5596 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ django==5.0.2 psycopg2-binary +gunicorn From 1dd3f4bc8471277a01dac98f23d1a82c63dfb174 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Fri, 12 Apr 2024 11:26:10 +0200 Subject: [PATCH 037/240] Implement static file serving with whitenoise --- .gitignore | 1 + app/app/settings.py | 14 +++++++++++++- requirements.txt | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 47866af8..ba78116b 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ venv/ ENV/ env.bak/ venv.bak/ +app/static_files/ diff --git a/app/app/settings.py b/app/app/settings.py index 3ff2d73f..51e49091 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -36,6 +36,7 @@ "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", + "whitenoise.runserver_nostatic", "django.contrib.staticfiles", "users", "general", @@ -49,6 +50,7 @@ MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", + "whitenoise.middleware.WhiteNoiseMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", @@ -126,11 +128,21 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.0/howto/static-files/ -STATIC_URL = "static/" +STATIC_URL = "/static/" STATICFILES_DIRS = [ BASE_DIR / "static", ] +STATIC_ROOT = BASE_DIR / "static_files" +STORAGES = { + "default": { + "BACKEND": "django.core.files.storage.FileSystemStorage", + }, + "staticfiles": { + "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage", + }, +} + # Default primary key field type # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field diff --git a/requirements.txt b/requirements.txt index df8a5596..62e806c8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ django==5.0.2 psycopg2-binary gunicorn +whitenoise From d181de093e8f2ca505e2b99e45060d9c981303ab Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Mon, 15 Apr 2024 16:38:23 +0200 Subject: [PATCH 038/240] Fix development docker files Changes in 0e4117e2 broke docker compose. --- Dockerfile | 1 + Dockerfile-dev | 27 +++++++++++++++++++++++++++ docker-compose.yml | 21 ++++++++++++++------- requirements-dev.txt | 4 ++++ 4 files changed, 46 insertions(+), 7 deletions(-) create mode 100644 Dockerfile-dev create mode 100644 requirements-dev.txt diff --git a/Dockerfile b/Dockerfile index 7cc53694..8992d548 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,4 @@ +# A minimalist image usable as a base for a production deployment. FROM python:3.12 # Set environment variables diff --git a/Dockerfile-dev b/Dockerfile-dev new file mode 100644 index 00000000..f429146d --- /dev/null +++ b/Dockerfile-dev @@ -0,0 +1,27 @@ +# A development image with extras for development. +# Don't use in production. +FROM python:3.12 + +# Set environment variables +ENV PYTHONDONTWRITEBYTECODE 1 +ENV PYTHONUNBUFFERED 1 + +# Set work directory +WORKDIR /app + +# Install dependencies +COPY requirements.txt /app/ +COPY requirements-dev.txt /app/ + +RUN apt-get update && apt-get -y upgrade +RUN apt-get install --no-install-recommends -y graphviz graphviz-dev + +# Install dependencies +RUN pip install --upgrade pip +RUN pip install -r requirements-dev.txt + +# Copy project +COPY ./app /app/ + +# Run the application +CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] diff --git a/docker-compose.yml b/docker-compose.yml index 3d60cf86..6e7c2f09 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,3 +1,5 @@ +# This is is meant for local development, but should give an idea of what you +# can consider in production. version: '3' services: @@ -13,7 +15,9 @@ services: ports: - "5432:5432" web: - build: . + build: + context: . + dockerfile: Dockerfile-dev container_name: sadilar-terminology-web command: python manage.py runserver 0.0.0.0:8000 volumes: @@ -23,9 +27,12 @@ services: depends_on: - db environment: - - DJANGO_DB_HOST=db - - DJANGO_DB_PORT=5432 - - DJANGO_DB_NAME=term_db - - DJANGO_DB_USER=sadilar - - DJANGO_DB_PASSWORD=sadilar - - DATABASE_URL=postgresql://sadilar:sadilar@db/term_db + # TODO: dollar signs in the KEY are interpolated, therefore escaped here + # with double "$$". Maybe move to .env file to simplify? + - SECRET_KEY='django-insecure-w!h85bp^$$e8gm%c23r!0%9i7yzd=6w$$s&ic+6!%306&kj8@k*5' + - DEBUG=True + - DB_HOST=db + - DB_PORT=5432 + - DB_NAME=term_db # see POSTGRES_DB above + - DB_USER=sadilar # see POSTGRES_USER above + - DB_PASSWORD=sadilar # see POSTGRES_PASSWORD above diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 00000000..04cd5276 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,4 @@ +-r requirements.txt +django-extensions +pygraphviz +ruff From d63f7ed4cdcabb3c1965869570c6c620bb45b58d Mon Sep 17 00:00:00 2001 From: OnaMosimege Date: Tue, 16 Apr 2024 11:41:06 +0200 Subject: [PATCH 039/240] Add institutions template, view, and responsive styling: Add view, template, url and styling for institutions --- app/app/urls.py | 1 + app/app/views.py | 10 ++ app/static/css/styles.css | 173 +++++++++++++++++++++++++--- app/templates/app/institutions.html | 41 +++++++ 4 files changed, 208 insertions(+), 17 deletions(-) create mode 100644 app/templates/app/institutions.html diff --git a/app/app/urls.py b/app/app/urls.py index 6171d332..c72e4f9b 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -27,4 +27,5 @@ path("admin/", admin.site.urls), path("_health/", views.health, name="health"), path("", views.home, name="home"), + path("institutions/", views.institutions, name="institutions"), ] diff --git a/app/app/views.py b/app/app/views.py index 4d8eea18..90321581 100644 --- a/app/app/views.py +++ b/app/app/views.py @@ -1,6 +1,7 @@ from django.http import HttpResponse from django.shortcuts import render +from general.models import Institution def health(request): """A very basic (minimal dependency) health endpoint.""" @@ -14,3 +15,12 @@ def home(request): context = {} return render(request, template_name=template, context=context) + + +def institutions(request): + template = "app/institutions.html" + + institutions = Institution.objects.all() + context = {"institutions_page": "active", "institutions": institutions} + + return render(request, template_name=template, context=context) diff --git a/app/static/css/styles.css b/app/static/css/styles.css index ec6de953..b0fa745c 100755 --- a/app/static/css/styles.css +++ b/app/static/css/styles.css @@ -8,6 +8,8 @@ --text-color: #000000; } + +/*General*/ body { margin: 0; padding: 0; @@ -79,15 +81,6 @@ body { color: var(--primary); } -.card{ - border-color: var(--primary-red); - margin-right: 50px; -} - -.fa-search{ - margin-right: 30px; -} - .footer { color: white; background: var(--primary); @@ -104,16 +97,162 @@ body { margin-bottom: 10px; } -.input-group{ - margin: 10px 10px 10px 10px; +.main-logo { + width: 140px; + height: 75px; + margin: 20px 10px 10px 10px; } -.form-group{ - margin-bottom: 10px; +.page-link { + background-color: var(--primary) !important; + color: var(--primary-fg) !important; } -.main-logo { - width: 140px; - height: 75px; - margin: 10px 10px 10px 10px; +.page-link:hover{ + background-color: var(--body-quiet-color) !important; + color: var(--primary-fg) !important; +} + +.row { + margin: 10px; + padding: 10px; +} + +/*Home page*/ +.content-card { + border-color: var(--primary-red); + margin: 0 10px 0 10px; + width: 100%; +} + +/*Institutions page*/ +.uni-card { + border-color: var(--primary-red); + margin: 0 10px 0 10px; +} + +.uni-logo { + width: 50px; + height: 50px; + margin: 10px 0 0 0; +} + +/*bootstrap icons*/ +.bi { + font-size: 50px; +} + +/* Extra small devices (phones, 600px and down) */ +@media only screen and (max-width: 600px) { + /*General*/ + .header-container { + width: 100%; + } + .section { + padding-right: 30px; + } + + /*Institutions page*/ + .uni-card { + width: 100%; + font-size: smaller; + } + .all-cards { + width: 98%; + padding-right: 30px; + } + .col-sm-6 { + width: 50%; + } + + /*Home page*/ + .content-card { + font-size: smaller; + padding-right: 20px; + margin-right: 10px; + } +} + +/*small devices (tablets, 600px and up) */ +@media screen and (min-width: 600px) { + /*General*/ + .header-container { + width: 100%; + } + .section { + padding-right: 20px; + } + + /*Institutions page*/ + .all-cards { + width: 100%; + padding-right: 10px; + } + .col-sm-6 { + width: 50%; + } + .uni-card { + width: 98%; + height: 200px; + padding-right: 20px; + } + + /*Home page*/ + .content-card { + font-size: smaller; + } +} + +/*Medium screens (tablets, between 768px and 1001px)*/ +@media screen and (min-width: 768px) and (max-width: 1001px) { + /*General*/ + .header-container { + width: 100%; + } + .section { + padding-right: 20px; + } + + /*Institutions page*/ + .uni-card { + width: 98%; + font-size: medium; + padding-right: 20px; + height: 200px; + } + .all-cards { + width: 100%; + padding-right: 10px; + } + .col-sm-6 { + width: 50%; + } +} + + /*Larger screens (desktops, 1001px and up)*/ +@media screen and (min-width: 1001px){ + /*General*/ + .section { + padding-right: 20px; + } + + /*Institutions page*/ + .institution-card{ + overflow: hidden; + } + .all-cards { + width: 48%; + margin: 10px 10px 10px 10px; + float: left; + padding-right: 10px; + } + .col-sm-6 { + width: 50%; + } + .uni-card { + margin: 10px; + width: 100%; + height: 200px; + font-size: small; + } } diff --git a/app/templates/app/institutions.html b/app/templates/app/institutions.html new file mode 100644 index 00000000..bbdff528 --- /dev/null +++ b/app/templates/app/institutions.html @@ -0,0 +1,41 @@ +{% extends "base.html" %} +{% load static %} + +{% block content %} + +
+ {% for institution in institutions %} +
+
+
+
+
{{ institution.name }}
+

+ {{ institution.abbreviation }} +

+

+ 4 applicable projects +

+
+
+
+

Profile completion:

+
+ + + + + +
+
+ +
+
+
+
+ {% endfor %} +
+ +{% endblock content %} From 5d61dd2b2ee62f896b7b870c3249d4ccf2e561be Mon Sep 17 00:00:00 2001 From: OnaMosimege Date: Tue, 16 Apr 2024 11:56:13 +0200 Subject: [PATCH 040/240] Add institutions to home and base templates, enable admin link, add logos, and fix footer: Add institutions to all revelant pages, fix footer to bottom, and enable link to admin site --- app/app/settings.py | 2 +- app/app/views.py | 3 ++- app/general/tests/test_views.py | 1 - app/static/img/UP_logo.png | Bin 0 -> 11941 bytes app/templates/app/home.html | 23 +++++++++++++--------- app/templates/base.html | 33 ++++++++++++++++---------------- 6 files changed, 33 insertions(+), 29 deletions(-) create mode 100644 app/static/img/UP_logo.png diff --git a/app/app/settings.py b/app/app/settings.py index 51e49091..65fe49b3 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -43,7 +43,7 @@ ] if DEBUG: INSTALLED_APPS += [ - "django_extensions", + "django_extensions", ] AUTH_USER_MODEL = "users.CustomUser" diff --git a/app/app/views.py b/app/app/views.py index 90321581..5fc62a25 100644 --- a/app/app/views.py +++ b/app/app/views.py @@ -3,6 +3,7 @@ from general.models import Institution + def health(request): """A very basic (minimal dependency) health endpoint.""" # If we want/need a health check for DB, cache, files, etc. that should @@ -12,7 +13,7 @@ def health(request): def home(request): template = "app/home.html" - context = {} + context = {"home_page": "active"} return render(request, template_name=template, context=context) diff --git a/app/general/tests/test_views.py b/app/general/tests/test_views.py index f3f0d32f..6dc5c063 100644 --- a/app/general/tests/test_views.py +++ b/app/general/tests/test_views.py @@ -2,7 +2,6 @@ class TestViews(TestCase): - def test_health(self): response = Client().get("/_health/") self.assertIn(b"OK", response.content) diff --git a/app/static/img/UP_logo.png b/app/static/img/UP_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0edeecd3fbc442a0e579864c9353d9b1a4bbe9c2 GIT binary patch literal 11941 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

88F4N~`((LKsyH@ zu+Ne~k?h-r2T)Mrrbxwa-&Cy6bKe_H9!|k2o(D1dq;A{og2#539pM+(cx!x< z2B|iOL$!Cr`9@nC+4I~Dr`SXkElM3zQcA$W_kyw`EA=5Fn(r9g1)sGAOH^{CFd2RS zS}_@)L7%{?m$i6SWL5ZK1qUXQY}5)7=7s~vUpkI=eJP-daFODUw3V&UKVIY$c$?UL<$jHcJUZNd)OfU586%X|l=a<>KOSm9 zD1Ka#e(-UEA&1%{=;9JG_w=;g#+{x#N1o3}S5VHaHpZiojHi>Km_Wmc&8!vF_H8a! z0%8{$ux4=sVJ1eleYzRXgZUx9!5pK4#ol?^xE^A+am`XCKtN6r^J-X606fCoyV#fZ zRqsEnt-obFnZ$7Tp-Bj1HKHx)K;J9*&J23x&Zpn0550-0iK>5O-|oa8r{MyLwv4(d zc*xr(tD3oc2>qONVU(@v zWaS74q?`?|h8~R?@%hIEvEb6=Z-dQFY_DgQOgsc7qvz&`C^mX&Z9n=akLy|b-UZE*=H-w#ug-^ep6`5!&i(L?RgUD5#Ouqk#Pv4gjqL+yrlxoRe zVz#fr*)~?uk51Wb;ZUXO@yvJ5L&wEVFr=%CGGPU2 zb;6=W9cyD4^+JAUG6_H0>9GTD`^EC?gwgZzP_rQp4|j+K#YV-6zt>VUM{eJ!SjBEz z9R|*utQspjv#+oPbcOJo&3;n0Wo=#7EX)Mj?>QoaZ7#&JdW(5tJC?HsTfE{-_(DEY znF|NNuGH+-*!Hq@KwxL79{aI4J4AVr4uH9tk7JV8bpRNMh010{aolLz@W01x>nb|5 zxl|)9`zV9MG=357RqFhpT3%2)OA`=Rw(|PSRNl3)FCX;<+liEvJbWjWndRI=JAoPd zkU*B&#N)@t%=yIpGkp4`D9oR3EehN9W+PkpC9w@eaF1G^_QqNMrto|goOd1b!VVqF ziSEqlC0os~$1LhXxx40*2<<{6aiRxsRi~5%78(A+yS$1lyaH`xG1T)fcspy?m0Q<~ zx+L#{@nZK8a~+O(=J^7R{4c(=K*mbkQU!z?*w95thPr|#udmB4`ip1;^KOcdEY4v& zJkC9VI>Wh?KSPdc?B&8;2OFDGg-$ysMGd}I6^GX|eglwAJKIXx@!miqR$la>KSTHz z`SBVEh<&aU&=qoIudR-02$u|w)oupPH!~8K(_g-`_CTx3fhpCNtUsnW?`MPhm5cyx-I3nK(Ws&+zkt5B$qQ9$7oxqF?h4qn-Z!N9jz7_}Ywq4= z9a}iCy>ni;Ps43;^>!nd`-yZ|2glUo2G19u8$4_)n9E?*sUU^HoCgWOzSxU`FS+$= zp0ir^C+9{cdJc+^Jq7rcw#02kE

-
+
About SADiLaR

@@ -19,26 +19,31 @@

About SADiLaR
-
+
Search
- +

+ Terminology Search +

+

+ Click here to search a term +

+
-
-
+
-
Register to site
+
Institutions

- Complete Registration + View Institutions

- - Start + + View
diff --git a/app/templates/base.html b/app/templates/base.html index f4c71d84..441a9406 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -10,10 +10,11 @@ + - - + + {% block app_header %}
@@ -24,17 +25,17 @@
@@ -47,9 +48,9 @@ {% endblock content %}
- +
-
+
Developed by SADiLaR
@@ -58,5 +59,3 @@ - - From 98c382dc9d44d3c43c302b8dd8932dab0f192363 Mon Sep 17 00:00:00 2001 From: OnaMosimege Date: Tue, 16 Apr 2024 13:46:46 +0200 Subject: [PATCH 041/240] Add custom error pages, with styling: Add url settings for custom error pages, add styling, views, and templates Add base error template, template inheritance for all templates --- app/app/urls.py | 5 +++++ app/app/views.py | 28 ++++++++++++++++++++++++++++ app/static/css/styles.css | 7 +++++++ app/templates/400.html | 11 +++++++++++ app/templates/403.html | 11 +++++++++++ app/templates/404.html | 11 +++++++++++ app/templates/500.html | 11 +++++++++++ app/templates/base_error.html | 18 ++++++++++++++++++ 8 files changed, 102 insertions(+) create mode 100644 app/templates/400.html create mode 100644 app/templates/403.html create mode 100644 app/templates/404.html create mode 100644 app/templates/500.html create mode 100644 app/templates/base_error.html diff --git a/app/app/urls.py b/app/app/urls.py index c72e4f9b..9fa8c57a 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -29,3 +29,8 @@ path("", views.home, name="home"), path("institutions/", views.institutions, name="institutions"), ] + +handler400 = "app.views.error_400" +handler403 = "app.views.error_403" +handler404 = "app.views.error_404" +handler500 = "app.views.error_500" diff --git a/app/app/views.py b/app/app/views.py index 5fc62a25..be5917bb 100644 --- a/app/app/views.py +++ b/app/app/views.py @@ -25,3 +25,31 @@ def institutions(request): context = {"institutions_page": "active", "institutions": institutions} return render(request, template_name=template, context=context) + + +def error_400(request, exception): + template = "400.html" + context = {} + + return render(request, template_name=template, context=context) + + +def error_403(request, exception): + template = "403.html" + context = {} + + return render(request, template_name=template, context=context) + + +def error_404(request, exception): + template = "404.html" + context = {} + + return render(request, template_name=template, context=context) + + +def error_500(request): + template = "500.html" + context = {} + + return render(request, template_name=template, context=context) diff --git a/app/static/css/styles.css b/app/static/css/styles.css index b0fa745c..e643f389 100755 --- a/app/static/css/styles.css +++ b/app/static/css/styles.css @@ -137,6 +137,13 @@ body { margin: 10px 0 0 0; } +/*Error pages*/ +.error-card { + border-color: var(--primary-red); + margin: 0 10px 0 10px; + width: 100%; +} + /*bootstrap icons*/ .bi { font-size: 50px; diff --git a/app/templates/400.html b/app/templates/400.html new file mode 100644 index 00000000..d861cf6c --- /dev/null +++ b/app/templates/400.html @@ -0,0 +1,11 @@ +{% extends "base_error.html" %} + +{% block error_content %} + +
+
Bad Request (400)
+

The server cannot process the request due to client error.

+ Go to home +
+ +{% endblock error_content %} diff --git a/app/templates/403.html b/app/templates/403.html new file mode 100644 index 00000000..6da09393 --- /dev/null +++ b/app/templates/403.html @@ -0,0 +1,11 @@ +{% extends "base_error.html" %} + +{% block error_content %} + +
+
Forbidden (403)
+

You do not have permission to access on this server.

+ Go to home +
+ +{% endblock error_content %} diff --git a/app/templates/404.html b/app/templates/404.html new file mode 100644 index 00000000..a13b12a4 --- /dev/null +++ b/app/templates/404.html @@ -0,0 +1,11 @@ +{% extends "base_error.html" %} + +{% block error_content %} + +
+
Not Found (404)
+

The requested resource was not found on this server.

+ Go to home +
+ +{% endblock error_content %} diff --git a/app/templates/500.html b/app/templates/500.html new file mode 100644 index 00000000..f314b680 --- /dev/null +++ b/app/templates/500.html @@ -0,0 +1,11 @@ +{% extends "base_error.html" %} + +{% block error_content %} + +
+
Internal Server Error (500)
+

The server encountered an unexpected condition that prevented it from fulfilling the request.

+ Go to home +
+ +{% endblock error_content %} diff --git a/app/templates/base_error.html b/app/templates/base_error.html new file mode 100644 index 00000000..18aaf041 --- /dev/null +++ b/app/templates/base_error.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} + +{% block content %} + +
+
+
+ Error +
+
+ {% block error_content %} + + {% endblock error_content %} +
+
+
+ +{% endblock content %} From eb5af8665dec9d34991cf458801455dd0f7fe98e Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Mon, 15 Apr 2024 15:22:57 +0200 Subject: [PATCH 042/240] Added document file upload to models - updated Admin forms with validation - added tests --- .gitignore | 1 + app/app/urls.py | 5 +++ app/general/admin.py | 47 +++++++++++++++++++++++- app/general/migrations/0001_initial.py | 17 +++++++++ app/general/models.py | 31 ++++++++++++++++ app/general/tests/test_document_file.py | 49 +++++++++++++++++++++++++ 6 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 app/general/tests/test_document_file.py diff --git a/.gitignore b/.gitignore index ba78116b..0a5d310e 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ ENV/ env.bak/ venv.bak/ app/static_files/ +/app/documents/ diff --git a/app/app/urls.py b/app/app/urls.py index 9fa8c57a..72b269cc 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -15,6 +15,8 @@ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ +from django.conf import settings +from django.conf.urls.static import static from django.contrib import admin from django.urls import path @@ -34,3 +36,6 @@ handler403 = "app.views.error_403" handler404 = "app.views.error_404" handler500 = "app.views.error_500" + +if settings.DEBUG: + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/app/general/admin.py b/app/general/admin.py index fb656f28..ac968b8d 100644 --- a/app/general/admin.py +++ b/app/general/admin.py @@ -1,6 +1,9 @@ +import mimetypes + from django.contrib import admin +from django.forms import ModelForm, fields_for_model -from .models import Institution, Language, Project, Subject +from .models import DocumentFile, Institution, Language, Project, Subject class ProjectAdminInline(admin.TabularInline): @@ -8,6 +11,47 @@ class ProjectAdminInline(admin.TabularInline): extra = 0 +class DocumentFileForm(ModelForm): + class Meta: + model = DocumentFile + fields = fields_for_model(DocumentFile) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields["mime_type"].widget.attrs["disabled"] = True + + def clean(self): + cleaned_data = super().clean() + url = cleaned_data.get("url", "") + uploaded_file = cleaned_data.get("uploaded_file", "") + cleaned_data["mime_type"] = ( + mimetypes.guess_type(uploaded_file.name)[0] if uploaded_file else "" + ) + + if not url and not uploaded_file: + self.add_error("url", "Either URL or uploaded file must be provided.") + self.add_error("uploaded_file", "Either URL or uploaded file must be provided.") + + if uploaded_file: + print("uploaded_file", uploaded_file.size) + limit = 10 * 1024 * 1024 + print("limit", limit) + if uploaded_file.size and uploaded_file.size > limit: + self.add_error("uploaded_file", "File size must not exceed 10MB.") + + return cleaned_data + + +class DocumentFileAdmin(admin.ModelAdmin): + list_display = ["title", "license", "document_type", "available"] + ordering = [ + "license", + ] + search_fields = ["title", "license", "document_type"] + + form = DocumentFileForm + + class ProjectAdmin(admin.ModelAdmin): inlines = [ProjectAdminInline] @@ -16,3 +60,4 @@ class ProjectAdmin(admin.ModelAdmin): admin.site.register(Institution, ProjectAdmin) admin.site.register(Language) admin.site.register(Subject) +admin.site.register(DocumentFile, DocumentFileAdmin) diff --git a/app/general/migrations/0001_initial.py b/app/general/migrations/0001_initial.py index bb69dc54..e86ebac8 100644 --- a/app/general/migrations/0001_initial.py +++ b/app/general/migrations/0001_initial.py @@ -1,3 +1,4 @@ +import django.core.validators import django.db.models.deletion from django.db import migrations, models @@ -50,4 +51,20 @@ class Migration(migrations.Migration): ('subjects', models.ManyToManyField(blank=True, to='general.subject')), ], ), + migrations.CreateModel( + name='DocumentFile', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=200)), + ('url', models.URLField(blank=True)), + ('uploaded_file', models.FileField(blank=True, help_text='Only PDF files are allowed.', upload_to='documents/', validators=[django.core.validators.FileExtensionValidator(['pdf'])])), + ('available', models.BooleanField(default=True)), + ('license', models.CharField(choices=[('MIT', 'MIT'), ('GNU', 'GNU'), ('Apache', 'Apache')], max_length=200)), + ('mime_type', models.CharField(blank=True, help_text='This input will auto-populate.', max_length=200)), + ('document_type', models.CharField(choices=[('Glossary', 'Glossary'), ('Translation', 'Translation')], max_length=200)), + ('Institution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='general.institution')), + ('languages', models.ManyToManyField(blank=True, to='general.language')), + ('subjects', models.ManyToManyField(blank=True, to='general.subject')), + ], + ), ] diff --git a/app/general/models.py b/app/general/models.py index 103787d7..143b07c9 100644 --- a/app/general/models.py +++ b/app/general/models.py @@ -1,3 +1,4 @@ +from django.core.validators import FileExtensionValidator from django.db import models @@ -43,3 +44,33 @@ class Subject(models.Model): def __str__(self): return self.name + + +class DocumentFile(models.Model): + file_validators = [FileExtensionValidator(["pdf"])] + + license_choices = [("MIT", "MIT"), ("GNU", "GNU"), ("Apache", "Apache")] + document_type_choices = [("Glossary", "Glossary"), ("Translation", "Translation")] + + file_type = "pdf" + + title = models.CharField(max_length=200) + url = models.URLField(max_length=200, blank=True) + uploaded_file = models.FileField( + upload_to="documents/", + validators=file_validators, + blank=True, + help_text="Only PDF files are allowed.", + ) + available = models.BooleanField(default=True) + license = models.CharField(max_length=200, choices=license_choices) + mime_type = models.CharField( + max_length=200, blank=True, help_text="This input will auto-populate." + ) + document_type = models.CharField(max_length=200, choices=document_type_choices) + Institution = models.ForeignKey("Institution", on_delete=models.CASCADE) + subjects = models.ManyToManyField("Subject", blank=True) + languages = models.ManyToManyField("Language", blank=True) + + def __str__(self): + return self.title diff --git a/app/general/tests/test_document_file.py b/app/general/tests/test_document_file.py new file mode 100644 index 00000000..a77c6600 --- /dev/null +++ b/app/general/tests/test_document_file.py @@ -0,0 +1,49 @@ +import unittest + +from django.core.files.uploadedfile import SimpleUploadedFile +from django.test import TestCase + +from general.models import DocumentFile, Institution, Language, Subject + + +class DocumentFileTest(TestCase): + def setUp(self): + self.subject = Subject.objects.create(name="Test Subject") + self.language = Language.objects.create(name="Test Language", iso_code="TL") + self.institution = Institution.objects.create(name="Test Institution") + + self.title = "Some document" + self.url = "https://example.com" + self.uploaded_file = SimpleUploadedFile( + "example.pdf", b"file_content", content_type="application/pdf" + ) + self.license = "MIT" + self.mime_type = "pdf" + self.document_type = "Glossary" + self.institution = self.institution + + self.document = DocumentFile.objects.create( + title=self.title, + url=self.url, + uploaded_file=self.uploaded_file, + license=self.license, + mime_type=self.mime_type, + document_type=self.document_type, + Institution=self.institution, + ) + self.document.subjects.add(self.subject) + self.document.languages.add(self.language) + + def test_document_creation(self): + self.assertEqual(DocumentFile.objects.count(), 1) + self.assertEqual(DocumentFile.objects.get().title, self.title) + + def test_document_str_representation(self): # Test __str__ method + self.assertEqual(str(self.document), self.title) + + def test_document_available_by_default(self): # Test default value + self.assertTrue(self.document.available) + + +if __name__ == "__main__": + unittest.main() From c02b2430c30f556fcd98017a48b45c03945fc86d Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Wed, 17 Apr 2024 23:28:04 +0200 Subject: [PATCH 043/240] Added tests, updated migrations, minor changes --- app/general/admin.py | 10 +-- app/general/migrations/0001_initial.py | 17 ---- app/general/migrations/0002_documentfile.py | 29 +++++++ app/general/tests/test_document_admin_file.py | 83 +++++++++++++++++++ app/general/tests/test_document_file.py | 4 + 5 files changed, 121 insertions(+), 22 deletions(-) create mode 100644 app/general/migrations/0002_documentfile.py create mode 100644 app/general/tests/test_document_admin_file.py diff --git a/app/general/admin.py b/app/general/admin.py index ac968b8d..2c861928 100644 --- a/app/general/admin.py +++ b/app/general/admin.py @@ -24,18 +24,18 @@ def clean(self): cleaned_data = super().clean() url = cleaned_data.get("url", "") uploaded_file = cleaned_data.get("uploaded_file", "") - cleaned_data["mime_type"] = ( - mimetypes.guess_type(uploaded_file.name)[0] if uploaded_file else "" - ) + + if cleaned_data["mime_type"] is not None: + cleaned_data["mime_type"] = ( + mimetypes.guess_type(uploaded_file.name)[0] if uploaded_file else "" + ) if not url and not uploaded_file: self.add_error("url", "Either URL or uploaded file must be provided.") self.add_error("uploaded_file", "Either URL or uploaded file must be provided.") if uploaded_file: - print("uploaded_file", uploaded_file.size) limit = 10 * 1024 * 1024 - print("limit", limit) if uploaded_file.size and uploaded_file.size > limit: self.add_error("uploaded_file", "File size must not exceed 10MB.") diff --git a/app/general/migrations/0001_initial.py b/app/general/migrations/0001_initial.py index e86ebac8..bb69dc54 100644 --- a/app/general/migrations/0001_initial.py +++ b/app/general/migrations/0001_initial.py @@ -1,4 +1,3 @@ -import django.core.validators import django.db.models.deletion from django.db import migrations, models @@ -51,20 +50,4 @@ class Migration(migrations.Migration): ('subjects', models.ManyToManyField(blank=True, to='general.subject')), ], ), - migrations.CreateModel( - name='DocumentFile', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(max_length=200)), - ('url', models.URLField(blank=True)), - ('uploaded_file', models.FileField(blank=True, help_text='Only PDF files are allowed.', upload_to='documents/', validators=[django.core.validators.FileExtensionValidator(['pdf'])])), - ('available', models.BooleanField(default=True)), - ('license', models.CharField(choices=[('MIT', 'MIT'), ('GNU', 'GNU'), ('Apache', 'Apache')], max_length=200)), - ('mime_type', models.CharField(blank=True, help_text='This input will auto-populate.', max_length=200)), - ('document_type', models.CharField(choices=[('Glossary', 'Glossary'), ('Translation', 'Translation')], max_length=200)), - ('Institution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='general.institution')), - ('languages', models.ManyToManyField(blank=True, to='general.language')), - ('subjects', models.ManyToManyField(blank=True, to='general.subject')), - ], - ), ] diff --git a/app/general/migrations/0002_documentfile.py b/app/general/migrations/0002_documentfile.py new file mode 100644 index 00000000..ca831d4b --- /dev/null +++ b/app/general/migrations/0002_documentfile.py @@ -0,0 +1,29 @@ +import django.core.validators +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('general', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='DocumentFile', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=200)), + ('url', models.URLField(blank=True)), + ('uploaded_file', models.FileField(blank=True, help_text='Only PDF files are allowed.', upload_to='documents/', validators=[django.core.validators.FileExtensionValidator(['pdf'])])), + ('available', models.BooleanField(default=True)), + ('license', models.CharField(choices=[('MIT', 'MIT'), ('GNU', 'GNU'), ('Apache', 'Apache')], max_length=200)), + ('mime_type', models.CharField(blank=True, help_text='This input will auto-populate.', max_length=200)), + ('document_type', models.CharField(choices=[('Glossary', 'Glossary'), ('Translation', 'Translation')], max_length=200)), + ('Institution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='general.institution')), + ('languages', models.ManyToManyField(blank=True, to='general.language')), + ('subjects', models.ManyToManyField(blank=True, to='general.subject')), + ], + ), + ] diff --git a/app/general/tests/test_document_admin_file.py b/app/general/tests/test_document_admin_file.py new file mode 100644 index 00000000..7bcc4820 --- /dev/null +++ b/app/general/tests/test_document_admin_file.py @@ -0,0 +1,83 @@ +import unittest +from unittest.mock import Mock + +from django.core.files.uploadedfile import SimpleUploadedFile + +from general.admin import DocumentFileForm +from general.models import Institution + + +class TestDocumentFileForm(unittest.TestCase): + def setUp(self): + self.file_mock = Mock(spec=SimpleUploadedFile) + self.file_mock.name = "test.pdf" + self.file_mock.size = 5242880 + + def test_clean_without_url_and_file(self): + tests_form = { + "title": "Test", + "license": "MIT", + "document_type": "Glossary", + "mime_type": "pdf", + "Institution": Institution.objects.create(name="Test Institution"), + "url": "", + "uploaded_file": "", + } + + form = DocumentFileForm(tests_form) + self.assertFalse(form.is_valid()) + self.assertEqual(form.errors["url"], ["Either URL or uploaded file must be provided."]) + self.assertEqual( + form.errors["uploaded_file"], ["Either URL or uploaded file must be provided."] + ) + + def test_clean_without_file(self): + tests_form = { + "title": "Test", + "license": "MIT", + "document_type": "Glossary", + "mime_type": "pdf", + "Institution": Institution.objects.create(name="Test Institution 2"), + "url": "www.example.com", + "uploaded_file": "", + } + + form = DocumentFileForm(tests_form) + self.assertTrue(form.is_valid()) + + def test_clean_without_url(self): + tests_form = { + "title": "Test", + "license": "MIT", + "document_type": "Glossary", + "mime_type": "pdf", + "Institution": Institution.objects.create(name="Test Institution 3"), + "url": "", + "uploaded_file": self.file_mock, + } + + form = DocumentFileForm(tests_form, files={"uploaded_file": self.file_mock}) + + self.assertTrue(form.is_valid()) + + def test_clean_with_large_file(self): + self.file_mock.size = 15728640 + + tests_form = { + "title": "Test", + "license": "MIT", + "document_type": "Glossary", + "mime_type": "pdf", + "Institution": Institution.objects.create(name="Test Institution 4"), + "url": "", + "uploaded_file": self.file_mock, + } + + form = DocumentFileForm(tests_form, files={"uploaded_file": self.file_mock}) + self.assertFalse(form.is_valid()) + self.assertIn("uploaded_file", form.errors) + self.assertEqual(form.errors["uploaded_file"], ["File size must not exceed 10MB."]) + + +if __name__ == "__main__": + unittest.main() diff --git a/app/general/tests/test_document_file.py b/app/general/tests/test_document_file.py index a77c6600..a076136f 100644 --- a/app/general/tests/test_document_file.py +++ b/app/general/tests/test_document_file.py @@ -44,6 +44,10 @@ def test_document_str_representation(self): # Test __str__ method def test_document_available_by_default(self): # Test default value self.assertTrue(self.document.available) + def tearDown(self): + if self.document.uploaded_file: + self.document.uploaded_file.delete() + if __name__ == "__main__": unittest.main() From 8feb1c1b720948cb5484376300450bf59b1ba0db Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Thu, 18 Apr 2024 12:19:50 +0200 Subject: [PATCH 044/240] Configure MEDIA_ settings for uploads This ensures that all uploads (e.g. documents and logos) are separate from code, regardless of `upload_to` attributes on FileFields. This also allows a single volume to be mounted for all uploads that won't also contain other project files. --- .gitignore | 1 + app/app/settings.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index 0a5d310e..ec66bba8 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ env.bak/ venv.bak/ app/static_files/ /app/documents/ +app/media/ diff --git a/app/app/settings.py b/app/app/settings.py index 65fe49b3..e83f4752 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -125,6 +125,11 @@ USE_TZ = True +# Media files (uploads) + +MEDIA_ROOT = BASE_DIR / "media" +MEDIA_URL = "media/" + # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.0/howto/static-files/ From e60c44e13a1e503563d7c3764b5303c8a4bff386 Mon Sep 17 00:00:00 2001 From: OnaMosimege Date: Fri, 19 Apr 2024 10:14:00 +0200 Subject: [PATCH 045/240] Change column sizes to 70/30 % for each institutions card --- app/static/css/styles.css | 23 ++++++++++++++++------- app/templates/app/institutions.html | 4 ++-- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/app/static/css/styles.css b/app/static/css/styles.css index e643f389..0cd5d4c7 100755 --- a/app/static/css/styles.css +++ b/app/static/css/styles.css @@ -195,8 +195,11 @@ body { width: 100%; padding-right: 10px; } - .col-sm-6 { - width: 50%; + .left-col { + width: 70%; + } + .right-col { + width: 30%; } .uni-card { width: 98%; @@ -231,8 +234,11 @@ body { width: 100%; padding-right: 10px; } - .col-sm-6 { - width: 50%; + .left-col { + width: 70%; + } + .right-col { + width: 30%; } } @@ -253,9 +259,12 @@ body { float: left; padding-right: 10px; } - .col-sm-6 { - width: 50%; - } + .left-col { + width: 70%; + } + .right-col { + width: 30%; + } .uni-card { margin: 10px; width: 100%; diff --git a/app/templates/app/institutions.html b/app/templates/app/institutions.html index bbdff528..378a9783 100644 --- a/app/templates/app/institutions.html +++ b/app/templates/app/institutions.html @@ -8,7 +8,7 @@
-
+
{{ institution.name }}

{{ institution.abbreviation }} @@ -18,7 +18,7 @@

{{ institution.name }}


-
+

Profile completion:

From 4242299121fd8a80a1a9f0d0ffc75090d1ce5f3e Mon Sep 17 00:00:00 2001 From: OnaMosimege Date: Fri, 19 Apr 2024 10:29:24 +0200 Subject: [PATCH 046/240] Order institutions in institutions comparison page alphabetically --- app/app/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/app/views.py b/app/app/views.py index be5917bb..72502033 100644 --- a/app/app/views.py +++ b/app/app/views.py @@ -21,7 +21,7 @@ def home(request): def institutions(request): template = "app/institutions.html" - institutions = Institution.objects.all() + institutions = Institution.objects.all().order_by("name").values() context = {"institutions_page": "active", "institutions": institutions} return render(request, template_name=template, context=context) From 85a2fa2cd662008bc53280ecee66467592780cb0 Mon Sep 17 00:00:00 2001 From: OnaMosimege Date: Fri, 19 Apr 2024 10:38:06 +0200 Subject: [PATCH 047/240] Make click on logo direct to home page, for landing site and admin site respectively --- app/templates/admin/base_site.html | 4 +++- app/templates/base.html | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/templates/admin/base_site.html b/app/templates/admin/base_site.html index 38b7f2a8..a5622bfb 100644 --- a/app/templates/admin/base_site.html +++ b/app/templates/admin/base_site.html @@ -16,7 +16,9 @@ {% block branding %}

- + + +

{% if user.is_anonymous %} {% include "admin/color_theme_toggle.html" %} diff --git a/app/templates/base.html b/app/templates/base.html index 441a9406..b04efe35 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -20,7 +20,9 @@

-
+
Developed by SADiLaR
From b3eb8364bbdeabb9e330ae7fa513ac23e70b3afb Mon Sep 17 00:00:00 2001 From: OnaMosimege Date: Tue, 23 Apr 2024 10:53:34 +0200 Subject: [PATCH 058/240] Add search page view and template: Add search page template, view, url, and styling --- app/app/urls.py | 1 + app/app/views.py | 7 +++++++ app/templates/app/search.html | 18 ++++++++++++++++++ app/templates/base.html | 2 +- 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 app/templates/app/search.html diff --git a/app/app/urls.py b/app/app/urls.py index 72b269cc..23a83302 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -30,6 +30,7 @@ path("_health/", views.health, name="health"), path("", views.home, name="home"), path("institutions/", views.institutions, name="institutions"), + path("search/", views.search, name="search"), ] handler400 = "app.views.error_400" diff --git a/app/app/views.py b/app/app/views.py index 72502033..13c44a52 100644 --- a/app/app/views.py +++ b/app/app/views.py @@ -27,6 +27,13 @@ def institutions(request): return render(request, template_name=template, context=context) +def search(request): + template = "app/search.html" + context = {"search_page": "active"} + + return render(request, template_name=template, context=context) + + def error_400(request, exception): template = "400.html" context = {} diff --git a/app/templates/app/search.html b/app/templates/app/search.html new file mode 100644 index 00000000..f4706562 --- /dev/null +++ b/app/templates/app/search.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} +{% load static %} + +{% block content %} + +
+
+
+
+
Search a term
+

+ Functionality of this page coming soon +

+
+
+
+ +{% endblock content %} diff --git a/app/templates/base.html b/app/templates/base.html index b04efe35..67fc5986 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -30,7 +30,7 @@ Home
From 8057fb12b1f5e03f9892cd95b3d2b51a8f7f4bf8 Mon Sep 17 00:00:00 2001 From: OnaMosimege Date: Fri, 3 May 2024 10:54:02 +0200 Subject: [PATCH 063/240] Change institution view to return context with project count, and display in template --- app/app/views.py | 9 ++++- app/general/tests/test_views_institution.py | 45 +++++++++++++++++++++ app/templates/app/institutions.html | 6 ++- 3 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 app/general/tests/test_views_institution.py diff --git a/app/app/views.py b/app/app/views.py index 7a360b19..dec78da5 100644 --- a/app/app/views.py +++ b/app/app/views.py @@ -1,3 +1,4 @@ +from django.db.models import Count from django.http import HttpResponse from django.shortcuts import render @@ -20,9 +21,13 @@ def home(request): def institutions(request): template = "app/institutions.html" + context = {} - institutions = Institution.objects.all().order_by("name").values() - context = {"current_page": "institutions", "institutions": institutions} + institutions = Institution.objects.annotate(project_count=Count("project")).order_by("name") + context = { + "current_page": "institutions", + "institutions": institutions, + } return render(request, template_name=template, context=context) diff --git a/app/general/tests/test_views_institution.py b/app/general/tests/test_views_institution.py new file mode 100644 index 00000000..71f27ba2 --- /dev/null +++ b/app/general/tests/test_views_institution.py @@ -0,0 +1,45 @@ +from django.test import Client, TestCase +from django.urls import reverse + +from general.models import Institution, Project + + +class InstitutionsViewTestCase(TestCase): + def setUp(self): + Institution.objects.create(name="Institution Banana") + Institution.objects.create(name="Institution Apple") + + inst1 = Institution.objects.get(name="Institution Banana") + Project.objects.create(name="Test Project 1", institution=inst1) + Project.objects.create(name="Test Project 2", institution=inst1) + + self.url = reverse("institutions") + + def test_institutions_view_correct_template_used(self): + response = self.client.get(self.url) + + self.assertTemplateUsed(response, "app/institutions.html") + + def test_institutions_view_correct_context_returned(self): + response = self.client.get(self.url) + + self.assertIn("current_page", response.context) + self.assertIn("institutions", response.context) + self.assertEqual(response.context["current_page"], "institutions") + + def test_institutions_view_correct_projects_returned(self): + response = self.client.get(self.url) + + institutions = response.context["institutions"] + self.assertEqual(len(institutions), 2) + self.assertTrue(all(hasattr(inst, "project_count") for inst in institutions)) + self.assertEqual(institutions[0].project_count, 0) + self.assertEqual(institutions[1].project_count, 2) + + def test_institutions_view_queries(self): + response = self.client.get(self.url) + + with self.assertNumQueries(1): + response = self.client.get(self.url) + + self.assertEqual(response.status_code, 200) diff --git a/app/templates/app/institutions.html b/app/templates/app/institutions.html index 378a9783..3a30bda0 100644 --- a/app/templates/app/institutions.html +++ b/app/templates/app/institutions.html @@ -14,7 +14,11 @@
{{ institution.name }}
{{ institution.abbreviation }}

- 4 applicable projects + {% if institution.project_count == 0 %} + no applicable project + {% else %} + {{institution.project_count}} applicable projects + {% endif %}


From 74760e41478b5bd2870508c56f2ce95f53d4d8d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9COMosimege=E2=80=9D?= <“onalerona.mosimege@gmail.com”> Date: Tue, 7 May 2024 08:41:32 +0200 Subject: [PATCH 064/240] Fix storage backend configuration to make static files work for view tests --- app/app/settings.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/app/settings.py b/app/app/settings.py index f8680adf..37a2fb35 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -135,6 +135,13 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.0/howto/static-files/ + +if DEBUG: + STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage" +else: + STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" + + STATIC_URL = "/static/" STATICFILES_DIRS = [ BASE_DIR / "static", @@ -146,7 +153,7 @@ "BACKEND": "django.core.files.storage.FileSystemStorage", }, "staticfiles": { - "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage", + "BACKEND": STATICFILES_STORAGE, }, } From 9c40100bac283a1e0d9fd6dbfa2bdb5e34c20c30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9COMosimege=E2=80=9D?= <“onalerona.mosimege@gmail.com”> Date: Tue, 7 May 2024 14:11:56 +0200 Subject: [PATCH 065/240] Variable name change for staticfiles storage, to a variable not used by django --- app/app/settings.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/app/settings.py b/app/app/settings.py index 37a2fb35..045e9306 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -137,9 +137,9 @@ if DEBUG: - STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage" + _STATICFILES_BACKEND = "django.contrib.staticfiles.storage.StaticFilesStorage" else: - STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" + _STATICFILES_BACKEND = "whitenoise.storage.CompressedManifestStaticFilesStorage" STATIC_URL = "/static/" @@ -153,7 +153,7 @@ "BACKEND": "django.core.files.storage.FileSystemStorage", }, "staticfiles": { - "BACKEND": STATICFILES_STORAGE, + "BACKEND": _STATICFILES_BACKEND, }, } From cfb9cd72beb26ea3c7cda2ae1acd39e44a76cc82 Mon Sep 17 00:00:00 2001 From: Jan-Rudolph Buhrmann Date: Tue, 7 May 2024 12:39:02 +0200 Subject: [PATCH 066/240] Create docker push action for main branch --- .github/workflows/{main.yml => develop.yml} | 4 +-- .github/workflows/production.yml | 30 +++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) rename .github/workflows/{main.yml => develop.yml} (96%) create mode 100644 .github/workflows/production.yml diff --git a/.github/workflows/main.yml b/.github/workflows/develop.yml similarity index 96% rename from .github/workflows/main.yml rename to .github/workflows/develop.yml index a1d1eeeb..b9f97e5d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/develop.yml @@ -1,5 +1,5 @@ # https://docs.docker.com/build/ci/github-actions/push-multi-registries/ -name: docker_push +name: docker_push_deploy_test on: workflow_dispatch: @@ -8,7 +8,7 @@ on: - "develop" jobs: - docker: + deploy_test: runs-on: ubuntu-latest steps: - name: Checkout diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml new file mode 100644 index 00000000..f1035361 --- /dev/null +++ b/.github/workflows/production.yml @@ -0,0 +1,30 @@ +# https://docs.docker.com/build/ci/github-actions/push-multi-registries/ +name: docker_push_prod + +on: + push: + tags: + - v** + +jobs: + docker_push: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to SADiLaR Container Registry + uses: docker/login-action@v3 + with: + registry: docker.sadilar.org + username: ${{ vars.SADILAR_DOCKER_REPOSITORY_USER }} + password: ${{ secrets.SADILAR_DOCKER_REPOSITORY_SECRET }} + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64 + push: true + tags: | + docker.sadilar.org/term_platform:${{ github.ref_name }} From 9ab5035fcfda53cca80991e6189a87e1e6eb6e96 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Mon, 29 Apr 2024 08:20:41 +0200 Subject: [PATCH 067/240] added basic logging and updated environment variables - added github actions testing - updated enviroment variables in settings - added tests for logging files - updated setting to check for testing --- .env.example | 11 ++++++ .env.testing | 11 ++++++ .github/workflows/testing.yml | 30 ++++++++++++++ .gitignore | 7 +++- Makefile | 8 ++++ README.md | 16 ++++++++ app/app/settings.py | 65 +++++++++++++++++++++++++++++++ app/general/tests/test_logging.py | 24 ++++++++++++ docker-compose.yml | 1 + requirements-test.txt | 3 ++ requirements.txt | 5 ++- 11 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 .env.example create mode 100644 .env.testing create mode 100644 .github/workflows/testing.yml create mode 100644 app/general/tests/test_logging.py create mode 100644 requirements-test.txt diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..c92bcb54 --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +SECRET_KEY='' +DEBUG=True +DB_HOST=db +DB_PORT=5432 +DB_NAME=term_db +DB_USER=sadilar +DB_PASSWORD=sadilar +LOGGING_FILE=debug.log +LOGGING_HANDLERS_LEVEL=INFO +LOGGING_LOGGERS_LEVEL=INFO +LOGGING_LOGGERS_DJANGO_LEVEL=INFO diff --git a/.env.testing b/.env.testing new file mode 100644 index 00000000..aa664bda --- /dev/null +++ b/.env.testing @@ -0,0 +1,11 @@ +SECRET_KEY='django-insecure-w!h85bp^$$e8gm%c23r!0%9i7yzd=6w$$s&ic+6!%306&kj8@k*5' +DEBUG=True +DB_HOST=db +DB_PORT=5432 +DB_NAME=term_db +DB_USER=sadilar +DB_PASSWORD=sadilar +LOGGING_FILE=debug.log +LOGGING_HANDLERS_LEVEL=INFO +LOGGING_LOGGERS_LEVEL=INFO +LOGGING_LOGGERS_DJANGO_LEVEL=INFO diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml new file mode 100644 index 00000000..8c1b06b8 --- /dev/null +++ b/.github/workflows/testing.yml @@ -0,0 +1,30 @@ +name: Testing Django +on: [ pull_request, push ] # activates the workflow when there is a push or pull request in the repo +jobs: + test_project: + runs-on: ubuntu-latest # operating system your code will run on + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-test.txt + - name: Run linting tools + run: | + cd app/ + ruff format . + - name: Create logging folder + run: | + sudo mkdir -p /logging + sudo chown runner:runner /logging + - name: Run Tests + run: | + cp .env.testing app/.env + cd app/ + mkdir -p static_files + python manage.py test + - name: Manager Check + run: | + cd app/ + python manage.py check diff --git a/.gitignore b/.gitignore index ec66bba8..627b4187 100644 --- a/.gitignore +++ b/.gitignore @@ -16,8 +16,7 @@ db.sqlite3 db.sqlite3-journal media -# macOS template -# General +# General Files .DS_Store .AppleDouble .LSOverride @@ -30,6 +29,10 @@ venv/ ENV/ env.bak/ venv.bak/ + +#folders app/static_files/ /app/documents/ app/media/ +/app/logging/ +/logging/ diff --git a/Makefile b/Makefile index 06e52de3..8dbea9fd 100644 --- a/Makefile +++ b/Makefile @@ -112,3 +112,11 @@ dev-quick-install: @make load-fixtures echo "Creating superuser" @make create-super-user + +docker-shell: + clear + docker exec -it sadilar-terminology-web bash + +check: + clear + @docker-compose run --rm web python manage.py check diff --git a/README.md b/README.md index d2f4ae1f..145b4bb5 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,24 @@ About the project: 3. Run `make run` to run the docker container 4. Run `make stop` to stop the docker container +## Production ### Plugins installed + #### Django Simple History https://django-simple-history.readthedocs.io/en/latest/ + +#### Basic setup for production + +### environment variables + +please use .env.example as example + + +## Production Information + +Docker Volumes for production: + +/media +/logging diff --git a/app/app/settings.py b/app/app/settings.py index f8680adf..d539c4b7 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -11,11 +11,17 @@ """ import os +import sys from pathlib import Path +import environ + # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent +# Take environment variables from .env file +environ.Env.read_env(os.path.join(BASE_DIR, ".env")) + # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ @@ -98,6 +104,9 @@ } } +if "test" in sys.argv or "test_coverage" in sys.argv: # Covers regular testing and django-coverage + DATABASES["default"]["ENGINE"] = "django.db.backends.sqlite3" + # Password validation # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators @@ -154,3 +163,59 @@ # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +LOGGING_FOLDER_DEFAULT = os.path.abspath(os.path.join("/logging/")) + +# Check if the application is under testing +if "test" in sys.argv: + DEBUG = False + +if DEBUG: + LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "handlers": { + "console": { + "class": "logging.StreamHandler", + }, + }, + "root": { + "handlers": ["console"], + "level": "DEBUG", + }, + } +else: + LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "handlers": { + "console": { + "class": "logging.StreamHandler", + }, + "file": { + "level": os.environ.get("LOGGING_HANDLERS_LEVEL", "WARNING"), + "class": "logging.FileHandler", + "filename": os.path.join( + LOGGING_FOLDER_DEFAULT, os.environ.get("LOGGING_FILE", "debug.log") + ), + "formatter": "verbose", + }, + }, + "root": { + "handlers": ["console", "file"], + "level": os.environ.get("LOGGING_LOGGERS_LEVEL", "WARNING"), + }, + "loggers": { + "django": { + "handlers": ["file"], + "level": os.environ.get("LOGGING_LOGGERS_DJANGO_LEVEL", "WARNING"), + "propagate": True, + }, + }, + "formatters": { + "verbose": { + "format": "{asctime} {levelname} - {name} {module}.py (line: {lineno:d}). - {message}", + "style": "{", + }, + }, + } diff --git a/app/general/tests/test_logging.py b/app/general/tests/test_logging.py new file mode 100644 index 00000000..00926204 --- /dev/null +++ b/app/general/tests/test_logging.py @@ -0,0 +1,24 @@ +import logging +import os +import sys + +from django.test import TestCase + + +class LoggingTest(TestCase): + def setUp(self): + LOGGING_FOLDER_DEFAULT = os.path.abspath(os.path.join("/logging/")) + self.logger = logging.getLogger("django") + self.log_file = os.path.join(LOGGING_FOLDER_DEFAULT, "debug.log") + + def test_log_file_created(self): + """Test if the log file is created.""" + self.logger.error("This is a test error message.") + + self.assertTrue(os.path.exists(self.log_file)) + + def test_log_message(self): + """Test if the log message is written to the file.""" + with open(self.log_file, "r") as f: + content = f.read() + self.assertIn("This is a test error message.", content) diff --git a/docker-compose.yml b/docker-compose.yml index 6e7c2f09..b0d5b2c0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,6 +22,7 @@ services: command: python manage.py runserver 0.0.0.0:8000 volumes: - ./app:/app + - ./logging:/logging ports: - "8000:8000" depends_on: diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 00000000..e00d50bd --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1,3 @@ +-r requirements.txt +django-extensions +ruff diff --git a/requirements.txt b/requirements.txt index 6c93a0f9..c2b69d85 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ django==5.0.2 -psycopg2-binary +django-environ +django-simple-history gunicorn +psycopg2-binary whitenoise -django-simple-history From 22bc5dce46146ed265c6c49c33a39b4b3003ac78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9COMosimege=E2=80=9D?= <“onalerona.mosimege@gmail.com”> Date: Wed, 8 May 2024 11:20:07 +0200 Subject: [PATCH 068/240] Add dark mode css file, and add to admin template --- app/static/css/admin.css | 16 +++++++++--- app/static/css/dark_mode_override.css | 37 +++++++++++++++++++++++++++ app/templates/admin/base_site.html | 2 +- 3 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 app/static/css/dark_mode_override.css diff --git a/app/static/css/admin.css b/app/static/css/admin.css index 0669673e..03c033e9 100644 --- a/app/static/css/admin.css +++ b/app/static/css/admin.css @@ -8,7 +8,10 @@ html[data-theme="light"], :root { --primary: #1a2f69; + --secondary: #b0b4d9; --primary-light: #8288bc; + --primary-black: #000000; + --primary-white: #fff; --header-color: var(--primary-light); --header-branding-color: var(--primary-light); @@ -47,9 +50,11 @@ a.section:link, a.section:visited { color: var(--link-fg); } -.theme-toggle svg.theme-icon-when-auto, .theme-toggle svg.theme-icon-when-dark, .theme-toggle svg.theme-icon-when-light { - fill: var(--link-fg); - color: #fff; +.theme-toggle svg.theme-icon-when-auto, +.theme-toggle svg.theme-icon-when-dark, +.theme-toggle svg.theme-icon-when-light { + fill: var(--primary-black); + color: var(--primary-white); } .header-title { @@ -61,3 +66,8 @@ a.section:link, a.section:visited { width: auto; height: 70px; } + + +.module h2, .module caption, .inline-group h2 { + color: var(--primary-white); +} diff --git a/app/static/css/dark_mode_override.css b/app/static/css/dark_mode_override.css new file mode 100644 index 00000000..66c95444 --- /dev/null +++ b/app/static/css/dark_mode_override.css @@ -0,0 +1,37 @@ +/* + Django admin styles + extended from https://github.com/django/django/blob/main/django/contrib/admin/static/admin/css/dark_mode.css + Remember to sync colours with front-end CSS. +*/ + +@media (prefers-color-scheme: dark) { + :root { + --primary: #1a2f69; + --secondary: #485d95; + --primary-black: #000000; + --primary-white: #fff; + + --link-hover-color: var(--primary-white); + + --link-fg: #81d4fa; + + --selected-row: #2d4481; + } + } + + +html[data-theme="dark"] { + --primary: #1a2f69; + --secondary: #485d95; + --primary-black: #000000; + --primary-white: #fff; + + --link-fg: #81d4fa; + --link-hover-color: var(--primary-white); + + --selected-row: #2d4481; +} + +#header a:link, #header a:visited, #logout-form button { + color: var(--primary-black); +} diff --git a/app/templates/admin/base_site.html b/app/templates/admin/base_site.html index 3fa9b3d7..5cfc3cf0 100644 --- a/app/templates/admin/base_site.html +++ b/app/templates/admin/base_site.html @@ -12,6 +12,7 @@ {% block extrastyle %} + {% endblock %} {% block branding %} @@ -24,4 +25,3 @@

{% include "admin/color_theme_toggle.html" %} {% endif %} {% endblock %} - From a4f13ba6a87542879d9651e0c4b9ad2920602909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9COMosimege=E2=80=9D?= <“onalerona.mosimege@gmail.com”> Date: Thu, 9 May 2024 17:20:04 +0200 Subject: [PATCH 069/240] changed colour of breadcrumbs and side bar table active headers --- app/static/css/admin.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/static/css/admin.css b/app/static/css/admin.css index 03c033e9..7f1be1ea 100644 --- a/app/static/css/admin.css +++ b/app/static/css/admin.css @@ -13,12 +13,12 @@ html[data-theme="light"], --primary-black: #000000; --primary-white: #fff; - --header-color: var(--primary-light); + --header-color: var(--primary-white); --header-branding-color: var(--primary-light); --header-bg: var(--primary); --header-link-color: #000000; - --breadcrumbs-bg: var(--primary-light); + --breadcrumbs-bg: var(--primary); --link-fg: var(--primary); --link-selected-fg: var(--primary); From 08bd89415e3791195f116f66bc315792de33d0ca Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Wed, 8 May 2024 13:45:33 +0200 Subject: [PATCH 070/240] added pillow and updated image inputs models - updated institution fixtures - updated project fixtures - updated institution templetes --- app/fixtures/institution.json | 50 +++++++++---------- app/fixtures/projects.json | 4 +- ...ter_institution_logo_alter_project_logo.py | 23 +++++++++ app/general/models.py | 4 +- app/static/css/styles.css | 2 +- app/templates/app/institutions.html | 5 +- requirements.txt | 1 + 7 files changed, 58 insertions(+), 31 deletions(-) create mode 100644 app/general/migrations/0005_alter_institution_logo_alter_project_logo.py diff --git a/app/fixtures/institution.json b/app/fixtures/institution.json index d6c38bcf..1337adb9 100644 --- a/app/fixtures/institution.json +++ b/app/fixtures/institution.json @@ -7,7 +7,7 @@ "abbreviation": "UNISA", "url": "https://www.unisa.ac.za/", "email": "email@example.dev", - "logo": "logo" + "logo": "" } }, { @@ -18,7 +18,7 @@ "abbreviation": "NWU", "url": "https://www.nwu.ac.za/", "email": "email@example.dev", - "logo": "logo" + "logo": "" } }, { @@ -29,7 +29,7 @@ "abbreviation": "UP", "url": "https://www.up.ac.za/", "email": "email@example.dev", - "logo": "logo" + "logo": "" } }, { @@ -40,7 +40,7 @@ "abbreviation": "UJ", "url": "https://www.uj.ac.za/", "email": "email@example.dev", - "logo": "logo" + "logo": "" } }, { @@ -51,7 +51,7 @@ "abbreviation": "UKZN", "url": "https://www.ukzn.ac.za/", "email": "email@example.dev", - "logo": "logo" + "logo": "" } }, { @@ -62,7 +62,7 @@ "abbreviation": "UFS", "url": "https://www.ufs.ac.za/", "email": "email@example.dev", - "logo": "logo" + "logo": "" } }, { @@ -73,7 +73,7 @@ "abbreviation": "CPUT", "url": "https://www.cput.ac.za/", "email": "email@example.dev", - "logo": "logo" + "logo": "" } }, { @@ -84,7 +84,7 @@ "abbreviation": "WITS", "url": "https://www.wits.ac.za/", "email": "email@example.dev", - "logo": "logo" + "logo": "" } }, { @@ -95,7 +95,7 @@ "abbreviation": "US", "url": "http://www.sun.ac.za/", "email": "email@example.dev", - "logo": "logo" + "logo": "" } }, { @@ -106,7 +106,7 @@ "abbreviation": "UCT", "url": "https://uct.ac.za/", "email": "email@example.dev", - "logo": "logo" + "logo": "" } }, { @@ -117,7 +117,7 @@ "abbreviation": "NMU", "url": "https://www.mandela.ac.za/", "email": "email@example.dev", - "logo": "logo" + "logo": "" } }, { @@ -128,7 +128,7 @@ "abbreviation": "WSU", "url": "https://www.wsu.ac.za/", "email": "email@example.dev", - "logo": "logo" + "logo": "" } }, { @@ -139,7 +139,7 @@ "abbreviation": "DUT", "url": "https://www.dut.ac.za/", "email": "email@example.dev", - "logo": "logo" + "logo": "" } }, { @@ -150,7 +150,7 @@ "abbreviation": "UL", "url": "https://www.ul.ac.za/", "email": "email@example.dev", - "logo": "logo" + "logo": "" } }, { @@ -161,7 +161,7 @@ "abbreviation": "VUT", "url": "https://www.vut.ac.za/", "email": "email@example.dev", - "logo": "logo" + "logo": "" } }, { @@ -172,7 +172,7 @@ "abbreviation": "UniZulu", "url": "https://www.unizulu.ac.za/", "email": "email@example.dev", - "logo": "logo" + "logo": "" } }, { @@ -183,7 +183,7 @@ "abbreviation": "UWC", "url": "https://www.uwc.ac.za/", "email": "email@example.dev", - "logo": "logo" + "logo": "" } }, { @@ -194,7 +194,7 @@ "abbreviation": "CUT", "url": "https://www.cut.ac.za/", "email": "email@example.dev", - "logo": "logo" + "logo": "" } }, { @@ -205,7 +205,7 @@ "abbreviation": "UFH", "url": "https://www.ufh.ac.za/", "email": "email@example.dev", - "logo": "logo" + "logo": "" } }, { @@ -216,7 +216,7 @@ "abbreviation": "RU", "url": "https://www.ru.ac.za/", "email": "email@example.dev", - "logo": "logo" + "logo": "" } }, { @@ -227,7 +227,7 @@ "abbreviation": "Venda", "url": "https://www.univen.ac.za/", "email": "email@example.dev", - "logo": "logo" + "logo": "" } }, { @@ -238,7 +238,7 @@ "abbreviation": "MUT", "url": "https://www.mut.ac.za/", "email": "email@example.dev", - "logo": "logo" + "logo": "" } }, { @@ -249,7 +249,7 @@ "abbreviation": "FMHSU", "url": "https://www.smu.ac.za/", "email": "email@example.dev", - "logo": "logo" + "logo": "" } }, { @@ -260,7 +260,7 @@ "abbreviation": "UMP", "url": "https://www.ump.ac.za/", "email": "email@example.dev", - "logo": "logo" + "logo": "" } }, { @@ -271,7 +271,7 @@ "abbreviation": "SPU", "url": "https://www.spu.ac.za/", "email": "email@example.dev", - "logo": "logo" + "logo": "" } } ] diff --git a/app/fixtures/projects.json b/app/fixtures/projects.json index 6c85eb2b..7f1874e0 100644 --- a/app/fixtures/projects.json +++ b/app/fixtures/projects.json @@ -5,7 +5,7 @@ "fields": { "name": "Study Centre", "url": "https://example.dev", - "logo": "logo.png", + "logo": "", "start_date": "2020-01-01", "end_date": "2020-01-01", "institution": 1 @@ -17,7 +17,7 @@ "fields": { "name": "Research Centre", "url": "https://example.dev", - "logo": "logo.png", + "logo": "", "start_date": "2020-01-01", "end_date": "2020-01-01", "institution": 2 diff --git a/app/general/migrations/0005_alter_institution_logo_alter_project_logo.py b/app/general/migrations/0005_alter_institution_logo_alter_project_logo.py new file mode 100644 index 00000000..aa039c46 --- /dev/null +++ b/app/general/migrations/0005_alter_institution_logo_alter_project_logo.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.2 on 2024-05-10 08:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('general', '0004_historicaldocumentfile_historicalinstitution_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='institution', + name='logo', + field=models.ImageField(blank=True, upload_to='institutions/logos/'), + ), + migrations.AlterField( + model_name='project', + name='logo', + field=models.ImageField(blank=True, upload_to='projects/logos/'), + ), + ] diff --git a/app/general/models.py b/app/general/models.py index 8de06076..930a8773 100644 --- a/app/general/models.py +++ b/app/general/models.py @@ -6,7 +6,7 @@ class Project(models.Model): name = models.CharField(max_length=200) url = models.URLField(max_length=200, blank=True, verbose_name="URL") - logo = models.FileField(upload_to="logos/", blank=True) + logo = models.ImageField(upload_to="projects/logos/", blank=True) start_date = models.DateField(blank=True, null=True) end_date = models.DateField(blank=True, null=True) institution = models.ForeignKey( @@ -27,7 +27,7 @@ class Institution(models.Model): abbreviation = models.CharField(max_length=200) url = models.URLField(max_length=200, blank=True, verbose_name="URL") email = models.EmailField(max_length=200, blank=True) - logo = models.FileField(upload_to="logos/", blank=True) + logo = models.ImageField(upload_to="institutions/logos/", blank=True) # added simple historical records to the model history = HistoricalRecords() diff --git a/app/static/css/styles.css b/app/static/css/styles.css index fae6824b..70d81857 100755 --- a/app/static/css/styles.css +++ b/app/static/css/styles.css @@ -142,7 +142,7 @@ html { } .uni-logo { - width: 50px; + width: auto; height: 50px; margin: 10px 0 0 0; } diff --git a/app/templates/app/institutions.html b/app/templates/app/institutions.html index 3a30bda0..39d99cdc 100644 --- a/app/templates/app/institutions.html +++ b/app/templates/app/institutions.html @@ -33,7 +33,10 @@
{{ institution.name }}


diff --git a/requirements.txt b/requirements.txt index c2b69d85..ebacdb41 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ django-simple-history gunicorn psycopg2-binary whitenoise +pillow From 0758587faae3732c0d1e51ec66e41114b3d8ec98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9COMosimege=E2=80=9D?= <“onalerona.mosimege@gmail.com”> Date: Tue, 14 May 2024 08:48:30 +0200 Subject: [PATCH 071/240] Changed some colours to assist with better lighthouse score --- app/static/css/admin.css | 2 +- app/static/css/dark_mode_override.css | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/static/css/admin.css b/app/static/css/admin.css index 7f1be1ea..727d08b6 100644 --- a/app/static/css/admin.css +++ b/app/static/css/admin.css @@ -26,7 +26,7 @@ html[data-theme="light"], --message-success-bg: #9fadd1; --message-warning-bg: var(--primary-light); - --selected-row: var(--primary-light); + --selected-row: var(--secondary); --button-bg: var(--primary); --button-hover-bg: #485D95; diff --git a/app/static/css/dark_mode_override.css b/app/static/css/dark_mode_override.css index 66c95444..aa594072 100644 --- a/app/static/css/dark_mode_override.css +++ b/app/static/css/dark_mode_override.css @@ -12,6 +12,7 @@ --primary-white: #fff; --link-hover-color: var(--primary-white); + --link-selected-fg: #6f94c6; --link-fg: #81d4fa; @@ -28,6 +29,7 @@ html[data-theme="dark"] { --link-fg: #81d4fa; --link-hover-color: var(--primary-white); + --link-selected-fg: #6f94c6; --selected-row: #2d4481; } From 13817ea86cade7da45efc8283f0682df6ea05897 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Tue, 14 May 2024 11:48:17 +0200 Subject: [PATCH 072/240] TERM-213: Copy for front page --- app/templates/app/home.html | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/app/templates/app/home.html b/app/templates/app/home.html index 4524d265..15732c27 100644 --- a/app/templates/app/home.html +++ b/app/templates/app/home.html @@ -7,12 +7,22 @@
-
About SADiLaR
+
Welcome

- South African Centre for Digital Language Resources + The South African Centre for Digital Language Resources (SADiLaR) is a national research + infrastructure that aims to ensure a digital future for our official languages. + It supports researchers in the fields of digital humanities and social sciences and also assists + institutions in their language implementation plans. + Read more about SADiLaR.

- Terminology Management System + SADiLaR conducted an audit at South African public higher education institutions that highlighted + areas that require attention. + Read the report. +

+

+ This platform highlights previous and current work at institutions to encourage collaboration and + disseminate resources.

@@ -24,11 +34,13 @@
About SADiLaR
Search

- Terminology Search + This platforms hosts terminology and other useful language resources.

- Click here to search a term + Search for a term or topic you are interested in. + (Functionality coming soon.)

+ {# TODO: create the search box right here so that no additional click is required. #}
@@ -40,7 +52,7 @@
Search

Profile completion:

@@ -31,7 +30,6 @@
{{ institution.name }}
-
{% endblock content %} diff --git a/docker-compose.yml b/docker-compose.yml index ece0836c..768de828 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -38,3 +38,4 @@ services: - DB_USER=sadilar # see POSTGRES_USER above - DB_PASSWORD=sadilar # see POSTGRES_PASSWORD above - TESTING_DIR=/app/general/tests/files/ + - FEATURE_FLAG=search_feature From c3befe75c3898525ee28d3066347771539428fed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9COMosimege=E2=80=9D?= <“onalerona.mosimege@gmail.com”> Date: Fri, 31 May 2024 09:34:57 +0200 Subject: [PATCH 090/240] Add language toggle, and styling: add languague toggle to base template, add 118n settings to url, add styling for toggle --- app/app/urls.py | 3 +- app/app/views.py | 5 +-- app/static/css/styles.css | 78 ++++++++++++++++++++++++++++++++++++++- app/templates/base.html | 26 +++++++++++-- 4 files changed, 101 insertions(+), 11 deletions(-) diff --git a/app/app/urls.py b/app/app/urls.py index 0e190377..8d8cd8d0 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -18,7 +18,7 @@ from django.conf import settings from django.conf.urls.static import static from django.contrib import admin -from django.urls import path +from django.urls import include, path from django.utils.translation import gettext_lazy as _ from . import views @@ -32,6 +32,7 @@ path("", views.home, name="home"), path("institutions/", views.institutions, name="institutions"), path("search/", views.search, name="search"), + path("i18n/", include("django.conf.urls.i18n")), ] if settings.DEBUG: diff --git a/app/app/views.py b/app/app/views.py index 0f9d47c8..fafada32 100644 --- a/app/app/views.py +++ b/app/app/views.py @@ -58,10 +58,7 @@ def institutions(request): institution_dict["project_count"] = institution.project_count institutions_array.append(institution_dict) - context = { - "current_page": "institutions", - "institutions": institutions_array, - } + context = {"current_page": "institutions", "institutions": institutions_array} return render(request, template_name=template, context=context) diff --git a/app/static/css/styles.css b/app/static/css/styles.css index c7ffaf86..668db5f0 100755 --- a/app/static/css/styles.css +++ b/app/static/css/styles.css @@ -22,12 +22,14 @@ html { } .footer { - color: white; + text-align: center; + padding: 1rem; background: var(--primary); + color: white; width: 100%; + overflow: hidden; position: absolute; - height: 60px; bottom: 0; } @@ -128,6 +130,35 @@ html { padding: 10px; } +.footer-class { + display: flex; + justify-content: center; + align-items: center; + position: relative; + flex-direction: column; +} + +.language-toggle { + margin-top: 1rem; +} + +.language-toggle form { + display: flex; + flex-direction: column; + align-items: center; +} + +.language-toggle select, +.language-toggle .btn-language { + margin-top: 0.5rem; +} + +.btn-language{ + color: var(--text-color); + outline-color: var(--primary-fg); + background-color: var(--primary-fg); + border-color: var(--primary-fg); +} /*Home page*/ .content-card { border-color: var(--primary-red); @@ -233,6 +264,28 @@ html { .section { padding-right: 20px; } + .footer-class { + flex-direction: row; + justify-content: center; + } + + .language-toggle { + margin-top: 0; + position: absolute; + right: 1rem; + } + + .language-toggle form { + display: flex; + flex-direction: row; + align-items: center; + } + + .language-toggle select, + .language-toggle .btn-language { + margin-left: 0.5rem; + margin-top: 0; + } /*Institutions page*/ .uni-card { @@ -259,7 +312,28 @@ html { .section { padding-right: 20px; } + .footer-class { + flex-direction: row; + justify-content: center; + } + + .language-toggle { + margin-top: 0; + position: absolute; + right: 1rem; + } + .language-toggle form { + display: flex; + flex-direction: row; + align-items: center; + } + + .language-toggle select, + .language-toggle .btn-language { + margin-left: 0.5rem; + margin-top: 0; + } /*Institutions page*/ .institution-card{ overflow: hidden; diff --git a/app/templates/base.html b/app/templates/base.html index f0e1f9c1..2272ac41 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -1,4 +1,5 @@ {% load static %} +{% load i18n %} @@ -61,11 +62,28 @@

-
-
- Developed by SADiLaR + +
+
+ {% csrf_token %} + + +
+
+
+
From b88f5d6266525baae86618f2f3302c1d072c5014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9COMosimege=E2=80=9D?= <“onalerona.mosimege@gmail.com”> Date: Thu, 6 Jun 2024 09:26:19 +0200 Subject: [PATCH 091/240] add translations wrapping to templates --- app/templates/400.html | 7 +++--- app/templates/403.html | 7 +++--- app/templates/404.html | 7 +++--- app/templates/500.html | 7 +++--- app/templates/app/home.html | 35 +++++++++++++++-------------- app/templates/app/institutions.html | 17 +++++++++----- app/templates/app/search.html | 31 ++++++++++++------------- app/templates/base.html | 14 ++++++------ app/templates/base_error.html | 3 ++- 9 files changed, 70 insertions(+), 58 deletions(-) diff --git a/app/templates/400.html b/app/templates/400.html index d861cf6c..f0becc31 100644 --- a/app/templates/400.html +++ b/app/templates/400.html @@ -1,11 +1,12 @@ {% extends "base_error.html" %} +{% load i18n %} {% block error_content %}
-
Bad Request (400)
-

The server cannot process the request due to client error.

- Go to home +
{% trans "Bad Request (400)" %}
+

{% trans "The server cannot process the request due to client error." %}"

+ {% trans "Go to home" %}
{% endblock error_content %} diff --git a/app/templates/403.html b/app/templates/403.html index 6da09393..817aebe9 100644 --- a/app/templates/403.html +++ b/app/templates/403.html @@ -1,11 +1,12 @@ {% extends "base_error.html" %} +{% load i18n %} {% block error_content %}
-
Forbidden (403)
-

You do not have permission to access on this server.

- Go to home +
{% trans "Forbidden (403)" %}
+

{% trans "You do not have permission to access on this server." %}

+ {% trans "Go to home" %}
{% endblock error_content %} diff --git a/app/templates/404.html b/app/templates/404.html index a13b12a4..35273803 100644 --- a/app/templates/404.html +++ b/app/templates/404.html @@ -1,11 +1,12 @@ {% extends "base_error.html" %} +{% load i18n %} {% block error_content %}
-
Not Found (404)
-

The requested resource was not found on this server.

- Go to home +
{% trans "Not Found (404)" %}
+

{% trans "The requested resource was not found on this server." %}

+ {% trans "Go to home" %}
{% endblock error_content %} diff --git a/app/templates/500.html b/app/templates/500.html index f314b680..577db847 100644 --- a/app/templates/500.html +++ b/app/templates/500.html @@ -1,11 +1,12 @@ {% extends "base_error.html" %} +{% load i18n %} {% block error_content %}
-
Internal Server Error (500)
-

The server encountered an unexpected condition that prevented it from fulfilling the request.

- Go to home +
{% trans "Internal Server Error (500)" %}
+

{% trans "The server encountered an unexpected condition that prevented it from fulfilling the request." %}

+ {% trans "Go to home" %}
{% endblock error_content %} diff --git a/app/templates/app/home.html b/app/templates/app/home.html index b051858f..51f7d962 100644 --- a/app/templates/app/home.html +++ b/app/templates/app/home.html @@ -1,5 +1,6 @@ {% extends "base.html" %} {% load static %} +{% load i18n %} {% block content %} @@ -7,22 +8,22 @@
-
Welcome
+
{% trans "Welcome" %}

- The South African Centre for Digital Language Resources (SADiLaR) is a national research + {% blocktrans %}The South African Centre for Digital Language Resources (SADiLaR) is a national research infrastructure that aims to ensure a digital future for our official languages. It supports researchers in the fields of digital humanities and social sciences and also assists - institutions in their language implementation plans. - Read more about SADiLaR. + institutions in their language implementation plans.{% endblocktrans %} + {% blocktrans %}Read more about SADiLaR.{% endblocktrans %}

- SADiLaR conducted an audit at South African public higher education institutions that highlighted - areas that require attention. - Read the report. + {% blocktrans %}SADiLaR conducted an audit at South African public higher education institutions that highlighted + areas that require attention.{% endblocktrans %} + {% blocktrans %}Read the report.{% endblocktrans %}

- This platform highlights previous and current work at institutions to encourage collaboration and - disseminate resources. + {% blocktrans %}This platform highlights previous and current work at institutions to encourage collaboration and + disseminate resources.{% endblocktrans %}

@@ -32,17 +33,17 @@
Welcome
-
Search
+
{% trans "Search" %}

- This platforms hosts terminology and other useful language resources. + {% blocktrans %}This platforms hosts terminology and other useful language resources.{% endblocktrans %}

- Search for a term or topic you are interested in. - (Functionality coming soon.) + {% blocktrans %}Search for a term or topic you are interested in. + (Functionality coming soon.){% endblocktrans %}

{# TODO: create the search box right here so that no additional click is required. #} - Search a term + {% trans "Search a term" %}
@@ -52,12 +53,12 @@
Search
-
Institutions
+
{% trans "Institutions" %}

- Browse information by institution. + {% trans "Browse information by institution." %}

- View + {% trans "View" %}
diff --git a/app/templates/app/institutions.html b/app/templates/app/institutions.html index 03aaee39..c0247b5f 100644 --- a/app/templates/app/institutions.html +++ b/app/templates/app/institutions.html @@ -1,5 +1,6 @@ {% extends "base.html" %} {% load static %} +{% load i18n %} {% block content %} @@ -14,15 +15,19 @@
{{ institution.name }}
{{ institution.abbreviation }}

- {% if institution.project_count == 0 %} - no applicable project - {% else %} - {{institution.project_count}} applicable projects - {% endif %} + {% if institution.project_count == 0 %} + {% blocktrans %}no applicable project{% endblocktrans %} + {% else %} + {% blocktrans count project_count=institution.project_count %} + {{ project_count }} applicable project + {% plural %} + {{ project_count }} applicable projects + {% endblocktrans %} + {% endif %}

-

Profile completion:

+

{% trans "Profile completion:" %}

{% if institution.rating <= 20 %} diff --git a/app/templates/app/search.html b/app/templates/app/search.html index 94498f18..55264498 100644 --- a/app/templates/app/search.html +++ b/app/templates/app/search.html @@ -1,5 +1,6 @@ {% extends "base.html" %} {% load static %} +{% load i18n %} {% block content %} @@ -7,19 +8,19 @@
-
Search a term
+
{% trans "Search a term" %}
{% if feature_flag == "search_feature" %} {# test section start#}
- +
- Results: + {% trans "Results:" %} {{ documents|length }}
@@ -27,31 +28,31 @@
Search a term
  • - Title: + {% trans "Title:" %} {{ document.title }}
  • - Institution: + {% trans "Institution:" %} {{ document.institution }}
  • - Headline: + {% trans "Headline:" %} {{ document.search_headline|safe }}
  • - File: + {% trans "File:" %} {{ document.uploaded_file }}
  • - License: + {% trans "License:" %} {{ document.license }}
  • - Mime Type: + {% trans "Mime Type:" %} {{ document.mime_type }}
  • - Rank: + {% trans "Rank:" %} {{ document.rank }}
@@ -61,15 +62,15 @@
Search a term
{# test section end#} {% else %} -

Functionality of this page coming soon

+

{% trans "Functionality of this page coming soon" %}

{% endif %}
diff --git a/app/templates/base.html b/app/templates/base.html index 2272ac41..42acde4f 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -4,9 +4,9 @@ - SADiLaR - - + {% trans "SADiLaR" %} + + @@ -30,23 +30,23 @@
diff --git a/app/templates/base_error.html b/app/templates/base_error.html index 18aaf041..ade2424f 100644 --- a/app/templates/base_error.html +++ b/app/templates/base_error.html @@ -1,11 +1,12 @@ {% extends "base.html" %} +{% load i18n %} {% block content %}
- Error + {% trans "Error" %}
{% block error_content %} From a59bc0d4b2533e78a669652e7adf6d9394969b27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9COMosimege=E2=80=9D?= <“onalerona.mosimege@gmail.com”> Date: Thu, 6 Jun 2024 14:33:47 +0200 Subject: [PATCH 092/240] add plural blocktrans in one line --- app/templates/app/institutions.html | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/app/templates/app/institutions.html b/app/templates/app/institutions.html index c0247b5f..4973269b 100644 --- a/app/templates/app/institutions.html +++ b/app/templates/app/institutions.html @@ -15,14 +15,9 @@
{{ institution.name }}
{{ institution.abbreviation }}

- {% if institution.project_count == 0 %} - {% blocktrans %}no applicable project{% endblocktrans %} + {% if institution.project_count == 0 %}{% blocktrans %}no applicable project{% endblocktrans %} {% else %} - {% blocktrans count project_count=institution.project_count %} - {{ project_count }} applicable project - {% plural %} - {{ project_count }} applicable projects - {% endblocktrans %} + {% blocktrans count project_count=institution.project_count %}{{ project_count }} applicable project{% plural %}{{ project_count }} applicable projects{% endblocktrans %} {% endif %}

From 1f4eb38a8dbf0e2aef6ddb6a8b112f55aebc9f6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9COMosimege=E2=80=9D?= <“onalerona.mosimege@gmail.com”> Date: Thu, 6 Jun 2024 15:21:36 +0200 Subject: [PATCH 093/240] remove on change event for language toggle --- app/templates/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/templates/base.html b/app/templates/base.html index 42acde4f..cf43ac7a 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -70,7 +70,7 @@
{% csrf_token %} - {% get_current_language as LANGUAGE_CODE %} {% get_available_languages as LANGUAGES %} {% for lang in LANGUAGES %} From caeda5684a5a6c1603d5f2db4df8a6cbb9f80057 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Mon, 3 Jun 2024 14:05:39 +0200 Subject: [PATCH 094/240] added mass pdf upload command for testing - added faker package - updated readme file - added new tests --- .gitignore | 2 + Makefile | 6 + README.md | 2 + app/general/management/__init__.py | 0 app/general/management/commands/__init__.py | 0 .../commands/dev_pdf_mass_upload.py | 107 ++++++++++++++++++ app/general/service/__init__.py | 0 app/general/service/extract_text.py | 27 +++++ app/general/tests/test_dev_mass_upload.py | 65 +++++++++++ .../tests/test_extract_text_service.py | 30 +++++ docker-compose.yml | 2 + requirements-dev.txt | 1 + requirements-test.txt | 1 + 13 files changed, 243 insertions(+) create mode 100644 app/general/management/__init__.py create mode 100644 app/general/management/commands/__init__.py create mode 100644 app/general/management/commands/dev_pdf_mass_upload.py create mode 100644 app/general/service/__init__.py create mode 100644 app/general/service/extract_text.py create mode 100644 app/general/tests/test_dev_mass_upload.py create mode 100644 app/general/tests/test_extract_text_service.py diff --git a/.gitignore b/.gitignore index f105c0e7..168936e7 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,5 @@ app/static_files/ app/media/ /app/logging/ /logging/ +/pdf_uploads/ +/pdf_upload_completed/ diff --git a/Makefile b/Makefile index dadae41d..a7ff7007 100644 --- a/Makefile +++ b/Makefile @@ -20,8 +20,11 @@ list: @echo "ruff-fix - Run ruff check --fix" @echo "pre-commit-install - Install pre-commit" @echo "dev-quick-install - Run all the necessary commands to start the project" + @echo "dev-mass-pdf-upload - Run command to upload all pdf files in the media folder" @echo "make-messages - Run command to ensure translation .po files are created" @echo "compile-messages - Run command to ensure translation .mo files are created" + @echo "docker-shell - Access the container shell" + @echo "check - Run the Django check command" up: @docker compose up @@ -93,6 +96,9 @@ dev-quick-install: echo "Creating superuser" @make create-super-user +dev-mass-pdf-upload: + @docker compose run --rm web python manage.py dev_pdf_mass_upload + docker-shell: docker exec -it sadilar-terminology-web bash diff --git a/README.md b/README.md index f3b66e7f..5340f57e 100644 --- a/README.md +++ b/README.md @@ -56,3 +56,5 @@ Docker Volumes for production: * /media * /logging +* /pdf_uploads +* /pdf_upload_completed diff --git a/app/general/management/__init__.py b/app/general/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/general/management/commands/__init__.py b/app/general/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/general/management/commands/dev_pdf_mass_upload.py b/app/general/management/commands/dev_pdf_mass_upload.py new file mode 100644 index 00000000..3dbc696e --- /dev/null +++ b/app/general/management/commands/dev_pdf_mass_upload.py @@ -0,0 +1,107 @@ +import os +import random +import shutil + +import magic +from django.core.files.base import ContentFile +from django.core.management.base import BaseCommand + +from general.models import DocumentFile +from general.service.extract_text import GetTextError, GetTextFromPDF + + +class Command(BaseCommand): + help = "Mass PDF uploader for testing purposes." + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.dir_main = "/pdf_uploads/" + self.dir_completed = "/pdf_upload_completed/completed/" + self.dir_error = "/pdf_upload_completed/error/" + + def handle(self, *args, **options): + os.system("clear") + print("Mass file uploader for testing purposes.") + + self.create_directory(self.dir_completed) + self.create_directory(self.dir_error) + + for root, dirs, files in os.walk(self.dir_main): + for file in files: + file_path = os.path.join(root, file) + + # Check if the file is a PDF file and save the data + self.handle_file(file_path, file) + + def handle_file(self, file_path, file): + # Get the file type + file_type = magic.from_file(file_path, mime=True) + + # Check if the file is a PDF file + directory = self.check_file_type(file_type) + self.print_pdf_file(file) + + # If file is a PDF file it saves the data and moves the file to the completed directory + if directory: + data = { + "title": file.strip(), + "file": file.strip(), + "uploaded_file": file_path, + } + # Save the data to the database and uploads the file + self.save_data(data) + + # Move the file to the completed directory + self.move_file(file_path, file, directory) + + # If the file is not a PDF file, print an error message and move the file to the error directory + else: + self.print_error() + # Move the file to the error directory + self.move_file(file_path, file, self.dir_error) + + def check_file_type(self, file_type): + return self.dir_completed if file_type == "application/pdf" else None + + def move_file(self, file_path, file, directory): + if not os.path.isfile(directory + file): + shutil.move(file_path, directory) + else: + print( + f"The file '{os.path.basename(directory + file)}' already exists in the destination directory." + ) + + def print_pdf_file(self, file): + print("\n") + print("\033[92m" + file + "\033[0m") + + def print_error(self): + print("\n") + print("\033[91m" + "Only PDF files are allowed" + "\033[0m") + + def save_data(self, data): + # Generate a random number for the institution ID + random_number = random.randint(1, 20) + content_file = ContentFile(data["uploaded_file"], name=data["title"]) + + try: + document_data = GetTextFromPDF(data["uploaded_file"]).to_text() + + instance = DocumentFile( + title=data["title"], + document_data=document_data, # Scraps the PDF file and extracts the text + uploaded_file=content_file, + document_type="Glossary", + institution_id=random_number, + ) + instance.save() + + except GetTextError as e: + print(f"Error: {e}") + return + + def create_directory(self, directory): + try: + os.makedirs(directory, exist_ok=True) + except OSError as error: + print(f"Directory '{directory}' can not be created. Error: {error}") diff --git a/app/general/service/__init__.py b/app/general/service/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/general/service/extract_text.py b/app/general/service/extract_text.py new file mode 100644 index 00000000..2da9a455 --- /dev/null +++ b/app/general/service/extract_text.py @@ -0,0 +1,27 @@ +from pypdf import PdfReader +from pypdf.errors import PdfStreamError + + +class GetTextError(Exception): + pass + + +class GetTextFromPDF: + def __init__(self, uploaded_file): + self.uploaded_file = uploaded_file + + def to_text(self): + if self.uploaded_file: + text_list = [] + # Read the PDF file and extract text + try: + reader = PdfReader(self.uploaded_file) + for page in reader.pages: + text_list.append(page.extract_text()) + + get_pdf_text = " ".join(text_list) + + return str(get_pdf_text) + + except PdfStreamError: + raise GetTextError("The uploaded PDF file is corrupted or not fully downloaded.") diff --git a/app/general/tests/test_dev_mass_upload.py b/app/general/tests/test_dev_mass_upload.py new file mode 100644 index 00000000..3a0a8055 --- /dev/null +++ b/app/general/tests/test_dev_mass_upload.py @@ -0,0 +1,65 @@ +import os +import unittest +from unittest.mock import MagicMock + +from faker import Faker + +from general.management.commands.dev_pdf_mass_upload import Command +from general.models import DocumentFile, Institution + + +class TestHandleFile(unittest.TestCase): + def setUp(self): + self.command = Command() + self.command.check_file_type = MagicMock() + self.command.move_file = MagicMock() + self.command.print_error = MagicMock() + self.command.print_pdf_file = MagicMock() + self.command.save_data = MagicMock() + self.test_dir = os.getenv("TESTING_DIR", "/app/general/tests/files") + self.test_file = self.test_dir + "Lorem.pdf" + self.fake = Faker() + + def test_handle_file_pdf(self): + self.command.check_file_type.return_value = self.test_dir + self.command.handle_file(self.test_file, self.test_file) + self.command.check_file_type.assert_called_once() + self.command.move_file.assert_called_once() + self.command.save_data.assert_called_once() + self.command.print_pdf_file.assert_called_once() + self.command.print_error.assert_not_called() + + def test_handle_file_non_pdf(self): + self.command.check_file_type.return_value = None + self.command.handle_file(self.test_file, self.test_file) + self.command.check_file_type.assert_called_once() + self.command.move_file.assert_called_once() + self.command.save_data.assert_not_called() + self.command.print_pdf_file.assert_called_once() + self.command.print_error.assert_called_once() + + def test_check_file_type_pdf(self): + self.assertNotEqual(self.command.check_file_type("application/pdf"), self.test_dir) + + def test_save_data(self): + self.command = Command() + # Create some Institutions instances for testing + for _ in range(20): + Institution.objects.create( + name=self.fake.company(), + abbreviation=self.fake.company_suffix(), + url=self.fake.url(), + email=self.fake.company_email(), + logo="", + ) + + data = { + "title": "Test file", + "file": "Test file", + "uploaded_file": self.test_file, + } + + self.command.save_data(data) + + document_file = DocumentFile.objects.get(title="Test file") + self.assertEqual(document_file.title, "Test file") diff --git a/app/general/tests/test_extract_text_service.py b/app/general/tests/test_extract_text_service.py new file mode 100644 index 00000000..c15b3b35 --- /dev/null +++ b/app/general/tests/test_extract_text_service.py @@ -0,0 +1,30 @@ +import os +import unittest + +from general.service.extract_text import GetTextFromPDF + + +class TestExtractTextService(unittest.TestCase): + def setUp(self): + test_dir = os.getenv("TESTING_DIR", "/app/general/tests/files") + self.file_mock = test_dir + "/Lorem.pdf" + + def test_in_text(self): + with open(self.file_mock, "rb") as file: + pypdf = GetTextFromPDF(file) + + result = pypdf.to_text().strip() + + words = result.split() + + self.assertIn("turpis.", words) + + def test_not_in_text(self): + with open(self.file_mock, "rb") as file: + pypdf = GetTextFromPDF(file) + + result = pypdf.to_text().strip() + + words = result.split() + + self.assertNotIn("notintext.", words) diff --git a/docker-compose.yml b/docker-compose.yml index 768de828..8de03968 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,6 +23,8 @@ services: volumes: - ./app:/app - ./logging:/logging + - ./pdf_uploads:/pdf_uploads + - ./pdf_upload_completed:/pdf_upload_completed ports: - "8000:8000" depends_on: diff --git a/requirements-dev.txt b/requirements-dev.txt index 04cd5276..eb0338c8 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,3 +2,4 @@ django-extensions pygraphviz ruff +faker diff --git a/requirements-test.txt b/requirements-test.txt index e00d50bd..d94fe908 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,3 +1,4 @@ -r requirements.txt django-extensions ruff +faker From 589ca5042c4c2e24d8ecb1fc893acaabd3dbf876 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Mon, 3 Jun 2024 08:46:02 +0200 Subject: [PATCH 095/240] added debug tool --- app/app/settings.py | 23 +++++++++++++++++++++-- app/app/urls.py | 1 + docker-compose.yml | 2 ++ requirements-dev.txt | 1 + requirements-test.txt | 1 + 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/app/app/settings.py b/app/app/settings.py index 3439c3e8..26b59e85 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -48,9 +48,12 @@ "general", "simple_history", ] + +# Add django-extensions to the installed apps if DEBUG is True if DEBUG: INSTALLED_APPS += [ "django_extensions", + "debug_toolbar", ] AUTH_USER_MODEL = "users.CustomUser" @@ -68,6 +71,10 @@ "django.middleware.locale.LocaleMiddleware", ] +# Add debug toolbar middleware +if DEBUG: + MIDDLEWARE.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware") + ROOT_URLCONF = "app.urls" TEMPLATES = [ @@ -104,6 +111,18 @@ } } +# toolbar settings +if DEBUG: + DEBUG_TOOLBAR_CONFIG = { + "IS_RUNNING_TESTS": False, + "SHOW_TOOLBAR_CALLBACK": lambda request: DEBUG, + } + + INTERNAL_IPS = [ + "host.docker.internal", + ] + +# Check if the application is under testing if "test" in sys.argv or "test_coverage" in sys.argv: # Covers regular testing and django-coverage DATABASES["default"]["ENGINE"] = "django.db.backends.sqlite3" @@ -144,13 +163,12 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.0/howto/static-files/ - +# Static files (CSS, JavaScript, Images) if DEBUG: _STATICFILES_BACKEND = "django.contrib.staticfiles.storage.StaticFilesStorage" else: _STATICFILES_BACKEND = "whitenoise.storage.CompressedManifestStaticFilesStorage" - STATIC_URL = "/static/" STATICFILES_DIRS = [ BASE_DIR / "static", @@ -192,6 +210,7 @@ if "test" in sys.argv: DEBUG = False +# Logging configuration if DEBUG: LOGGING = { "version": 1, diff --git a/app/app/urls.py b/app/app/urls.py index 8d8cd8d0..ff50810b 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -37,3 +37,4 @@ if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + urlpatterns.append(path("__debug__/", include("debug_toolbar.urls"))) diff --git a/docker-compose.yml b/docker-compose.yml index 8de03968..07be97f8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,6 +29,8 @@ services: - "8000:8000" depends_on: - db + extra_hosts: + - "host.docker.internal:host-gateway" environment: # TODO: dollar signs in the KEY are interpolated, therefore escaped here # with double "$$". Maybe move to .env file to simplify? diff --git a/requirements-dev.txt b/requirements-dev.txt index eb0338c8..429d6706 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,5 @@ -r requirements.txt +django-debug-toolbar django-extensions pygraphviz ruff diff --git a/requirements-test.txt b/requirements-test.txt index d94fe908..362c8669 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,4 +1,5 @@ -r requirements.txt +django-debug-toolbar django-extensions ruff faker From b072bff9ca93080d657cd21b86acbc1b7f054e5c Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Fri, 7 Jun 2024 13:36:33 +0200 Subject: [PATCH 096/240] Handle unexpected pypdf errors better Any error in pypdf crashes the bulk import command instead of just skipping the problematic file. E.g. Traceback (most recent call last): ... File "/app/general/service/extract_text.py", line 18, in to_text reader = PdfReader(self.uploaded_file) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.12/site-packages/pypdf/_reader.py", line 127, in __init__ self.read(stream) File "/usr/local/lib/python3.12/site-packages/pypdf/_reader.py", line 543, in read xref_issue_nr = self._get_xref_issues(stream, startxref) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.12/site-packages/pypdf/_reader.py", line 945, in _get_xref_issues stream.seek(startxref - 1, 0) # -1 to check character before ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ValueError: negative seek value -1 --- app/general/service/extract_text.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/general/service/extract_text.py b/app/general/service/extract_text.py index 2da9a455..613ce038 100644 --- a/app/general/service/extract_text.py +++ b/app/general/service/extract_text.py @@ -25,3 +25,5 @@ def to_text(self): except PdfStreamError: raise GetTextError("The uploaded PDF file is corrupted or not fully downloaded.") + except Exception: + raise GetTextError("Error during text extraction from PDF file.") From 0af24e726a6cd320fb7c6caba3603408ecd28e34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9COMosimege=E2=80=9D?= <“onalerona.mosimege@gmail.com”> Date: Mon, 10 Jun 2024 09:34:45 +0200 Subject: [PATCH 097/240] Change nav pill active colour --- app/static/css/styles.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/static/css/styles.css b/app/static/css/styles.css index 668db5f0..9ce1f73d 100755 --- a/app/static/css/styles.css +++ b/app/static/css/styles.css @@ -73,7 +73,7 @@ html { .nav > li > a:active{ margin-top: 15px; background-color: var(--primary); - color: var(--primary); + color: var(--primary-fg); } .btn-primary{ From de0746e61e364b5713d42e99b2d0293042b65055 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Mon, 10 Jun 2024 10:22:40 +0200 Subject: [PATCH 098/240] fixed issue with uploading data and saving --- app/general/management/commands/dev_pdf_mass_upload.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/general/management/commands/dev_pdf_mass_upload.py b/app/general/management/commands/dev_pdf_mass_upload.py index 3dbc696e..cb81cf5d 100644 --- a/app/general/management/commands/dev_pdf_mass_upload.py +++ b/app/general/management/commands/dev_pdf_mass_upload.py @@ -82,7 +82,12 @@ def print_error(self): def save_data(self, data): # Generate a random number for the institution ID random_number = random.randint(1, 20) - content_file = ContentFile(data["uploaded_file"], name=data["title"]) + + # Read the uploaded file data + with open(data["uploaded_file"], "rb") as f: + get_content_file = f.read() + + content_file = ContentFile(get_content_file, name=data["title"]) try: document_data = GetTextFromPDF(data["uploaded_file"]).to_text() From 9c60c3c95cb5314bb510c02cdeba8c758ffca360 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Fri, 7 Jun 2024 14:01:27 +0200 Subject: [PATCH 099/240] updated admin with GetTextFromPdf service - removed pdf_to_text --- app/general/admin.py | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/app/general/admin.py b/app/general/admin.py index e91dd51f..a9b55bd0 100644 --- a/app/general/admin.py +++ b/app/general/admin.py @@ -1,10 +1,10 @@ import magic from django.contrib import admin from django.forms import HiddenInput, ModelForm -from pypdf import PdfReader -from pypdf.errors import PdfStreamError from simple_history.admin import SimpleHistoryAdmin +from general.service.extract_text import GetTextError, GetTextFromPDF + from .models import DocumentFile, Institution, Language, Project, Subject @@ -34,8 +34,14 @@ def clean(self): if file_type != "application/pdf": self.add_error("uploaded_file", "Only PDF files are allowed.") - # Extract text from PDF file - cleaned_data["document_data"] = self.pdf_to_text(uploaded_file) + try: + # Extract text from PDF file + cleaned_data["document_data"] = GetTextFromPDF(uploaded_file).to_text() + + except GetTextError: + return self.add_error( + "uploaded_file", "The uploaded PDF file is corrupted or not fully downloaded." + ) cleaned_data["mime_type"] = file_type @@ -52,25 +58,6 @@ def clean(self): return cleaned_data - def pdf_to_text(self, uploaded_file): - if uploaded_file: - text_list = [] - # Read the PDF file and extract text - try: - reader = PdfReader(uploaded_file) - for page in reader.pages: - text_list.append(page.extract_text()) - - get_pdf_text = " ".join(text_list) - - return str(get_pdf_text) - - except PdfStreamError: - return self.add_error( - "uploaded_file", "The uploaded PDF file is corrupted or not fully downloaded." - ) - return None - class DocumentFileAdmin(SimpleHistoryAdmin): ordering = ["title"] From e50c1478be658a8093e85663151d0c79e1bed3ad Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Tue, 11 Jun 2024 10:35:09 +0200 Subject: [PATCH 100/240] added description input to documentfile and project - updated tests --- .../0008_documentfile_description_and_more.py | 39 +++++++++++++++++++ app/general/models.py | 2 + app/general/tests/test_document_admin_file.py | 4 ++ 3 files changed, 45 insertions(+) create mode 100644 app/general/migrations/0008_documentfile_description_and_more.py diff --git a/app/general/migrations/0008_documentfile_description_and_more.py b/app/general/migrations/0008_documentfile_description_and_more.py new file mode 100644 index 00000000..b6aa2fd4 --- /dev/null +++ b/app/general/migrations/0008_documentfile_description_and_more.py @@ -0,0 +1,39 @@ +# Generated by Django 5.0.2 on 2024-06-11 08:21 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('general', '0007_alter_documentfile_options_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='documentfile', + name='description', + field=models.TextField(blank=True, verbose_name='description'), + ), + migrations.AddField( + model_name='historicaldocumentfile', + name='description', + field=models.TextField(blank=True, verbose_name='description'), + ), + migrations.AddField( + model_name='historicalproject', + name='description', + field=models.TextField(blank=True, verbose_name='description'), + ), + migrations.AddField( + model_name='project', + name='description', + field=models.TextField(blank=True, verbose_name='description'), + ), + migrations.AlterField( + model_name='project', + name='institution', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='general.institution', verbose_name='institution'), + ), + ] diff --git a/app/general/models.py b/app/general/models.py index 010221bc..e2d23cdf 100644 --- a/app/general/models.py +++ b/app/general/models.py @@ -6,6 +6,7 @@ class Project(models.Model): name = models.CharField(max_length=200, verbose_name=_("name")) + description = models.TextField(blank=True, verbose_name=_("description")) url = models.URLField(max_length=200, blank=True, verbose_name=_("URL")) logo = models.ImageField(upload_to="projects/logos/", blank=True, verbose_name=_("logo")) start_date = models.DateField(blank=True, null=True, verbose_name=_("start date")) @@ -106,6 +107,7 @@ class DocumentFile(models.Model): file_type = "pdf" title = models.CharField(max_length=200, verbose_name=_("title")) + description = models.TextField(blank=True, verbose_name=_("description")) url = models.URLField(max_length=200, blank=True, verbose_name=_("URL")) uploaded_file = models.FileField( upload_to="documents/", diff --git a/app/general/tests/test_document_admin_file.py b/app/general/tests/test_document_admin_file.py index bd8cf6d8..76c8a5c8 100644 --- a/app/general/tests/test_document_admin_file.py +++ b/app/general/tests/test_document_admin_file.py @@ -30,6 +30,7 @@ def test_clean_without_url_and_file(self): "institution": Institution.objects.create(name="Test Institution"), "url": "", "uploaded_file": "", + "description": "Test description", } form = DocumentFileForm(tests_form) @@ -49,6 +50,7 @@ def test_clean_without_file(self): "url": "www.example.com", "uploaded_file": "", "document_data": "", + "description": "", } form = DocumentFileForm(tests_form) @@ -65,6 +67,7 @@ def test_clean_without_url(self): "url": "", "uploaded_file": self.file_mock, "document_data": "", + "description": "Test description", } form = DocumentFileForm(tests_form, files={"uploaded_file": self.file_mock}) @@ -81,6 +84,7 @@ def test_clean_with_large_file(self): "institution": Institution.objects.create(name="Test Institution 4"), "url": "", "uploaded_file": self.file_mock, + "description": "Test description", } form = DocumentFileForm(tests_form, files={"uploaded_file": self.file_mock}) From 9063aa39138d5249e85dca5a6ca7218c5ca4aefc Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Tue, 11 Jun 2024 15:01:42 +0200 Subject: [PATCH 101/240] fixed projects institution validation --- app/general/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/general/models.py b/app/general/models.py index e2d23cdf..0192530e 100644 --- a/app/general/models.py +++ b/app/general/models.py @@ -12,7 +12,7 @@ class Project(models.Model): start_date = models.DateField(blank=True, null=True, verbose_name=_("start date")) end_date = models.DateField(blank=True, null=True, verbose_name=_("end date")) institution = models.ForeignKey( - "Institution", on_delete=models.CASCADE, blank=True, verbose_name=_("institution") + "Institution", on_delete=models.CASCADE, blank=False, verbose_name=_("institution") ) subjects = models.ManyToManyField("Subject", blank=True, verbose_name=_("subjects")) languages = models.ManyToManyField("Language", blank=True, verbose_name=_("languages")) From 9a27eb025d1147b4944c469bfac0c5f3b010074b Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Thu, 13 Jun 2024 12:20:10 +0200 Subject: [PATCH 102/240] updated settings file to use postgress --- app/app/settings.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/app/settings.py b/app/app/settings.py index 26b59e85..88064c6b 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -108,6 +108,7 @@ "NAME": os.environ.get("DB_NAME"), "USER": os.environ.get("DB_USER"), "PASSWORD": os.environ.get("DB_PASSWORD"), + "TEST": {"NAME": "test_db"}, } } @@ -122,9 +123,6 @@ "host.docker.internal", ] -# Check if the application is under testing -if "test" in sys.argv or "test_coverage" in sys.argv: # Covers regular testing and django-coverage - DATABASES["default"]["ENGINE"] = "django.db.backends.sqlite3" # Password validation # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators From 90726bf3cc68dc610adf068cd97fe78e0cfe1083 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Thu, 13 Jun 2024 12:23:59 +0200 Subject: [PATCH 103/240] added postgres to pipeline tests --- .env.testing | 2 +- .github/workflows/testing.yml | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.env.testing b/.env.testing index d8d2568a..deeebc6c 100644 --- a/.env.testing +++ b/.env.testing @@ -1,6 +1,6 @@ SECRET_KEY='django-insecure-w!h85bp^$$e8gm%c23r!0%9i7yzd=6w$$s&ic+6!%306&kj8@k*5' DEBUG=True -DB_HOST=db +DB_HOST=localhost DB_PORT=5432 DB_NAME=term_db DB_USER=sadilar diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index defaa2d2..f0b7be32 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -3,6 +3,16 @@ on: [ pull_request, push ] # activates the workflow when there is a push or pull jobs: test_project: runs-on: ubuntu-latest # operating system your code will run on + services: + postgres: + image: postgres:13 + env: + POSTGRES_USER: sadilar + POSTGRES_PASSWORD: sadilar + POSTGRES_DB: test_db_1 + ports: + - 5432:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 @@ -20,6 +30,7 @@ jobs: sudo chown runner:runner /logging - name: Run validate_templates run: | + export DJANGO_TEST_PROCESSES=1 cp .env.testing app/.env cd app/ mkdir -p static_files @@ -30,6 +41,9 @@ jobs: cd app/ mkdir -p static_files python manage.py test + env: + DJANGO_SETTINGS_MODULE: app.settings + DATABASE_URL: postgres://sadilar:sadilar@localhost:5432/test_db - name: Manager Check run: | cd app/ From f73f9836b3bc56fe1068fb4b889c82766c5330f3 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Thu, 13 Jun 2024 12:28:58 +0200 Subject: [PATCH 104/240] updated tests --- app/general/tests/test_dev_mass_upload.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/general/tests/test_dev_mass_upload.py b/app/general/tests/test_dev_mass_upload.py index 3a0a8055..85c7bbe0 100644 --- a/app/general/tests/test_dev_mass_upload.py +++ b/app/general/tests/test_dev_mass_upload.py @@ -1,4 +1,5 @@ import os +import random import unittest from unittest.mock import MagicMock @@ -44,12 +45,15 @@ def test_check_file_type_pdf(self): def test_save_data(self): self.command = Command() # Create some Institutions instances for testing - for _ in range(20): + for i in range(1, 30): + random_number = random.randint(1, 1000) + Institution.objects.create( - name=self.fake.company(), - abbreviation=self.fake.company_suffix(), - url=self.fake.url(), - email=self.fake.company_email(), + id=i, + name=str(random_number) + "_" + self.fake.company(), + abbreviation=str(random_number) + "_" + self.fake.company_suffix(), + url=str(random_number) + "_" + self.fake.url(), + email=str(random_number) + "_" + self.fake.company_email(), logo="", ) From 924eda3c5c6b4c61fcf49a6d45c5b5fe9b2de05b Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Mon, 10 Jun 2024 14:44:34 +0200 Subject: [PATCH 105/240] added vector search - updated migrations - updated views - added new command to upgrade --- Makefile | 3 ++ app/app/views.py | 11 ++---- app/general/admin.py | 1 + .../commands/dev_update_vector_search.py | 20 +++++++++++ ...0010_documentfile_search_vector_trigger.py | 35 +++++++++++++++++++ ...009_documentfile_search_vector_and_more.py | 24 +++++++++++++ app/general/models.py | 10 +++++- 7 files changed, 95 insertions(+), 9 deletions(-) create mode 100644 app/general/management/commands/dev_update_vector_search.py create mode 100644 app/general/migrations/00010_documentfile_search_vector_trigger.py create mode 100644 app/general/migrations/0009_documentfile_search_vector_and_more.py diff --git a/Makefile b/Makefile index a7ff7007..2d2f9bd8 100644 --- a/Makefile +++ b/Makefile @@ -99,6 +99,9 @@ dev-quick-install: dev-mass-pdf-upload: @docker compose run --rm web python manage.py dev_pdf_mass_upload +dev_update_vector_search: + @docker compose run --rm web python manage.py dev_update_vector_search + docker-shell: docker exec -it sadilar-terminology-web bash diff --git a/app/app/views.py b/app/app/views.py index fafada32..f574f87c 100644 --- a/app/app/views.py +++ b/app/app/views.py @@ -1,11 +1,6 @@ import os -from django.contrib.postgres.search import ( - SearchHeadline, - SearchQuery, - SearchRank, - SearchVector, -) +from django.contrib.postgres.search import SearchHeadline, SearchQuery, SearchRank from django.core.paginator import Paginator from django.db.models import Count from django.http import HttpResponse @@ -67,13 +62,13 @@ def search(request): q = request.GET.get("q") if q: - vector = SearchVector("title", "document_data") queue = SearchQuery(q) search_headline = SearchHeadline("document_data", queue) documents = ( - DocumentFile.objects.annotate(rank=SearchRank(vector, queue)) + DocumentFile.objects.annotate(rank=SearchRank("search_vector", queue)) .annotate(search_headline=search_headline) + .filter(search_vector=queue) .order_by("-rank") ) diff --git a/app/general/admin.py b/app/general/admin.py index a9b55bd0..cca680b6 100644 --- a/app/general/admin.py +++ b/app/general/admin.py @@ -17,6 +17,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields["document_data"].widget = HiddenInput() + self.fields["search_vector"].widget = HiddenInput() # If the instance has a mime_type, the field should be disabled if not self.instance.mime_type: diff --git a/app/general/management/commands/dev_update_vector_search.py b/app/general/management/commands/dev_update_vector_search.py new file mode 100644 index 00000000..8fb0d096 --- /dev/null +++ b/app/general/management/commands/dev_update_vector_search.py @@ -0,0 +1,20 @@ +import os + +from django.core.management.base import BaseCommand + +from general.models import DocumentFile + + +class Command(BaseCommand): + help = "Updating the Vector Search index on document_file." + + def handle(self, *args, **options): + os.system("clear") + print("Querying the Vector Search index and Updating.") + + all_document_files = DocumentFile.objects.all() + + for document_file in all_document_files: + document_file.save() # This line updates the vector search for the document file + print(f"Updated {document_file.title}.") + print() diff --git a/app/general/migrations/00010_documentfile_search_vector_trigger.py b/app/general/migrations/00010_documentfile_search_vector_trigger.py new file mode 100644 index 00000000..d955104c --- /dev/null +++ b/app/general/migrations/00010_documentfile_search_vector_trigger.py @@ -0,0 +1,35 @@ +from django.contrib.postgres.search import SearchVector +from django.db import migrations + + +def compute_search_vector(apps, schema_editor): + Quote = apps.get_model("general", "DocumentFile") + Quote.objects.update(search_vector=SearchVector("document_data", "title")) + + +class Migration(migrations.Migration): + dependencies = [ + ("general", "0009_documentfile_search_vector_and_more"), + ] + + operations = [ + migrations.RunSQL( + sql=""" + CREATE TRIGGER search_vector_trigger + BEFORE INSERT OR UPDATE OF document_data, title, search_vector + ON general_documentfile + FOR EACH ROW EXECUTE PROCEDURE + tsvector_update_trigger( + search_vector, 'pg_catalog.english', document_data, title + ); + UPDATE general_documentfile SET search_vector = NULL; + """, + reverse_sql=""" + DROP TRIGGER IF EXISTS search_vector_trigger + ON general_documentfile; + """, + ), + migrations.RunPython( + compute_search_vector, reverse_code=migrations.RunPython.noop + ), + ] diff --git a/app/general/migrations/0009_documentfile_search_vector_and_more.py b/app/general/migrations/0009_documentfile_search_vector_and_more.py new file mode 100644 index 00000000..e8e883d3 --- /dev/null +++ b/app/general/migrations/0009_documentfile_search_vector_and_more.py @@ -0,0 +1,24 @@ +# Generated by Django 5.0.2 on 2024-06-14 10:33 + +import django.contrib.postgres.indexes +import django.contrib.postgres.search +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('general', '0008_documentfile_description_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='documentfile', + name='search_vector', + field=django.contrib.postgres.search.SearchVectorField(blank=True, null=True), + ), + migrations.AddIndex( + model_name='documentfile', + index=django.contrib.postgres.indexes.GinIndex(fields=['search_vector'], name='general_doc_search__752b22_gin'), + ), + ] diff --git a/app/general/models.py b/app/general/models.py index 0192530e..b523243f 100644 --- a/app/general/models.py +++ b/app/general/models.py @@ -1,3 +1,5 @@ +from django.contrib.postgres.indexes import GinIndex +from django.contrib.postgres.search import SearchVectorField from django.core.validators import FileExtensionValidator from django.db import models from django.utils.translation import gettext_lazy as _ @@ -140,12 +142,18 @@ class DocumentFile(models.Model): subjects = models.ManyToManyField("Subject", blank=True, verbose_name=_("subjects")) languages = models.ManyToManyField("Language", blank=True, verbose_name=_("languages")) + search_vector = SearchVectorField(null=True, blank=True) + # added simple historical records to the model - history = HistoricalRecords(excluded_fields=["document_data"]) + history = HistoricalRecords(excluded_fields=["document_data", "search_vector"]) class Meta: verbose_name = _("Document File") verbose_name_plural = _("Document Files") + indexes = [ + GinIndex(fields=["search_vector"]), + ] + def __str__(self): return self.title From e018c41d72add294383f428e90cb9a1ffb825739 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Tue, 18 Jun 2024 10:05:31 +0200 Subject: [PATCH 106/240] added Translation Messages Check in CI pipeline --- .github/workflows/testing.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index f0b7be32..cee182ac 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -20,6 +20,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements-test.txt + sudo apt-get install -y gettext - name: Run linting tools run: | cd app/ @@ -28,6 +29,12 @@ jobs: run: | sudo mkdir -p /logging sudo chown runner:runner /logging + - name: Compile Translation Messages + run: | + cp .env.testing app/.env + cd app/ + python manage.py makemessages --all + python manage.py compilemessages - name: Run validate_templates run: | export DJANGO_TEST_PROCESSES=1 From 09545c95a7584bb8fbb10e6ca80055dfffe1740a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9COMosimege=E2=80=9D?= <“onalerona.mosimege@gmail.com”> Date: Tue, 11 Jun 2024 11:25:20 +0200 Subject: [PATCH 107/240] Add projects list page: Add projects list view, template, url, and styling Add translation text wrapping, and testing --- app/app/urls.py | 1 + app/app/views.py | 97 ++++++++++++++++++++++- app/general/tests/test_projects.py | 107 +++++++++++++++++++++++++ app/static/css/styles.css | 121 ++++++++++++++++++++++++++++- app/templates/app/projects.html | 104 +++++++++++++++++++++++++ app/templates/base.html | 6 ++ 6 files changed, 434 insertions(+), 2 deletions(-) create mode 100644 app/general/tests/test_projects.py create mode 100644 app/templates/app/projects.html diff --git a/app/app/urls.py b/app/app/urls.py index ff50810b..364e8184 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -31,6 +31,7 @@ path("_health/", views.health, name="health"), path("", views.home, name="home"), path("institutions/", views.institutions, name="institutions"), + path("projects/", views.projects, name="projects"), path("search/", views.search, name="search"), path("i18n/", include("django.conf.urls.i18n")), ] diff --git a/app/app/views.py b/app/app/views.py index fafada32..50f75fc5 100644 --- a/app/app/views.py +++ b/app/app/views.py @@ -10,8 +10,9 @@ from django.db.models import Count from django.http import HttpResponse from django.shortcuts import render +from django.utils.translation import gettext as _ -from general.models import DocumentFile, Institution +from general.models import DocumentFile, Institution, Language, Project, Subject def health(request): @@ -28,6 +29,100 @@ def home(request): return render(request, template_name=template, context=context) +def get_date_range(project): + start_date = project.start_date + end_date = project.end_date + + if (start_date is not None) and (end_date is not None) and (start_date.year != end_date.year): + date = f"{start_date.year} – {end_date.year}" + elif (start_date is not None) and (end_date is not None) and (start_date.year == end_date.year): + date = start_date.year + elif start_date is not None: + date = _("Since {year}").format(year=start_date.year) + elif end_date is not None: + date = _("Until {year}").format(year=end_date.year) + else: + date = None + return date + + +def get_logo(project): + if project.logo: + logo = project.logo + elif project.institution.logo: + logo = project.institution.logo + else: + logo = None + return logo + + +def projects(request): + template = "app/projects.html" + + subject_id = request.GET.get("subject") + language_id = request.GET.get("language") + institution_id = request.GET.get("institution") + + projects = ( + Project.objects.select_related("institution") + .prefetch_related("subjects", "languages") + .all() + ) + + if subject_id: + projects = projects.filter(subjects__id=subject_id) + if language_id: + projects = projects.filter(languages__id=language_id) + if institution_id: + projects = projects.filter(institution__id=institution_id) + + subjects = Subject.objects.all() + languages = Language.objects.all() + institutions = Institution.objects.all() + + project_data = [] + for project in projects: + project_subjects = project.subjects.all() + project_languages = project.languages.all() + + if project_languages.count() < 4: + languages_data = ", ".join(sorted(language.name for language in project_languages)) + else: + languages_data = _("Multilingual") + + if project_subjects.count() < 4: + subjects_data = ", ".join(sorted([subject.name for subject in project_subjects])) + else: + subjects_data = _("Multiple subjects") + + logo = get_logo(project) + + institution_name = project.institution.name + + date = get_date_range(project) + + project_data.append( + { + "project": project, + "logo": logo, + "subjects": subjects_data, + "languages": languages_data, + "date": date, + "institution_name": institution_name, + } + ) + + context = { + "current_page": "projects", + "projects": project_data, + "subjects": subjects, + "languages": languages, + "institutions": institutions, + } + + return render(request, template_name=template, context=context) + + def institutions(request): template = "app/institutions.html" context = {} diff --git a/app/general/tests/test_projects.py b/app/general/tests/test_projects.py new file mode 100644 index 00000000..77ffea78 --- /dev/null +++ b/app/general/tests/test_projects.py @@ -0,0 +1,107 @@ +from datetime import date + +from django.test import Client, TestCase +from django.urls import reverse + +from app.views import get_date_range +from general.models import Institution, Language, Project, Subject + + +class ProjectViewTests(TestCase): + def setUp(self): + self.client = Client() + self.subject1 = Subject.objects.create(name="Subject 1") + self.subject2 = Subject.objects.create(name="Subject 2") + self.language1 = Language.objects.create(name="Language 1", iso_code="lang1") + self.language2 = Language.objects.create(name="Language 2", iso_code="lang2") + self.language3 = Language.objects.create(name="Language 3", iso_code="lang3") + self.language4 = Language.objects.create(name="Language 4", iso_code="lang4") + self.institution = Institution.objects.create( + name="Institution", logo="institution_logo.png" + ) + + self.project1 = Project.objects.create( + name="Project 1", + start_date="2020-01-01", + end_date="2021-01-01", + institution=self.institution, + logo="project_logo.png", + ) + self.project1.subjects.add(self.subject1) + self.project1.languages.add(self.language1) + + self.project2 = Project.objects.create( + name="Project 2", + end_date="2021-01-01", + institution=self.institution, + logo="project_logo.png", + ) + self.project2.subjects.add(self.subject1) + self.project2.languages.add(self.language1) + self.project2.languages.add(self.language2) + self.project2.languages.add(self.language3) + self.project2.languages.add(self.language4) + + self.url = reverse("projects") + + def test_projects_view(self): + response = self.client.get(reverse("projects")) + self.assertEqual(response.status_code, 200) + self.assertIn("projects", response.context) + self.assertEqual(len(response.context["projects"]), 2) + self.assertEqual(response.context["projects"][0]["project"].name, "Project 1") + self.assertEqual(response.context["projects"][1]["project"].name, "Project 2") + + def test_projects_view_with_filters(self): + response = self.client.get(reverse("projects"), {"language": self.language3.id}) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context["projects"]), 1) + self.assertEqual(response.context["projects"][0]["project"].name, "Project 2") + + def test_projects_view_multilingual(self): + response = self.client.get(reverse("projects")) + self.assertEqual(response.status_code, 200) + projects = response.context["projects"] + self.assertEqual(projects[1]["languages"], "Multilingual") + + def test_projects_view_queries(self): + response = self.client.get(self.url) + + with self.assertNumQueries(6): + response = self.client.get(self.url) + + +class GetDateRangeTests(TestCase): + def setUp(self): + self.institution = Institution.objects.create(name="Institution") + self.project = Project.objects.create(name="Project", institution=self.institution) + + def test_get_date_range_different_years(self): + self.project.start_date = date(2020, 1, 1) + self.project.end_date = date(2021, 1, 1) + self.project.save() + self.assertEqual(get_date_range(self.project), "2020 – 2021") + + def test_get_date_range_same_year(self): + self.project.start_date = date(2020, 1, 1) + self.project.end_date = date(2020, 12, 31) + self.project.save() + self.assertEqual(get_date_range(self.project), 2020) + + def test_get_date_range_only_start_date(self): + self.project.start_date = date(2020, 1, 1) + self.project.end_date = None + self.project.save() + self.assertEqual(get_date_range(self.project), "Since 2020") + + def test_get_date_range_only_end_date(self): + self.project.start_date = None + self.project.end_date = date(2020, 12, 31) + self.project.save() + self.assertEqual(get_date_range(self.project), "Until 2020") + + def test_get_date_range_no_dates(self): + self.project.start_date = None + self.project.end_date = None + self.project.save() + self.assertIsNone(get_date_range(self.project)) diff --git a/app/static/css/styles.css b/app/static/css/styles.css index 9ce1f73d..94b91659 100755 --- a/app/static/css/styles.css +++ b/app/static/css/styles.css @@ -179,6 +179,98 @@ html { padding: 0 10px 10px 0; } +/*Projects page*/ +.filter-form { + display: flex; + margin: 30px; + font-size: 0.875rem; +} +.filter-form label { + margin-right: 10px; +} +.filter-form .form-group { + margin-right: 10px; +} +.filter-form .form-control { + border: 1px solid #000; +} +.filter-form select { + padding: 0 10px; + width: 230px; +} +.btn-apply, .btn-reset{ + color: var(--primary-fg); + outline-color: var(--primary); + background-color: var(--primary); + border-color: var(--primary); + margin-right: 10px; +} +.btn-apply:hover, .btn-reset:hover{ + color: var(--primary-fg); + outline-color: var(--primary); + background-color: var(--primary); + border-color: var(--primary); +} + +.project-list { + margin: 20px; + padding: 0 30px 0 30px; +} + +.card-text-project { + font-size: 0.875rem; + margin: 3px 0 3px 0; +} + +.project-logo { + height: 100px; + width: auto; + +} +.project-border { + margin: 0 0 5px 0; + overflow: hidden; + padding: 0 5px 0 0; + word-wrap: break-word; +} +.project-left-col { + width: 70%; +} +.project-right-col { + width: 30%; + flex: 0 0 auto; + display: flex; + justify-content: flex-end; +} +.project-header { + display: flex; + justify-content: space-between; + align-items: center; + max-width: 800px; +} +.project-body { + width: 100%; + max-width: 800px; + +} +.card-text-description { + word-wrap: break-word; + overflow: hidden; + font-size: 0.875rem; + margin: 3px 0 3px 0; +} +.project-row { + display: flex; + justify-content: space-between; + width: 100%; +} +.project-text { + font-size: 1.25rem; +} +.icon-text { + font-size: 0.95rem; +} + /*Error pages*/ .error-card { border-color: var(--primary-red); @@ -190,6 +282,10 @@ html { .bi { font-size: 50px; } +.project-icon { + font-size: 20px; + margin-right: 3px; +} /* Extra small devices (phones, 600px and down) */ @media only screen and (max-width: 600px) { @@ -213,13 +309,18 @@ html { .col-sm-6 { width: 50%; } - /*Home page*/ .content-card { font-size: smaller; padding-right: 20px; margin-right: 10px; } + + /*Projects page*/ + .filter-form select { + padding: 0 10px; + width: 100px; + } } /*small devices (tablets, 600px and up) */ @@ -253,6 +354,12 @@ html { .content-card { font-size: smaller; } + + /*Projects page*/ + .filter-form select { + padding: 0 10px; + width: 150px; + } } /*Medium screens (tablets, between 768px and 1001px)*/ @@ -304,6 +411,12 @@ html { .right-col { width: 30%; } + + /*Projects page*/ + .filter-form select { + padding: 0 10px; + width: 200px; + } } /*Larger screens (desktops, 1001px and up)*/ @@ -356,4 +469,10 @@ html { height: 200px; font-size: small; } + + /*Projects page*/ + .filter-form select { + padding: 0 10px; + width: 230px; + } } diff --git a/app/templates/app/projects.html b/app/templates/app/projects.html new file mode 100644 index 00000000..710ba9fb --- /dev/null +++ b/app/templates/app/projects.html @@ -0,0 +1,104 @@ +{% extends "base.html" %} +{% load static %} +{% load i18n %} + +{% block content %} + +
+ + + +
+ +
+ +
+ +
+ +
+ +
+ + + {% trans 'Reset' %} + + +
+ +
+
+ + +
+ {% for project in projects %} +
+
+
+
+

{{ project.project.name }}

+ +
+ {{ project.institution_name }} +
+
+
+ {% if project.logo %} + + {% endif %} +
+
+
+
+
+ {{ project.project.description }} +
+
+ + {{ project.languages }} +
+
+ + {{ project.subjects }} +
+
+ + {{ project.date }} +
+ +
+
+ +
+
+
+ {% endfor %} +
+ + + +{% endblock content %} diff --git a/app/templates/base.html b/app/templates/base.html index cf43ac7a..2b8a3967 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -45,6 +45,12 @@ class="nav-link" href="{% url 'institutions' %}">{% trans "Institutions" %} + From ba1b6cc3903b53a4cc62ed399c015385fa8b0ec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9COMosimege=E2=80=9D?= <“onalerona.mosimege@gmail.com”> Date: Tue, 18 Jun 2024 09:38:31 +0200 Subject: [PATCH 108/240] Change document files to documents, and document type to document category --- app/general/admin.py | 1 - app/general/models.py | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/general/admin.py b/app/general/admin.py index a9b55bd0..e22ede8d 100644 --- a/app/general/admin.py +++ b/app/general/admin.py @@ -97,7 +97,6 @@ class InstitutionAdmin(SimpleHistoryAdmin): ordering = ["name"] search_fields = ["name"] list_display = ["name"] - inlines = [ProjectAdminInline] history_list_display = ["name", "abbreviation"] diff --git a/app/general/models.py b/app/general/models.py index 0192530e..c83ffffc 100644 --- a/app/general/models.py +++ b/app/general/models.py @@ -131,7 +131,7 @@ class DocumentFile(models.Model): verbose_name=_("MIME type"), ) document_type = models.CharField( - max_length=200, choices=document_type_choices, verbose_name=_("document type") + max_length=200, choices=document_type_choices, verbose_name=_("document category") ) document_data = models.TextField(blank=True, verbose_name=_("document data")) institution = models.ForeignKey( @@ -144,8 +144,8 @@ class DocumentFile(models.Model): history = HistoricalRecords(excluded_fields=["document_data"]) class Meta: - verbose_name = _("Document File") - verbose_name_plural = _("Document Files") + verbose_name = _("Document") + verbose_name_plural = _("Documents") def __str__(self): return self.title From 0a3f1df087dae3c4e609240f327c23f87d8bcc4b Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Fri, 21 Jun 2024 08:48:26 +0200 Subject: [PATCH 109/240] Fix data errors in fixtures E.g. incorrect language codes, fake e-mail addresses, missing institution, etc. --- app/fixtures/institution.json | 103 +++++++----- app/fixtures/language.json | 6 +- app/fixtures/subjects.json | 309 ++++++++++++++++------------------ 3 files changed, 204 insertions(+), 214 deletions(-) diff --git a/app/fixtures/institution.json b/app/fixtures/institution.json index 1337adb9..a20ce4b3 100644 --- a/app/fixtures/institution.json +++ b/app/fixtures/institution.json @@ -6,7 +6,7 @@ "name": "University of South Africa", "abbreviation": "UNISA", "url": "https://www.unisa.ac.za/", - "email": "email@example.dev", + "email": "", "logo": "" } }, @@ -17,7 +17,7 @@ "name": "North-West University", "abbreviation": "NWU", "url": "https://www.nwu.ac.za/", - "email": "email@example.dev", + "email": "", "logo": "" } }, @@ -28,249 +28,260 @@ "name": "University of Pretoria", "abbreviation": "UP", "url": "https://www.up.ac.za/", - "email": "email@example.dev", + "email": "", "logo": "" } }, { "model": "general.Institution", "pk": 4, + "fields": { + "name": "Tshwane University of Technology", + "abbreviation": "TUT", + "url": "https://tut.ac.za/", + "email": "", + "logo": "" + } + }, + { + "model": "general.Institution", + "pk": 5, "fields": { "name": "University of Johannesburg", "abbreviation": "UJ", "url": "https://www.uj.ac.za/", - "email": "email@example.dev", + "email": "", "logo": "" } }, { "model": "general.Institution", - "pk": 5, + "pk": 6, "fields": { "name": "University of KwaZulu-Natal", "abbreviation": "UKZN", "url": "https://www.ukzn.ac.za/", - "email": "email@example.dev", + "email": "", "logo": "" } }, { "model": "general.Institution", - "pk": 5, + "pk": 7, "fields": { "name": "University of the Free State", "abbreviation": "UFS", "url": "https://www.ufs.ac.za/", - "email": "email@example.dev", + "email": "", "logo": "" } }, { "model": "general.Institution", - "pk": 6, + "pk": 8, "fields": { "name": "Cape Peninsula University of Technology", "abbreviation": "CPUT", "url": "https://www.cput.ac.za/", - "email": "email@example.dev", + "email": "", "logo": "" } }, { "model": "general.Institution", - "pk": 7, + "pk": 9, "fields": { "name": "University of the Witwatersrand", "abbreviation": "WITS", "url": "https://www.wits.ac.za/", - "email": "email@example.dev", + "email": "", "logo": "" } }, { "model": "general.Institution", - "pk": 8, + "pk": 10, "fields": { "name": "University of Stellenbosch", "abbreviation": "US", "url": "http://www.sun.ac.za/", - "email": "email@example.dev", + "email": "", "logo": "" } }, { "model": "general.Institution", - "pk": 9, + "pk": 11, "fields": { "name": "University of Cape Town", "abbreviation": "UCT", "url": "https://uct.ac.za/", - "email": "email@example.dev", + "email": "", "logo": "" } }, { "model": "general.Institution", - "pk": 10, + "pk": 12, "fields": { "name": "Nelson Mandela Metropolitan University", "abbreviation": "NMU", "url": "https://www.mandela.ac.za/", - "email": "email@example.dev", + "email": "", "logo": "" } }, { "model": "general.Institution", - "pk": 11, + "pk": 13, "fields": { "name": "Walter Sisulu University", "abbreviation": "WSU", "url": "https://www.wsu.ac.za/", - "email": "email@example.dev", + "email": "", "logo": "" } }, { "model": "general.Institution", - "pk": 12, + "pk": 14, "fields": { "name": "Durban University of Technology", "abbreviation": "DUT", "url": "https://www.dut.ac.za/", - "email": "email@example.dev", + "email": "", "logo": "" } }, { "model": "general.Institution", - "pk": 13, + "pk": 15, "fields": { "name": "University of Limpopo", "abbreviation": "UL", "url": "https://www.ul.ac.za/", - "email": "email@example.dev", + "email": "", "logo": "" } }, { "model": "general.Institution", - "pk": 14, + "pk": 16, "fields": { "name": "Vaal University of Technology", "abbreviation": "VUT", "url": "https://www.vut.ac.za/", - "email": "email@example.dev", + "email": "", "logo": "" } }, { "model": "general.Institution", - "pk": 15, + "pk": 17, "fields": { "name": "University of Zululand", "abbreviation": "UniZulu", "url": "https://www.unizulu.ac.za/", - "email": "email@example.dev", + "email": "", "logo": "" } }, { "model": "general.Institution", - "pk": 16, + "pk": 18, "fields": { "name": "University of the Western Cape", "abbreviation": "UWC", "url": "https://www.uwc.ac.za/", - "email": "email@example.dev", + "email": "", "logo": "" } }, { "model": "general.Institution", - "pk": 17, + "pk": 19, "fields": { "name": "Central University of Technology", "abbreviation": "CUT", "url": "https://www.cut.ac.za/", - "email": "email@example.dev", + "email": "", "logo": "" } }, { "model": "general.Institution", - "pk": 18, + "pk": 20, "fields": { "name": "University of Fort Hare", "abbreviation": "UFH", "url": "https://www.ufh.ac.za/", - "email": "email@example.dev", + "email": "", "logo": "" } }, { "model": "general.Institution", - "pk": 19, + "pk": 21, "fields": { "name": "Rhodes University", "abbreviation": "RU", "url": "https://www.ru.ac.za/", - "email": "email@example.dev", + "email": "", "logo": "" } }, { "model": "general.Institution", - "pk": 20, + "pk": 22, "fields": { "name": "University of Venda", "abbreviation": "Venda", "url": "https://www.univen.ac.za/", - "email": "email@example.dev", + "email": "", "logo": "" } }, { "model": "general.Institution", - "pk": 21, + "pk": 23, "fields": { "name": "Mangosuthu University of Technology", "abbreviation": "MUT", "url": "https://www.mut.ac.za/", - "email": "email@example.dev", + "email": "", "logo": "" } }, { "model": "general.Institution", - "pk": 22, + "pk": 24, "fields": { "name": "Sefako Makgatho Health Sciences University", "abbreviation": "FMHSU", "url": "https://www.smu.ac.za/", - "email": "email@example.dev", + "email": "", "logo": "" } }, { "model": "general.Institution", - "pk": 23, + "pk": 25, "fields": { "name": "University of Mpumalanga", "abbreviation": "UMP", "url": "https://www.ump.ac.za/", - "email": "email@example.dev", + "email": "", "logo": "" } }, { "model": "general.Institution", - "pk": 24, + "pk": 26, "fields": { "name": "Sol Plaatje University", "abbreviation": "SPU", "url": "https://www.spu.ac.za/", - "email": "email@example.dev", + "email": "", "logo": "" } } diff --git a/app/fixtures/language.json b/app/fixtures/language.json index f4839cf6..bca08c22 100644 --- a/app/fixtures/language.json +++ b/app/fixtures/language.json @@ -27,7 +27,7 @@ "model": "general.Language", "pk": 4, "fields": { - "name": "Pedi", + "name": "Northern Sotho", "iso_code": "nso" } }, @@ -36,7 +36,7 @@ "pk": 5, "fields": { "name": "Sotho", - "iso_code": "sot" + "iso_code": "st" } }, { @@ -52,7 +52,7 @@ "pk": 7, "fields": { "name": "Swati", - "iso_code": "swz" + "iso_code": "ss" } }, { diff --git a/app/fixtures/subjects.json b/app/fixtures/subjects.json index f6f1a22e..ab46c7ca 100644 --- a/app/fixtures/subjects.json +++ b/app/fixtures/subjects.json @@ -3,1029 +3,1008 @@ "model": "general.Subject", "pk": 1, "fields": { - "name": "Accounting data processing" + "name": "Accounting" } }, { "model": "general.Subject", "pk": 2, "fields": { - "name": "African politics" + "name": "Agricultural management" } }, { "model": "general.Subject", "pk": 3, "fields": { - "name": "Agricultural business and management" + "name": "Agricultural science" } }, { "model": "general.Subject", "pk": 4, "fields": { - "name": "Agricultural management" + "name": "Agronomy" } }, { "model": "general.Subject", "pk": 5, "fields": { - "name": "Agricultural science" + "name": "Anaesthesiology" } }, { "model": "general.Subject", "pk": 6, "fields": { - "name": "Agronomy" + "name": "Anatomy" } }, { "model": "general.Subject", "pk": 7, "fields": { - "name": "Anaesthesiology" + "name": "Ancient history" } }, { "model": "general.Subject", "pk": 8, "fields": { - "name": "Anatomy" + "name": "Ancient Near Eastern studies" } }, { "model": "general.Subject", "pk": 9, "fields": { - "name": "Ancient history" + "name": "Animal health" } }, { "model": "general.Subject", "pk": 10, "fields": { - "name": "Ancient near eastern studies" + "name": "Anthropology" } }, { "model": "general.Subject", "pk": 11, "fields": { - "name": "Animal health" + "name": "Applied information science" } }, { "model": "general.Subject", "pk": 12, "fields": { - "name": "Anthropology" + "name": "Applied mathematics" } }, { "model": "general.Subject", "pk": 13, "fields": { - "name": "Applied information science" + "name": "Archaeology" } }, { "model": "general.Subject", "pk": 14, "fields": { - "name": "Applied mathematics" + "name": "Archives and records management" } }, { "model": "general.Subject", "pk": 15, "fields": { - "name": "Archaeology" + "name": "Art history" } }, { "model": "general.Subject", "pk": 16, "fields": { - "name": "Archives and records management" + "name": "Astronomy" } }, { "model": "general.Subject", "pk": 17, "fields": { - "name": "Art history" + "name": "Auditing" } }, { "model": "general.Subject", "pk": 18, "fields": { - "name": "Astronomy" + "name": "Banking and finance" } }, { "model": "general.Subject", "pk": 19, "fields": { - "name": "Auditing" + "name": "Biblical archaeology" } }, { "model": "general.Subject", "pk": 20, "fields": { - "name": "Banking and finance" + "name": "Biochemistry" } }, { "model": "general.Subject", "pk": 21, "fields": { - "name": "Biblical archaeology" + "name": "Biology" } }, { "model": "general.Subject", "pk": 22, "fields": { - "name": "Biochemistry" + "name": "Biomedical science" } }, { "model": "general.Subject", "pk": 23, "fields": { - "name": "Biology" + "name": "Biotechnology" } }, { "model": "general.Subject", "pk": 24, "fields": { - "name": "Biomedical science" + "name": "Botany" } }, { "model": "general.Subject", "pk": 25, "fields": { - "name": "Biotechnology" + "name": "Business management" } }, { "model": "general.Subject", "pk": 26, "fields": { - "name": "Botany" + "name": "Chemical engineering" } }, { "model": "general.Subject", "pk": 27, "fields": { - "name": "Business management" + "name": "Chemistry" } }, { "model": "general.Subject", "pk": 28, "fields": { - "name": "Chemical engineering" + "name": "Christian leadership" } }, { "model": "general.Subject", "pk": 29, "fields": { - "name": "Chemistry" + "name": "Christian spirituality" } }, { "model": "general.Subject", "pk": 30, "fields": { - "name": "Christian leadership" + "name": "Church history" } }, { "model": "general.Subject", "pk": 31, "fields": { - "name": "Christian spirituality" + "name": "Civil engineering" } }, { "model": "general.Subject", "pk": 32, "fields": { - "name": "Church history" + "name": "Classical culture" } }, { "model": "general.Subject", "pk": 33, "fields": { - "name": "Civil engineering" + "name": "Classical studies" } }, { "model": "general.Subject", "pk": 34, "fields": { - "name": "Classical culture" + "name": "Communication science" } }, { "model": "general.Subject", "pk": 35, "fields": { - "name": "Classical studies" + "name": "Communication studies" } }, { "model": "general.Subject", "pk": 36, "fields": { - "name": "Communication science" + "name": "Community health" } }, { "model": "general.Subject", "pk": 37, "fields": { - "name": "Communication studies" + "name": "Composition studies" } }, { "model": "general.Subject", "pk": 38, "fields": { - "name": "Community health" + "name": "Computer science" } }, { "model": "general.Subject", "pk": 39, "fields": { - "name": "Composition studies" + "name": "Consumer science" } }, { "model": "general.Subject", "pk": 40, "fields": { - "name": "Computer science" + "name": "Corrections management" } }, { "model": "general.Subject", "pk": 41, "fields": { - "name": "Consumer science" + "name": "Credit management" } }, { "model": "general.Subject", "pk": 42, "fields": { - "name": "Corrections management" + "name": "Criminology" } }, { "model": "general.Subject", "pk": 43, "fields": { - "name": "Credit management" + "name": "Curriculum and instructional studies" } }, { "model": "general.Subject", "pk": 44, "fields": { - "name": "Criminology" + "name": "Decoloniality" } }, { "model": "general.Subject", "pk": 45, "fields": { - "name": "Curriculum and instructional studies" + "name": "Development studies" } }, { "model": "general.Subject", "pk": 46, "fields": { - "name": "Decoloniality" + "name": "Early childhood education" } }, { "model": "general.Subject", "pk": 47, "fields": { - "name": "Development studies" + "name": "Economics" } }, { "model": "general.Subject", "pk": 48, "fields": { - "name": "Early childhood education" + "name": "Educational foundations" } }, { "model": "general.Subject", "pk": 49, "fields": { - "name": "Economics" + "name": "Educational leadership and managment" } }, { "model": "general.Subject", "pk": 50, "fields": { - "name": "Educational foundations" + "name": "Electrical engineering" } }, { "model": "general.Subject", "pk": 51, "fields": { - "name": "Educational leadership and managment" + "name": "Entrepreneurship" } }, { "model": "general.Subject", "pk": 52, "fields": { - "name": "Electrical engineering" + "name": "Environmental sciences" } }, { "model": "general.Subject", "pk": 53, "fields": { - "name": "Entrepreneurship" + "name": "Epidemiology" } }, { "model": "general.Subject", "pk": 54, "fields": { - "name": "Environmental sciences" + "name": "Financial accounting" } }, { "model": "general.Subject", "pk": 55, "fields": { - "name": "Epidemiology" + "name": "Financial governance" } }, { "model": "general.Subject", "pk": 56, "fields": { - "name": "Financial accounting" + "name": "Financial intelligence" } }, { "model": "general.Subject", "pk": 57, "fields": { - "name": "Financial governance" + "name": "Food service management" } }, { "model": "general.Subject", "pk": 58, "fields": { - "name": "Financial intelligence" + "name": "Forensic auditing" } }, { "model": "general.Subject", "pk": 59, "fields": { - "name": "Food service management" + "name": "Forensic investigation" } }, { "model": "general.Subject", "pk": 60, "fields": { - "name": "Forensic auditing" + "name": "Gender studies" } }, { "model": "general.Subject", "pk": 61, "fields": { - "name": "Forensic investigation" + "name": "Genetics" } }, { "model": "general.Subject", "pk": 62, "fields": { - "name": "Gender studies" + "name": "Geography" } }, { "model": "general.Subject", "pk": 63, "fields": { - "name": "Genetics" + "name": "Health sciences education" } }, { "model": "general.Subject", "pk": 64, "fields": { - "name": "Geography" + "name": "Health services management" } }, { "model": "general.Subject", "pk": 65, "fields": { - "name": "Health sciences education" + "name": "Health studies" } }, { "model": "general.Subject", "pk": 66, "fields": { - "name": "Health services management" + "name": "History" } }, { "model": "general.Subject", "pk": 67, "fields": { - "name": "Health studies" + "name": "Horticulture" } }, { "model": "general.Subject", "pk": 68, "fields": { - "name": "History" + "name": "Hospitality management" } }, { "model": "general.Subject", "pk": 69, "fields": { - "name": "Horticulture" + "name": "Human resource management" } }, { "model": "general.Subject", "pk": 70, "fields": { - "name": "Hospitality management" + "name": "Inclusive education" } }, { "model": "general.Subject", "pk": 71, "fields": { - "name": "Human resource management" + "name": "Industrial and organisational psychology" } }, { "model": "general.Subject", "pk": 72, "fields": { - "name": "Inclusive education" + "name": "Information resource management" } }, { "model": "general.Subject", "pk": 73, "fields": { - "name": "Industrial and organisational psychology" + "name": "Information science" } }, { "model": "general.Subject", "pk": 74, "fields": { - "name": "Information resource management" + "name": "Information systems" } }, { "model": "general.Subject", "pk": 75, "fields": { - "name": "Information science" + "name": "Information technology" } }, { "model": "general.Subject", "pk": 76, "fields": { - "name": "Information systems" + "name": "Insurance" } }, { "model": "general.Subject", "pk": 77, "fields": { - "name": "Information technology" + "name": "Internal auditing" } }, { "model": "general.Subject", "pk": 78, "fields": { - "name": "Insurance" + "name": "International politics" } }, { "model": "general.Subject", "pk": 79, "fields": { - "name": "Internal auditing" + "name": "Investment" } }, { "model": "general.Subject", "pk": 80, "fields": { - "name": "International politics" + "name": "Islamic studies" } }, { "model": "general.Subject", "pk": 81, "fields": { - "name": "Investment" + "name": "Language education" } }, { "model": "general.Subject", "pk": 82, "fields": { - "name": "Islamic studies" + "name": "Law" } }, { "model": "general.Subject", "pk": 83, "fields": { - "name": "Language education" + "name": "Life and consumer sciences" } }, { "model": "general.Subject", "pk": 84, "fields": { - "name": "Law" + "name": "Linguistics" } }, { "model": "general.Subject", "pk": 85, "fields": { - "name": "Life and consumer sciences" + "name": "Management accounting" } }, { "model": "general.Subject", "pk": 86, "fields": { - "name": "Linguistics" + "name": "Marketing and retail management" } }, { "model": "general.Subject", "pk": 87, "fields": { - "name": "Management accounting" + "name": "Mathematics" } }, { "model": "general.Subject", "pk": 88, "fields": { - "name": "Marketing and retail management" + "name": "Mathematics education" } }, { "model": "general.Subject", "pk": 89, "fields": { - "name": "Mathematics" + "name": "Mechanical and industrial engineering" } }, { "model": "general.Subject", "pk": 90, "fields": { - "name": "Mathematics education" + "name": "Microbiology" } }, { "model": "general.Subject", "pk": 91, "fields": { - "name": "Mechanical and industrial engineering" + "name": "Midwifery" } }, { "model": "general.Subject", "pk": 92, "fields": { - "name": "Microbiology" + "name": "Mine engineering" } }, { "model": "general.Subject", "pk": 93, "fields": { - "name": "Midwifery" + "name": "Ministry" } }, { "model": "general.Subject", "pk": 94, "fields": { - "name": "Mine engineering" + "name": "Missiology" } }, { "model": "general.Subject", "pk": 95, "fields": { - "name": "Ministry" + "name": "Music" } }, { "model": "general.Subject", "pk": 96, "fields": { - "name": "Missiology" + "name": "Nature conservation" } }, { "model": "general.Subject", "pk": 97, "fields": { - "name": "Music in history and society" + "name": "Neurology" } }, { "model": "general.Subject", "pk": 98, "fields": { - "name": "Nature conservation" + "name": "New Testament" } }, { "model": "general.Subject", "pk": 99, "fields": { - "name": "Neurology" + "name": "Nursing" } }, { "model": "general.Subject", "pk": 100, "fields": { - "name": "New Testament" + "name": "Nutrition" } }, { "model": "general.Subject", "pk": 101, "fields": { - "name": "Nursing" + "name": "Obstetrics" } }, { "model": "general.Subject", "pk": 102, "fields": { - "name": "Nutrition" + "name": "Occupational therapy" } }, { "model": "general.Subject", "pk": 103, "fields": { - "name": "Obstetrics" + "name": "Oenology" } }, { "model": "general.Subject", "pk": 104, "fields": { - "name": "Occupational therapy" + "name": "Old Testament" } }, { "model": "general.Subject", "pk": 105, "fields": { - "name": "Oenology" + "name": "Operations management" } }, { "model": "general.Subject", "pk": 106, "fields": { - "name": "Old Testament" + "name": "Orthodontics" } }, { "model": "general.Subject", "pk": 107, "fields": { - "name": "Operations management" + "name": "Paediatrics" } }, { "model": "general.Subject", "pk": 108, "fields": { - "name": "Orthodontics" + "name": "Pathology" } }, { "model": "general.Subject", "pk": 109, "fields": { - "name": "Paediatrics" + "name": "Pharmacology" } }, { "model": "general.Subject", "pk": 110, "fields": { - "name": "Pathology" + "name": "Philosophy" } }, { "model": "general.Subject", "pk": 111, "fields": { - "name": "Pharmacology" + "name": "Physics" } }, { "model": "general.Subject", "pk": 112, "fields": { - "name": "Philosophy" + "name": "Physiology" } }, { "model": "general.Subject", "pk": 113, "fields": { - "name": "Physics" + "name": "Policing" } }, { "model": "general.Subject", "pk": 114, "fields": { - "name": "Physiology" + "name": "Politics" } }, { "model": "general.Subject", "pk": 115, "fields": { - "name": "Policing" + "name": "Practical theology" } }, { "model": "general.Subject", "pk": 116, "fields": { - "name": "Politics" + "name": "Private law" } }, { "model": "general.Subject", "pk": 117, "fields": { - "name": "Practical theology" + "name": "Psychology" } }, { "model": "general.Subject", "pk": 118, "fields": { - "name": "Private law" + "name": "Psychiatry" } }, { "model": "general.Subject", "pk": 119, "fields": { - "name": "Psychology" + "name": "Public administration" } }, { "model": "general.Subject", "pk": 120, "fields": { - "name": "Psychiatry" + "name": "Public health" } }, { "model": "general.Subject", "pk": 121, "fields": { - "name": "Public administration" + "name": "Public relations" } }, { "model": "general.Subject", "pk": 122, "fields": { - "name": "Public health" + "name": "Radiology" } }, { "model": "general.Subject", "pk": 123, "fields": { - "name": "Public relations" + "name": "Religious studies" } }, { "model": "general.Subject", "pk": 124, "fields": { - "name": "Radiology" + "name": "Risk management" } }, { "model": "general.Subject", "pk": 125, "fields": { - "name": "Religious studies" + "name": "Safety management" } }, { "model": "general.Subject", "pk": 126, "fields": { - "name": "Risk management" + "name": "Science and technology education" } }, { "model": "general.Subject", "pk": 127, "fields": { - "name": "Safety management" + "name": "Security management" } }, { "model": "general.Subject", "pk": 128, "fields": { - "name": "Science and technology education" + "name": "Social sciences" } }, { "model": "general.Subject", "pk": 129, "fields": { - "name": "Security management" + "name": "Social work" } }, { "model": "general.Subject", "pk": 130, "fields": { - "name": "Social auxiliary work" + "name": "Sociology" } }, { "model": "general.Subject", "pk": 131, "fields": { - "name": "Social sciences" + "name": "Statistics" } }, { "model": "general.Subject", "pk": 132, "fields": { - "name": "Social work" + "name": "Supply chain management" } }, { "model": "general.Subject", "pk": 133, "fields": { - "name": "Sociology" + "name": "Systematic theology" } }, { "model": "general.Subject", "pk": 134, "fields": { - "name": "Statistics" + "name": "Taxation" } }, { "model": "general.Subject", "pk": 135, "fields": { - "name": "Supply chain management" + "name": "Theological ethics" } }, { "model": "general.Subject", "pk": 136, "fields": { - "name": "Systematic theology" + "name": "Theology" } }, { "model": "general.Subject", "pk": 137, "fields": { - "name": "Taxation" + "name": "Theory of literature" } }, { "model": "general.Subject", "pk": 138, "fields": { - "name": "Theological ethics" + "name": "Tourism management" } }, { "model": "general.Subject", "pk": 139, "fields": { - "name": "Theology" + "name": "Transport economics and logistics" } }, { "model": "general.Subject", "pk": 140, "fields": { - "name": "Theory of literature" + "name": "Urology" } }, { "model": "general.Subject", "pk": 141, "fields": { - "name": "Tourism management" + "name": "Veterinary medicine" } }, { "model": "general.Subject", "pk": 142, - "fields": { - "name": "Transport economics and logistics" - } - }, - { - "model": "general.Subject", - "pk": 143, - "fields": { - "name": "Urology" - } - }, - { - "model": "general.Subject", - "pk": 144, - "fields": { - "name": "Veterinary publich health" - } - }, - { - "model": "general.Subject", - "pk": 145, "fields": { "name": "Viticulture" } }, { "model": "general.Subject", - "pk": 146, + "pk": 143, "fields": { "name": "Visual arts" } }, { "model": "general.Subject", - "pk": 147, + "pk": 144, "fields": { "name": "Zoology" } } -] \ No newline at end of file +] From b96ccf74a02badfb7cc36518b048d9eda5fae2a2 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Fri, 21 Jun 2024 10:56:58 +0200 Subject: [PATCH 110/240] Improve Dockerfiles This reduces layers. --- Dockerfile | 17 ++++++++++------- Dockerfile-dev | 21 +++++++++++---------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/Dockerfile b/Dockerfile index c336b6d0..283a6eb5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,19 +5,22 @@ FROM python:3.12 ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONUNBUFFERED 1 +SHELL ["/bin/bash", "-c"] + # Set work directory WORKDIR /app # Install dependencies COPY requirements.txt /app/ -RUN apt-get update && apt-get -y upgrade -RUN apt-get install libmagic1 -y -RUN apt-get install -y gettext - -# Install dependencies -RUN pip install --upgrade pip -RUN pip install -r requirements.txt +RUN < Date: Fri, 21 Jun 2024 10:59:55 +0200 Subject: [PATCH 111/240] +gunicorn configuration --- app/gunicorn.conf.py | 12 ++++++++++++ entrypoint.sh | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 app/gunicorn.conf.py diff --git a/app/gunicorn.conf.py b/app/gunicorn.conf.py new file mode 100644 index 00000000..2aa9408e --- /dev/null +++ b/app/gunicorn.conf.py @@ -0,0 +1,12 @@ +# Send output from the web app to the error log: +capture_output = True +errorlog = "-" + +keepalive = 30 +max_requests = 1000000 +max_requests_jitter = 1000 +graceful_timeout = 5 +preload_app = True + +workers = 4 +threads = 3 diff --git a/entrypoint.sh b/entrypoint.sh index 58051188..11ebf19f 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -9,4 +9,4 @@ python manage.py migrate --no-input python manage.py collectstatic --no-input python manage.py compilemessages -gunicorn app.wsgi:application --bind 0.0.0.0:8000 +gunicorn app.wsgi:application --bind 0.0.0.0:8000 --config gunicorn.conf.py From 1037176a18d6c5e610fad83809ff9bd929bf1d91 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Fri, 21 Jun 2024 11:05:10 +0200 Subject: [PATCH 112/240] Enable persistent DB connections --- app/app/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/app/settings.py b/app/app/settings.py index 88064c6b..af92a265 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -109,6 +109,8 @@ "USER": os.environ.get("DB_USER"), "PASSWORD": os.environ.get("DB_PASSWORD"), "TEST": {"NAME": "test_db"}, + "CONN_MAX_AGE": None, + "CONN_HEALTH_CHECKS": True, } } From cc64f9ed95c3615bb11ee2471102eb2d3f9ddef0 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Fri, 21 Jun 2024 11:19:10 +0200 Subject: [PATCH 113/240] Upgrade requirements --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e89d4c52..f6d8f0c2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -django==5.0.2 +django==5.0.6 django-environ django-simple-history gunicorn From abad3c74448558ff33a8154cd4d6d28296316e1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9COMosimege=E2=80=9D?= <“onalerona.mosimege@gmail.com”> Date: Mon, 24 Jun 2024 12:14:38 +0200 Subject: [PATCH 114/240] Add individial project page, with styling and view --- app/app/urls.py | 4 + app/app/views.py | 57 ++++++++++++++- app/general/tests/test_project_detail.py | 56 ++++++++++++++ app/static/css/styles.css | 89 +++++++++++++++++++++++ app/templates/app/institution_detail.html | 0 app/templates/app/language_detail.html | 0 app/templates/app/project_detail.html | 80 ++++++++++++++++++++ app/templates/app/projects.html | 8 +- app/templates/app/subject_detail.html | 0 9 files changed, 291 insertions(+), 3 deletions(-) create mode 100644 app/general/tests/test_project_detail.py create mode 100644 app/templates/app/institution_detail.html create mode 100644 app/templates/app/language_detail.html create mode 100644 app/templates/app/project_detail.html create mode 100644 app/templates/app/subject_detail.html diff --git a/app/app/urls.py b/app/app/urls.py index 364e8184..d2e4b13f 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -32,6 +32,10 @@ path("", views.home, name="home"), path("institutions/", views.institutions, name="institutions"), path("projects/", views.projects, name="projects"), + path("projects//", views.project_detail, name="project_detail"), + path("institution//", views.institution_detail, name="institution_detail"), + path("language//", views.language_detail, name="language_detail"), + path("subject//", views.subject_detail, name="subject_detail"), path("search/", views.search, name="search"), path("i18n/", include("django.conf.urls.i18n")), ] diff --git a/app/app/views.py b/app/app/views.py index 9724fd70..af335750 100644 --- a/app/app/views.py +++ b/app/app/views.py @@ -4,7 +4,7 @@ from django.core.paginator import Paginator from django.db.models import Count from django.http import HttpResponse -from django.shortcuts import render +from django.shortcuts import get_object_or_404, render from django.utils.translation import gettext as _ from general.models import DocumentFile, Institution, Language, Project, Subject @@ -104,6 +104,7 @@ def projects(request): "languages": languages_data, "date": date, "institution_name": institution_name, + "description": project.description, } ) @@ -118,6 +119,60 @@ def projects(request): return render(request, template_name=template, context=context) +def project_detail(request, project_id): + template = "app/project_detail.html" + + project = get_object_or_404( + Project.objects.select_related("institution").prefetch_related("subjects", "languages"), + id=project_id, + ) + + logo = get_logo(project) + + context = { + "current_page": "project_detail", + "project": project, + "logo": logo, + "subjects": project.subjects.all(), + "languages": project.languages.all(), + } + return render(request, template_name=template, context=context) + + +def institution_detail(request, project_id): + template = "app/institution_detail.html" + + project = Project.objects.get(id=project_id) + + context = { + "current_page": "institution_detail", + } + return render(request, template_name=template, context=context) + + +def language_detail(request, project_id): + template = "app/language_detail.html" + + project = Project.objects.get(id=project_id) + + context = { + "current_page": "language_detail", + } + return render(request, template_name=template, context=context) + + +def subject_detail(request, project_id): + template = "app/subject_detail.html" + + project = Project.objects.get(id=project_id) + + context = { + "current_page": "subject_detail", + "project": project, + } + return render(request, template_name=template, context=context) + + def institutions(request): template = "app/institutions.html" context = {} diff --git a/app/general/tests/test_project_detail.py b/app/general/tests/test_project_detail.py new file mode 100644 index 00000000..bce7593d --- /dev/null +++ b/app/general/tests/test_project_detail.py @@ -0,0 +1,56 @@ +from django.test import TestCase +from django.urls import reverse + +from general.models import Institution, Language, Project, Subject + + +class ProjectDetailViewTests(TestCase): + def setUp(self): + self.institution = Institution.objects.create(name="Test University") + + self.project = Project.objects.create( + name="Test Project", + description="This is a test project", + url="https://example.com", + logo="path/to/logo.png", + start_date="2020-01-01", + end_date="2021-01-01", + institution=self.institution, + ) + self.subject1 = Subject.objects.create(name="Subject 1") + self.subject2 = Subject.objects.create(name="Subject 2") + + self.project.subjects.add(self.subject1, self.subject2) + + self.language1 = Language.objects.create(name="Language 1", iso_code="lang1") + self.language2 = Language.objects.create(name="Language 2", iso_code="lang2") + + self.project.languages.add(self.language1, self.language2) + + def test_project_detail_view(self): + response = self.client.get(reverse("project_detail", args=[self.project.id])) + self.assertEqual(response.status_code, 200) + self.assertContains(response, self.project.name) + self.assertContains(response, self.project.url) + self.assertContains(response, self.institution.name) + self.assertContains(response, self.subject1.name) + self.assertContains(response, self.language1.name) + + def test_project_detail_view_context(self): + response = self.client.get(reverse("project_detail", args=[self.project.id])) + self.assertIn("project", response.context) + self.assertIn("logo", response.context) + self.assertIn("subjects", response.context) + self.assertIn("languages", response.context) + self.assertEqual(response.context["project"], self.project) + self.assertEqual(list(response.context["subjects"]), [self.subject1, self.subject2]) + self.assertEqual(list(response.context["languages"]), [self.language1, self.language2]) + + def test_project_detail_view_num_queries(self): + with self.assertNumQueries(3): + response = self.client.get(reverse("project_detail", args=[self.project.id])) + + def test_project_detail_view_invalid_id(self): + invalid_project_id = self.project.id + 1 + response = self.client.get(reverse("project_detail", args=[invalid_project_id])) + self.assertEqual(response.status_code, 404) diff --git a/app/static/css/styles.css b/app/static/css/styles.css index 94b91659..3a5aece8 100755 --- a/app/static/css/styles.css +++ b/app/static/css/styles.css @@ -6,6 +6,7 @@ --primary-fg: #ffffff; --body-quiet-color: #485d95; --text-color: #000000; + --link-text: #2c6bce; } @@ -217,6 +218,15 @@ html { padding: 0 30px 0 30px; } +.project-name { + color: var(--text-color); + text-decoration: none; +} + +.project-name:hover { + text-decoration: underline; +} + .card-text-project { font-size: 0.875rem; margin: 3px 0 3px 0; @@ -271,6 +281,85 @@ html { font-size: 0.95rem; } +/*Project detail*/ +.project-detail { + width: 80%; + margin: 0 auto; + font-size: 0.875rem; +} +.project-detail h1 { + font-size: 2em; +} +.project-detail a { + text-decoration: none; + color: var(--link-text); +} +.project-detail a:hover, +.detail-col ul li a:hover { + text-decoration: underline; +} + +.project-detail { + width: 80%; + margin-left: 40px; +} + +.project-div-row { + display: flex; + justify-content: space-between; + margin-bottom: 20px; +} + +.project-date-left-col { + flex: 1; + padding-right: 10px; +} + +.project-date-right-col { + flex: 1; + display: flex; + justify-content: left; +} + +.detail-col { + flex: 1; + border-radius: 5px; +} + +.detail-col h2 { + font-size: 1.5em; + margin-bottom: 10px; +} + +.detail-col ul { + list-style: none; + padding: 0; +} + +.detail-col ul li { + margin-bottom: 5px; +} + +.detail-col ul li a { + text-decoration: none; + color: var(--link-text); +} + +.project-detail-left-col { + width: 50%; +} + +.project-detail-right-col { + width: 50%; + flex: 0 0 auto; + display: flex; + justify-content: left; +} +.project-date-row { + display: flex; + font-size: 0.875rem; +} + /*Error pages*/ .error-card { border-color: var(--primary-red); diff --git a/app/templates/app/institution_detail.html b/app/templates/app/institution_detail.html new file mode 100644 index 00000000..e69de29b diff --git a/app/templates/app/language_detail.html b/app/templates/app/language_detail.html new file mode 100644 index 00000000..e69de29b diff --git a/app/templates/app/project_detail.html b/app/templates/app/project_detail.html new file mode 100644 index 00000000..4b9159c5 --- /dev/null +++ b/app/templates/app/project_detail.html @@ -0,0 +1,80 @@ +{% extends "base.html" %} +{% load static %} +{% load i18n %} + +{% block content %} +
+
+

{% trans "Projects" %} > {{ project.name }}

+ +
+ +
+

{{ project.name }}

+ {{ project.url }} +

+ + {{ project.institution.name }} + +

+ +
+
+

+ + {% trans "Start Date:" %} {{ project.start_date|date:"Y-m-d" }} +

+
+ +
+

+ + {% trans "End Date:" %} {{ project.end_date|date:"Y-m-d" }} +

+
+
+ +
+ +
+ {% if project.logo %} + + {% endif %} +
+ +
+ + +
+
+
+

{% trans "Subjects" %}

+ +
+
+ +
+
+

{% trans "Languages" %}

+ +
+
+
+
+ +{% endblock content %} diff --git a/app/templates/app/projects.html b/app/templates/app/projects.html index 710ba9fb..8887d3e9 100644 --- a/app/templates/app/projects.html +++ b/app/templates/app/projects.html @@ -57,7 +57,11 @@
-

{{ project.project.name }}

+

+ + {{ project.project.name }} + +

@@ -75,7 +79,7 @@

{{ project.project.name }}

- {{ project.project.description }} + {{ project.description|truncatewords:30 }} Read more
diff --git a/app/templates/app/subject_detail.html b/app/templates/app/subject_detail.html new file mode 100644 index 00000000..e69de29b From bd146d4249da57b7fca97367d5ea9a0c0eff70e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9COMosimege=E2=80=9D?= <“onalerona.mosimege@gmail.com”> Date: Fri, 28 Jun 2024 10:39:44 +0200 Subject: [PATCH 115/240] Add individual institution page view, template, and styling add tests --- app/app/urls.py | 3 +- app/app/views.py | 25 +++++++- app/general/tests/test_institution_detail.py | 30 +++++++++ app/static/css/styles.css | 21 ++++--- app/templates/app/institution_detail.html | 66 ++++++++++++++++++++ app/templates/app/institutions.html | 2 +- app/templates/app/project_detail.html | 14 ++--- 7 files changed, 140 insertions(+), 21 deletions(-) create mode 100644 app/general/tests/test_institution_detail.py diff --git a/app/app/urls.py b/app/app/urls.py index d2e4b13f..74103c3c 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -33,7 +33,8 @@ path("institutions/", views.institutions, name="institutions"), path("projects/", views.projects, name="projects"), path("projects//", views.project_detail, name="project_detail"), - path("institution//", views.institution_detail, name="institution_detail"), + path("institution//", views.institution_detail, name="institution_detail"), + path("documents//", views.document_detail, name="document_detail"), path("language//", views.language_detail, name="language_detail"), path("subject//", views.subject_detail, name="subject_detail"), path("search/", views.search, name="search"), diff --git a/app/app/views.py b/app/app/views.py index af335750..da58000f 100644 --- a/app/app/views.py +++ b/app/app/views.py @@ -139,13 +139,33 @@ def project_detail(request, project_id): return render(request, template_name=template, context=context) -def institution_detail(request, project_id): +def institution_detail(request, institution_id): template = "app/institution_detail.html" - project = Project.objects.get(id=project_id) + institution = get_object_or_404(Institution, id=institution_id) + projects = Project.objects.filter(institution=institution) + documents = DocumentFile.objects.filter(institution=institution) + + logo = institution.logo context = { "current_page": "institution_detail", + "institution": institution, + "projects": projects, + "documents": documents, + "logo": logo, + } + + return render(request, template_name=template, context=context) + + +def document_detail(request, project_id): + template = "app/document_detail.html" + + document = DocumentFile.objects.get(id=document_id) + + context = { + "current_page": "document_detail", } return render(request, template_name=template, context=context) @@ -201,6 +221,7 @@ def institutions(request): rating = (completed_fields_count / len(institution_dict)) * 100 institution_dict["rating"] = round(rating) institution_dict["project_count"] = institution.project_count + institution_dict["id"] = institution.id institutions_array.append(institution_dict) context = {"current_page": "institutions", "institutions": institutions_array} diff --git a/app/general/tests/test_institution_detail.py b/app/general/tests/test_institution_detail.py new file mode 100644 index 00000000..71da4cb7 --- /dev/null +++ b/app/general/tests/test_institution_detail.py @@ -0,0 +1,30 @@ +from django.http import Http404 +from django.test import TestCase +from django.urls import reverse + +from general.models import DocumentFile, Institution, Project + + +class InstitutionDetailViewTests(TestCase): + def setUp(self): + self.institution = Institution.objects.create( + name="Sample Institution", + abbreviation="SI", + url="https://example.com", + email="info@example.com", + logo="path/to/logo.png", + ) + + def test_institution_detail_view_with_valid_id(self): + response = self.client.get(reverse("institution_detail", args=[self.institution.id])) + self.assertEqual(response.status_code, 200) + self.assertContains(response, self.institution.name) + + def test_institution_detail_view_with_invalid_id(self): + invalid_id = self.institution.id + 1 + response = self.client.get(reverse("institution_detail", args=[invalid_id])) + self.assertEqual(response.status_code, 404) + + def test_project_detail_view_num_queries(self): + with self.assertNumQueries(1): + response = self.client.get(reverse("project_detail", args=[self.institution.id])) diff --git a/app/static/css/styles.css b/app/static/css/styles.css index 3a5aece8..059f1586 100755 --- a/app/static/css/styles.css +++ b/app/static/css/styles.css @@ -219,7 +219,6 @@ html { } .project-name { - color: var(--text-color); text-decoration: none; } @@ -281,16 +280,16 @@ html { font-size: 0.95rem; } -/*Project detail*/ -.project-detail { +/*Project & Institution detail*/ +.detail { width: 80%; margin: 0 auto; font-size: 0.875rem; } -.project-detail h1 { +.detail h1 { font-size: 2em; } -.project-detail a { +.detail a { text-decoration: none; color: var(--link-text); } @@ -299,12 +298,12 @@ html { text-decoration: underline; } -.project-detail { +.detail { width: 80%; margin-left: 40px; } -.project-div-row { +.detail-row { display: flex; justify-content: space-between; margin-bottom: 20px; @@ -345,11 +344,11 @@ html { color: var(--link-text); } -.project-detail-left-col { +.detail-left-col { width: 50%; } -.project-detail-right-col { +.detail-right-col { width: 50%; flex: 0 0 auto; display: flex; @@ -360,6 +359,8 @@ html { font-size: 0.875rem; } + + /*Error pages*/ .error-card { border-color: var(--primary-red); @@ -371,7 +372,7 @@ html { .bi { font-size: 50px; } -.project-icon { +.detail-icon { font-size: 20px; margin-right: 3px; } diff --git a/app/templates/app/institution_detail.html b/app/templates/app/institution_detail.html index e69de29b..08d8131c 100644 --- a/app/templates/app/institution_detail.html +++ b/app/templates/app/institution_detail.html @@ -0,0 +1,66 @@ +{% extends "base.html" %} +{% load static %} +{% load i18n %} + +{% block content %} +
+
+

{% trans "Institutions" %} > {{ institution.name }}

+ +
+ +
+

{{ institution.name }} ({{ institution.abbreviation }})

+ {% if institution.url %} +

{{ institution.url }}

+ {% endif %} + {% if institution.email %} +

{{ institution.email }}

+ {% endif %} +
+ +
+ {% if institution.logo %} + + {% endif %} +
+
+ +
+ +
+
+

{% trans "Projects" %}

+ {% if projects %} + + {% else %} +

{% trans "No projects available for this institution." %}

+ {% endif %} +
+
+

{% trans "Documents" %}

+ {% if documents %} + + {% else %} +

{% trans "No documents available for this institution." %}

+ {% endif %} +
+
+ +
+ +{% endblock content %} diff --git a/app/templates/app/institutions.html b/app/templates/app/institutions.html index 4973269b..663ab27f 100644 --- a/app/templates/app/institutions.html +++ b/app/templates/app/institutions.html @@ -10,7 +10,7 @@
-
{{ institution.name }}
+
{{ institution.name }}

{{ institution.abbreviation }}

diff --git a/app/templates/app/project_detail.html b/app/templates/app/project_detail.html index 4b9159c5..c94c1ce1 100644 --- a/app/templates/app/project_detail.html +++ b/app/templates/app/project_detail.html @@ -4,12 +4,12 @@ {% block content %}
-
+

{% trans "Projects" %} > {{ project.name }}

-
+
-
+

{{ project.name }}

{{ project.url }}

@@ -36,7 +36,7 @@

{{ project.name }}

-
+
{% if project.logo %} @@ -46,14 +46,14 @@

{{ project.name }}

-
+

{% trans "Subjects" %}

    {% for subject in subjects %}
  • - + {{ subject.name }}
  • {% endfor %} @@ -67,7 +67,7 @@

    {% trans "Languages" %}

      {% for language in languages %}
    • - + {{ language.name }}
    • {% endfor %} From 9c54918e1d88b5aa59508c4523e0f48d4c5f81f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9COMosimege=E2=80=9D?= <“onalerona.mosimege@gmail.com”> Date: Tue, 2 Jul 2024 07:18:56 +0200 Subject: [PATCH 116/240] add migrations --- ...011_alter_documentfile_options_and_more.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 app/general/migrations/0011_alter_documentfile_options_and_more.py diff --git a/app/general/migrations/0011_alter_documentfile_options_and_more.py b/app/general/migrations/0011_alter_documentfile_options_and_more.py new file mode 100644 index 00000000..4010da6f --- /dev/null +++ b/app/general/migrations/0011_alter_documentfile_options_and_more.py @@ -0,0 +1,31 @@ +# Generated by Django 5.0.2 on 2024-07-02 05:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('general', '00010_documentfile_search_vector_trigger'), + ] + + operations = [ + migrations.AlterModelOptions( + name='documentfile', + options={'verbose_name': 'Document', 'verbose_name_plural': 'Documents'}, + ), + migrations.AlterModelOptions( + name='historicaldocumentfile', + options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical Document', 'verbose_name_plural': 'historical Documents'}, + ), + migrations.AlterField( + model_name='documentfile', + name='document_type', + field=models.CharField(choices=[('Glossary', 'Glossary'), ('Policy', 'Policy')], max_length=200, verbose_name='document category'), + ), + migrations.AlterField( + model_name='historicaldocumentfile', + name='document_type', + field=models.CharField(choices=[('Glossary', 'Glossary'), ('Policy', 'Policy')], max_length=200, verbose_name='document category'), + ), + ] From de85130c7feb3f102aa3448d050f312319b5b10f Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Mon, 1 Jul 2024 19:11:22 +0200 Subject: [PATCH 117/240] Adding user management - Added accounts - Auth Templates - Updated Environmentzs - Added Form tests - Added Email support --- .env.example | 7 ++ README.md | 18 ++++ app/accounts/__init__.py | 0 app/accounts/admin.py | 3 + app/accounts/apps.py | 6 ++ app/accounts/forms.py | 22 +++++ app/accounts/migrations/__init__.py | 0 app/accounts/models.py | 3 + app/accounts/tests/__init__.py | 0 app/accounts/tests/test_signup_form.py | 88 +++++++++++++++++++ app/accounts/urls.py | 33 +++++++ app/accounts/views.py | 18 ++++ app/app/settings.py | 15 ++++ app/app/urls.py | 3 + app/templates/accounts/login.html | 22 +++++ .../accounts/password_reset_complete.html | 11 +++ .../accounts/password_reset_confirm.html | 43 +++++++++ .../accounts/password_reset_done.html | 13 +++ .../accounts/password_reset_form.html | 28 ++++++ app/templates/accounts/register.html | 14 +++ docker-compose.yml | 6 ++ 21 files changed, 353 insertions(+) create mode 100644 app/accounts/__init__.py create mode 100644 app/accounts/admin.py create mode 100644 app/accounts/apps.py create mode 100644 app/accounts/forms.py create mode 100644 app/accounts/migrations/__init__.py create mode 100644 app/accounts/models.py create mode 100644 app/accounts/tests/__init__.py create mode 100644 app/accounts/tests/test_signup_form.py create mode 100644 app/accounts/urls.py create mode 100644 app/accounts/views.py create mode 100644 app/templates/accounts/login.html create mode 100644 app/templates/accounts/password_reset_complete.html create mode 100644 app/templates/accounts/password_reset_confirm.html create mode 100644 app/templates/accounts/password_reset_done.html create mode 100644 app/templates/accounts/password_reset_form.html create mode 100644 app/templates/accounts/register.html diff --git a/.env.example b/.env.example index ec78dd8b..436b34ff 100644 --- a/.env.example +++ b/.env.example @@ -11,3 +11,10 @@ LOGGING_LOGGERS_LEVEL=INFO LOGGING_LOGGERS_DJANGO_LEVEL=INFO TESTING_DIR=/app/general/tests/files/ FEATURE_FLAG='' +EMAIL_HOST='' +EMAIL_USE_TLS=True +EMAIL_PORT=587 +EMAIL_HOST_USER='' +EMAIL_HOST_PASSWORD='' +EMAIL_BACKEND_CONSOLE='True/False' +EMAIL_USE_TLS=True diff --git a/README.md b/README.md index 5340f57e..5a1ea029 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,19 @@ About the project: --- +### Email Settings in Development + +.env file + +* EMAIL_HOST='sandbo x.smtp.mailtrap.io' +* EMAIL_HOST_USER='*********' +* EMAIL_HOST_PASSWORD='******' +* EMAIL_PORT='2525' +* EMAIL_BACKEND_CONSOLE=True + +By default, the email backend is set to console, so you can see the email in the console. +To send an email, you need to set the EMAIL_BACKEND_CONSOLE to False. + ### Plugins installed #### Django Simple History @@ -58,3 +71,8 @@ Docker Volumes for production: * /logging * /pdf_uploads * /pdf_upload_completed + +### Email Settings in Production + +.env file +* EMAIL_BACKEND_CONSOLE=False diff --git a/app/accounts/__init__.py b/app/accounts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/accounts/admin.py b/app/accounts/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/app/accounts/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/app/accounts/apps.py b/app/accounts/apps.py new file mode 100644 index 00000000..0cb51e63 --- /dev/null +++ b/app/accounts/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AccountsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "accounts" diff --git a/app/accounts/forms.py b/app/accounts/forms.py new file mode 100644 index 00000000..e8cb29e7 --- /dev/null +++ b/app/accounts/forms.py @@ -0,0 +1,22 @@ +from django import forms +from django.contrib.auth.forms import UserCreationForm + +from users.models import CustomUser + + +class CustomUserCreationForm(UserCreationForm): + email = forms.EmailField(required=True, help_text="Required. Add a valid email address.") + username = forms.CharField(required=True, help_text="Required. Add a valid username.") + first_name = forms.CharField(required=True, help_text="Required. Add a valid first name.") + last_name = forms.CharField(required=True, help_text="Required. Add a valid last name.") + + class Meta: + model = CustomUser + fields = ("username", "email", "first_name", "last_name") + + def __init__(self, *args, **kwargs): + super(CustomUserCreationForm, self).__init__(*args, **kwargs) + self.fields["email"].required = True + self.fields["username"].required = True + self.fields["first_name"].required = True + self.fields["last_name"].required = True diff --git a/app/accounts/migrations/__init__.py b/app/accounts/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/accounts/models.py b/app/accounts/models.py new file mode 100644 index 00000000..71a83623 --- /dev/null +++ b/app/accounts/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/app/accounts/tests/__init__.py b/app/accounts/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/accounts/tests/test_signup_form.py b/app/accounts/tests/test_signup_form.py new file mode 100644 index 00000000..250c6dcd --- /dev/null +++ b/app/accounts/tests/test_signup_form.py @@ -0,0 +1,88 @@ +import unittest + +from django.test import TestCase + +from accounts.forms import CustomUserCreationForm + + +class CustomUserCreationFormTest(TestCase): + def setUp(self): + self.username = "testuser" + self.email = "testuser@gmail.com" + self.first_name = "Test" + self.last_name = "User" + self.password1 = "sadilar2024" + self.password2 = "sadilar2024" + + def test_valid_data(self): + form = CustomUserCreationForm( + { + "username": self.username, + "email": self.email, + "first_name": self.first_name, + "last_name": self.last_name, + "password1": self.password1, + "password2": self.password2, + } + ) + + self.assertTrue(form.is_valid()) + + def test_blank_data(self): + form = CustomUserCreationForm({}) + self.assertFalse(form.is_valid()) + + self.assertEqual( + form.errors, + { + "username": ["This field is required."], + "email": ["This field is required."], + "first_name": ["This field is required."], + "last_name": ["This field is required."], + "password1": ["This field is required."], + "password2": ["This field is required."], + }, + ) + + def test_invalid_email(self): + form = CustomUserCreationForm( + { + "username": self.username, + "email": "not a valid email", + "first_name": self.first_name, + "last_name": self.last_name, + "password1": self.password1, + "password2": self.password2, + } + ) + self.assertFalse(form.is_valid()) + self.assertEqual( + form.errors, + { + "email": ["Enter a valid email address."], + }, + ) + + def test_passwords_do_not_match(self): + form = CustomUserCreationForm( + { + "username": self.username, + "email": self.email, + "first_name": self.first_name, + "last_name": self.last_name, + "password1": self.password1, + "password2": "wrong password", + } + ) + + self.assertFalse(form.is_valid()) + self.assertEqual( + form.errors, + { + "password2": ["The two password fields didn’t match."], + }, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/app/accounts/urls.py b/app/accounts/urls.py new file mode 100644 index 00000000..eb6d59cb --- /dev/null +++ b/app/accounts/urls.py @@ -0,0 +1,33 @@ +from django.contrib.auth import views as auth_views +from django.urls import path + +from . import views + +urlpatterns = [ + path("register/", views.register, name="accounts_register"), + path("login/", auth_views.LoginView.as_view(template_name="accounts/login.html"), name="login"), + path( + "password_reset/", + auth_views.PasswordResetView.as_view(template_name="accounts/password_reset_form.html"), + name="password_reset", + ), + path( + "password_reset/done/", + auth_views.PasswordResetDoneView.as_view(template_name="accounts/password_reset_done.html"), + name="password_reset_done", + ), + path( + "reset///", + auth_views.PasswordResetConfirmView.as_view( + template_name="accounts/password_reset_confirm.html" + ), + name="password_reset_confirm", + ), + path( + "reset/done/", + auth_views.PasswordResetCompleteView.as_view( + template_name="accounts/password_reset_complete.html" + ), + name="password_reset_complete", + ), +] diff --git a/app/accounts/views.py b/app/accounts/views.py new file mode 100644 index 00000000..b2eabcb4 --- /dev/null +++ b/app/accounts/views.py @@ -0,0 +1,18 @@ +from django.contrib.auth import login +from django.shortcuts import redirect, render + +from .forms import CustomUserCreationForm + + +def register(request): + if request.method == "POST": + form = CustomUserCreationForm(request.POST) + if form.is_valid(): + user = form.save(commit=False) + user.is_staff = True + user.save() + login(request, user) + return redirect("home") + else: + form = CustomUserCreationForm() + return render(request, "accounts/register.html", {"form": form}) diff --git a/app/app/settings.py b/app/app/settings.py index af92a265..e03a5c95 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -47,6 +47,7 @@ "users", "general", "simple_history", + "accounts", ] # Add django-extensions to the installed apps if DEBUG is True @@ -125,6 +126,20 @@ "host.docker.internal", ] +# Email settings +EMAIL_HOST = os.environ.get("EMAIL_HOST") +EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER") +EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD") +EMAIL_PORT = os.environ.get("EMAIL_PORT") +EMAIL_USE_TLS = os.environ.get("EMAIL_USE_TLS") + +email_backend_env = os.environ.get("EMAIL_BACKEND_CONSOLE", "False").lower() in ["true", "1", "yes"] + +if DEBUG and email_backend_env: + EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" + +LOGIN_REDIRECT_URL = "/" +LOGOUT_REDIRECT_URL = "/" # Password validation # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators diff --git a/app/app/urls.py b/app/app/urls.py index 74103c3c..649d0d9b 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -18,6 +18,7 @@ from django.conf import settings from django.conf.urls.static import static from django.contrib import admin +from django.contrib.auth import views as auth_views from django.urls import include, path from django.utils.translation import gettext_lazy as _ @@ -39,6 +40,8 @@ path("subject//", views.subject_detail, name="subject_detail"), path("search/", views.search, name="search"), path("i18n/", include("django.conf.urls.i18n")), + path("accounts/", include("accounts.urls")), + path("accounts/", include("django.contrib.auth.urls")), ] if settings.DEBUG: diff --git a/app/templates/accounts/login.html b/app/templates/accounts/login.html new file mode 100644 index 00000000..99b7250d --- /dev/null +++ b/app/templates/accounts/login.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} + +{% block title %}Log In{% endblock %} + +{% block content %} +
      +

      Log In

      +
      + {% csrf_token %} + {{ form }} + +
      + +
      +
      +
      + {% csrf_token %} + +
      +
      + +{% endblock %} diff --git a/app/templates/accounts/password_reset_complete.html b/app/templates/accounts/password_reset_complete.html new file mode 100644 index 00000000..caec7e04 --- /dev/null +++ b/app/templates/accounts/password_reset_complete.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} + +{% block title %}Password reset complete{% endblock %} + +{% block content %} +
      +

      Your password has been set. You may go ahead and log in now.

      +
      +

      Log in

      +
      +{% endblock %} diff --git a/app/templates/accounts/password_reset_confirm.html b/app/templates/accounts/password_reset_confirm.html new file mode 100644 index 00000000..74974523 --- /dev/null +++ b/app/templates/accounts/password_reset_confirm.html @@ -0,0 +1,43 @@ +{% extends "base.html" %} + +{% block title %}Enter new password{% endblock %} + +{% block content %} + + {% if validlink %} + +
      +

      Please enter your new password twice so we can verify you typed it in correctly.

      + +
      {% csrf_token %} +
      +
      + {{ form.new_password1.errors }} +
      + + {{ form.new_password1 }} +
      +
      +
      + {{ form.new_password2.errors }} +
      + + {{ form.new_password2 }} +
      +
      +
      +
      +
      +
      + +
      +
      + + {% else %} + +

      "The password reset link was invalid, possibly because it has already been used. Please request a new + password reset.

      + + {% endif %} +
      +{% endblock %} diff --git a/app/templates/accounts/password_reset_done.html b/app/templates/accounts/password_reset_done.html new file mode 100644 index 00000000..0083930c --- /dev/null +++ b/app/templates/accounts/password_reset_done.html @@ -0,0 +1,13 @@ +{% extends "base.html" %} + +{% block title %}Email Sent{% endblock %} + +{% block content %} +
      +

      We’ve emailed you instructions for setting your password, if an account exists with the email you entered. + You should receive them shortly.

      + +

      If you don’t receive an email, please make sure you’ve entered the address you registered with, and check + your spam folder.

      +
      +{% endblock %} diff --git a/app/templates/accounts/password_reset_form.html b/app/templates/accounts/password_reset_form.html new file mode 100644 index 00000000..1f4ea298 --- /dev/null +++ b/app/templates/accounts/password_reset_form.html @@ -0,0 +1,28 @@ +{% extends 'base.html' %} + +{% block title %}Forgot Your Password?{% endblock %} + +{% block content %} +
      +

      Forgotten your password? Enter your email address below, and we’ll email instructions for setting a new + one.

      + +
      + {% csrf_token %} +
      +
      + {{ form.email.errors }} +
      + + {{ form.email }} +
      +
      +
      +
      +
      +
      + +
      +
      +
      +{% endblock %} diff --git a/app/templates/accounts/register.html b/app/templates/accounts/register.html new file mode 100644 index 00000000..8cfaafb3 --- /dev/null +++ b/app/templates/accounts/register.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} + +{% block title %}Sign Up{% endblock %} + +{% block content %} +
      +

      Register

      +
      + {% csrf_token %} + {{ form.as_p }} + +
      +
      +{% endblock %} diff --git a/docker-compose.yml b/docker-compose.yml index 07be97f8..b18cb7c9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -43,3 +43,9 @@ services: - DB_PASSWORD=sadilar # see POSTGRES_PASSWORD above - TESTING_DIR=/app/general/tests/files/ - FEATURE_FLAG=search_feature + - EMAIL_HOST=${EMAIL_HOST-} + - EMAIL_HOST_USER=${EMAIL_HOST_USER-} + - EMAIL_HOST_PASSWORD=${EMAIL_HOST_PASSWORD-"none"} + - EMAIL_PORT=${EMAIL_PORT-"none"} + - EMAIL_BACKEND_CONSOLE=${EMAIL_BACKEND_CONSOLE:-True} + - EMAIL_USE_TLS=${EMAIL_USE_TLS:-True} From 4024d26617e1fda89c8a6abb91ea88bc95aa02d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9COMosimege=E2=80=9D?= <“onalerona.mosimege@gmail.com”> Date: Wed, 26 Jun 2024 07:32:10 +0200 Subject: [PATCH 118/240] Add changes to styling and template according to new spec --- app/app/views.py | 8 +- app/static/css/styles.css | 183 +++++++++++++--------- app/templates/app/institution_detail.html | 22 ++- app/templates/app/institutions.html | 6 +- app/templates/app/project_detail.html | 16 +- app/templates/app/projects.html | 181 +++++++++++---------- 6 files changed, 231 insertions(+), 185 deletions(-) diff --git a/app/app/views.py b/app/app/views.py index da58000f..8285f7ce 100644 --- a/app/app/views.py +++ b/app/app/views.py @@ -61,7 +61,7 @@ def projects(request): projects = ( Project.objects.select_related("institution") .prefetch_related("subjects", "languages") - .all() + .order_by("name") ) if subject_id: @@ -71,9 +71,9 @@ def projects(request): if institution_id: projects = projects.filter(institution__id=institution_id) - subjects = Subject.objects.all() - languages = Language.objects.all() - institutions = Institution.objects.all() + subjects = Subject.objects.order_by("name") + languages = Language.objects.order_by("name") + institutions = Institution.objects.order_by("name") project_data = [] for project in projects: diff --git a/app/static/css/styles.css b/app/static/css/styles.css index 059f1586..d8ff1f36 100755 --- a/app/static/css/styles.css +++ b/app/static/css/styles.css @@ -33,104 +33,81 @@ html { position: absolute; bottom: 0; } - .header-container { display: inline-block; } - .nav-menu { float: left; padding: 5px; margin: 10px 10px 10px 10px; } - .nav-logo { margin: 3px 10px 10px 10px; float: left; } - .nav { background: var(--primary-fg); width: 100%; } - .nav > li > a{ margin-top: 15px; color: var(--text-color); } - .nav > li > .active{ margin-top: 15px; background-color: var(--primary) !important; color: var(--primary-fg); } - .nav > li > a:hover{ margin-top: 15px; background-color: var(--body-quiet-color); color: var(--primary-fg); } - .nav > li > a:active{ margin-top: 15px; background-color: var(--primary); color: var(--primary-fg); } - .btn-primary{ margin-top:20px; outline-color: var(--primary); background-color: var(--primary); border-color: var(--primary); } - .btn-outline-primary{ margin-top:20px; background: #2D4481; outline-color: var(--primary); } - .btn-primary:hover{ margin-top:20px; background: var(--body-quiet-color); outline-color: var(--primary); } - .app-footer .footer-title { color: var(--primary); } - .footer > a { color: var(--primary-fg); } - .section{ margin-left: 10px; margin-right: 10px; margin-bottom: 10px; } - .main-logo { width: 140px; height: 75px; margin: 20px 10px 10px 10px; } - .page-link { background-color: var(--primary) !important; color: var(--primary-fg) !important; } - .page-link:hover{ background-color: var(--body-quiet-color) !important; color: var(--primary-fg) !important; } - -.row { - margin: 10px; - padding: 10px; -} - .footer-class { display: flex; justify-content: center; @@ -138,22 +115,18 @@ html { position: relative; flex-direction: column; } - .language-toggle { margin-top: 1rem; } - .language-toggle form { display: flex; flex-direction: column; align-items: center; } - .language-toggle select, .language-toggle .btn-language { margin-top: 0.5rem; } - .btn-language{ color: var(--text-color); outline-color: var(--primary-fg); @@ -172,39 +145,70 @@ html { border-color: var(--primary-red); margin: 0 10px 0 10px; } - .uni-logo { width: auto; height: 75px; margin: 10px 0 10px 0; padding: 0 10px 10px 0; } +.institution-row { + margin: 10px; + padding: 10px; + display: flex; + justify-content: space-between; + align-items: flex-start; + flex-wrap: wrap; +} +.left-col, .right-col { + flex: 1; + min-width: 200px; + display: flex; + flex-direction: column; + justify-content: center; +} /*Projects page*/ -.filter-form { - display: flex; +.filter-class label { + font-size: 1.15rem; +} +.filter-class { margin: 30px; font-size: 0.875rem; } +.filter-form { + display: flex; + flex-wrap: wrap; + gap: 10px; + align-items: center; + justify-content: flex-start; + width: 100%; + box-sizing: border-box; +} +.filter-label { + width: 100%; + margin-bottom: 10px; +} .filter-form label { margin-right: 10px; } .filter-form .form-group { - margin-right: 10px; + margin: 0 10px 10px 0; } .filter-form .form-control { border: 1px solid #000; } -.filter-form select { - padding: 0 10px; - width: 230px; +.button-group { + display: flex; + align-items: center; } .btn-apply, .btn-reset{ color: var(--primary-fg); outline-color: var(--primary); background-color: var(--primary); border-color: var(--primary); + padding: 10px 20px; margin-right: 10px; + white-space: nowrap; } .btn-apply:hover, .btn-reset:hover{ color: var(--primary-fg); @@ -242,15 +246,6 @@ html { padding: 0 5px 0 0; word-wrap: break-word; } -.project-left-col { - width: 70%; -} -.project-right-col { - width: 30%; - flex: 0 0 auto; - display: flex; - justify-content: flex-end; -} .project-header { display: flex; justify-content: space-between; @@ -270,7 +265,11 @@ html { } .project-row { display: flex; + align-items: center; justify-content: space-between; + flex-wrap: wrap; + gap: 10px; + box-sizing: border-box; width: 100%; } .project-text { @@ -280,7 +279,7 @@ html { font-size: 0.95rem; } -/*Project & Institution detail*/ +/*Projects & Institution detail pages*/ .detail { width: 80%; margin: 0 auto; @@ -304,8 +303,7 @@ html { } .detail-row { - display: flex; - justify-content: space-between; + flex-wrap: wrap; margin-bottom: 20px; } @@ -321,7 +319,6 @@ html { } .detail-col { - flex: 1; border-radius: 5px; } @@ -343,19 +340,7 @@ html { text-decoration: none; color: var(--link-text); } - -.detail-left-col { - width: 50%; -} - -.detail-right-col { - width: 50%; - flex: 0 0 auto; - display: flex; - justify-content: left; -} .project-date-row { - display: flex; font-size: 0.875rem; } @@ -396,21 +381,20 @@ html { width: 98%; padding-right: 30px; } - .col-sm-6 { - width: 50%; + .institution-row { + flex-direction: column; + align-items: flex-start; } + .left-col, .right-col { + width: 100%; + } + /*Home page*/ .content-card { font-size: smaller; padding-right: 20px; margin-right: 10px; } - - /*Projects page*/ - .filter-form select { - padding: 0 10px; - width: 100px; - } } /*small devices (tablets, 600px and up) */ @@ -444,12 +428,6 @@ html { .content-card { font-size: smaller; } - - /*Projects page*/ - .filter-form select { - padding: 0 10px; - width: 150px; - } } /*Medium screens (tablets, between 768px and 1001px)*/ @@ -507,6 +485,35 @@ html { padding: 0 10px; width: 200px; } + .project-row { + display: flex; + justify-content: space-between; + } + .project-left-col { + width: 70%; + flex: 1; + min-width: 200px; + } + .project-right-col { + width: 30%; + flex: 0 0 auto; + display: flex; + align-items: center; + justify-content: center; + } + + /*Projects & Institution detail pages*/ + .detail-left-col, .detail-right-col { + flex: 1; + padding: 10px; + } + .project-date-row { + display: flex; + font-size: 0.875rem; + } + .filter-form { + display: flex; + } } /*Larger screens (desktops, 1001px and up)*/ @@ -565,4 +572,34 @@ html { padding: 0 10px; width: 230px; } + .project-row { + display: flex; + justify-content: space-between; + } + .project-left-col { + width: 70%; + flex: 1; + min-width: 200px; + } + .project-right-col { + width: 30%; + flex: 0 0 auto; + display: flex; + align-items: center; + justify-content: center; + } + + /*Projects & Institution detail pages*/ + .detail-right-col { + flex: 0 0 auto; + display: flex; + justify-content: left; + } + .project-date-row { + display: flex; + font-size: 0.875rem; + } + .filter-form { + display: flex; + } } diff --git a/app/templates/app/institution_detail.html b/app/templates/app/institution_detail.html index 08d8131c..23fab35b 100644 --- a/app/templates/app/institution_detail.html +++ b/app/templates/app/institution_detail.html @@ -7,9 +7,8 @@

      {% trans "Institutions" %} > {{ institution.name }}

      -
      - -
      +
      + -
      - {% if institution.logo %} - - {% endif %} + {% if institution.logo %} +
      +
      + {% endif %}
      -
      - -
      -
      +
      +

      {% trans "Projects" %}

      {% if projects %}
        @@ -44,7 +41,7 @@

        {% trans "Projects" %}

        {% trans "No projects available for this institution." %}

        {% endif %}
      -
      +

      {% trans "Documents" %}

      {% if documents %}
        @@ -60,7 +57,6 @@

        {% trans "Documents" %}

        {% endif %}
      -
      {% endblock content %} diff --git a/app/templates/app/institutions.html b/app/templates/app/institutions.html index 663ab27f..8a002a7f 100644 --- a/app/templates/app/institutions.html +++ b/app/templates/app/institutions.html @@ -8,8 +8,8 @@ {% for institution in institutions %}
      -
      -
      +
      +
      {{ institution.name }}

      {{ institution.abbreviation }} @@ -21,7 +21,7 @@

      { {% endif %}

      -
      +

      {% trans "Profile completion:" %}

      {% if institution.rating <= 20 %} diff --git a/app/templates/app/project_detail.html b/app/templates/app/project_detail.html index c94c1ce1..e16f817e 100644 --- a/app/templates/app/project_detail.html +++ b/app/templates/app/project_detail.html @@ -7,9 +7,9 @@

      {% trans "Projects" %} > {{ project.name }}

      -
      +
      -
      + -
      - {% if project.logo %} - {% blocktrans with project_name=project.name %}{{ project_name }} logo{% endblocktrans %} + {% if logo %} + {% endif %}
      @@ -46,8 +46,8 @@

      {{ project.name }}

      -
      -
      +
      +

      {% trans "Subjects" %}

        @@ -61,7 +61,7 @@

        {% trans "Subjects" %}

      -
      +

      {% trans "Languages" %}

        diff --git a/app/templates/app/projects.html b/app/templates/app/projects.html index 8887d3e9..a0bc745d 100644 --- a/app/templates/app/projects.html +++ b/app/templates/app/projects.html @@ -3,105 +3,118 @@ {% load i18n %} {% block content %} +
        +
        -
        - - - -
        - -
        +
        + +
        -
        - -
        +
        + + +
        -
        - -
        +
        + + +
        - - {% trans 'Reset' %} +
        + + +
        - + -
        -
        + +
        +
        -
        - {% for project in projects %} -
        -
        -
        -
        -

        - - {{ project.project.name }} - -

        - -
        - {{ project.institution_name }} -
        -
        -
        - {% if project.logo %} - - {% endif %} -
        -
        -
        -
        -
        - {{ project.description|truncatewords:30 }} Read more -
        +
        + {% for project in projects %} +
        +
        +
        +
        +

        + + {{ project.project.name }} + +

        - - {{ project.languages }} + + {{ project.project.url }} +
        -
        - - {{ project.subjects }} +
        + {{ project.institution_name }}
        -
        - - {{ project.date }} -
        -
        +
        + {% if project.logo %} + + {% endif %}
        - -
        -
        -
        - {% endfor %}
        +
        +
        +
        + {{ project.description|truncatewords:30 }} {% trans "Read more" %} +
        +
        + {% if project.languages %} + + {{ project.languages }} + {% endif %} +
        +
        + {% if project.subjects %} + + {{ project.subjects }} + {% endif %} +
        +
        + {% if project.date %} + + {{ project.date }} + {% endif %} +
        + +
        +
        + +
        +
        +
        + {% endfor %} +
        From e9a0bc2aca9182ff99245704273bbf9fcf86f2f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9COMosimege=E2=80=9D?= <“onalerona.mosimege@gmail.com”> Date: Wed, 10 Jul 2024 14:26:12 +0200 Subject: [PATCH 119/240] Add user manangement views, templates, and styling --- app/accounts/forms.py | 38 ++++++-- app/accounts/views.py | 20 ++++- app/static/css/styles.css | 12 ++- app/templates/accounts/login.html | 86 +++++++++++++++---- .../accounts/password_reset_complete.html | 20 +++-- .../accounts/password_reset_confirm.html | 63 ++++++++------ .../accounts/password_reset_done.html | 16 ++-- .../accounts/password_reset_form.html | 44 ++++++---- app/templates/accounts/register.html | 49 +++++++++-- app/templates/base_error.html | 2 +- 10 files changed, 254 insertions(+), 96 deletions(-) diff --git a/app/accounts/forms.py b/app/accounts/forms.py index e8cb29e7..c58c33a4 100644 --- a/app/accounts/forms.py +++ b/app/accounts/forms.py @@ -1,14 +1,26 @@ from django import forms -from django.contrib.auth.forms import UserCreationForm +from django.contrib.auth.forms import AuthenticationForm, UserCreationForm +from django.utils.translation import gettext_lazy as _ from users.models import CustomUser class CustomUserCreationForm(UserCreationForm): - email = forms.EmailField(required=True, help_text="Required. Add a valid email address.") - username = forms.CharField(required=True, help_text="Required. Add a valid username.") - first_name = forms.CharField(required=True, help_text="Required. Add a valid first name.") - last_name = forms.CharField(required=True, help_text="Required. Add a valid last name.") + email = forms.EmailField( + required=True, + help_text=_("Required. Add a valid email address."), + ) + username = forms.CharField( + required=True, + help_text=_("Required. Add a valid username."), + ) + first_name = forms.CharField( + required=True, + help_text=_("Required. Add a valid first name."), + ) + last_name = forms.CharField( + required=True, + ) class Meta: model = CustomUser @@ -16,7 +28,15 @@ class Meta: def __init__(self, *args, **kwargs): super(CustomUserCreationForm, self).__init__(*args, **kwargs) - self.fields["email"].required = True - self.fields["username"].required = True - self.fields["first_name"].required = True - self.fields["last_name"].required = True + for field_name, field in self.fields.items(): + field.widget.attrs["class"] = "form-control" + + +class CustomAuthenticationForm(AuthenticationForm): + username = forms.CharField(label=_("Username"), help_text=_("Required. Enter your username.")) + password = forms.CharField(label=_("Password"), help_text=_("Required. Enter your password.")) + + def __init__(self, *args, **kwargs): + super(CustomAuthenticationForm, self).__init__(*args, **kwargs) + for field in self.fields.values(): + field.widget.attrs.update({"class": "form-control"}) diff --git a/app/accounts/views.py b/app/accounts/views.py index b2eabcb4..e02657fa 100644 --- a/app/accounts/views.py +++ b/app/accounts/views.py @@ -1,7 +1,8 @@ -from django.contrib.auth import login +from django.contrib.auth import authenticate +from django.contrib.auth import login as auth_login from django.shortcuts import redirect, render -from .forms import CustomUserCreationForm +from .forms import CustomAuthenticationForm, CustomUserCreationForm def register(request): @@ -11,8 +12,21 @@ def register(request): user = form.save(commit=False) user.is_staff = True user.save() - login(request, user) + auth_login(request, user) return redirect("home") else: form = CustomUserCreationForm() return render(request, "accounts/register.html", {"form": form}) + + +def user_login(request): + if request.method == "POST": + form = CustomAuthenticationForm(request, data=request.POST) + if form.is_valid(): + user = form.get_user() + auth_login(request, user) + return redirect("home") + else: + form = CustomAuthenticationForm() + + return render(request, "accounts/login.html", {"form": form}) diff --git a/app/static/css/styles.css b/app/static/css/styles.css index d8ff1f36..438c5e2e 100755 --- a/app/static/css/styles.css +++ b/app/static/css/styles.css @@ -344,12 +344,20 @@ html { font-size: 0.875rem; } +/*Accounts*/ +.user-account-body { + margin: 30px; +} +.login-form-group .form-control { + width: 100%; + max-width: 500px; +} /*Error pages*/ -.error-card { +.body-card { border-color: var(--primary-red); - margin: 0 10px 0 10px; + margin: 10px; width: 100%; } diff --git a/app/templates/accounts/login.html b/app/templates/accounts/login.html index 99b7250d..b782503b 100644 --- a/app/templates/accounts/login.html +++ b/app/templates/accounts/login.html @@ -1,22 +1,76 @@ {% extends "base.html" %} +{% load static %} +{% load i18n %} -{% block title %}Log In{% endblock %} +{% block title %}{% trans "Log In" %}{% endblock %} {% block content %} -
        -

        Log In

        -
        - {% csrf_token %} - {{ form }} - -
        - -
        -
        -
        - {% csrf_token %} - -
        -
        +
        +
        +
        + +
        +
        +
        {% endblock %} diff --git a/app/templates/accounts/password_reset_complete.html b/app/templates/accounts/password_reset_complete.html index caec7e04..47d75de7 100644 --- a/app/templates/accounts/password_reset_complete.html +++ b/app/templates/accounts/password_reset_complete.html @@ -1,11 +1,21 @@ {% extends 'base.html' %} +{% load static %} +{% load i18n %} -{% block title %}Password reset complete{% endblock %} +{% block title %}{% trans "Password reset complete" %}{% endblock %} {% block content %} -
        -

        Your password has been set. You may go ahead and log in now.

        -
        -

        Log in

        +
        +
        + +
        {% endblock %} diff --git a/app/templates/accounts/password_reset_confirm.html b/app/templates/accounts/password_reset_confirm.html index 74974523..89a7d6cb 100644 --- a/app/templates/accounts/password_reset_confirm.html +++ b/app/templates/accounts/password_reset_confirm.html @@ -1,43 +1,50 @@ {% extends "base.html" %} +{% load static %} +{% load i18n %} -{% block title %}Enter new password{% endblock %} +{% block title %}{% trans "Enter new password" %}{% endblock %} {% block content %} {% if validlink %} -
        -

        Please enter your new password twice so we can verify you typed it in correctly.

        - -
        {% csrf_token %} -
        -
        - {{ form.new_password1.errors }} -
        - - {{ form.new_password1 }} +
        +
        + -
        - {{ form.new_password2.errors }} -
        - - {{ form.new_password2 }} +
        + {{ form.new_password2.errors }} +
        + + {{ form.new_password2 }} +
        +
        +
        +
        +
        +
        - -
        -
        -
        - -
        -
        + - {% else %} + {% else %} -

        "The password reset link was invalid, possibly because it has already been used. Please request a new - password reset.

        +

        {% blocktrans %}The password reset link was invalid, possibly because it has already been used. Please request a new + password reset.{% endblocktrans %}

        - {% endif %} + {% endif %} +
        +
        +
        {% endblock %} diff --git a/app/templates/accounts/password_reset_done.html b/app/templates/accounts/password_reset_done.html index 0083930c..3bb37be6 100644 --- a/app/templates/accounts/password_reset_done.html +++ b/app/templates/accounts/password_reset_done.html @@ -1,13 +1,19 @@ {% extends "base.html" %} +{% load static %} +{% load i18n %} {% block title %}Email Sent{% endblock %} {% block content %} -
        -

        We’ve emailed you instructions for setting your password, if an account exists with the email you entered. - You should receive them shortly.

        +
        +
        +
        +
        {% endblock %} diff --git a/app/templates/accounts/password_reset_form.html b/app/templates/accounts/password_reset_form.html index 1f4ea298..a23fafcf 100644 --- a/app/templates/accounts/password_reset_form.html +++ b/app/templates/accounts/password_reset_form.html @@ -1,28 +1,34 @@ {% extends 'base.html' %} +{% load static %} +{% load i18n %} -{% block title %}Forgot Your Password?{% endblock %} +{% block title %}{% trans "Forgot Your Password?" %}{% endblock %} {% block content %} -
        -

        Forgotten your password? Enter your email address below, and we’ll email instructions for setting a new - one.

        +
        +
        + +
        {% endblock %} diff --git a/app/templates/accounts/register.html b/app/templates/accounts/register.html index 8cfaafb3..9f0c4bf5 100644 --- a/app/templates/accounts/register.html +++ b/app/templates/accounts/register.html @@ -1,14 +1,47 @@ {% extends "base.html" %} +{% load static %} +{% load i18n %} -{% block title %}Sign Up{% endblock %} +{% block title %}{% trans "Sign Up" %}{% endblock %} {% block content %} -
        -

        Register

        -
        - {% csrf_token %} - {{ form.as_p }} - -
        +
        +
        +
        + +
        +
        {% endblock %} diff --git a/app/templates/base_error.html b/app/templates/base_error.html index ade2424f..fb417d62 100644 --- a/app/templates/base_error.html +++ b/app/templates/base_error.html @@ -4,7 +4,7 @@ {% block content %}
        -
        +
        {% trans "Error" %}
        From bf9258b6ffb4742d517ba0d0b0432938ac160473 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Thu, 11 Jul 2024 08:01:51 +0200 Subject: [PATCH 120/240] added Django filter plugin and updated testing pipeline - added flag to validate_templates tests, to ignore django_filters templates --- .github/workflows/testing.yml | 2 +- app/app/settings.py | 1 + requirements.txt | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index cee182ac..48fd9ed2 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -41,7 +41,7 @@ jobs: cp .env.testing app/.env cd app/ mkdir -p static_files - python manage.py validate_templates + python manage.py validate_templates --ignore-app django_filters - name: Run Tests run: | cp .env.testing app/.env diff --git a/app/app/settings.py b/app/app/settings.py index e03a5c95..db46e5d6 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -48,6 +48,7 @@ "general", "simple_history", "accounts", + "django_filters", ] # Add django-extensions to the installed apps if DEBUG is True diff --git a/requirements.txt b/requirements.txt index f6d8f0c2..4d793de9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ whitenoise pillow python-magic pypdf +django-filter From 5f700a7368ea1beb19af4ab36393b1339300e49e Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Thu, 11 Jul 2024 09:45:09 +0200 Subject: [PATCH 121/240] added filters updated search - Updated search templates - Added search filters - Updated search View - Added tests --- app/app/views.py | 60 ++++---- app/general/filters.py | 59 ++++++++ app/general/tests/test_filter.py | 91 ++++++++++++ app/general/tests/test_view_search.py | 75 ++++++++++ app/static/css/styles.css | 9 ++ app/templates/app/search.html | 201 +++++++++++++++++--------- 6 files changed, 399 insertions(+), 96 deletions(-) create mode 100644 app/general/filters.py create mode 100644 app/general/tests/test_filter.py create mode 100644 app/general/tests/test_view_search.py diff --git a/app/app/views.py b/app/app/views.py index 8285f7ce..9e0eb798 100644 --- a/app/app/views.py +++ b/app/app/views.py @@ -1,12 +1,11 @@ -import os - -from django.contrib.postgres.search import SearchHeadline, SearchQuery, SearchRank -from django.core.paginator import Paginator +from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator from django.db.models import Count from django.http import HttpResponse from django.shortcuts import get_object_or_404, render +from django.utils.http import urlencode from django.utils.translation import gettext as _ +from general.filters import DocumentFileFilter from general.models import DocumentFile, Institution, Language, Project, Subject @@ -230,39 +229,40 @@ def institutions(request): def search(request): - q = request.GET.get("q") - - if q: - queue = SearchQuery(q) - search_headline = SearchHeadline("document_data", queue) - - documents = ( - DocumentFile.objects.annotate(rank=SearchRank("search_vector", queue)) - .annotate(search_headline=search_headline) - .filter(search_vector=queue) - .order_by("-rank") - ) - - else: - documents = None + page_number = request.GET.get("page", "1") + if not page_number.isdigit(): + page_number = "1" - # Create a Paginator instance with the documents and the number of items per page - paginator = Paginator(documents, 10) if documents else None # Show 10 documents per page + f = DocumentFileFilter(request.GET, queryset=DocumentFile.objects.all()) - # Get the page number from the request's GET parameters - page_number = request.GET.get("page") + template = "app/search.html" - # Use the get_page method to get the Page object for that page number - page_obj = paginator.get_page(page_number) if paginator else None + paginator = Paginator(f.qs, 5) # 5 documents per page - feature_flag = os.getenv("FEATURE_FLAG", False) + try: + page_obj = paginator.page(page_number) + except PageNotAnInteger: + page_obj = paginator.page(1) + except EmptyPage: + page_obj = paginator.page(paginator.num_pages) - template = "app/search.html" context = { + "search_results": paginator.page(page_obj.number), + "filter": f, "documents": page_obj, - "current_page": "search", - "document_count": len(documents) if documents else 0, - "feature_flag": feature_flag, + "search_params": pagination_url(request), } return render(request, template_name=template, context=context) + + +def pagination_url(request): + url_params = { + "search": request.GET.get("search", ""), + "document_type": request.GET.getlist("document_type", []), + "institution": request.GET.getlist("institution", []), + "subjects": request.GET.getlist("subjects", []), + "languages": request.GET.getlist("languages", []), + } + + return "?" + urlencode(url_params, doseq=True) diff --git a/app/general/filters.py b/app/general/filters.py new file mode 100644 index 00000000..5f3ecbc4 --- /dev/null +++ b/app/general/filters.py @@ -0,0 +1,59 @@ +import django_filters +from django import forms +from django.contrib.postgres.search import SearchHeadline, SearchQuery, SearchRank +from django.db.models import F +from django_filters import ModelMultipleChoiceFilter, MultipleChoiceFilter + +from general.models import DocumentFile, Institution, Language, Subject + + +class DocumentFileFilter(django_filters.FilterSet): + search = django_filters.CharFilter(method="filter_search", label="Search") + + institution = ModelMultipleChoiceFilter( + queryset=Institution.objects.all(), widget=forms.CheckboxSelectMultiple + ) + document_type = MultipleChoiceFilter( + choices=DocumentFile.document_type_choices, widget=forms.CheckboxSelectMultiple + ) + subjects = ModelMultipleChoiceFilter( + queryset=Subject.objects.all(), widget=forms.CheckboxSelectMultiple + ) + languages = ModelMultipleChoiceFilter( + queryset=Language.objects.all(), widget=forms.CheckboxSelectMultiple + ) + + class Meta: + model = DocumentFile + fields = [ + "document_type", + "institution", + "subjects", + "languages", + ] + + def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) + + search = self.form.cleaned_data.get("search", "") + queue = SearchQuery(search.strip()) + search_rank = SearchRank(F("search_vector"), queue) + search_headline = SearchHeadline("document_data", queue) + queryset = ( + queryset.annotate( + rank=search_rank, + search_headline=search_headline, + ) + .defer("document_data") + .select_related("institution") + ).order_by("-rank") + + return queryset + + def filter_search(self, queryset, name, value): + if value: + queue = SearchQuery(value.strip()) + return queryset.filter(search_vector=queue) + + else: + return queryset diff --git a/app/general/tests/test_filter.py b/app/general/tests/test_filter.py new file mode 100644 index 00000000..1cc5650c --- /dev/null +++ b/app/general/tests/test_filter.py @@ -0,0 +1,91 @@ +import unittest + +from django.test import TestCase + +from general.filters import DocumentFileFilter +from general.models import DocumentFile, Institution, Language, Subject + + +class TestSearchFilter(TestCase): + def setUp(self): + self.institution1 = Institution.objects.create(name="Institution 1") + self.institution2 = Institution.objects.create(name="Institution 2") + + # Create languages + self.language1 = Language.objects.create(name="English", iso_code="EN") + self.language2 = Language.objects.create(name="Afrikaans", iso_code="AF") + + # Create subjects + self.subject1 = Subject.objects.create(name="Science") + self.subject2 = Subject.objects.create(name="Math") + + # Create DocumentFiles + self.doc1 = DocumentFile.objects.create( + title="Document 1", + document_data="Document 1 content", + institution=self.institution1, + document_type="glossary", + ) + self.doc1.subjects.add(self.subject1) + self.doc1.languages.add(self.language1) + + self.doc2 = DocumentFile.objects.create( + title="Document 2", + document_data="Document 2 content", + institution=self.institution2, + document_type="glossary", + ) + self.doc2.subjects.add(self.subject2) + self.doc2.languages.add(self.language2) + + def test_institution_filter(self): + data = {"institution": [self.institution1.id]} + filter = DocumentFileFilter(data=data) + self.assertEqual(len(filter.qs), 1) + self.assertIn(self.doc1, filter.qs) + + def test_document_type_filter(self): + data = {"document_type": ["glossary"]} + filter = DocumentFileFilter(data=data) + self.assertEqual(len(filter.qs), 2) + self.assertIn(self.doc1, filter.qs) + self.assertIn(self.doc2, filter.qs) + + def test_subjects_filter(self): + data = {"subjects": [self.subject1.id]} + filter = DocumentFileFilter(data=data) + self.assertEqual(len(filter.qs), 1) + self.assertIn(self.doc1, filter.qs) + + def test_languages_filter(self): + data = {"languages": [self.language1.id]} + filter = DocumentFileFilter(data=data) + self.assertEqual(len(filter.qs), 1) + self.assertIn(self.doc1, filter.qs) + + def test_combined_filters(self): + data = { + "institution": [self.institution1.id], + "document_type": ["glossary"], + "subjects": [self.subject1.id], + "languages": [self.language1.id], + } + filter = DocumentFileFilter(data=data) + self.assertEqual(len(filter.qs), 1) + self.assertIn(self.doc1, filter.qs) + + def test_search_filter(self): + data = {"search": "Document"} + filter = DocumentFileFilter(data=data) + self.assertEqual(len(filter.qs), 2) + self.assertIn(self.doc1, filter.qs) + self.assertIn(self.doc2, filter.qs) + + data = {"search": "Document 1"} + filter = DocumentFileFilter(data=data) + self.assertEqual(len(filter.qs), 1) + self.assertIn(self.doc1, filter.qs) + + +if __name__ == "__main__": + unittest.main() diff --git a/app/general/tests/test_view_search.py b/app/general/tests/test_view_search.py new file mode 100644 index 00000000..5d0386ab --- /dev/null +++ b/app/general/tests/test_view_search.py @@ -0,0 +1,75 @@ +import unittest + +from django.test import Client, TestCase +from django.urls import reverse + +from general.models import DocumentFile, Institution, Language, Subject + + +class SearchViewTest(TestCase): + def setUp(self): + # Create institutions + self.institution1 = Institution.objects.create(name="Institution 1") + self.institution2 = Institution.objects.create(name="Institution 2") + + # Create languages + self.language1 = Language.objects.create(name="English", iso_code="EN") + self.language2 = Language.objects.create(name="Afrikaans", iso_code="AF") + + # Create subjects + self.subject1 = Subject.objects.create(name="Science") + self.subject2 = Subject.objects.create(name="Math") + + # Create DocumentFiles + for i in range(10): + doc = DocumentFile.objects.create( + title=f"Document {i + 1}", + institution=self.institution1 if i % 2 == 0 else self.institution2, + document_type="report" if i % 2 == 0 else "article", + document_data="Document {i + 1} content", + ) + doc.subjects.add(self.subject1 if i % 2 == 0 else self.subject2) + doc.languages.add(self.language1 if i % 2 == 0 else self.language2) + + def test_search_pagination(self): + client = Client() + response = client.get(reverse("search"), {"page": "1"}) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context["documents"]), 5) + + response = client.get(reverse("search"), {"page": "2"}) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context["documents"]), 5) + + response = client.get(reverse("search"), {"page": "3"}) + self.assertEqual(response.status_code, 200) + + def test_search_filtering(self): + client = Client() + response = client.get(reverse("search"), {"search": "Document 1"}) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context["documents"][0].title, "Document 1") + + def test_invalid_page_number(self): + client = Client() + response = client.get(reverse("search"), {"page": "invalid"}) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context["documents"]), 5) + + def test_combined_filters(self): + client = Client() + response = client.get( + reverse("search"), + { + "institution": self.institution1.id, + "document_type": "report", + "subjects": self.subject1.id, + "languages": self.language1.id, + }, + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context["documents"]), 5) + + +if __name__ == "__main__": + unittest.main() diff --git a/app/static/css/styles.css b/app/static/css/styles.css index 438c5e2e..d6e97f89 100755 --- a/app/static/css/styles.css +++ b/app/static/css/styles.css @@ -611,3 +611,12 @@ html { display: flex; } } + +/*Search Css*/ +.checkbox-container { + cursor: pointer; + max-height: 130px; /* Adjust based on your line-height and padding to show only 4 rows */ + overflow-y: auto; /* Enable vertical scrollbar when content overflows */ + border: 1px solid #ced4da; /* Bootstrap's form control border color */ + border-radius: 0.25rem; /* Bootstrap's form control border radius */ +} diff --git a/app/templates/app/search.html b/app/templates/app/search.html index 55264498..4709731b 100644 --- a/app/templates/app/search.html +++ b/app/templates/app/search.html @@ -3,84 +3,153 @@ {% load i18n %} {% block content %} -
        -
        +
        {% trans "Search a term" %}
        - {% if feature_flag == "search_feature" %} - {# test section start#} -
        -
        - -
        - + + +
        + + +
        +
        +
        + {% for document in search_results %} +
        +
          +
        • + {% trans "Title:" %} + {{ document.title }} +
        • +
        • + {% trans "Institution:" %} + {{ document.institution }} +
        • +
        • + {% trans "Headline:" %} + {{ document.search_headline|safe }} +
        • +
        • + {% trans "File:" %} + {{ document.uploaded_file }} +
        • +
        • + {% trans "License:" %} + {{ document.license }} +
        • +
        • + {% trans "License:" %} + {{ document.document_type }} +
        • +
        • + {% trans "Mime Type:" %} + {{ document.mime_type }} +
        • +
        • + {% trans "Rank:" %} + {{ document.rank }} +
        • +
        +
        + {% endfor %} +
        +
        +
        +
        + +
        +
        +
        + +
        +
        +
        +
        + +
        + {% for checkbox in filter.form.institution %} +
        + {{ checkbox.tag }} + +
        + {% endfor %}
        - -
        - {% trans "Results:" %} - {{ documents|length }}
        -
        - {% for document in documents %} -
        -
          -
        • - {% trans "Title:" %} - {{ document.title }} -
        • -
        • - {% trans "Institution:" %} - {{ document.institution }} -
        • -
        • - {% trans "Headline:" %} - {{ document.search_headline|safe }} -
        • -
        • - {% trans "File:" %} - {{ document.uploaded_file }} -
        • -
        • - {% trans "License:" %} - {{ document.license }} -
        • -
        • - {% trans "Mime Type:" %} - {{ document.mime_type }} -
        • -
        • - {% trans "Rank:" %} - {{ document.rank }} -
        • -
        +
        +
        + +
        + {% for checkbox in filter.form.document_type %} +
        + {{ checkbox.tag }} + +
        + {% endfor %}
        - {% endfor %} +
        - +
        + +
        +
        +
        From 5126c40c8ac6fae77b8f04806d59ec5be078836e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9COMosimege=E2=80=9D?= <“onalerona.mosimege@gmail.com”> Date: Thu, 18 Jul 2024 12:14:35 +0200 Subject: [PATCH 122/240] Add document detail view, url, and template --- app/app/urls.py | 1 + app/app/views.py | 22 ++++++- app/general/tests/test_document_detail.py | 41 +++++++++++++ app/templates/app/document_detail.html | 71 +++++++++++++++++++++++ app/templates/app/documents.html | 7 +++ 5 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 app/general/tests/test_document_detail.py create mode 100644 app/templates/app/document_detail.html create mode 100644 app/templates/app/documents.html diff --git a/app/app/urls.py b/app/app/urls.py index 649d0d9b..a9986360 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -32,6 +32,7 @@ path("_health/", views.health, name="health"), path("", views.home, name="home"), path("institutions/", views.institutions, name="institutions"), + path("documents/", views.documents, name="documents"), path("projects/", views.projects, name="projects"), path("projects//", views.project_detail, name="project_detail"), path("institution//", views.institution_detail, name="institution_detail"), diff --git a/app/app/views.py b/app/app/views.py index 9e0eb798..7d4538e9 100644 --- a/app/app/views.py +++ b/app/app/views.py @@ -1,5 +1,5 @@ from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator -from django.db.models import Count +from django.db.models import Count, Prefetch from django.http import HttpResponse from django.shortcuts import get_object_or_404, render from django.utils.http import urlencode @@ -158,13 +158,29 @@ def institution_detail(request, institution_id): return render(request, template_name=template, context=context) -def document_detail(request, project_id): +def documents(request): + template = "app/documents.html" + + context = { + "current_page": "documents", + } + return render(request, template_name=template, context=context) + + +def document_detail(request, document_id): template = "app/document_detail.html" - document = DocumentFile.objects.get(id=document_id) + document = get_object_or_404( + DocumentFile.objects.select_related("institution").prefetch_related( + Prefetch("subjects", queryset=Subject.objects.order_by("name")), + Prefetch("languages", queryset=Language.objects.order_by("name")), + ), + id=document_id, + ) context = { "current_page": "document_detail", + "document": document, } return render(request, template_name=template, context=context) diff --git a/app/general/tests/test_document_detail.py b/app/general/tests/test_document_detail.py new file mode 100644 index 00000000..f02687fa --- /dev/null +++ b/app/general/tests/test_document_detail.py @@ -0,0 +1,41 @@ +from django.test import TestCase +from django.urls import reverse + +from general.models import DocumentFile, Institution, Language, Project, Subject + + +class DocumentDetailViewTest(TestCase): + def setUp(self): + self.institution = Institution.objects.create(name="University of Cape Town") + + self.subject1 = Subject.objects.create(name="Anatomy") + self.subject2 = Subject.objects.create(name="Biology") + + self.language1 = Language.objects.create(name="Afrikaans", iso_code="af") + self.language2 = Language.objects.create(name="English", iso_code="en") + + self.document = DocumentFile.objects.create( + title="Afrikaans_HL_P1_Feb-March_2011", + description="This is a description of the document.", + url="https://externaldocumentrepository.com/document1", + uploaded_file="path/to/document.pdf", + available=True, + license="(c)", + document_type="Glossary", + institution=self.institution, + mime_type="application/pdf", + document_data="", + search_vector=None, + ) + self.document.subjects.add(self.subject1, self.subject2) + self.document.languages.add(self.language1, self.language2) + + def test_document_detail_num_queries(self): + url = reverse("document_detail", args=[self.document.id]) + with self.assertNumQueries(3): + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertContains(response, self.document.title) + self.assertContains(response, self.institution.name) + self.assertContains(response, self.subject1.name) + self.assertContains(response, self.language1.name) diff --git a/app/templates/app/document_detail.html b/app/templates/app/document_detail.html new file mode 100644 index 00000000..1bab19ca --- /dev/null +++ b/app/templates/app/document_detail.html @@ -0,0 +1,71 @@ +{% extends "base.html" %} +{% load static %} +{% load i18n %} + +{% block content %} +
        +
        +

        {% trans "Documents" %} > {{ document.title }}

        + +
        +

        {{ document.title }}

        + + + {{ document.url }} + + + + +
        +

        {{ document.description }}

        +
        + +

        + + {{ document.institution.name }} + +

        + +

        {% trans "License:" %} {{ document.license }}

        +

        {% trans "Category:" %} {{ document.document_type }}

        +
        + + +
        +
        +
        +

        {% trans "Subjects" %}

        +
          + {% for subject in document.subjects.all %} +
        • + + {{ subject.name }} +
        • + {% endfor %} +
        +
        +
        + +
        +
        +

        {% trans "Languages" %}

        + +
        +
        +
        +
        + +{% endblock content %} diff --git a/app/templates/app/documents.html b/app/templates/app/documents.html new file mode 100644 index 00000000..84d76d25 --- /dev/null +++ b/app/templates/app/documents.html @@ -0,0 +1,7 @@ +{% extends "base.html" %} +{% load static %} +{% load i18n %} + +{% block content %} + +{% endblock content%} From 3bcbefa3200437543a18b6e632fd049cd1f18d8b Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Mon, 22 Jul 2024 09:42:40 +0200 Subject: [PATCH 123/240] Sort lists on institution page --- app/app/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/app/views.py b/app/app/views.py index 7d4538e9..05020a17 100644 --- a/app/app/views.py +++ b/app/app/views.py @@ -142,8 +142,8 @@ def institution_detail(request, institution_id): template = "app/institution_detail.html" institution = get_object_or_404(Institution, id=institution_id) - projects = Project.objects.filter(institution=institution) - documents = DocumentFile.objects.filter(institution=institution) + projects = Project.objects.filter(institution=institution).order_by("name") + documents = DocumentFile.objects.filter(institution=institution).order_by("title") logo = institution.logo From 24632d06cd2c96a1fa0ee3dc31e8ad611b313326 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Mon, 22 Jul 2024 09:42:52 +0200 Subject: [PATCH 124/240] Enable search box on front page --- app/templates/app/home.html | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/app/templates/app/home.html b/app/templates/app/home.html index 51f7d962..eb4485a7 100644 --- a/app/templates/app/home.html +++ b/app/templates/app/home.html @@ -38,13 +38,12 @@
        {% trans "Search" %}
        {% blocktrans %}This platforms hosts terminology and other useful language resources.{% endblocktrans %}

        - {% blocktrans %}Search for a term or topic you are interested in. - (Functionality coming soon.){% endblocktrans %} +

        + + +

        - {# TODO: create the search box right here so that no additional click is required. #} - - {% trans "Search a term" %} -
        From 963eb3f3292ebfbe83cf663b15a8e9b789540e2c Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Mon, 22 Jul 2024 09:43:28 +0200 Subject: [PATCH 125/240] Show project description on project detail --- app/templates/app/project_detail.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/templates/app/project_detail.html b/app/templates/app/project_detail.html index e16f817e..a6d32cf1 100644 --- a/app/templates/app/project_detail.html +++ b/app/templates/app/project_detail.html @@ -12,6 +12,9 @@
      @@ -59,7 +59,7 @@

      {% trans "Languages" %}

      {% for language in document.languages.all %}
    • - {{ language.name }} + {{ language.name }}
    • {% endfor %}
    diff --git a/app/templates/app/project_detail.html b/app/templates/app/project_detail.html index a6d32cf1..140e1d3b 100644 --- a/app/templates/app/project_detail.html +++ b/app/templates/app/project_detail.html @@ -57,7 +57,7 @@

    {% trans "Subjects" %}

    {% for subject in subjects %}
  • - {{ subject.name }} + {{ subject.name }}
  • {% endfor %}
@@ -71,7 +71,7 @@

{% trans "Languages" %}

{% for language in languages %}
  • - {{ language.name }} + {{ language.name }}
  • {% endfor %} From 6e39882cfd4231308446e377ec65ea9e87b3caca Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Tue, 23 Jul 2024 11:02:02 +0200 Subject: [PATCH 129/240] Simplify handling of url parameters with pagination --- app/app/views.py | 5 +++++ app/templates/app/documents.html | 10 +++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/app/app/views.py b/app/app/views.py index 086037c4..a50ade74 100644 --- a/app/app/views.py +++ b/app/app/views.py @@ -170,6 +170,7 @@ def institution_detail(request, institution_id): def documents(request): template = "app/documents.html" + url_params = {} subject_id = request.GET.get("subject") language_id = request.GET.get("language") institution_id = request.GET.get("institution") @@ -182,10 +183,13 @@ def documents(request): if subject_id: documents = documents.filter(subjects__id=subject_id) + url_params["subject"] = subject_id if language_id: documents = documents.filter(languages__id=language_id) + url_params["language"] = language_id if institution_id: documents = documents.filter(institution__id=institution_id) + url_params["institution"] = institution_id paginator = Paginator(documents, 10) @@ -220,6 +224,7 @@ def documents(request): "current_page": "documents", "page_obj": page_obj, "documents": document_data, + "url_params": urlencode(url_params), "subjects": subjects, "languages": languages, "institutions": institutions, diff --git a/app/templates/app/documents.html b/app/templates/app/documents.html index 43e174f7..b1e7fd4e 100644 --- a/app/templates/app/documents.html +++ b/app/templates/app/documents.html @@ -85,8 +85,8 @@

      {% if page_obj.has_previous %} -
    • « {% trans "First" %}
    • -
    • ‹ {% trans "Previous" %}
    • +
    • « {% trans "First" %}
    • +
    • ‹ {% trans "Previous" %}
    • {% else %}
    • « {% trans "First" %}
    • ‹ {% trans "Previous" %}
    • @@ -96,13 +96,13 @@

      {% if page_obj.number == num %}
    • {{ num }}
    • {% elif num > page_obj.number|add:-3 and num < page_obj.number|add:3 %} -
    • {{ num }}
    • +
    • {{ num }}
    • {% endif %} {% endfor %} {% if page_obj.has_next %} -
    • {% trans "Next" %} ›
    • -
    • {% trans "Last" %} »
    • +
    • {% trans "Next" %} ›
    • +
    • {% trans "Last" %} »
    • {% else %}
    • {% trans "Next" %} ›
    • {% trans "Last" %} »
    • From 0c4723f1b6e7c6f59579221b0a24ffb4c5a54af5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9COMosimege=E2=80=9D?= <“onalerona.mosimege@gmail.com”> Date: Tue, 23 Jul 2024 11:15:38 +0200 Subject: [PATCH 130/240] remove download button if there is no document uploaded --- app/templates/app/document_detail.html | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/templates/app/document_detail.html b/app/templates/app/document_detail.html index 478670aa..d0f90b5c 100644 --- a/app/templates/app/document_detail.html +++ b/app/templates/app/document_detail.html @@ -15,11 +15,13 @@

      {{ document.title }}

      - - - + {% if document.uploaded_file %} + + + + {% endif %}
      From 5d3cb14257061a98d4510ed96d42a69cf68332f2 Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Tue, 23 Jul 2024 15:54:32 +0200 Subject: [PATCH 131/240] updating environment variables - removed docker compose version not needed anymore - updated readme file --- .env.example | 1 + README.md | 17 ++++++++++++++++- docker-compose.yml | 11 ++++------- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/.env.example b/.env.example index 436b34ff..3b276aba 100644 --- a/.env.example +++ b/.env.example @@ -18,3 +18,4 @@ EMAIL_HOST_USER='' EMAIL_HOST_PASSWORD='' EMAIL_BACKEND_CONSOLE='True/False' EMAIL_USE_TLS=True +SECRET_KEY='' diff --git a/README.md b/README.md index 5a1ea029..e9d6f3a5 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,20 @@ About the project: --- +### How to setup SECRET_KEY in Development + +.env file + +SECRET_KEY='' + +To generate a new secret key, you can use the following command: + +1. python3 manage.py shell +2. from django.core.management.utils import get_random_secret_key +3. print(get_random_secret_key()) + +--- + ### Using Makefile 1. Clone the repository @@ -34,7 +48,7 @@ About the project: .env file -* EMAIL_HOST='sandbo x.smtp.mailtrap.io' +* EMAIL_HOST='sandbo x.smtp.mailtrap.io' * EMAIL_HOST_USER='*********' * EMAIL_HOST_PASSWORD='******' * EMAIL_PORT='2525' @@ -75,4 +89,5 @@ Docker Volumes for production: ### Email Settings in Production .env file + * EMAIL_BACKEND_CONSOLE=False diff --git a/docker-compose.yml b/docker-compose.yml index b18cb7c9..dd86f6cf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,5 @@ -# This is is meant for local development, but should give an idea of what you +# This is meant for local development, but should give an idea of what you # can consider in production. -version: '3.8' services: db: @@ -9,7 +8,7 @@ services: environment: - POSTGRES_DB=term_db - POSTGRES_USER=sadilar - - POSTGRES_PASSWORD=sadilar + - POSTGRES_PASSWORD=${DB_PASSWORD} volumes: - .:/app ports: @@ -32,15 +31,13 @@ services: extra_hosts: - "host.docker.internal:host-gateway" environment: - # TODO: dollar signs in the KEY are interpolated, therefore escaped here - # with double "$$". Maybe move to .env file to simplify? - - SECRET_KEY='django-insecure-w!h85bp^$$e8gm%c23r!0%9i7yzd=6w$$s&ic+6!%306&kj8@k*5' + - SECRET_KEY=${SECRET_KEY} - DEBUG=True - DB_HOST=db - DB_PORT=5432 - DB_NAME=term_db # see POSTGRES_DB above - DB_USER=sadilar # see POSTGRES_USER above - - DB_PASSWORD=sadilar # see POSTGRES_PASSWORD above + - DB_PASSWORD=${DB_PASSWORD} # see POSTGRES_PASSWORD above - TESTING_DIR=/app/general/tests/files/ - FEATURE_FLAG=search_feature - EMAIL_HOST=${EMAIL_HOST-} From c7f90bff8d92581aed379bb3d6a2619adcb6bc0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9COMosimege=E2=80=9D?= <“onalerona.mosimege@gmail.com”> Date: Tue, 23 Jul 2024 09:16:02 +0200 Subject: [PATCH 132/240] add language page template, view, and styling --- app/app/urls.py | 4 +- app/app/views.py | 39 +++++++++++--- app/general/tests/test_languages.py | 38 +++++++++++++ app/templates/app/languages.html | 54 +++++++++++++++++++ app/templates/app/subject_detail.html | 0 .../{language_detail.html => subjects.html} | 0 app/templates/base.html | 6 +++ 7 files changed, 133 insertions(+), 8 deletions(-) create mode 100644 app/general/tests/test_languages.py create mode 100644 app/templates/app/languages.html delete mode 100644 app/templates/app/subject_detail.html rename app/templates/app/{language_detail.html => subjects.html} (100%) diff --git a/app/app/urls.py b/app/app/urls.py index a9986360..7ba0c260 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -37,8 +37,8 @@ path("projects//", views.project_detail, name="project_detail"), path("institution//", views.institution_detail, name="institution_detail"), path("documents//", views.document_detail, name="document_detail"), - path("language//", views.language_detail, name="language_detail"), - path("subject//", views.subject_detail, name="subject_detail"), + path("languages/", views.languages, name="languages"), + path("subjects/", views.subjects, name="subjects"), path("search/", views.search, name="search"), path("i18n/", include("django.conf.urls.i18n")), path("accounts/", include("accounts.urls")), diff --git a/app/app/views.py b/app/app/views.py index 9747ad8a..356dc858 100644 --- a/app/app/views.py +++ b/app/app/views.py @@ -250,19 +250,46 @@ def document_detail(request, document_id): return render(request, template_name=template, context=context) -def language_detail(request, project_id): - template = "app/language_detail.html" +def languages(request): + template = "app/languages.html" + + languages = ( + Language.objects.all() + .prefetch_related( + Prefetch( + "documentfile_set", + queryset=DocumentFile.objects.only("id", "title", "languages").order_by("title"), + ), + Prefetch( + "project_set", + queryset=Project.objects.only("id", "name", "languages").order_by("name"), + ), + ) + .order_by("name") + ) - project = Project.objects.get(id=project_id) + language_data = [] + + for language in languages: + documents = language.documentfile_set.all() + projects = language.project_set.all() + language_data.append( + { + "language": language, + "documents": documents, + "projects": projects, + } + ) context = { - "current_page": "language_detail", + "current_page": "languages", + "language_data": language_data, } return render(request, template_name=template, context=context) -def subject_detail(request, project_id): - template = "app/subject_detail.html" +def subjects(request): + template = "app/subjects.html" project = Project.objects.get(id=project_id) diff --git a/app/general/tests/test_languages.py b/app/general/tests/test_languages.py new file mode 100644 index 00000000..8029af05 --- /dev/null +++ b/app/general/tests/test_languages.py @@ -0,0 +1,38 @@ +from django.test import TestCase +from django.urls import reverse + +from general.models import DocumentFile, Institution, Language, Project + + +class LanguagesViewTest(TestCase): + def setUp(self): + self.institution = Institution.objects.create( + name="Test Institution", + abbreviation="TI", + url="http://testinstitution.org", + email="info@testinstitution.org", + ) + + self.language1 = Language.objects.create(name="English", iso_code="lang1") + self.language2 = Language.objects.create(name="Spanish", iso_code="lang2") + + self.document1 = DocumentFile.objects.create( + title="Document 1", institution=self.institution, document_type="report" + ) + self.document1.languages.add(self.language1) + + self.document2 = DocumentFile.objects.create( + title="Document 2", institution=self.institution, document_type="report" + ) + self.document2.languages.add(self.language2) + + self.project1 = Project.objects.create(name="Project 1", institution=self.institution) + self.project1.languages.add(self.language1) + + self.project2 = Project.objects.create(name="Project 2", institution=self.institution) + self.project2.languages.add(self.language2) + + def test_languages_view_num_queries(self): + with self.assertNumQueries(3): + response = self.client.get(reverse("languages")) + self.assertEqual(response.status_code, 200) diff --git a/app/templates/app/languages.html b/app/templates/app/languages.html new file mode 100644 index 00000000..cdb95cea --- /dev/null +++ b/app/templates/app/languages.html @@ -0,0 +1,54 @@ +{% extends "base.html" %} +{% load static %} +{% load i18n %} + +{% block content %} +
      +
      +

      {% trans "Languages" %}

      +
      +
      + {% for item in language_data %} +
      {{ item.language.name }}
      +
      +
      {% trans "Documents" %}
      + {% if item.documents.exists %} +
      + +
      + {% else %} +

      {% trans "No documents available for this language." %}

      + {% endif %} +
      +
      +
      {% trans "Projects" %}
      + {% if item.projects.exists %} +
      + +
      + {% else %} +

      {% trans "No projects available for this language." %}

      + {% endif %} +
      +
      +
      +
      + {% endfor %} +
      +
      + +{% endblock content %} diff --git a/app/templates/app/subject_detail.html b/app/templates/app/subject_detail.html deleted file mode 100644 index e69de29b..00000000 diff --git a/app/templates/app/language_detail.html b/app/templates/app/subjects.html similarity index 100% rename from app/templates/app/language_detail.html rename to app/templates/app/subjects.html diff --git a/app/templates/base.html b/app/templates/base.html index d5dc7da3..052c5865 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -57,6 +57,12 @@ class="nav-link" href="{% url 'documents' %}">{% trans "Documents" %} + From 13077b737d68336701770ecaf26d4d104843eaeb Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Tue, 16 Jul 2024 08:27:01 +0200 Subject: [PATCH 133/240] added projects to search filters updated tests --- app/general/filters.py | 36 +++++++++++++++++++++++++++----- app/general/tests/test_filter.py | 4 ++-- app/templates/app/search.html | 21 +++++++++++++++++-- 3 files changed, 52 insertions(+), 9 deletions(-) diff --git a/app/general/filters.py b/app/general/filters.py index 5f3ecbc4..26d20e97 100644 --- a/app/general/filters.py +++ b/app/general/filters.py @@ -1,10 +1,15 @@ import django_filters from django import forms -from django.contrib.postgres.search import SearchHeadline, SearchQuery, SearchRank +from django.contrib.postgres.search import ( + SearchHeadline, + SearchQuery, + SearchRank, + SearchVector, +) from django.db.models import F from django_filters import ModelMultipleChoiceFilter, MultipleChoiceFilter -from general.models import DocumentFile, Institution, Language, Subject +from general.models import DocumentFile, Institution, Language, Project, Subject class DocumentFileFilter(django_filters.FilterSet): @@ -35,8 +40,27 @@ class Meta: def filter_queryset(self, queryset): queryset = super().filter_queryset(queryset) - search = self.form.cleaned_data.get("search", "") - queue = SearchQuery(search.strip()) + search = self.form.cleaned_data.get("search", "").strip() + queue = SearchQuery(search) + + search_document_files = ( + DocumentFile.objects.annotate( + search=SearchVector("title") + SearchVector("description"), + search_headline=SearchHeadline("description", queue), + ) + .filter(search=SearchQuery(search)) + .only("title", "description") + ) + + project_query = ( + Project.objects.annotate( + search=SearchVector("name") + SearchVector("description"), + search_headline=SearchHeadline("description", queue), + ) + .filter(search=SearchQuery(search)) + .defer("institution_id", "subjects", "languages") + ) + search_rank = SearchRank(F("search_vector"), queue) search_headline = SearchHeadline("document_data", queue) queryset = ( @@ -48,7 +72,9 @@ def filter_queryset(self, queryset): .select_related("institution") ).order_by("-rank") - return queryset + combined_results = list(project_query) + list(search_document_files) + list(queryset) + + return combined_results def filter_search(self, queryset, name, value): if value: diff --git a/app/general/tests/test_filter.py b/app/general/tests/test_filter.py index 1cc5650c..c9d0ca92 100644 --- a/app/general/tests/test_filter.py +++ b/app/general/tests/test_filter.py @@ -77,13 +77,13 @@ def test_combined_filters(self): def test_search_filter(self): data = {"search": "Document"} filter = DocumentFileFilter(data=data) - self.assertEqual(len(filter.qs), 2) + self.assertEqual(len(filter.qs), 4) self.assertIn(self.doc1, filter.qs) self.assertIn(self.doc2, filter.qs) data = {"search": "Document 1"} filter = DocumentFileFilter(data=data) - self.assertEqual(len(filter.qs), 1) + self.assertEqual(len(filter.qs), 2) self.assertIn(self.doc1, filter.qs) diff --git a/app/templates/app/search.html b/app/templates/app/search.html index ea46519f..15081352 100644 --- a/app/templates/app/search.html +++ b/app/templates/app/search.html @@ -22,6 +22,23 @@
      {% trans "Search a term" %}
      -

      {{ document.title }}

      - {{ document.institution }} -
      - {% trans "Excerpt:" %} - … {{ document.search_headline|safe }} … -
      -
      - {% trans "License:" %} - {{ document.license }} -
      -
      -{{ document.languages.all |join:", " }} -
      -
      -{{ document.subjects.all |join:", " }} -
      -
      +

      {{ result.heading }}

      +

      {{ result.description | truncatewords:10 }}

      +

      + {% trans "Excerpt:" %} + {{ result.search_headline|safe }} +

      +

      + {{ result.rank }} +

      {% endfor %}
      From cd28611d0f3183e2bff93cc195083ea0d8acad9c Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Tue, 23 Jul 2024 16:59:17 +0200 Subject: [PATCH 135/240] First stab at search weighting and normalization --- app/general/filters.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/app/general/filters.py b/app/general/filters.py index 7a1ce4a9..73f195e4 100644 --- a/app/general/filters.py +++ b/app/general/filters.py @@ -7,7 +7,7 @@ SearchVector, ) from django.db.models import F, Value -from django.db.models.functions import Left +from django.db.models.functions import Greatest, Left from django_filters import ModelMultipleChoiceFilter, MultipleChoiceFilter from general.models import DocumentFile, Institution, Language, Project, Subject @@ -39,6 +39,8 @@ class Meta: ] def filter_queryset(self, queryset): + # More information about weighting and normalization in postgres: + # https://www.postgresql.org/docs/current/textsearch-controls.html#TEXTSEARCH-RANKING queryset = super().filter_queryset(queryset) search = self.form.cleaned_data.get("search", "").strip() @@ -50,13 +52,15 @@ def filter_queryset(self, queryset): # In the queries below, any differences between models must be fixed # through e.g. `Value` or `F` annotations. - project_search_vector = SearchVector("name") + SearchVector("description") + project_search_vector = SearchVector("name", weight="A") + SearchVector( + "description", weight="B" + ) project_query = ( Project.objects.annotate( heading=F("name"), view=Value("project_detail"), search_headline=SearchHeadline("description", query), - rank=SearchRank(project_search_vector, query), + rank=SearchRank(project_search_vector, query, normalization=16), search=project_search_vector, ) .filter(search=SearchQuery(search)) @@ -66,11 +70,19 @@ def filter_queryset(self, queryset): # We limit the headline to limit the performance impact. On very large # documents, this slows things down if unconstrained. search_headline = SearchHeadline(Left("document_data", 200_000), query) - search_rank = SearchRank(F("search_vector"), query) + doc_search_rank = SearchRank(F("search_vector"), query) + # While the search_vector field doesn't provide weighted vectors, we + # work around that to ensure that hits in the title or description are + # still ranked high. See `doc_rank` and `rank` below. + extra_search_vector = SearchVector("title", weight="A") + SearchVector( + "description", weight="B" + ) + search_rank = SearchRank(extra_search_vector, query, normalization=16) queryset = queryset.annotate( heading=F("title"), view=Value("document_detail"), - rank=search_rank, + doc_rank=doc_search_rank, + rank=Greatest(F("doc_rank"), search_rank), search_headline=search_headline, ).values(*fields) return queryset.union(project_query).order_by("-rank") From c2fff8538d134ba445f93f446d9bbf42c0769ad5 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Wed, 24 Jul 2024 15:14:58 +0200 Subject: [PATCH 136/240] Small cleanups --- app/general/filters.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/general/filters.py b/app/general/filters.py index 73f195e4..2dded844 100644 --- a/app/general/filters.py +++ b/app/general/filters.py @@ -63,7 +63,7 @@ def filter_queryset(self, queryset): rank=SearchRank(project_search_vector, query, normalization=16), search=project_search_vector, ) - .filter(search=SearchQuery(search)) + .filter(search=query) .values(*fields) ) @@ -89,8 +89,8 @@ def filter_queryset(self, queryset): def filter_search(self, queryset, name, value): if value: - queue = SearchQuery(value.strip()) - return queryset.filter(search_vector=queue) + query = SearchQuery(value.strip()) + return queryset.filter(search_vector=query) else: return queryset From 8d9799828ff3efec169bb3784c90fa147f5be37a Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Wed, 24 Jul 2024 15:19:26 +0200 Subject: [PATCH 137/240] Store weights in generated documentfile.search_vector This stores the correct weighted vector, and includes the document description that was missing. --- app/general/admin.py | 1 - app/general/filters.py | 12 ++----- .../0012_alter_documentfile_search_vector.py | 32 +++++++++++++++++++ app/general/models.py | 16 ++++++++-- 4 files changed, 48 insertions(+), 13 deletions(-) create mode 100644 app/general/migrations/0012_alter_documentfile_search_vector.py diff --git a/app/general/admin.py b/app/general/admin.py index a91edac3..e22ede8d 100644 --- a/app/general/admin.py +++ b/app/general/admin.py @@ -17,7 +17,6 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields["document_data"].widget = HiddenInput() - self.fields["search_vector"].widget = HiddenInput() # If the instance has a mime_type, the field should be disabled if not self.instance.mime_type: diff --git a/app/general/filters.py b/app/general/filters.py index 2dded844..b49adddf 100644 --- a/app/general/filters.py +++ b/app/general/filters.py @@ -70,19 +70,11 @@ def filter_queryset(self, queryset): # We limit the headline to limit the performance impact. On very large # documents, this slows things down if unconstrained. search_headline = SearchHeadline(Left("document_data", 200_000), query) - doc_search_rank = SearchRank(F("search_vector"), query) - # While the search_vector field doesn't provide weighted vectors, we - # work around that to ensure that hits in the title or description are - # still ranked high. See `doc_rank` and `rank` below. - extra_search_vector = SearchVector("title", weight="A") + SearchVector( - "description", weight="B" - ) - search_rank = SearchRank(extra_search_vector, query, normalization=16) + search_rank = SearchRank(F("search_vector"), query, normalization=16) queryset = queryset.annotate( heading=F("title"), view=Value("document_detail"), - doc_rank=doc_search_rank, - rank=Greatest(F("doc_rank"), search_rank), + rank=search_rank, search_headline=search_headline, ).values(*fields) return queryset.union(project_query).order_by("-rank") diff --git a/app/general/migrations/0012_alter_documentfile_search_vector.py b/app/general/migrations/0012_alter_documentfile_search_vector.py new file mode 100644 index 00000000..2bde464c --- /dev/null +++ b/app/general/migrations/0012_alter_documentfile_search_vector.py @@ -0,0 +1,32 @@ +import django.contrib.postgres.search +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('general', '0011_alter_documentfile_options_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='documentfile', + name='search_vector', + ), # also deletes `search_vector_trigger` and general_doc_search__752b22_gin + migrations.AddField( + model_name='documentfile', + name='search_vector', + field=models.GeneratedField(db_persist=True, expression=django.contrib.postgres.search.CombinedSearchVector( + django.contrib.postgres.search.CombinedSearchVector( + django.contrib.postgres.search.SearchVector('title', config='english', weight='A'), '||', + django.contrib.postgres.search.SearchVector('description', config='english', weight='B'), + django.contrib.postgres.search.SearchConfig('english')), '||', + django.contrib.postgres.search.SearchVector('document_data', config='english', weight='C'), + django.contrib.postgres.search.SearchConfig('english')), null=True, + output_field=django.contrib.postgres.search.SearchVectorField()), + ), + migrations.AddIndex( + model_name='documentfile', + index=django.contrib.postgres.indexes.GinIndex(fields=['search_vector'], + name='general_doc_search__752b22_gin'), + ), + ] diff --git a/app/general/models.py b/app/general/models.py index b7dd012c..064d7b8a 100644 --- a/app/general/models.py +++ b/app/general/models.py @@ -1,5 +1,5 @@ from django.contrib.postgres.indexes import GinIndex -from django.contrib.postgres.search import SearchVectorField +from django.contrib.postgres.search import SearchVector, SearchVectorField from django.core.validators import FileExtensionValidator from django.db import models from django.utils.translation import gettext_lazy as _ @@ -142,7 +142,19 @@ class DocumentFile(models.Model): subjects = models.ManyToManyField("Subject", blank=True, verbose_name=_("subjects")) languages = models.ManyToManyField("Language", blank=True, verbose_name=_("languages")) - search_vector = SearchVectorField(null=True, blank=True) + # `config="english"` is required to ensure that the `expression` is + # immutable, otherwise the migration to the GeneratedField fails. + search_vector = models.GeneratedField( + expression=( + SearchVector("title", config="english", weight="A") + + SearchVector("description", config="english", weight="B") + + SearchVector("document_data", config="english", weight="C") + ), + output_field=SearchVectorField(), + db_persist=True, + null=True, + blank=True, + ) # added simple historical records to the model history = HistoricalRecords(excluded_fields=["document_data", "search_vector"]) From 5bdc4ee759a8fab81e6d11b5969b5a5cfddc866d Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Thu, 25 Jul 2024 10:03:17 +0200 Subject: [PATCH 138/240] Search performance improvements The `UNION ALL` improves performance, particularly of the `COUNT` query that is needed for pagination. Smaller headlines are hopefully also a usability improvement. --- app/general/filters.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/general/filters.py b/app/general/filters.py index b49adddf..90fb1b95 100644 --- a/app/general/filters.py +++ b/app/general/filters.py @@ -59,7 +59,7 @@ def filter_queryset(self, queryset): Project.objects.annotate( heading=F("name"), view=Value("project_detail"), - search_headline=SearchHeadline("description", query), + search_headline=SearchHeadline("description", query, max_words=15, min_words=10), rank=SearchRank(project_search_vector, query, normalization=16), search=project_search_vector, ) @@ -69,7 +69,9 @@ def filter_queryset(self, queryset): # We limit the headline to limit the performance impact. On very large # documents, this slows things down if unconstrained. - search_headline = SearchHeadline(Left("document_data", 200_000), query) + search_headline = SearchHeadline( + Left("document_data", 20_000), query, max_words=15, min_words=10 + ) search_rank = SearchRank(F("search_vector"), query, normalization=16) queryset = queryset.annotate( heading=F("title"), @@ -77,7 +79,7 @@ def filter_queryset(self, queryset): rank=search_rank, search_headline=search_headline, ).values(*fields) - return queryset.union(project_query).order_by("-rank") + return queryset.union(project_query, all=True).order_by("-rank") def filter_search(self, queryset, name, value): if value: From 3d9c74a7261c5e3a22e8aee8ac8b47fe203880bb Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Thu, 25 Jul 2024 10:39:04 +0200 Subject: [PATCH 139/240] +logo_url in search results --- app/general/filters.py | 12 +++++++++++- app/templates/app/search.html | 8 +++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/app/general/filters.py b/app/general/filters.py index 90fb1b95..12f670a4 100644 --- a/app/general/filters.py +++ b/app/general/filters.py @@ -48,7 +48,15 @@ def filter_queryset(self, queryset): # A fixed list of identical fields are required to join queries of # different classes with `.union()`: - fields = ("id", "heading", "description", "rank", "search_headline", "view") + fields = ( + "id", + "heading", + "description", + "rank", + "search_headline", + "view", + "logo_url", + ) # In the queries below, any differences between models must be fixed # through e.g. `Value` or `F` annotations. @@ -59,6 +67,7 @@ def filter_queryset(self, queryset): Project.objects.annotate( heading=F("name"), view=Value("project_detail"), + logo_url=F("logo"), search_headline=SearchHeadline("description", query, max_words=15, min_words=10), rank=SearchRank(project_search_vector, query, normalization=16), search=project_search_vector, @@ -76,6 +85,7 @@ def filter_queryset(self, queryset): queryset = queryset.annotate( heading=F("title"), view=Value("document_detail"), + logo_url=Value(""), rank=search_rank, search_headline=search_headline, ).values(*fields) diff --git a/app/templates/app/search.html b/app/templates/app/search.html index f5ee4b62..910fa73f 100644 --- a/app/templates/app/search.html +++ b/app/templates/app/search.html @@ -20,12 +20,18 @@
      {% trans "Search a term" %}
      {% for result in search_results %}
      -

      {{ result.heading }}

      +

      {{ result.heading }}

      {{ result.description | truncatewords:10 }}

      {% trans "Excerpt:" %} {{ result.search_headline|safe }}

      +

      + {% if result.logo_url %} + + {% endif %} +

      {{ result.rank }}

      From 92390b032cd197d9caf0d3439f7a80552a2d038e Mon Sep 17 00:00:00 2001 From: Daniel Gray Date: Thu, 25 Jul 2024 10:41:14 +0200 Subject: [PATCH 140/240] Update search tests for combined results --- app/general/tests/test_filter.py | 70 ++++++++++++++++++++------- app/general/tests/test_view_search.py | 2 +- 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/app/general/tests/test_filter.py b/app/general/tests/test_filter.py index c9d0ca92..3f78cdbf 100644 --- a/app/general/tests/test_filter.py +++ b/app/general/tests/test_filter.py @@ -3,7 +3,7 @@ from django.test import TestCase from general.filters import DocumentFileFilter -from general.models import DocumentFile, Institution, Language, Subject +from general.models import DocumentFile, Institution, Language, Project, Subject class TestSearchFilter(TestCase): @@ -38,30 +38,48 @@ def setUp(self): self.doc2.subjects.add(self.subject2) self.doc2.languages.add(self.language2) + # Create Projects for search testing + self.project1 = Project.objects.create( + name="Project 1", + description="Project 1 description", + institution=self.institution1, + logo="logo1.png", + ) + def test_institution_filter(self): data = {"institution": [self.institution1.id]} filter = DocumentFileFilter(data=data) - self.assertEqual(len(filter.qs), 1) - self.assertIn(self.doc1, filter.qs) + qs = filter.qs + self.assertEqual(len(qs), 1) + self.assertEqual(qs[0]["id"], self.doc1.id) + + def test_institution_filter(self): + data = {"institution": [self.institution1.id]} + filter = DocumentFileFilter(data=data) + qs = filter.qs + self.assertEqual(len(qs), 1) + self.assertEqual(qs[0]["id"], self.doc1.id) def test_document_type_filter(self): data = {"document_type": ["glossary"]} filter = DocumentFileFilter(data=data) - self.assertEqual(len(filter.qs), 2) - self.assertIn(self.doc1, filter.qs) - self.assertIn(self.doc2, filter.qs) + qs = filter.qs + self.assertEqual(len(qs), 2) + self.assertCountEqual([qs[0]["id"], qs[1]["id"]], [self.doc1.id, self.doc2.id]) def test_subjects_filter(self): data = {"subjects": [self.subject1.id]} filter = DocumentFileFilter(data=data) - self.assertEqual(len(filter.qs), 1) - self.assertIn(self.doc1, filter.qs) + qs = filter.qs + self.assertEqual(len(qs), 1) + self.assertEqual(qs[0]["id"], self.doc1.id) def test_languages_filter(self): data = {"languages": [self.language1.id]} filter = DocumentFileFilter(data=data) - self.assertEqual(len(filter.qs), 1) - self.assertIn(self.doc1, filter.qs) + qs = filter.qs + self.assertEqual(len(qs), 1) + self.assertEqual(qs[0]["id"], self.doc1.id) def test_combined_filters(self): data = { @@ -71,20 +89,36 @@ def test_combined_filters(self): "languages": [self.language1.id], } filter = DocumentFileFilter(data=data) - self.assertEqual(len(filter.qs), 1) - self.assertIn(self.doc1, filter.qs) + qs = filter.qs + self.assertEqual(len(qs), 1) + self.assertEqual(qs[0]["id"], self.doc1.id) - def test_search_filter(self): + def test_search_filter_documents(self): data = {"search": "Document"} filter = DocumentFileFilter(data=data) - self.assertEqual(len(filter.qs), 4) - self.assertIn(self.doc1, filter.qs) - self.assertIn(self.doc2, filter.qs) + qs = filter.qs + self.assertEqual(len(qs), 2) + self.assertCountEqual([qs[0]["id"], qs[1]["id"]], [self.doc1.id, self.doc2.id]) data = {"search": "Document 1"} filter = DocumentFileFilter(data=data) - self.assertEqual(len(filter.qs), 2) - self.assertIn(self.doc1, filter.qs) + qs = filter.qs + self.assertEqual(len(qs), 1) + self.assertEqual(qs[0]["id"], self.doc1.id) + + def test_search_filter_projects(self): + data = {"search": "Project 1"} + filter = DocumentFileFilter(data=data) + qs = filter.qs + self.assertEqual(len(qs), 1) + self.assertEqual(qs[0]["id"], self.project1.id) + + def test_search_filter_combined(self): + data = {"search": "1"} + filter = DocumentFileFilter(data=data) + qs = filter.qs + self.assertEqual(len(qs), 2) + self.assertCountEqual([qs[0]["id"], qs[1]["id"]], [self.doc1.id, self.project1.id]) if __name__ == "__main__": diff --git a/app/general/tests/test_view_search.py b/app/general/tests/test_view_search.py index 5d0386ab..21ab0c42 100644 --- a/app/general/tests/test_view_search.py +++ b/app/general/tests/test_view_search.py @@ -48,7 +48,7 @@ def test_search_filtering(self): client = Client() response = client.get(reverse("search"), {"search": "Document 1"}) self.assertEqual(response.status_code, 200) - self.assertEqual(response.context["documents"][0].title, "Document 1") + self.assertEqual(response.context["documents"][0]["heading"], "Document 1") def test_invalid_page_number(self): client = Client() From fc9149513861b575c1e730502ac28f10b4f41fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9COMosimege=E2=80=9D?= <“onalerona.mosimege@gmail.com”> Date: Thu, 25 Jul 2024 11:51:22 +0200 Subject: [PATCH 141/240] Add view, template, unit test, and styling for subjects page --- app/app/views.py | 39 ++++++++++++-- app/general/tests/tests_subject.py | 39 +++++++++++--- app/templates/app/languages.html | 4 +- app/templates/app/subjects.html | 83 ++++++++++++++++++++++++++++++ app/templates/base.html | 6 +++ 5 files changed, 160 insertions(+), 11 deletions(-) diff --git a/app/app/views.py b/app/app/views.py index 356dc858..3f24687f 100644 --- a/app/app/views.py +++ b/app/app/views.py @@ -291,11 +291,44 @@ def languages(request): def subjects(request): template = "app/subjects.html" - project = Project.objects.get(id=project_id) + subjects = ( + Subject.objects.all() + .prefetch_related( + Prefetch( + "documentfile_set", + queryset=DocumentFile.objects.only("id", "title", "subjects").order_by("title"), + ), + Prefetch( + "project_set", + queryset=Project.objects.only("id", "name", "subjects").order_by("name"), + ), + ) + .order_by("name") + ) + + subject_data = [] + + for subject in subjects: + documents = subject.documentfile_set.all() + projects = subject.project_set.all() + if documents or projects: + subject_data.append( + { + "subject": subject, + "documents": documents, + "projects": projects, + } + ) + + paginator = Paginator(subject_data, 10) + + page_number = request.GET.get("page") + page_obj = paginator.get_page(page_number) context = { - "current_page": "subject_detail", - "project": project, + "current_page": "subjects", + "subject_data": page_obj.object_list, + "page_obj": page_obj, } return render(request, template_name=template, context=context) diff --git a/app/general/tests/tests_subject.py b/app/general/tests/tests_subject.py index 53cc60c0..4e7666f2 100644 --- a/app/general/tests/tests_subject.py +++ b/app/general/tests/tests_subject.py @@ -1,15 +1,37 @@ -import unittest - from django.test import TestCase +from django.urls import reverse -from general.models import Subject +from general.models import DocumentFile, Institution, Project, Subject -class TestSubject(TestCase): +class TestSubjects(TestCase): def setUp(self): + self.institution = Institution.objects.create( + name="Test Institution", + abbreviation="TI", + url="http://testinstitution.org", + email="info@testinstitution.org", + ) + self.subject1 = Subject.objects.create(name="Mathematics") self.subject2 = Subject.objects.create(name="Science") + self.document1 = DocumentFile.objects.create( + title="Document 1", institution=self.institution, document_type="report" + ) + self.document1.subjects.add(self.subject1) + + self.document2 = DocumentFile.objects.create( + title="Document 2", institution=self.institution, document_type="report" + ) + self.document2.subjects.add(self.subject2) + + self.project1 = Project.objects.create(name="Project 1", institution=self.institution) + self.project1.subjects.add(self.subject1) + + self.project2 = Project.objects.create(name="Project 2", institution=self.institution) + self.project2.subjects.add(self.subject2) + def test_subject_creation(self): self.assertEqual(str(self.subject1), "Mathematics") self.assertEqual(str(self.subject2), "Science") @@ -22,6 +44,11 @@ def test_history_records_creation(self): self.assertEqual(self.subject1.history.count(), 1) self.assertEqual(self.subject1.history.first().name, "Mathematics") + def test_subjects_view_num_queries(self): + with self.assertNumQueries(3): + response = self.client.get(reverse("subjects")) -if __name__ == "__main__": - unittest.main() + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, "app/subjects.html") + self.assertTrue("subject_data" in response.context) + self.assertTrue("page_obj" in response.context) diff --git a/app/templates/app/languages.html b/app/templates/app/languages.html index cdb95cea..39f1fd69 100644 --- a/app/templates/app/languages.html +++ b/app/templates/app/languages.html @@ -4,10 +4,10 @@ {% block content %}
      -
      +

      {% trans "Languages" %}


      -
      +
      {% for item in language_data %}
      {{ item.language.name }}
      diff --git a/app/templates/app/subjects.html b/app/templates/app/subjects.html index e69de29b..f2b25e77 100644 --- a/app/templates/app/subjects.html +++ b/app/templates/app/subjects.html @@ -0,0 +1,83 @@ +{% extends "base.html" %} +{% load static %} +{% load i18n %} + +{% block content %} +
      +
      +

      {% trans "Subjects" %}

      +
      +
      + {% for item in subject_data %} +
      {{ item.subject.name }}
      +
      +
      {% trans "Documents" %}
      + {% if item.documents.exists %} +
      + +
      + {% else %} +

      {% trans "No documents available for this subject." %}

      + {% endif %} +
      +
      +
      {% trans "Projects" %}
      + {% if item.projects.exists %} +
      + +
      + {% else %} +

      {% trans "No projects available for this subject." %}

      + {% endif %} +
      +
      +
      +
      + {% endfor %} +
      + + {% if page_obj.has_other_pages %} +
      + +
      + {% endif %} +
      +{% endblock content %} diff --git a/app/templates/base.html b/app/templates/base.html index 052c5865..f8dce1e9 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -63,6 +63,12 @@ class="nav-link" href="{% url 'languages' %}">{% trans "Languages" %} + From 27d554f613b001c9c4421846921f586fa4bb646d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9COMosimege=E2=80=9D?= <“onalerona.mosimege@gmail.com”> Date: Fri, 26 Jul 2024 08:43:20 +0200 Subject: [PATCH 142/240] Add about and legal notices pages --- app/app/urls.py | 2 + app/app/views.py | 14 ++++++ app/static/css/styles.css | 14 +++++- app/templates/app/contact.html | 53 +++++++++++++++++++++ app/templates/app/legal_notices.html | 71 ++++++++++++++++++++++++++++ app/templates/base.html | 9 +++- 6 files changed, 159 insertions(+), 4 deletions(-) create mode 100644 app/templates/app/contact.html create mode 100644 app/templates/app/legal_notices.html diff --git a/app/app/urls.py b/app/app/urls.py index 7ba0c260..00a8f37f 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -31,6 +31,8 @@ path("admin/", admin.site.urls), path("_health/", views.health, name="health"), path("", views.home, name="home"), + path("contact/", views.contact, name="contact"), + path("legal_notices/", views.legal_notices, name="legal_notices"), path("institutions/", views.institutions, name="institutions"), path("documents/", views.documents, name="documents"), path("projects/", views.projects, name="projects"), diff --git a/app/app/views.py b/app/app/views.py index 3f24687f..24d521d8 100644 --- a/app/app/views.py +++ b/app/app/views.py @@ -23,6 +23,20 @@ def home(request): return render(request, template_name=template, context=context) +def contact(request): + template = "app/contact.html" + context = {"current_page": "contact"} + + return render(request, template_name=template, context=context) + + +def legal_notices(request): + template = "app/legal_notices.html" + context = {"current_page": "legal_notices"} + + return render(request, template_name=template, context=context) + + def get_date_range(project): start_date = project.start_date end_date = project.end_date diff --git a/app/static/css/styles.css b/app/static/css/styles.css index d17e3ae5..a14dfde2 100755 --- a/app/static/css/styles.css +++ b/app/static/css/styles.css @@ -13,13 +13,17 @@ /*General*/ body { padding: 0; - margin-bottom: 60px; + margin: 0; } html { position: relative; min-height: 100%; - padding-bottom: 60px; +} + +.content { + overflow: hidden; + padding-bottom: 130px; } .footer { @@ -449,6 +453,9 @@ html { .gap-2 > * { margin-right: 0; } + .content { + padding-bottom: 200px; + } } /*small devices (tablets, 600px and up) */ @@ -489,6 +496,9 @@ html { .gap-2 > * { margin-right: 0; } + .content { + padding-bottom: 200px; + } } /*Medium screens (tablets, between 768px and 1001px)*/ diff --git a/app/templates/app/contact.html b/app/templates/app/contact.html new file mode 100644 index 00000000..a8127217 --- /dev/null +++ b/app/templates/app/contact.html @@ -0,0 +1,53 @@ +{% extends "base.html" %} +{% load static %} +{% load i18n %} + +{% block content %} + +
      +
      +
      +
      +

      {% trans "Contact us" %}

      +
      +
      +
      {% trans "Contact details for more information: "%}
      +

      {% blocktrans %}Juan Steyn
      + South African Centre for Digital Language Resources (SADiLaR)
      + North-West University
      + South Africa{% endblocktrans %}

      +

      +27 18 285 2750

      +

      info@sadilar.org

      +
      + +
      +
      {% trans "Physical address:" %}
      +
      {% blocktrans %} + Buildings F16 C & F16 D
      + North-West University
      + Potchefstroom Campus
      + Potchefstroom
      + South Africa{% endblocktrans %} +
      +

      + Directions +

      +
      + +
      +
      {% trans "Postal address:" %}
      +
      {% blocktrans %} + SADiLaR
      + Internal Box 340
      + Private bag X6001
      + Potchefstroom
      + South Africa
      + 2520{% endblocktrans %} +
      +
      +
      +
      +
      +
      +
      +{% endblock content %} diff --git a/app/templates/app/legal_notices.html b/app/templates/app/legal_notices.html new file mode 100644 index 00000000..d4279063 --- /dev/null +++ b/app/templates/app/legal_notices.html @@ -0,0 +1,71 @@ +{% extends "base.html" %} +{% load static %} +{% load i18n %} + +{% block content %} + +
      +
      +
      +
      +

      {% trans "Terms of use" %}

      +

      + {% trans "The terms of use statement for the SADiLaR website and services" %} +

      +
      +

      {% trans "Copyright" %}

      +

      + {% blocktrans %}Intellectual property rights concerning the materials shared via this website and the website + as such belong to the original creator or to SADiLaR. Where exceptions apply, the license + agreement governing a particular resource will be explicitly listed. + Content included in the website is shared under the Creative Commons License Attribution + 2.0, unless indicated otherwise for specific materials. This license agreement does not allow + the inclusion of any elements contained in the website (such as images, logos, videos and + other resources) in a frame-set or an in-line link to another web page without giving due + credit to the origin of the material.{% endblocktrans %} +

      +
      {% trans "Disclaimer" %}
      +

      + {% blocktrans %}SADiLaR and its partners make a conscious effort to obtain copyright clearance for any + materials shared on or used in creation of the website before publication. Should you notice + any potential infringement on your own or anyone else’s intellectual property rights, we ask + that you inform us directly so that we may remove the material and/or clear any outstanding + copyright matters with the rightful owner immediately.{% endblocktrans %} +

      +
      +

      {% trans "Privacy Policy and Privacy Statement" %}

      +

      + {% blocktrans %}Please find the privacy policy and privacy statement for the SADiLaR website and services + here.{% endblocktrans %} +

      +
      {% trans "Protection of Personal Information" %}
      +

      + {% blocktrans %}The purpose of the South African Protection of Personal Information Act 4 of 2013 (POPIA) + is to ensure responsible handling of an individual’s personal information and to hold all South + African institutions accountable should they not safeguard the collection, storing or sharing + of such information against possible abuse in any way. SADiLaR is strongly committed to + protecting personal information/data and strives to adhere to the {% endblocktrans %}{% trans "guidelines" %} + {% blocktrans %} as set out by the host institution for the Centre, the North-West University.{% endblocktrans %} +

      +
      {% trans "Use of cookies" %}
      +

      + {% blocktrans %}Cookies are data saved on the device with which you access this website to customise your + experience or provide essential functions of the website such as enabling you to log in to + secure areas, e.g. the Repository. You can learn more about the different types of cookies + and how to disable non-essential cookies {% endblocktrans %}{% trans "here." %} +

      +

      + {% blocktrans %}By using the SADiLaR website, you consent to our use of cookies and storage thereof on + your device. Should you not accept our use of cookies (or your browser is set to block all + cookies), your ability to access all functions on this website might be impaired.{% endblocktrans %} +

      +
      {% trans "User support" %}
      +

      + {% blocktrans %}Any questions concerning the website or services offered via the website can be addressed + to SADiLaR via email to {% endblocktrans %}info@sadilar.org +

      +
      +
      +
      + +{% endblock content %} diff --git a/app/templates/base.html b/app/templates/base.html index f8dce1e9..26f3c70b 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -79,7 +79,7 @@ {% endblock app_header %} -
      +
      {% block content %} {% endblock content %} @@ -89,8 +89,13 @@
    +

    -
    -
    - -
    - {% for checkbox in filter.form.subjects %} -
    - {{ checkbox.tag }} - + + + +
    +
    + +
    +
    +
    + +
    + {% for checkbox in filter.form.institution %} +
    + +
    +
    + {% endfor %}
    - {% endfor %}
    -
    -
    -
    -
    - -
    - {% for checkbox in filter.form.languages %} -
    - {{ checkbox.tag }} - +
    + +
    + {% for checkbox in filter.form.document_type %} +
    + +
    +
    + {% endfor %} +
    +
    +
    + +
    + {% for checkbox in filter.form.subjects %} +
    + +
    +
    + {% endfor %}
    - {% endfor %}
    +
    + +
    + {% for checkbox in filter.form.languages %} +
    + +
    +
    + {% endfor %} +
    +
    +
    + + {% trans 'Reset' %} +
    +
    + +
    + +
    + +
    - +

    - + +
    +

    diff --git a/app/templates/base.html b/app/templates/base.html index 26f3c70b..30cd53fd 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -111,6 +111,7 @@
    +
    From 5014e2dd86c73179e01289923fe1aeb4eb325a73 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Thu, 1 Aug 2024 09:45:24 +0200 Subject: [PATCH 144/240] Allow separate toggling of DEBUG_TOOLBAR --- app/app/settings.py | 10 ++++++---- app/app/urls.py | 3 ++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/app/settings.py b/app/app/settings.py index db46e5d6..dd70f369 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -29,6 +29,7 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = bool(os.getenv("DEBUG", "")) +DEBUG_TOOLBAR = DEBUG # to toggle separately if we want to ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "").split() USE_X_FORWARDED_HOST = bool(os.getenv("USE_X_FORWARDED_HOST", "")) @@ -50,13 +51,14 @@ "accounts", "django_filters", ] - -# Add django-extensions to the installed apps if DEBUG is True if DEBUG: INSTALLED_APPS += [ "django_extensions", - "debug_toolbar", ] + if DEBUG_TOOLBAR: + INSTALLED_APPS += [ + "debug_toolbar", + ] AUTH_USER_MODEL = "users.CustomUser" @@ -74,7 +76,7 @@ ] # Add debug toolbar middleware -if DEBUG: +if DEBUG and DEBUG_TOOLBAR: MIDDLEWARE.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware") ROOT_URLCONF = "app.urls" diff --git a/app/app/urls.py b/app/app/urls.py index 00a8f37f..d5dbdcbc 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -49,4 +49,5 @@ if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) - urlpatterns.append(path("__debug__/", include("debug_toolbar.urls"))) + if settings.DEBUG_TOOLBAR: + urlpatterns.append(path("__debug__/", include("debug_toolbar.urls"))) From 99148b201dfdf2173c3d4429a16a39092e48d2cc Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Thu, 1 Aug 2024 11:49:36 +0200 Subject: [PATCH 145/240] Rework DEBUG_TOOLBAR_CONFIG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ... by removing it. The setting for SHOW_TOOLBAR_CALLBACK seems to have been an attempt to work around issues when running inside docker. Debug toolbar tries to handle this case, but it doesn't work sufficiently (at least in my case). The documentation has an opinion on this matter: Do not use DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": lambda request: DEBUG} in your project’s settings.py file. This commit is longer than I'd like, but the setting of INTERNAL_IPS seems to really fix it, and solves the fact that the debug context processor wasn't working either. With this fixed, {% debug %} works in templates. INTERNAL_IPS should best contain IP addresses rather than hostnames. IS_RUNNING_TESTS... no idea why we would want the toolbar while running tests. --- app/app/settings.py | 43 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/app/app/settings.py b/app/app/settings.py index dd70f369..dbcf4790 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -118,16 +118,43 @@ } } -# toolbar settings if DEBUG: - DEBUG_TOOLBAR_CONFIG = { - "IS_RUNNING_TESTS": False, - "SHOW_TOOLBAR_CALLBACK": lambda request: DEBUG, - } + # Some things rely on the setting INTERNAL_IPS: + # - debug_toolbar.middleware.show_toolbar + # - django.template.context_processors.debug + # See https://docs.djangoproject.com/en/stable/ref/settings/#internal-ips + # Inside a docker container, it isn't trivial to get the IP address of the + # Docker host that will appear in REMOTE_ADDR. The following seems to work + # for now to add support for a range of IP addresses without having to put + # a huge list in INTERNAL_IPS, e.g. with + # map(str, ipaddress.ip_network('172.0.0.0/24')) + # If this can't resolve the name "host.docker.internal", we assume that the + # browser will contact localhost. + import socket + + try: + host_ip = socket.gethostbyname("host.docker.internal") + except socket.gaierror: + # presumably not in docker + host_ip = None + + import ipaddress + + # Based on https://code.djangoproject.com/ticket/3237#comment:12 + class CIDRList(list): + def __init__(self, addresses): + """Create a new ip_network object for each address range provided.""" + self.networks = [ipaddress.ip_network(address, strict=False) for address in addresses] + + def __contains__(self, address): + """Check if the given address is contained in any of the networks.""" + return any([ipaddress.ip_address(address) in network for network in self.networks]) + + if host_ip: + INTERNAL_IPS = CIDRList([f"{host_ip}/8"]) + else: + INTERNAL_IPS = ["127.0.0.1"] - INTERNAL_IPS = [ - "host.docker.internal", - ] # Email settings EMAIL_HOST = os.environ.get("EMAIL_HOST") From a4e27ddaf6037577e2ffa065e7fba902bbdfe811 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Thu, 1 Aug 2024 11:50:05 +0200 Subject: [PATCH 146/240] Insert DebugToolbarMiddleware at correct place --- app/app/settings.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/app/settings.py b/app/app/settings.py index dbcf4790..439040dc 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -65,6 +65,8 @@ MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware", + # DebugToolbarMiddleware should go here if enabled. Done below. See + # https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#add-the-middleware "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", @@ -77,7 +79,7 @@ # Add debug toolbar middleware if DEBUG and DEBUG_TOOLBAR: - MIDDLEWARE.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware") + MIDDLEWARE.insert(2, "debug_toolbar.middleware.DebugToolbarMiddleware") ROOT_URLCONF = "app.urls" From 18b8fca0763dc8471d00ba487a8b1108ff68329c Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Thu, 1 Aug 2024 11:50:22 +0200 Subject: [PATCH 147/240] Insert LocaleMiddleware at correct place --- app/app/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/app/settings.py b/app/app/settings.py index 439040dc..514f6e5a 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -68,13 +68,13 @@ # DebugToolbarMiddleware should go here if enabled. Done below. See # https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#add-the-middleware "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.locale.LocaleMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", "simple_history.middleware.HistoryRequestMiddleware", - "django.middleware.locale.LocaleMiddleware", ] # Add debug toolbar middleware From ea17e4b9912725da1f79f244d15548ad6d0e2391 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Thu, 1 Aug 2024 11:55:24 +0200 Subject: [PATCH 148/240] Rename LOGGING_FOLDER_DEFAULT -> LOGGING_DIR --- app/app/settings.py | 6 ++---- app/general/tests/test_logging.py | 5 ++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/app/app/settings.py b/app/app/settings.py index 514f6e5a..d071fb8a 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -236,7 +236,7 @@ def __contains__(self, address): DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" -LOGGING_FOLDER_DEFAULT = os.path.abspath(os.path.join("/logging/")) +LOGGING_DIR = Path("/logging") # Internationalization @@ -283,9 +283,7 @@ def __contains__(self, address): "file": { "level": os.environ.get("LOGGING_HANDLERS_LEVEL", "WARNING"), "class": "logging.FileHandler", - "filename": os.path.join( - LOGGING_FOLDER_DEFAULT, os.environ.get("LOGGING_FILE", "debug.log") - ), + "filename": LOGGING_DIR / os.environ.get("LOGGING_FILE", "debug.log"), "formatter": "verbose", }, }, diff --git a/app/general/tests/test_logging.py b/app/general/tests/test_logging.py index 00926204..b24d6ccb 100644 --- a/app/general/tests/test_logging.py +++ b/app/general/tests/test_logging.py @@ -1,15 +1,14 @@ import logging import os -import sys from django.test import TestCase class LoggingTest(TestCase): def setUp(self): - LOGGING_FOLDER_DEFAULT = os.path.abspath(os.path.join("/logging/")) + LOGGING_DIR = "/logging" self.logger = logging.getLogger("django") - self.log_file = os.path.join(LOGGING_FOLDER_DEFAULT, "debug.log") + self.log_file = os.path.join(LOGGING_DIR, "debug.log") def test_log_file_created(self): """Test if the log file is created.""" From f7ba460eeb0a65fdfe9528ca3412e89f92e35d81 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Fri, 2 Aug 2024 21:42:36 +0200 Subject: [PATCH 149/240] Remove document_type from search This was never going to work this way on projects, but filtering on projects is broken, which hid this. (Fix to follow.) --- app/general/filters.py | 4 ---- app/general/tests/test_filter.py | 8 -------- app/templates/app/search.html | 30 ------------------------------ 3 files changed, 42 deletions(-) diff --git a/app/general/filters.py b/app/general/filters.py index 0f87d0b2..e75f96bc 100644 --- a/app/general/filters.py +++ b/app/general/filters.py @@ -19,9 +19,6 @@ class DocumentFileFilter(django_filters.FilterSet): institution = ModelMultipleChoiceFilter( queryset=Institution.objects.all(), widget=forms.CheckboxSelectMultiple ) - document_type = MultipleChoiceFilter( - choices=DocumentFile.document_type_choices, widget=forms.CheckboxSelectMultiple - ) subjects = ModelMultipleChoiceFilter( queryset=Subject.objects.all(), widget=forms.CheckboxSelectMultiple ) @@ -32,7 +29,6 @@ class DocumentFileFilter(django_filters.FilterSet): class Meta: model = DocumentFile fields = [ - "document_type", "institution", "subjects", "languages", diff --git a/app/general/tests/test_filter.py b/app/general/tests/test_filter.py index 3f78cdbf..9eea3982 100644 --- a/app/general/tests/test_filter.py +++ b/app/general/tests/test_filter.py @@ -60,13 +60,6 @@ def test_institution_filter(self): self.assertEqual(len(qs), 1) self.assertEqual(qs[0]["id"], self.doc1.id) - def test_document_type_filter(self): - data = {"document_type": ["glossary"]} - filter = DocumentFileFilter(data=data) - qs = filter.qs - self.assertEqual(len(qs), 2) - self.assertCountEqual([qs[0]["id"], qs[1]["id"]], [self.doc1.id, self.doc2.id]) - def test_subjects_filter(self): data = {"subjects": [self.subject1.id]} filter = DocumentFileFilter(data=data) @@ -84,7 +77,6 @@ def test_languages_filter(self): def test_combined_filters(self): data = { "institution": [self.institution1.id], - "document_type": ["glossary"], "subjects": [self.subject1.id], "languages": [self.language1.id], } diff --git a/app/templates/app/search.html b/app/templates/app/search.html index 7540164c..7bb40e30 100644 --- a/app/templates/app/search.html +++ b/app/templates/app/search.html @@ -32,22 +32,6 @@
    -
    - -
    - {% for checkbox in filter.form.document_type %} -
    - -
    -
    - {% endfor %} -
    -
    - -
    @@ -208,20 +192,6 @@

    {% endfor %}
    -
    -
    - -
    - {% for checkbox in filter.form.document_type %} -
    - -
    -
    - {% endfor %} -
    From 6c0d4671f7611afb78b008ae04f9a1080bde6627 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Fri, 2 Aug 2024 21:51:54 +0200 Subject: [PATCH 150/240] Rework filtering and searching for projects Two intertwined issues are fixed: - An empty search query would cause no projects to be returned. Now we only search if a query is given. - We weren't filtering projects at all. We're really going against the intention of django_filters with this one, but let's see... The test was simply wrong (and duplicated). --- app/general/filters.py | 46 +++++++++++++++++--------------- app/general/tests/test_filter.py | 10 ++----- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/app/general/filters.py b/app/general/filters.py index e75f96bc..72fe7ef9 100644 --- a/app/general/filters.py +++ b/app/general/filters.py @@ -14,7 +14,7 @@ class DocumentFileFilter(django_filters.FilterSet): - search = django_filters.CharFilter(method="filter_search", label="Search") + search = django_filters.CharFilter(method="ignore", label="Search") institution = ModelMultipleChoiceFilter( queryset=Institution.objects.all(), widget=forms.CheckboxSelectMultiple @@ -37,7 +37,6 @@ class Meta: def filter_queryset(self, queryset): # More information about weighting and normalization in postgres: # https://www.postgresql.org/docs/current/textsearch-controls.html#TEXTSEARCH-RANKING - queryset = super().filter_queryset(queryset) search = self.form.cleaned_data.get("search", "").strip() query = SearchQuery(search) @@ -60,20 +59,18 @@ def filter_queryset(self, queryset): project_search_vector = SearchVector("name", weight="A") + SearchVector( "description", weight="B" ) - project_query = ( - Project.objects.annotate( - heading=F("name"), - view=Value("project_detail"), - logo_url=F("logo"), - associated_url=F("url"), - search_headline=SearchHeadline("description", query, max_words=15, min_words=10), - rank=SearchRank(project_search_vector, query, normalization=16), - search=project_search_vector, - ) - .filter(search=query) - .values(*fields) + project_query = Project.objects.annotate( + heading=F("name"), + view=Value("project_detail"), + logo_url=F("logo"), + associated_url=F("url"), + search_headline=SearchHeadline("description", query, max_words=15, min_words=10), + rank=SearchRank(project_search_vector, query, normalization=16), ) + queryset = super().filter_queryset(queryset) + project_query = super().filter_queryset(project_query) + # We limit the headline to limit the performance impact. On very large # documents, this slows things down if unconstrained. search_headline = SearchHeadline( @@ -87,13 +84,18 @@ def filter_queryset(self, queryset): associated_url=F("url"), rank=search_rank, search_headline=search_headline, - ).values(*fields) - return queryset.union(project_query, all=True).order_by("-rank") + ) + if search: + # An empty search on Project filters out everything. + queryset = queryset.filter(search_vector=query) + project_query = project_query.annotate(search=project_search_vector).filter( + search=query + ) - def filter_search(self, queryset, name, value): - if value: - query = SearchQuery(value.strip()) - return queryset.filter(search_vector=query) + queryset = queryset.values(*fields) + project_query = project_query.values(*fields) + return queryset.union(project_query, all=True).order_by("-rank") - else: - return queryset + def ignore(self, queryset, name, value): + # All fields are handled in `.filter_queryset()` + return queryset diff --git a/app/general/tests/test_filter.py b/app/general/tests/test_filter.py index 9eea3982..bc328769 100644 --- a/app/general/tests/test_filter.py +++ b/app/general/tests/test_filter.py @@ -50,14 +50,8 @@ def test_institution_filter(self): data = {"institution": [self.institution1.id]} filter = DocumentFileFilter(data=data) qs = filter.qs - self.assertEqual(len(qs), 1) - self.assertEqual(qs[0]["id"], self.doc1.id) - - def test_institution_filter(self): - data = {"institution": [self.institution1.id]} - filter = DocumentFileFilter(data=data) - qs = filter.qs - self.assertEqual(len(qs), 1) + self.assertEqual(len(qs), 2) + # TODO: ordering between documents and projects are not yet defined self.assertEqual(qs[0]["id"], self.doc1.id) def test_subjects_filter(self): From 6f33ecb0ae00f3bfffed04ee0d8bcc14c657b0aa Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Mon, 29 Jul 2024 12:30:44 +0200 Subject: [PATCH 151/240] Rework HTML headers --- app/templates/app/document_detail.html | 1 + app/templates/app/institution_detail.html | 1 + app/templates/app/project_detail.html | 1 + app/templates/base.html | 8 ++++---- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/templates/app/document_detail.html b/app/templates/app/document_detail.html index d0f90b5c..713a9685 100644 --- a/app/templates/app/document_detail.html +++ b/app/templates/app/document_detail.html @@ -2,6 +2,7 @@ {% load static %} {% load i18n %} +{% block title %}{{ document.title }}{% endblock %} {% block content %}
    diff --git a/app/templates/app/institution_detail.html b/app/templates/app/institution_detail.html index 23fab35b..11c7a677 100644 --- a/app/templates/app/institution_detail.html +++ b/app/templates/app/institution_detail.html @@ -2,6 +2,7 @@ {% load static %} {% load i18n %} +{% block title %}{{ institution.name }}{% endblock %} {% block content %}
    diff --git a/app/templates/app/project_detail.html b/app/templates/app/project_detail.html index 140e1d3b..0fc83390 100644 --- a/app/templates/app/project_detail.html +++ b/app/templates/app/project_detail.html @@ -2,6 +2,7 @@ {% load static %} {% load i18n %} +{% block title %}{{ project.name }}{% endblock %} {% block content %}
    diff --git a/app/templates/base.html b/app/templates/base.html index 30cd53fd..03693d6a 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -4,10 +4,10 @@ - {% trans "SADiLaR" %} - - - + {% block title %}{% trans "SADiLaR" %}{% endblock %} + + + From 6b2b08c1b9109c7699db673826e0ba78864015dd Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Mon, 29 Jul 2024 12:33:41 +0200 Subject: [PATCH 152/240] +templatetag for a Bootstrap icon Repetative with too much room to get this wrong. --- app/general/templatetags/bs_icons.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 app/general/templatetags/bs_icons.py diff --git a/app/general/templatetags/bs_icons.py b/app/general/templatetags/bs_icons.py new file mode 100644 index 00000000..9695f978 --- /dev/null +++ b/app/general/templatetags/bs_icons.py @@ -0,0 +1,21 @@ +import re + +from django import template +from django.utils.safestring import mark_safe + +"""A simple template tag to add a single Bootstrap icon. + +If our needs grow, we should probably switch to something like +django-bootstrap-icons: +https://pypi.org/project/django-bootstrap-icons/ +""" + + +register = template.Library() +icon_name_re = re.compile(r"[a-z0-9\-]+") + + +@register.simple_tag +def bs_icon(name): + assert icon_name_re.fullmatch(name) + return mark_safe(f'') From ae4246f46b39a5f8a3c1fc43fa31004563514c3e Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Mon, 29 Jul 2024 12:45:20 +0200 Subject: [PATCH 153/240] Factor out pagination Also improved some layout bugs (particularly at small sizes), added ellipsis (...), and improved accessibility. --- app/templates/app/_pagination.html | 44 ++++++++++++++++++++++++++++++ app/templates/app/documents.html | 24 +--------------- app/templates/app/subjects.html | 30 ++------------------ 3 files changed, 48 insertions(+), 50 deletions(-) create mode 100644 app/templates/app/_pagination.html diff --git a/app/templates/app/_pagination.html b/app/templates/app/_pagination.html new file mode 100644 index 00000000..2d71c847 --- /dev/null +++ b/app/templates/app/_pagination.html @@ -0,0 +1,44 @@ +{% load i18n %} +{% if page_obj.has_previous or page_obj.has_next %} + +{% endif %} diff --git a/app/templates/app/documents.html b/app/templates/app/documents.html index b1e7fd4e..88478034 100644 --- a/app/templates/app/documents.html +++ b/app/templates/app/documents.html @@ -82,31 +82,9 @@

    {% endfor %}

    -
    -
    {% endblock content %} diff --git a/app/templates/app/subjects.html b/app/templates/app/subjects.html index f2b25e77..7cd842fb 100644 --- a/app/templates/app/subjects.html +++ b/app/templates/app/subjects.html @@ -50,34 +50,10 @@
    {% trans "Projects" %}
    {% endfor %}
    - {% if page_obj.has_other_pages %} -
    -
    + + {% include "app/_pagination.html" %} - {% if page_obj.has_next %} -
  • {% trans "Next" %} ›
  • -
  • {% trans "Last" %} »
  • - {% else %} -
  • {% trans "Next" %} ›
  • -
  • {% trans "Last" %} »
  • - {% endif %} - -
    - {% endif %}
    {% endblock content %} From ea4de0ac765945d529354e0314addcc08b310d17 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Mon, 29 Jul 2024 12:48:07 +0200 Subject: [PATCH 154/240] Rework header This fixes duplicate class attributes, and gives a hamburger icon. --- app/templates/app/_nav_item.html | 9 ++++ app/templates/base.html | 82 ++++++++++---------------------- 2 files changed, 33 insertions(+), 58 deletions(-) create mode 100644 app/templates/app/_nav_item.html diff --git a/app/templates/app/_nav_item.html b/app/templates/app/_nav_item.html new file mode 100644 index 00000000..956cc824 --- /dev/null +++ b/app/templates/app/_nav_item.html @@ -0,0 +1,9 @@ + + diff --git a/app/templates/base.html b/app/templates/base.html index 03693d6a..142548ad 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -16,68 +16,34 @@ - -{% block app_header %}
    -
    - - + +{% endblock header %}
    -{% endblock app_header %} -
    {% block content %} From 36e153b5e16940989646ca12ca31b0446e90c7ea Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Mon, 29 Jul 2024 12:50:50 +0200 Subject: [PATCH 155/240] Rework footer Fixes lack of label for select (as reported by Lighthouse). This has a slightly different layout, but is expandable to more links, and uses less height on wide screen sizes. --- app/templates/base.html | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/app/templates/base.html b/app/templates/base.html index 142548ad..16004684 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -51,21 +51,22 @@ {% endblock content %}

    - -
    -
    - + From a47080d484722fba0bbe7fb6d257e48f7627d8e9 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Wed, 31 Jul 2024 20:25:53 +0200 Subject: [PATCH 167/240] Reduce "no info available" messages On wide screens (two columns) these are maintained for layout, but we reduce it on smaller screens where it doesn't serve enough of a purpose. --- app/templates/app/languages.html | 16 ++++++++++++---- app/templates/app/subjects.html | 16 ++++++++++++---- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/app/templates/app/languages.html b/app/templates/app/languages.html index bcb20cb7..a9417b94 100644 --- a/app/templates/app/languages.html +++ b/app/templates/app/languages.html @@ -10,8 +10,8 @@

    {% trans "Information by language" %}

    {% for item in language_data %}

    {{ item.language.name }}

    -

    {% trans "Documents" %}

    {% if item.documents.exists %} +

    {% trans "Documents" %}

      {% for document in item.documents %} @@ -25,12 +25,16 @@

      {% trans "Documents" %}

    {% else %} -

    {% trans "No documents available for this language." %}

    + {# Only show this in two-column mode to ensure consistent layout and placement #} +
    +

    {% trans "Documents" %}

    +

    {% trans "No documents available for this language." %}

    +
    {% endif %}
    -

    {% trans "Projects" %}

    {% if item.projects.exists %} +

    {% trans "Projects" %}

      {% for project in item.projects %} @@ -44,7 +48,11 @@

      {% trans "Projects" %}

    {% else %} -

    {% trans "No projects available for this language." %}

    + {# Only show this in two-column mode to ensure consistent layout and placement #} +
    +

    {% trans "Projects" %}

    +

    {% trans "No projects available for this language." %}

    +
    {% endif %}

    diff --git a/app/templates/app/subjects.html b/app/templates/app/subjects.html index 8a11cb8f..3a0c5eec 100644 --- a/app/templates/app/subjects.html +++ b/app/templates/app/subjects.html @@ -10,8 +10,8 @@

    {% trans "Information by subject" %}

    {% for item in subject_data %}

    {{ item.subject.name }}

    -

    {% trans "Documents" %}

    {% if item.documents.exists %} +

    {% trans "Documents" %}

      {% for document in item.documents %} @@ -25,12 +25,16 @@

      {% trans "Documents" %}

    {% else %} -

    {% trans "No documents available for this subject." %}

    + {# Only show this in two-column mode to ensure consistent layout and placement #} +
    +

    {% trans "Documents" %}

    +

    {% trans "No documents available for this subject." %}

    +
    {% endif %}
    -

    {% trans "Projects" %}

    {% if item.projects.exists %} +

    {% trans "Projects" %}

      {% for project in item.projects %} @@ -44,7 +48,11 @@

      {% trans "Projects" %}

    {% else %} -

    {% trans "No projects available for this subject." %}

    + {# Only show this in two-column mode to ensure consistent layout and placement #} +
    + {% trans "Projects" %} +

    {% trans "No projects available for this subject." %}

    +
    {% endif %}

    From 375d9b563cdfbe112306d620b0b7b15b56901c40 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Wed, 31 Jul 2024 20:47:23 +0200 Subject: [PATCH 168/240] Make icons and their spacing more consistent The layout of text after {% icon %} would affect the layout, which would require discipline in spacing around the tag. This relies on a space after the icon span. --- app/general/templatetags/bs_icons.py | 9 ++++++++- app/templates/app/document_detail.html | 6 +++--- app/templates/app/institution_detail.html | 4 ++-- app/templates/app/project_detail.html | 4 ++-- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/app/general/templatetags/bs_icons.py b/app/general/templatetags/bs_icons.py index d2cc20e9..4a7653ff 100644 --- a/app/general/templatetags/bs_icons.py +++ b/app/general/templatetags/bs_icons.py @@ -17,7 +17,14 @@ def _bs_icon(name): assert icon_name_re.fullmatch(name) - return mark_safe(f'') + return mark_safe(f' ') + # The trailing space is intentional: Since this is an inline element + # usually followed by text, the absence/presence of a space is significant, + # and usually wanted for layout. That's too hard to remember, so we always + # add it. Multiple spaces are equal to one. That way the exact layout of + # code in the templates doesn't matter. Beware of using {% spaceless %} + # which will negate this. A pure CSS solution escaped me thus far, since a + # space will take additional space in addition to a margin. @register.simple_tag diff --git a/app/templates/app/document_detail.html b/app/templates/app/document_detail.html index bac29de2..11e84c1c 100644 --- a/app/templates/app/document_detail.html +++ b/app/templates/app/document_detail.html @@ -23,7 +23,7 @@

    {{ document.title }}

    @@ -50,7 +50,7 @@

    {{ document.title }}

    {% trans "Subjects" %}

      {% for subject in document.subjects.all %} -
    • {% icon "subject" %}{{ subject.name }}
    • +
    • {% icon "subject" %}{{ subject.name }}
    • {% endfor %}
    @@ -61,7 +61,7 @@

    {% trans "Subjects" %}

    {% trans "Languages" %}

      {% for language in document.languages.all %} -
    • {% icon "language" %}{{ language.name }}
    • +
    • {% icon "language" %}{{ language.name }}
    • {% endfor %}
    diff --git a/app/templates/app/institution_detail.html b/app/templates/app/institution_detail.html index 439f0bc5..e3f2ba15 100644 --- a/app/templates/app/institution_detail.html +++ b/app/templates/app/institution_detail.html @@ -32,7 +32,7 @@

    {% trans "Projects" %}

    {% if projects %}
      {% for project in projects %} -
    • +
    • {% icon "project" %} {{ project.name }}
    • @@ -47,7 +47,7 @@

      {% trans "Documents" %}

      {% if documents %}
        {% for document in documents %} -
      • +
      • {% icon "document" %} {{ document.title }}
      • diff --git a/app/templates/app/project_detail.html b/app/templates/app/project_detail.html index 13874630..9ce68844 100644 --- a/app/templates/app/project_detail.html +++ b/app/templates/app/project_detail.html @@ -60,7 +60,7 @@

        {{ project.name }}

        {% trans "Subjects" %}

          {% for subject in subjects %} -
        • {% icon "subject" %}{{ subject.name }}
        • +
        • {% icon "subject" %}{{ subject.name }}
        • {% endfor %}
        @@ -71,7 +71,7 @@

        {% trans "Subjects" %}

        {% trans "Languages" %}

          {% for language in languages %} -
        • {% icon "language" %}{{ language.name }}
        • +
        • {% icon "language" %}{{ language.name }}
        • {% endfor %}
        From 895b8ce03bae94fc9c5dd96307d3aeb834bb59a1 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Wed, 31 Jul 2024 20:49:42 +0200 Subject: [PATCH 169/240] Implement more page titles --- app/templates/app/document_detail.html | 1 + app/templates/app/documents.html | 2 ++ app/templates/app/institution_detail.html | 1 + app/templates/app/institutions.html | 2 ++ app/templates/app/languages.html | 2 ++ app/templates/app/project_detail.html | 1 + app/templates/app/projects.html | 4 +++- app/templates/app/subjects.html | 2 ++ app/templates/base_error.html | 1 + 9 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/templates/app/document_detail.html b/app/templates/app/document_detail.html index 11e84c1c..6f25709a 100644 --- a/app/templates/app/document_detail.html +++ b/app/templates/app/document_detail.html @@ -4,6 +4,7 @@ {% load bs_icons %} {% block title %}{{ document.title }}{% endblock %} + {% block content %}

        {% trans "Documents" %} > {{ document.title }}

        diff --git a/app/templates/app/documents.html b/app/templates/app/documents.html index e4d79fde..e29b4825 100644 --- a/app/templates/app/documents.html +++ b/app/templates/app/documents.html @@ -3,6 +3,8 @@ {% load i18n %} {% load bs_icons %} +{% block title %}{% trans "Documents" %}{% endblock %} + {% block content %}

        {% trans "Documents" %}

        diff --git a/app/templates/app/institution_detail.html b/app/templates/app/institution_detail.html index e3f2ba15..2f6944b1 100644 --- a/app/templates/app/institution_detail.html +++ b/app/templates/app/institution_detail.html @@ -4,6 +4,7 @@ {% load bs_icons %} {% block title %}{{ institution.name }}{% endblock %} + {% block content %}

        {% trans "Institutions" %} > {{ institution.name }}

        diff --git a/app/templates/app/institutions.html b/app/templates/app/institutions.html index 68ff9119..c4594fa2 100644 --- a/app/templates/app/institutions.html +++ b/app/templates/app/institutions.html @@ -3,6 +3,8 @@ {% load i18n %} {% load bs_icons %} +{% block title %}{% trans "Institutions" %}{% endblock %} + {% block content %}
        {# row has unwanted margin #} {% for institution in institutions %} diff --git a/app/templates/app/languages.html b/app/templates/app/languages.html index a9417b94..c82c141f 100644 --- a/app/templates/app/languages.html +++ b/app/templates/app/languages.html @@ -3,6 +3,8 @@ {% load i18n %} {% load bs_icons %} +{% block title %}{% trans "Languages" %}{% endblock %} + {% block content %}

        {% trans "Information by language" %}

        diff --git a/app/templates/app/project_detail.html b/app/templates/app/project_detail.html index 9ce68844..1a95ead9 100644 --- a/app/templates/app/project_detail.html +++ b/app/templates/app/project_detail.html @@ -4,6 +4,7 @@ {% load bs_icons %} {% block title %}{{ project.name }}{% endblock %} + {% block content %}

        {% trans "Projects" %} > {{ project.name }}

        diff --git a/app/templates/app/projects.html b/app/templates/app/projects.html index fc9c65d2..2cfc4568 100644 --- a/app/templates/app/projects.html +++ b/app/templates/app/projects.html @@ -3,9 +3,11 @@ {% load i18n %} {% load bs_icons %} +{% block title %}{% trans "Projects" %}{% endblock %} + {% block content %}
        -

        {%trans "Projects" %}

        +

        {% trans "Projects" %}

        {% include "app/_subj_lang_institution_filter.html" %} diff --git a/app/templates/app/subjects.html b/app/templates/app/subjects.html index 3a0c5eec..83ea3290 100644 --- a/app/templates/app/subjects.html +++ b/app/templates/app/subjects.html @@ -3,6 +3,8 @@ {% load i18n %} {% load bs_icons %} +{% block title %}{% trans "Subjects" %}{% endblock %} + {% block content %}

        {% trans "Information by subject" %}

        diff --git a/app/templates/base_error.html b/app/templates/base_error.html index 84ad432f..0eda3258 100644 --- a/app/templates/base_error.html +++ b/app/templates/base_error.html @@ -2,6 +2,7 @@ {% load i18n %} {% block title %}{% trans "Error" %}{% endblock %} + {% block content %}
        From 95dd02a9e998ae26120df33a27ee2808de13f4bc Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Wed, 31 Jul 2024 20:53:27 +0200 Subject: [PATCH 170/240] Reduce size of h3 --- app/templates/app/languages.html | 8 ++++---- app/templates/app/subjects.html | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/templates/app/languages.html b/app/templates/app/languages.html index c82c141f..747dc47b 100644 --- a/app/templates/app/languages.html +++ b/app/templates/app/languages.html @@ -13,7 +13,7 @@

        {% trans "Information by language" %}

        {{ item.language.name }}

        {% if item.documents.exists %} -

        {% trans "Documents" %}

        +

        {% trans "Documents" %}

          {% for document in item.documents %} @@ -29,14 +29,14 @@

          {% trans "Documents" %}

          {% else %} {# Only show this in two-column mode to ensure consistent layout and placement #}
          -

          {% trans "Documents" %}

          +

          {% trans "Documents" %}

          {% trans "No documents available for this language." %}

          {% endif %}
        {% if item.projects.exists %} -

        {% trans "Projects" %}

        +

        {% trans "Projects" %}

          {% for project in item.projects %} @@ -52,7 +52,7 @@

          {% trans "Projects" %}

          {% else %} {# Only show this in two-column mode to ensure consistent layout and placement #}
          -

          {% trans "Projects" %}

          +

          {% trans "Projects" %}

          {% trans "No projects available for this language." %}

          {% endif %} diff --git a/app/templates/app/subjects.html b/app/templates/app/subjects.html index 83ea3290..adbaa16a 100644 --- a/app/templates/app/subjects.html +++ b/app/templates/app/subjects.html @@ -13,7 +13,7 @@

          {% trans "Information by subject" %}

          {{ item.subject.name }}

          {% if item.documents.exists %} -

          {% trans "Documents" %}

          +

          {% trans "Documents" %}

            {% for document in item.documents %} @@ -29,14 +29,14 @@

            {% trans "Documents" %}

            {% else %} {# Only show this in two-column mode to ensure consistent layout and placement #}
            -

            {% trans "Documents" %}

            +

            {% trans "Documents" %}

            {% trans "No documents available for this subject." %}

            {% endif %}
          {% if item.projects.exists %} -

          {% trans "Projects" %}

          +

          {% trans "Projects" %}

            {% for project in item.projects %} @@ -52,7 +52,7 @@

            {% trans "Projects" %}

            {% else %} {# Only show this in two-column mode to ensure consistent layout and placement #}
            - {% trans "Projects" %} +

            {% trans "Projects" %}

            {% trans "No projects available for this subject." %}

            {% endif %} From c592a3def87b99f7cd92ac26a20fa2a0e58623c8 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Wed, 31 Jul 2024 21:09:00 +0200 Subject: [PATCH 171/240] Cosmetic changes that reduce HTML output charset="utf-8" is supurfluous in HTML 5. {% spaceless %} is not used in
            due to issues it might cause with icons. See commit 375d9b5. --- app/general/templatetags/bs_icons.py | 2 +- app/templates/app/_subj_lang_institution_filter.html | 2 ++ app/templates/app/languages.html | 8 ++------ app/templates/app/subjects.html | 8 ++------ app/templates/base.html | 6 ++++-- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/app/general/templatetags/bs_icons.py b/app/general/templatetags/bs_icons.py index 4a7653ff..c508dca5 100644 --- a/app/general/templatetags/bs_icons.py +++ b/app/general/templatetags/bs_icons.py @@ -17,7 +17,7 @@ def _bs_icon(name): assert icon_name_re.fullmatch(name) - return mark_safe(f' ') + return mark_safe(f' ') # The trailing space is intentional: Since this is an inline element # usually followed by text, the absence/presence of a space is significant, # and usually wanted for layout. That's too hard to remember, so we always diff --git a/app/templates/app/_subj_lang_institution_filter.html b/app/templates/app/_subj_lang_institution_filter.html index 7bcc6bb4..6f78106e 100644 --- a/app/templates/app/_subj_lang_institution_filter.html +++ b/app/templates/app/_subj_lang_institution_filter.html @@ -1,4 +1,5 @@ {% load i18n %} +{% spaceless %}
            @@ -43,3 +44,4 @@ {% trans "Reset" %}
            +{% endspaceless %} diff --git a/app/templates/app/languages.html b/app/templates/app/languages.html index 747dc47b..a7609fb7 100644 --- a/app/templates/app/languages.html +++ b/app/templates/app/languages.html @@ -19,9 +19,7 @@

            {% trans "Documents" %}

            {% for document in item.documents %}
          • {% icon "document" %} - - {{ document.title }} - + {{ document.title }}
          • {% endfor %}
          @@ -42,9 +40,7 @@

          {% trans "Projects" %}

          {% for project in item.projects %}
        • {% icon "project" %} - - {{ project.name }} - + {{ project.name }}
        • {% endfor %}
        diff --git a/app/templates/app/subjects.html b/app/templates/app/subjects.html index adbaa16a..c645ff3b 100644 --- a/app/templates/app/subjects.html +++ b/app/templates/app/subjects.html @@ -19,9 +19,7 @@

        {% trans "Documents" %}

        {% for document in item.documents %}
      • {% icon "document" %} - - {{ document.title }} - + {{ document.title }}
      • {% endfor %}
      @@ -42,9 +40,7 @@

      {% trans "Projects" %}

      {% for project in item.projects %}
    • {% icon "project" %} - - {{ project.name }} - + {{ project.name }}
    • {% endfor %}
    diff --git a/app/templates/base.html b/app/templates/base.html index ece3155b..50a96779 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -1,13 +1,12 @@ {% load static %} {% load i18n %} - +{% spaceless %} {% block title %}{% trans "SADiLaR" %}{% endblock %} - @@ -83,12 +82,14 @@ {% endblock header %} +{% endspaceless %}
    {% block content %} {% endblock content %}
    +{% spaceless %}
    @@ -120,6 +121,7 @@
    +{% endspaceless %} From 656a6f35aade827ea2d299178120053e55d6df31 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Wed, 31 Jul 2024 21:11:24 +0200 Subject: [PATCH 172/240] Use repr(institution) in filter dropdown --- app/templates/app/_subj_lang_institution_filter.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/templates/app/_subj_lang_institution_filter.html b/app/templates/app/_subj_lang_institution_filter.html index 6f78106e..de3e81ce 100644 --- a/app/templates/app/_subj_lang_institution_filter.html +++ b/app/templates/app/_subj_lang_institution_filter.html @@ -33,7 +33,7 @@ {% for institution in institutions %} {% endfor %} From 182460bd43486295a540c1e25d3f78a9545c37c9 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Wed, 31 Jul 2024 21:11:46 +0200 Subject: [PATCH 173/240] Pass view name to template for search page --- app/app/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/app/views.py b/app/app/views.py index 67249f37..c7110c4e 100644 --- a/app/app/views.py +++ b/app/app/views.py @@ -413,6 +413,7 @@ def search(request): page_obj = paginator.page(paginator.num_pages) context = { + "current_page": "search", "search_results": paginator.page(page_obj.number), "filter": f, "documents": page_obj, From f7e6c2cae7767f9c97d8461ba8304aa575e8e6ae Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Sat, 3 Aug 2024 22:31:04 +0200 Subject: [PATCH 174/240] Rework search page Several things needed attention, such as broken pagination, code duplication and general complexity. The tag soup "worked", but was obviously not tennable. This tries to solve all of that, gets rid of almost all custom CSS classes in favour of bootstrap, and moves to more semantic HTML and a few usability and accessibility improvements, e.g. - blockquote, section, etc. - larger checkboxes - Show message when there are no results - aria-label here and there This moves the filters to the right, which hopefully ensures more attention on the results. The second search input is removed in favour of links that assist users on small screens to go to the "other" part of the page. The assumption is that enough context is visible on larger screens. And the page is down from 220kB to under 50kB. --- app/app/views.py | 7 +- app/general/filters.py | 15 +- app/general/tests/test_view_search.py | 10 +- app/templates/app/_search_filter.html | 13 ++ app/templates/app/search.html | 313 +++++++------------------- app/templates/base.html | 6 + 6 files changed, 116 insertions(+), 248 deletions(-) create mode 100644 app/templates/app/_search_filter.html diff --git a/app/app/views.py b/app/app/views.py index c7110c4e..d81e0033 100644 --- a/app/app/views.py +++ b/app/app/views.py @@ -414,10 +414,9 @@ def search(request): context = { "current_page": "search", - "search_results": paginator.page(page_obj.number), "filter": f, - "documents": page_obj, - "search_params": pagination_url(request), + "page_obj": page_obj, + "url_params": pagination_url(request), } return render(request, template_name=template, context=context) @@ -432,4 +431,4 @@ def pagination_url(request): "languages": request.GET.getlist("languages", []), } - return "?" + urlencode(url_params, doseq=True) + return urlencode(url_params, doseq=True) diff --git a/app/general/filters.py b/app/general/filters.py index 72fe7ef9..5e6f875c 100644 --- a/app/general/filters.py +++ b/app/general/filters.py @@ -8,22 +8,29 @@ ) from django.db.models import F, Value from django.db.models.functions import Greatest, Left +from django.utils.translation import gettext_lazy as _ from django_filters import ModelMultipleChoiceFilter, MultipleChoiceFilter from general.models import DocumentFile, Institution, Language, Project, Subject class DocumentFileFilter(django_filters.FilterSet): - search = django_filters.CharFilter(method="ignore", label="Search") + search = django_filters.CharFilter(method="ignore", label=_("Search")) institution = ModelMultipleChoiceFilter( - queryset=Institution.objects.all(), widget=forms.CheckboxSelectMultiple + label=_("Institution"), + queryset=Institution.objects.all().order_by("name"), + widget=forms.CheckboxSelectMultiple(attrs={"class": "form-check-input"}), ) subjects = ModelMultipleChoiceFilter( - queryset=Subject.objects.all(), widget=forms.CheckboxSelectMultiple + label=_("Subjects"), + queryset=Subject.objects.all().order_by("name"), + widget=forms.CheckboxSelectMultiple(attrs={"class": "form-check-input"}), ) languages = ModelMultipleChoiceFilter( - queryset=Language.objects.all(), widget=forms.CheckboxSelectMultiple + label=_("Languages"), + queryset=Language.objects.all().order_by("name"), + widget=forms.CheckboxSelectMultiple(attrs={"class": "form-check-input"}), ) class Meta: diff --git a/app/general/tests/test_view_search.py b/app/general/tests/test_view_search.py index 21ab0c42..57b2807d 100644 --- a/app/general/tests/test_view_search.py +++ b/app/general/tests/test_view_search.py @@ -35,11 +35,11 @@ def test_search_pagination(self): client = Client() response = client.get(reverse("search"), {"page": "1"}) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context["documents"]), 5) + self.assertEqual(len(response.context["page_obj"]), 5) response = client.get(reverse("search"), {"page": "2"}) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context["documents"]), 5) + self.assertEqual(len(response.context["page_obj"]), 5) response = client.get(reverse("search"), {"page": "3"}) self.assertEqual(response.status_code, 200) @@ -48,13 +48,13 @@ def test_search_filtering(self): client = Client() response = client.get(reverse("search"), {"search": "Document 1"}) self.assertEqual(response.status_code, 200) - self.assertEqual(response.context["documents"][0]["heading"], "Document 1") + self.assertEqual(response.context["page_obj"][0]["heading"], "Document 1") def test_invalid_page_number(self): client = Client() response = client.get(reverse("search"), {"page": "invalid"}) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context["documents"]), 5) + self.assertEqual(len(response.context["page_obj"]), 5) def test_combined_filters(self): client = Client() @@ -68,7 +68,7 @@ def test_combined_filters(self): }, ) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context["documents"]), 5) + self.assertEqual(len(response.context["page_obj"]), 5) if __name__ == "__main__": diff --git a/app/templates/app/_search_filter.html b/app/templates/app/_search_filter.html new file mode 100644 index 00000000..b3c0ff4d --- /dev/null +++ b/app/templates/app/_search_filter.html @@ -0,0 +1,13 @@ +{% spaceless %} +

    {{ field.label }}

    +
    + {% for checkbox in field %} +
    + +
    + {% endfor %} +
    +{% endspaceless %} diff --git a/app/templates/app/search.html b/app/templates/app/search.html index 7bb40e30..68110cb0 100644 --- a/app/templates/app/search.html +++ b/app/templates/app/search.html @@ -3,247 +3,90 @@ {% load i18n %} {% block content %} -
    -
    -
    -
    -
    - -
    - - - -
    -
    - -
    - -
    - {% for checkbox in filter.form.institution %} -
    - -
    -
    - {% endfor %} -
    -
    - - -
    - -
    - {% for checkbox in filter.form.subjects %} -
    - -
    -
    - {% endfor %} -
    -
    - - -
    - -
    - {% for checkbox in filter.form.languages %} -
    - -
    -
    - {% endfor %} -
    -
    -
    - - -
    - - -
    -
    - - -
    - -
    -
    -
    {% trans "Search a term" %}
    -
    -
    - -
    - -
    - - - - -
    -
    - - -
    - {% for result in search_results %} - -
    -
    -

    - - {{ result.heading }} - -

    - -
    - {{ result.description|truncatewords:20 }} {% trans "Read more" %} -
    -

    - {% trans "Excerpt:" %} - {{ result.search_headline|safe }} -

    - {% comment %} -

    - {{ result.rank }} -

    - {% endcomment %} -
    -
    -

    - {% if result.logo_url %} - {{ result.name }} logo - {% endif %} -

    -
    -
    - {% endfor %} -
    - - - -
    -
    -
    - -
    -
    + {# Results #} +
    + {% for result in page_obj %} +
    +
    +

    + + {{ result.heading }} + +

    + + {{ result.associated_url }} + +

    {{ result.description|truncatewords:20 }}

    + {% if result.search_headline.strip %} +
    +

    … {{ result.search_headline|safe }} …

    +
    + {% endif %} + {% comment "Left for debugging of search ranking" %} +

    {{ result.rank }}

    + {% endcomment %}
    - - - -
    - - -
    -
    -
    - -
    - {% for checkbox in filter.form.institution %} -
    - -
    -
    - {% endfor %} -
    -
    -
    - -
    - {% for checkbox in filter.form.subjects %} -
    - -
    -
    - {% endfor %} -
    -
    -
    - -
    - {% for checkbox in filter.form.languages %} -
    - -
    -
    - {% endfor %} -
    -
    -
    -
    - - {% trans 'Reset' %} -
    - + {% if result.logo_url %} +
    +
    - -
    - - + {% endif %}
    - + {% empty %} +

    {% trans "No results." %}

    + {% endfor %} +
    -
    -
    -
    + {# Pagination #} + {% include "app/_pagination.html" %} -
    -
    -
    -
    -
    + {# Filters #} +
    {# target for link at the top #} +

    {% trans "Filter results" %}

    +
    + {% include "app/_search_filter.html" with field=filter.form.institution %} + {% include "app/_search_filter.html" with field=filter.form.subjects %} + {% include "app/_search_filter.html" with field=filter.form.languages %} + +
    + + {% trans 'Reset' %} +
    + + +
    +{% endspaceless %} {% endblock content %} diff --git a/app/templates/base.html b/app/templates/base.html index 50a96779..c349f7cb 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -41,6 +41,12 @@ aspect-ratio: auto; } +.checkbox-container { + max-height: 150px; + max-width: 600px; + overflow-y: auto; +} + {# The following two are needed for correct footer placement #} body { min-height: 100vh; From b694f279af3a1c08338673dc1a057a9162c5daa3 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Sat, 3 Aug 2024 22:49:20 +0200 Subject: [PATCH 175/240] Layout refinement of pagination --- app/templates/app/_pagination.html | 49 ++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/app/templates/app/_pagination.html b/app/templates/app/_pagination.html index 2d71c847..b1112138 100644 --- a/app/templates/app/_pagination.html +++ b/app/templates/app/_pagination.html @@ -1,44 +1,61 @@ {% load i18n %} {% if page_obj.has_previous or page_obj.has_next %} - {% endblock header %} + +{% block error %} +{# Render the error block hidden, so that it can be updated on the front-end. #} + +{% endblock error %} + {% endspaceless %} -
    {% block content %} diff --git a/app/templates/base_error.html b/app/templates/base_error.html index 3bd861f9..1a18e526 100644 --- a/app/templates/base_error.html +++ b/app/templates/base_error.html @@ -4,17 +4,8 @@ {% block title %}{% trans "Error" %}{% endblock %} -{% block content %} - -
    -
    - {% trans "Error" %} -
    -
    -

    {% block error_title %}Error{% endblock %}

    -

    {% block error_message %}An error occured{% endblock %}

    - {% trans "Go to home" %} -
    -
    - -{% endblock content %} +{% block error %} +{% with show_error=True%} + {{ block.super }} +{% endwith %} +{% endblock %} diff --git a/app/templates/base_htmx.html b/app/templates/base_htmx.html index b156b42a..ed8628cf 100644 --- a/app/templates/base_htmx.html +++ b/app/templates/base_htmx.html @@ -5,9 +5,22 @@ {% block title %}{% trans "SADiLaR" %}{% endblock %} -{# The marking of the active/current page is done outside
    , so we repeat it here: #} +{% comment %} +Some elements outside of
    might need to be updated. + - Navbar with indication of active/current page. + - Error messages +These use hx-swap-oob so that they are swapped in from the HTMX response. +{% endcomment %} + {% include "app/_navbar_items.html" %} +{% block error %} + {% if show_error %} +

    {% block error_title %}{% endblock %}

    +

    {% block error_message %}{% endblock %}

    + {% endif %} +{% endblock %} + {% endspaceless %}
    {% block content %}{% endblock content %}
    {% spaceless %} From c3697547857641f01f1233fcb2443128c38c95e9 Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Wed, 7 Aug 2024 15:01:47 +0200 Subject: [PATCH 187/240] Avoid unnecessary spaces in HTML output --- app/templates/app/_pagination.html | 2 ++ app/templates/app/_search_filter.html | 5 +---- app/templates/app/home.html | 2 ++ app/templates/app/search.html | 13 +++++++++++++ 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/app/templates/app/_pagination.html b/app/templates/app/_pagination.html index b1112138..a8fcf67c 100644 --- a/app/templates/app/_pagination.html +++ b/app/templates/app/_pagination.html @@ -1,4 +1,5 @@ {% load i18n %} +{% spaceless %} {% if page_obj.has_previous or page_obj.has_next %} {% endif %} +{% endspaceless %} diff --git a/app/templates/app/_search_filter.html b/app/templates/app/_search_filter.html index b3c0ff4d..1330e48b 100644 --- a/app/templates/app/_search_filter.html +++ b/app/templates/app/_search_filter.html @@ -3,10 +3,7 @@

    {{ field.label }}

    {% for checkbox in field %}
    - +
    {% endfor %}
    diff --git a/app/templates/app/home.html b/app/templates/app/home.html index 74f37a4b..96bd822d 100644 --- a/app/templates/app/home.html +++ b/app/templates/app/home.html @@ -3,6 +3,7 @@ {% load i18n %} {% block content %} +{% spaceless %}
    {% trans "Welcome" %}
    @@ -87,4 +88,5 @@
    {% trans "Explore" %}
    +{% endspaceless %} {% endblock content %} diff --git a/app/templates/app/search.html b/app/templates/app/search.html index 24c62603..e6bbdb22 100644 --- a/app/templates/app/search.html +++ b/app/templates/app/search.html @@ -1,4 +1,13 @@ {% extends BASE_TEMPLATE %} +{% comment %} + +This page is bigger due to forms, etc. "{% spaceless %}" reduces unnecessary +spaces in a few places, but there are places where it can't be used. The spaces +in the headline can be relevant, e.g. term1 term2. + +The spaceless tags are kept on the left since they don't take part in the HTML +structure. +{% endcomment %} {% load static %} {% load i18n %} @@ -29,10 +38,12 @@

    {% trans "Search a term" %}

    {% endif %}
    +{% endspaceless %} {# Results #}
    {% for result in page_obj %} +{% spaceless %}

    @@ -45,6 +56,7 @@

    {{ result.associated_url }}

    {{ result.description|truncatewords:20 }}

    +{% endspaceless %} {% if result.search_headline.strip %}

    … {{ result.search_headline|safe }} …

    @@ -72,6 +84,7 @@

    {# Filters #} +{% spaceless %}
    {# target for link at the top #}

    {% trans "Filter results" %}

    From 6b40e91219329c051861e0aed8b8f046c9386adb Mon Sep 17 00:00:00 2001 From: Friedel Wolff Date: Wed, 7 Aug 2024 15:06:54 +0200 Subject: [PATCH 188/240] Improve reliability of back-forward after HTMX requests This seems to work well now, with the exception of Django (debug) error pages and the debug toolbar. This might be related to https://github.com/bigskysoftware/htmx/issues/854 --- app/templates/base.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/templates/base.html b/app/templates/base.html index c778f9f9..aee213b3 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -11,11 +11,13 @@ + {# htmx JS at the end of body seems to create problems when navigating through browser history #} + - +
    {% block header %}

    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%0D1QNe!zzSlvC_XxVUqZ?a;E|vIK#%Pi`Aqk0 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¨&o&@MBw>wX(r8ocE_WMn4ff)B=6W` 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