Skip to content

Commit

Permalink
Merge pull request #62 from i-dot-ai/feature/add-django-app-2
Browse files Browse the repository at this point in the history
Added initial django app
  • Loading branch information
252afh authored Mar 7, 2024
2 parents 88d5a75 + 3ad4d18 commit 14571b1
Show file tree
Hide file tree
Showing 80 changed files with 11,662 additions and 294 deletions.
27 changes: 27 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# https://gds-way.digital.cabinet-office.gov.uk/manuals/programming-languages/editorconfig

root = true

[*.html]
indent_size = 2
indent_style = space
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.css]
indent_size = 2
indent_style = space
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.py]
indent_size = 4
indent_style = space
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
11 changes: 11 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,14 @@ RABBITMQ_HOST=rabbitmq
RABBITMQ_PORT=5672
RABBITMQ_USER=guest
RABBITMQ_PASSWORD=guest

# === Django App ===
DJANGO_SETTINGS_MODULE=redbox_app.settings
DEBUG=True
DJANGO_SECRET_KEY=1n53cur3K3y
ENVIRONMENT=LOCAL
POSTGRES_USER=redbox-core
POSTGRES_DB=redbox-core
POSTGRES_PASSWORD=insecure
CONTACT_EMAIL=test@example.com
POSTGRES_HOST=db
11 changes: 11 additions & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,14 @@ RABBITMQ_HOST=localhost
RABBITMQ_PORT=5672
RABBITMQ_USER=guest
RABBITMQ_PASSWORD=guest

# === Django App ===
DJANGO_SETTINGS_MODULE=redbox_app.settings
DEBUG=True
DJANGO_SECRET_KEY=1n53cur3K3y
ENVIRONMENT=LOCAL
POSTGRES_USER=redbox-core
POSTGRES_DB=redbox-core
POSTGRES_PASSWORD=insecure
CONTACT_EMAIL=test@example.com
POSTGRES_HOST=db
52 changes: 52 additions & 0 deletions .github/workflows/django-checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Django

env:
DOCKER_BUILDKIT: 1

on:
push:
branches:
- 'main'
- 'feature/**'
- 'chore/**'
- 'bugfix/**'
- 'hotfix/**'
- 'develop'
workflow_dispatch:


jobs:

check_migrations:
name: Check migrations

runs-on: ubuntu-latest
environment: release

steps:
- uses: actions/checkout@v3

- name: Copy env file
run: |
cp .env.example .env
- name: Run migrations
run: |
make check-migrations
run_tests:
name: Run tests

runs-on: ubuntu-latest
environment: release

steps:
- uses: actions/checkout@v3

- name: Copy env file
run: |
cp .env.example .env
- name: Run tests
run: |
make test-django
1 change: 0 additions & 1 deletion .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ on:
- 'develop'
workflow_dispatch:


permissions:
contents: read

Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,7 @@ cython_debug/
#.idea/

.terraform

# Django app
*.pyc
django_app/staticfiles/*
21 changes: 20 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ app:
poetry run streamlit run legacy_app/Welcome.py --server.port 8501

run:
docker compose up -d elasticsearch kibana app embed minio miniocreatebuckets rabbitmq core-api
docker compose up -d elasticsearch kibana app embed minio miniocreatebuckets rabbitmq core-api db django-app

stop:
docker compose down
Expand All @@ -34,6 +34,10 @@ test-redbox:
poetry install --no-root --no-ansi --with worker,api,dev --without embed,ai,streamlit-app,ingest
poetry run pytest redbox/tests --cov=redbox -v --cov-report=term-missing --cov-fail-under=45

test-django:
docker-compose up -d db
docker-compose run django-app poetry run pytest django_app/tests/ --ds redbox_app.settings -v --cov=redbox_app.redbox_core --cov-fail-under 10

lint:
poetry run ruff check .

Expand All @@ -42,6 +46,21 @@ format:
# additionally we format, but not lint, the notebooks
# poetry run ruff format **/*.ipynb

safe:
poetry run bandit -ll -r ./redbox
poetry run bandit -ll -r ./django_app
poetry run mypy ./redbox --ignore-missing-imports
poetry run mypy ./django_app --ignore-missing-imports

checktypes:
poetry run mypy redbox embed ingest --ignore-missing-imports
# poetry run mypy legacy_app --follow-imports skip --ignore-missing-imports

check-migrations:
docker-compose build django-app
docker-compose run django-app poetry run python django_app/manage.py migrate
docker-compose run django-app poetry run python django_app/manage.py makemigrations --check

reset-db:
docker-compose down db --volumes
docker-compose up -d db
17 changes: 17 additions & 0 deletions django_app/docker/web/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
FROM python:3.11
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

RUN pip install poetry

COPY django_app/ /app/
COPY poetry.lock /app/poetry.lock
COPY pyproject.toml /app/pyproject.toml
WORKDIR app/
RUN poetry install --no-root --no-ansi --with django-app,dev,pytest-django --without ai,ingest,embed,worker

COPY django_app/docker/web/start.sh app/start.sh
RUN chmod +x app/start.sh

EXPOSE 8090
CMD ["sh", "django_app/docker/web/start.sh"]
8 changes: 8 additions & 0 deletions django_app/docker/web/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/sh

set -o errexit
set -o nounset

poetry run python django_app/manage.py migrate
poetry run python django_app/manage.py collectstatic --noinput
poetry run python django_app/manage.py runserver 0.0.0.0:8090
22 changes: 22 additions & 0 deletions django_app/manage.py
Original file line number Diff line number Diff line change
@@ -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", "redbox_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()
Empty file.
16 changes: 16 additions & 0 deletions django_app/redbox_app/asgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
ASGI config for redbox copilot 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/3.2/howto/deployment/asgi/
"""

import os

from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "redbox_app.settings")

application = get_asgi_application()
47 changes: 47 additions & 0 deletions django_app/redbox_app/custom_password_validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import re
import string

from django.core.exceptions import ValidationError

from redbox_app.redbox_core.constants import BUSINESS_SPECIFIC_WORDS


class SpecialCharacterValidator:
msg = "The password must contain at least one special character."

def validate(self, password, user=None):
special_characters = string.punctuation

if not any(char in special_characters for char in password):
raise ValidationError(self.msg)

def get_help_text(self):
return self.msg


class LowercaseUppercaseValidator:
msg = "The password must contain at least one lowercase character and one uppercase character."

def validate(self, password, user=None):
contains_lowercase = any(char.islower() for char in password)
contains_uppercase = any(char.isupper() for char in password)

if (not contains_lowercase) or (not contains_uppercase):
raise ValidationError(self.msg)

def get_help_text(self):
return self.msg


class BusinessPhraseSimilarityValidator:
msg = "The password should not contain business specific words."

def validate(self, password, user=None):
password_lower = password.lower()
for phrase in BUSINESS_SPECIFIC_WORDS:
phrase_no_space = phrase.replace(" ", "")
phrase_underscore = phrase.replace(" ", "_")
phrase_dash = phrase.replace(" ", "-")
search_phrase = "|".join([phrase_no_space, phrase_underscore, phrase_dash])
if re.search(search_phrase, password_lower):
raise ValidationError(self.msg)
36 changes: 36 additions & 0 deletions django_app/redbox_app/hosting_environment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import ast
import os
import subprocess

import environ

env = environ.Env()


class HostingEnvironment:
@staticmethod
def is_beanstalk() -> bool:
"""is this application deployed to AWS Elastic Beanstalk?"""
return os.path.exists("/opt/elasticbeanstalk")

@staticmethod
def get_beanstalk_environ_vars() -> dict:
"""get env vars from ec2
https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/custom-platforms-scripts.html
"""
completed_process = subprocess.run(
["/opt/elasticbeanstalk/bin/get-config", "environment"],
stdout=subprocess.PIPE,
text=True,
check=True,
)

return ast.literal_eval(completed_process.stdout)

@staticmethod
def is_local() -> bool:
return env.str("ENVIRONMENT", "").upper() == "LOCAL"

@staticmethod
def is_test() -> bool:
return env.str("ENVIRONMENT", "").upper() == "TEST"
64 changes: 64 additions & 0 deletions django_app/redbox_app/jinja2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import datetime

import humanize

import jinja2
from django.templatetags.static import static
from django.urls import reverse
from markdown_it import MarkdownIt

# `js-default` setting required to sanitize inputs
# https://markdown-it-py.readthedocs.io/en/latest/security.html
markdown_converter = MarkdownIt("js-default")


def url(path, *args, **kwargs):
assert not (args and kwargs)
return reverse(path, args=args, kwargs=kwargs)


def markdown(text, cls=None):
"""
Converts the given text into markdown.
The `replace` statement replaces the outer <p> tag with one that contains the given class, otherwise the markdown
ends up double wrapped with <p> tags.
Args:
text: The text to convert to markdown
cls (optional): The class to apply to the outermost <p> tag surrounding the markdown
Returns:
Text converted to markdown
"""
html = markdown_converter.render(text).strip()
html = html.replace("<p>", f'<p class="{cls or ""}">', 1).replace("</p>", "", 1)
return html


def humanize_timedelta(minutes=0, hours_limit=200, too_large_msg=""):
if minutes > (hours_limit * 60):
if not too_large_msg:
return f"More than {hours_limit} hours"
else:
return too_large_msg
else:
delta = datetime.timedelta(minutes=minutes)
return humanize.precisedelta(delta, minimum_unit="minutes")


def environment(**options):
extra_options = dict()
env = jinja2.Environment( # nosec B701
**{
"autoescape": True,
**options,
**extra_options,
}
)
env.globals.update(
{
"static": static,
"url": url,
"humanize_timedelta": humanize_timedelta,
}
)
return env
Empty file.
6 changes: 6 additions & 0 deletions django_app/redbox_app/redbox_core/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.contrib import admin

from . import models


admin.site.register(models.User)
Loading

0 comments on commit 14571b1

Please sign in to comment.