Skip to content

Commit

Permalink
updates
Browse files Browse the repository at this point in the history
  • Loading branch information
mjhea0 committed Nov 29, 2023
1 parent a1bb51a commit 2001c53
Show file tree
Hide file tree
Showing 312 changed files with 32,515 additions and 47 deletions.
7 changes: 7 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
venv
.dockerignore
Dockerfile
.git
.gitignore
.pytest_cache
.github
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,5 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

cypress.env.json
78 changes: 78 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
###########
# BUILDER #
###########

# pull official base image
FROM python:3.12-slim-bookworm as builder

# install system dependencies
RUN apt-get update \
&& apt-get -y install g++ ca-certificates curl gnupg \
&& apt-get clean

# install node
ENV NODE_MAJOR=20
RUN mkdir -p /etc/apt/keyrings && \
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && \
apt-get update && apt-get install nodejs -y

# set work directory
WORKDIR /usr/src/app

# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# install python dependencies
COPY . .
RUN pip install --upgrade pip && pip wheel --no-cache-dir --wheel-dir /usr/src/app/wheels -r requirements.txt

# Build Tailwind
RUN pip install -r requirements.txt && python manage.py tailwind install && python manage.py tailwind build && python manage.py collectstatic --noinput

#########
# FINAL #
#########

# pull official base image
FROM python:3.12-slim-bookworm

# upgrade system packages
RUN apt-get update && apt-get upgrade -y && apt-get clean

# create directory for the app user
RUN mkdir -p /home/app

# create the app user
RUN addgroup --system app && adduser --system --group app

# create the appropriate directories
ENV HOME=/home/app
ENV APP_HOME=/home/app/web
RUN mkdir $APP_HOME
WORKDIR $APP_HOME

# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV ENVIRONMENT prod
ENV TESTING 0
ENV PYTHONPATH $APP_HOME

# install dependencies
COPY --from=builder /usr/src/app/wheels /wheels
COPY --from=builder /usr/src/app/requirements.txt .
COPY --from=builder /usr/src/app/staticfiles $APP_HOME/staticfiles
RUN pip install --upgrade pip
RUN pip install --no-cache /wheels/*

# copy project
COPY . $APP_HOME

# chown all the files to the app user
RUN chown -R app:app $HOME
# change to the app user
USER app
# serve the application
CMD gunicorn core.wsgi:application --bind 0.0.0.0:$PORT
67 changes: 54 additions & 13 deletions core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,29 @@
https://docs.djangoproject.com/en/4.0/ref/settings/
"""

import os
from pathlib import Path

import dj_database_url

# 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/4.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-%hussx3!p=8@%iej1k_7vxd*!6acbv7ln93_+_2ia8b-becqc1'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
SECRET_KEY = os.environ.get("SECRET_KEY", default="hussx3!p=8@%iej1k_7vxd*!6acbv7ln93_+_2ia8kb-becqc1")
DEBUG = int(os.environ.get("DEBUG", default=0))
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", default="127.0.0.1 localhost [::1]").split(" ")

ALLOWED_HOSTS = []
CSRF_TRUSTED_ORIGINS = os.environ.get("CSRF_TRUSTED_ORIGINS", default="http://127.0.0.1:8000 http://localhost:8000").split(" ")

SESSION_COOKIE_SECURE = True # ensures cookie is only sent under an HTTPS connection
CSRF_COOKIE_SECURE = True # ensures CSRF cookie is only sent under an HTTPS connection
SECURE_HSTS_SECONDS = 604800 # determines how long browsers should remember that your site should only be accessed using HTTPS
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") # signifies a request is secure despite using proxy
ACCOUNT_DEFAULT_HTTP_PROTOCOL = "https" # django-allauth's default protocol for generating URLs

# Application definition

Expand All @@ -37,21 +43,31 @@
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"django.contrib.sites",
"allauth",
"allauth.account",
"allauth.socialaccount",
"allauth.socialaccount.providers.google",
"allauth.socialaccount.providers.facebook",
"tailwind",
"theme",
"django_browser_reload",
"crispy_forms",
"crispy_bootstrap4",
"party",
]

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",
"whitenoise.middleware.WhiteNoiseMiddleware",
"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_browser_reload.middleware.BrowserReloadMiddleware",
"allauth.account.middleware.AccountMiddleware",
]

ROOT_URLCONF = 'core.urls'
Expand Down Expand Up @@ -85,6 +101,8 @@
}
}

DATABASES["default"] = dj_database_url.config(default="sqlite:///db.sqlite3")


# Password validation
# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators
Expand Down Expand Up @@ -121,6 +139,13 @@
# https://docs.djangoproject.com/en/4.0/howto/static-files/

STATIC_URL = 'static/'
STATIC_ROOT = BASE_DIR / "staticfiles"

STORAGES = {
"staticfiles": {
"BACKEND": "whitenoise.storage.CompressedStaticFilesStorage",
},
}

# Default primary key field type
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
Expand All @@ -134,3 +159,19 @@
INTERNAL_IPS = [
"127.0.0.1",
]

CRISPY_TEMPLATE_PACK = "bootstrap4"

LOGIN_REDIRECT_URL = "page_party_list" # where to redirect after login
LOGIN_URL = 'party_login' # where to redirect when login is required to access a view


AUTHENTICATION_BACKENDS = (
"allauth.account.auth_backends.AuthenticationBackend",
)

SITE_ID = 1 # needs to match the Site ID in the admin
ACCOUNT_EMAIL_VERIFICATION = "none" # no email verification needed
SOCIALACCOUNT_LOGIN_ON_GET = True # skip additional confirm page, less secure
ACCOUNT_LOGOUT_ON_GET = True # skip the confirm logout page
ACCOUNT_UNIQUE_EMAIL = True
1 change: 1 addition & 0 deletions core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
path("admin/", admin.site.urls),
path("__reload__/", include("django_browser_reload.urls")),
path("", include("party.urls")),
path("accounts/", include("allauth.urls")),
]
11 changes: 11 additions & 0 deletions cypress.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const {defineConfig} = require('cypress');

module.exports = defineConfig({
e2e: {
baseUrl: 'http://127.0.0.1:8000/',
env: {
'username': '',
'password': ''
}
},
});
98 changes: 98 additions & 0 deletions cypress/e2e/e2e.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
function fillAndSubmitPartyForm(party) {
cy.get('input[name="party_date"]').clear().type(party.party_date);
cy.get('input[name="party_time"]').clear().type(party.party_time);
cy.get('input[name="venue"]').clear().type(party.venue);
cy.get('textarea[name="invitation"]').clear().type(party.invitation);
cy.get('form').submit();
}

function partyExistsOnPage(party) {
cy.get('#party-invitation').should('contain', party.venue);
cy.get('#party-invitation').should('contain', party.invitation);
}

function fillAndSubmitGiftForm(gift) {
cy.get('input[name="gift"]').clear().type(gift.gift);
cy.get('input[name="price"]').clear().type(gift.price);
cy.get('input[name="link"]').clear().type(gift.gift_link);
cy.get('form').submit();
}

function giftExistsOnPage(gift) {
cy.get('#gift-registry').should('contain', gift.gift);
cy.get('#gift-registry').should('contain', gift.price);
cy.get('#gift-registry').should('contain', gift.gift_link);
}

function giftNotExistsOnPage(gift) {
cy.get('#gift-registry').should('not.contain', gift.gift);
cy.get('#gift-registry').should('not.contain', gift.price);
cy.get('#gift-registry').should('not.contain', gift.gift_link);
}


function create_party() {
cy.visit('/');
cy.get('[data-cy="new-party-link"]').click();

cy.fixture('party.json').then((party) => {
fillAndSubmitPartyForm(party);
cy.url().should('match', /\/party\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/$/);
partyExistsOnPage(party);
});
}

function edit_party() {
cy.get('[data-cy="edit-party-button"]').click();
cy.fixture('updated_party.json').then((updated_party) => {
fillAndSubmitPartyForm(updated_party);
partyExistsOnPage(updated_party);
cy.get('form').should('not.exist');
});
}

function add_gift() {
cy.visit('/');
cy.get('[data-cy="gift-registry-link"]').first().click();

cy.get('[data-cy="add-gift-button"]').click();
cy.fixture('gift.json').then((gift) => {
fillAndSubmitGiftForm(gift);
cy.get('form').should('not.exist');
giftExistsOnPage(gift);
});
}

function edit_gift() {
cy.get('[data-cy="edit-gift-button"]').first().click();
cy.fixture('updated_gift.json').then((updated_gift) => {
fillAndSubmitGiftForm(updated_gift);

giftExistsOnPage(updated_gift);
cy.get('form').should('not.exist');
});
}

function delete_gift() {
cy.get('[data-cy="delete-gift-button"]').first().click();
cy.get('[data-cy="gift-removed-alert"]').should('be.visible');

cy.fixture('updated_gift.json').then((updated_gift) => {
giftNotExistsOnPage(updated_gift);
});
}


describe('Logged in user creating and managing a party', () => {
before(() => {
cy.login();
});

it('Performs a complete party and gift registry workflow', function () {
create_party();
edit_party();
add_gift();
edit_gift();
delete_gift();
});
});
5 changes: 5 additions & 0 deletions cypress/fixtures/example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "[email protected]",
"body": "Fixtures are a great way to mock data for responses to routes"
}
5 changes: 5 additions & 0 deletions cypress/fixtures/gift.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"gift": "Chocolates",
"price": "12.25",
"gift_link": "https://testlink.org/"
}
6 changes: 6 additions & 0 deletions cypress/fixtures/party.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"party_date": "2028-08-26",
"party_time": "12:00",
"venue": "the party place",
"invitation": "Come to my awesome party!"
}
5 changes: 5 additions & 0 deletions cypress/fixtures/updated_gift.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"gift": "Better chocolates",
"price": "20.25",
"gift_link": "https://updatedtestlink.org/"
}
6 changes: 6 additions & 0 deletions cypress/fixtures/updated_party.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"party_date": "2030-01-01",
"party_time": "20:00",
"venue": "Updated venue",
"invitation": "Updated invitation"
}
17 changes: 17 additions & 0 deletions cypress/support/commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Cypress.Commands.add('login', () => {
cy.session("logged-in-user", () => {
const username = Cypress.env('username');
const password = Cypress.env('password');

cy.visit('/login/');
cy.get('input[name="username"]').type(username);
cy.get('input[name="password"]').type(password);
cy.get('form').submit();
}, {
validate() {
cy.document()
.its('cookie')
.should('contain', 'csrftoken');
}
});
});
20 changes: 20 additions & 0 deletions cypress/support/e2e.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// ***********************************************************
// This example support/e2e.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************

// Import commands.js using ES2015 syntax:
import './commands'

// Alternatively you can use CommonJS syntax:
// require('./commands')
Loading

0 comments on commit 2001c53

Please sign in to comment.