forked from data.coop/membersystem
Compare commits
73 commits
feature/al
...
main
7
.dockerignore
Normal file
7
.dockerignore
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
*
|
||||||
|
.*
|
||||||
|
*/.*
|
||||||
|
|
||||||
|
!src/
|
||||||
|
!requirements/
|
||||||
|
!entrypoint.sh
|
27
.drone.yml
Normal file
27
.drone.yml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: default
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: docker
|
||||||
|
image: plugins/docker
|
||||||
|
environment:
|
||||||
|
DJANGO_ENV: production
|
||||||
|
BUILD: "${DRONE_COMMIT_SHA}"
|
||||||
|
settings:
|
||||||
|
repo: docker.data.coop/membersystem
|
||||||
|
registry: docker.data.coop
|
||||||
|
username:
|
||||||
|
from_secret: DOCKER_USERNAME
|
||||||
|
password:
|
||||||
|
from_secret: DOCKER_PASSWORD
|
||||||
|
build_args_from_env:
|
||||||
|
- DJANGO_ENV
|
||||||
|
- BUILD
|
||||||
|
tags:
|
||||||
|
- "${DRONE_BUILD_NUMBER}"
|
||||||
|
- "latest"
|
||||||
|
when:
|
||||||
|
branch:
|
||||||
|
- main
|
9
.env.example
Normal file
9
.env.example
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
SECRET_KEY=something-very-random
|
||||||
|
POSTGRES_HOST=postgres
|
||||||
|
POSTGRES_PASSWORD=postgres
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
DATABASE_URL=postgres://postgres:postgres@postgres:5432/postgres
|
||||||
|
# Use something along the the following if you are not using docker
|
||||||
|
# DATABASE_URL=postgres://postgres:postgres@localhost:5432/datacoop_membersystem
|
||||||
|
DEBUG=True
|
||||||
|
DJANGO_ENV=development
|
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -2,5 +2,9 @@ __pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
*.sw*
|
*.sw*
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
project/settings/local.py
|
|
||||||
.pytest_cache
|
.pytest_cache
|
||||||
|
.idea/
|
||||||
|
*.mo
|
||||||
|
.env
|
||||||
|
venv/
|
||||||
|
.venv/
|
||||||
|
|
|
@ -1,23 +1,60 @@
|
||||||
|
default_language_version:
|
||||||
|
python: python3.11
|
||||||
|
exclude: ^.*\b(migrations)\b.*$
|
||||||
repos:
|
repos:
|
||||||
- repo: git://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v2.3.0
|
rev: v4.5.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: trailing-whitespace
|
- id: check-ast
|
||||||
- id: flake8
|
- id: check-merge-conflict
|
||||||
args: [--max-line-length=120, --exclude=*/migrations/*]
|
- id: check-case-conflict
|
||||||
- id: check-yaml
|
- id: detect-private-key
|
||||||
- id: check-added-large-files
|
- id: check-added-large-files
|
||||||
- id: debug-statements
|
- id: check-json
|
||||||
- id: end-of-file-fixer
|
- id: check-symlinks
|
||||||
- id: check-toml
|
- id: check-toml
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: trailing-whitespace
|
||||||
|
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||||
|
rev: 'v0.1.11'
|
||||||
|
hooks:
|
||||||
|
- id: ruff
|
||||||
|
args:
|
||||||
|
- --fix
|
||||||
- repo: https://github.com/asottile/reorder_python_imports
|
- repo: https://github.com/asottile/reorder_python_imports
|
||||||
rev: v1.6.1
|
rev: v3.12.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: reorder-python-imports
|
- id: reorder-python-imports
|
||||||
types: [file, python]
|
args:
|
||||||
|
- --py310-plus
|
||||||
|
- --application-directories=.:src
|
||||||
|
exclude: migrations/
|
||||||
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
|
rev: v3.15.0
|
||||||
|
hooks:
|
||||||
|
- id: pyupgrade
|
||||||
|
args:
|
||||||
|
- --py311-plus
|
||||||
|
exclude: migrations/
|
||||||
|
- repo: https://github.com/adamchainz/django-upgrade
|
||||||
|
rev: 1.15.0
|
||||||
|
hooks:
|
||||||
|
- id: django-upgrade
|
||||||
|
args:
|
||||||
|
- --target-version=4.1
|
||||||
|
- repo: https://github.com/asottile/yesqa
|
||||||
|
rev: v1.5.0
|
||||||
|
hooks:
|
||||||
|
- id: yesqa
|
||||||
|
- repo: https://github.com/asottile/add-trailing-comma
|
||||||
|
rev: v3.1.0
|
||||||
|
hooks:
|
||||||
|
- id: add-trailing-comma
|
||||||
|
- repo: https://github.com/hadialqattan/pycln
|
||||||
|
rev: v2.4.0
|
||||||
|
hooks:
|
||||||
|
- id: pycln
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: stable
|
rev: 23.12.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
language: python
|
|
||||||
types: [file, python]
|
|
||||||
|
|
40
Dockerfile
Normal file
40
Dockerfile
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
FROM python:3.11-slim-bullseye
|
||||||
|
|
||||||
|
ENV PYTHONFAULTHANDLER=1 \
|
||||||
|
PYTHONUNBUFFERED=1 \
|
||||||
|
PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
PYTHONHASHSEED=random \
|
||||||
|
PIP_NO_CACHE_DIR=off \
|
||||||
|
PIP_DISABLE_PIP_VERSION_CHECK=on \
|
||||||
|
PIP_DEFAULT_TIMEOUT=100
|
||||||
|
ARG DJANGO_ENV
|
||||||
|
ARG BUILD
|
||||||
|
ENV BUILD ${BUILD}
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y \
|
||||||
|
binutils \
|
||||||
|
libpq-dev \
|
||||||
|
build-essential \
|
||||||
|
netcat-openbsd \
|
||||||
|
libcairo2 \
|
||||||
|
libpango-1.0-0 \
|
||||||
|
libpangocairo-1.0-0 \
|
||||||
|
libgdk-pixbuf2.0-0 \
|
||||||
|
libffi-dev \
|
||||||
|
shared-mime-info \
|
||||||
|
gettext
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN groupadd -g 1000 www && useradd -u 1000 -ms /bin/bash -g www www
|
||||||
|
COPY --chown=www:www . /app/
|
||||||
|
RUN mkdir /app/src/static && chown www:www /app/src/static
|
||||||
|
RUN pip install -r requirements/$([ "$DJANGO_ENV" = "production" ] && echo "base.txt" || echo "dev.txt") &&\
|
||||||
|
django-admin compilemessages
|
||||||
|
|
||||||
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
CMD ["uvicorn", "project.asgi:application", "--host", "0.0.0.0", "--port", "8000", "--workers", "3", "--lifespan", "off", "--app-dir", "/app/src"]
|
65
Makefile
65
Makefile
|
@ -1,13 +1,58 @@
|
||||||
# These are just some make targets, expressing how things
|
DOCKER_COMPOSE = COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker compose
|
||||||
# are supposed to be run, but feel free to change them!
|
DOCKER_RUN = ${DOCKER_COMPOSE} run -u `id -u`
|
||||||
|
DOCKER_BUILD = DOCKER_BUILDKIT=1 docker build
|
||||||
|
DOCKER_CONTAINER_NAME = backend
|
||||||
|
MANAGE_EXEC = python /app/src/manage.py
|
||||||
|
MANAGE_COMMAND = ${DOCKER_RUN} ${DOCKER_CONTAINER_NAME} ${MANAGE_EXEC}
|
||||||
|
|
||||||
dev-setup:
|
init: setup_venv pre_commit_install migrate
|
||||||
poetry run pre-commit install
|
|
||||||
poetry run python manage.py migrate
|
|
||||||
poetry run python manage.py createsuperuser
|
|
||||||
|
|
||||||
lint:
|
run:
|
||||||
poetry run pre-commit run --all
|
${DOCKER_COMPOSE} up
|
||||||
|
|
||||||
test:
|
setup_venv:
|
||||||
poetry run pytest
|
rm -rf venv
|
||||||
|
python3.11 -m venv venv;
|
||||||
|
venv/bin/python -m pip install wheel setuptools;
|
||||||
|
venv/bin/python -m pip install pre-commit boto3 pip-tools;
|
||||||
|
|
||||||
|
pre_commit_install:
|
||||||
|
venv/bin/pre-commit install
|
||||||
|
|
||||||
|
pre_commit_run_all:
|
||||||
|
venv/bin/pre-commit run --all-files
|
||||||
|
|
||||||
|
makemigrations:
|
||||||
|
${MANAGE_COMMAND} makemigrations ${ARGS}
|
||||||
|
|
||||||
|
migrate:
|
||||||
|
${MANAGE_COMMAND} migrate ${ARGS}
|
||||||
|
|
||||||
|
createsuperuser:
|
||||||
|
${MANAGE_COMMAND} createsuperuser
|
||||||
|
|
||||||
|
shell:
|
||||||
|
${MANAGE_COMMAND} shell
|
||||||
|
|
||||||
|
manage_command:
|
||||||
|
${MANAGE_COMMAND} ${ARGS}
|
||||||
|
|
||||||
|
add_dependency:
|
||||||
|
${DOCKER_RUN} ${DOCKER_CONTAINER_NAME} poetry add --lock ${DEPENDENCY}
|
||||||
|
|
||||||
|
add_dev_dependency:
|
||||||
|
${DOCKER_RUN} ${DOCKER_CONTAINER_NAME} poetry add -D --lock ${DEPENDENCY}
|
||||||
|
|
||||||
|
poetry_lock:
|
||||||
|
${DOCKER_RUN} ${DOCKER_CONTAINER_NAME} poetry lock --no-update
|
||||||
|
|
||||||
|
poetry_command:
|
||||||
|
${DOCKER_RUN} ${DOCKER_CONTAINER_NAME} poetry ${COMMAND}
|
||||||
|
|
||||||
|
build_dev_docker_image: compile_requirements
|
||||||
|
${DOCKER_COMPOSE} build ${DOCKER_CONTAINER_NAME}
|
||||||
|
|
||||||
|
compile_requirements:
|
||||||
|
./venv/bin/pip-compile --output-file requirements/base.txt requirements/base.in
|
||||||
|
./venv/bin/pip-compile --output-file requirements/test.txt requirements/test.in
|
||||||
|
./venv/bin/pip-compile --output-file requirements/dev.txt requirements/dev.in
|
||||||
|
|
69
README.md
69
README.md
|
@ -1,24 +1,71 @@
|
||||||
# member.data.coop
|
# member.data.coop
|
||||||
|
|
||||||
To start developing:
|
## Development
|
||||||
|
|
||||||
Get poetry
|
### Setup environment
|
||||||
|
|
||||||
$ python3 -m pip install --user pipx
|
Copy over the .env.example file to .env and adjust DATABASE_URL accordingly
|
||||||
$ pipx install poetry
|
|
||||||
|
|
||||||
Run poetry to setup environment
|
$ cp .env.example .env
|
||||||
|
|
||||||
$ poetry install
|
### Docker
|
||||||
|
|
||||||
Run this make target, which installs all the requirements and sets up a development database.
|
#### Requirements
|
||||||
|
|
||||||
$ make dev-setup
|
- Docker
|
||||||
|
- Docker compose
|
||||||
|
- pre-commit (preferred for contributions)
|
||||||
|
|
||||||
To run the Django development server:
|
#### Setup
|
||||||
|
|
||||||
$ poetry run python manage.py runserver
|
Given that the requirements above are installed, it should be as easy as:
|
||||||
|
|
||||||
Before you push your stuff, run tests:
|
$ make migrate
|
||||||
|
|
||||||
|
This will setup the database. Next run:
|
||||||
|
|
||||||
|
$ make run
|
||||||
|
|
||||||
|
This will build the docker image and start the member system on http://localhost:8000.
|
||||||
|
|
||||||
|
You can create a superuser by running:
|
||||||
|
|
||||||
|
$ make createsuperuser
|
||||||
|
|
||||||
|
Make migrations:
|
||||||
|
|
||||||
|
$ make makemigrations
|
||||||
|
|
||||||
|
Make messages:
|
||||||
|
|
||||||
|
$ make makemessages
|
||||||
|
|
||||||
|
Running tests:
|
||||||
|
|
||||||
$ make test
|
$ make test
|
||||||
|
|
||||||
|
### Non-docker
|
||||||
|
|
||||||
|
Create a venv
|
||||||
|
|
||||||
|
$ python3 -m venv venv
|
||||||
|
|
||||||
|
Activate the venv
|
||||||
|
|
||||||
|
$ source venv/bin/activate
|
||||||
|
|
||||||
|
Install requirements
|
||||||
|
|
||||||
|
$ pip install -r requirements/dev.txt
|
||||||
|
|
||||||
|
Run migrations
|
||||||
|
|
||||||
|
$ ./src/manage.py migrate
|
||||||
|
|
||||||
|
Create a superuser
|
||||||
|
|
||||||
|
$ ./src/manage.py createsuperuser
|
||||||
|
|
||||||
|
Run the server
|
||||||
|
|
||||||
|
$ ./src/manage.py runserver
|
||||||
|
|
30
docker-compose.yml
Normal file
30
docker-compose.yml
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
version: '3.7'
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
backend:
|
||||||
|
image: data_coop_membersystem:dev
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
command: python /app/src/manage.py runserver 0.0.0.0:8000
|
||||||
|
tty: true
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
volumes:
|
||||||
|
- ./:/app/
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: postgres:13-alpine
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data/
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
21
entrypoint.sh
Executable file
21
entrypoint.sh
Executable file
|
@ -0,0 +1,21 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
echo "Waiting for postgres..."
|
||||||
|
|
||||||
|
POSTGRES_PORT=${POSTGRES_PORT:-5432}
|
||||||
|
POSTGRES_HOST=${POSTGRES_HOST:-localhost}
|
||||||
|
|
||||||
|
while ! nc -z "$POSTGRES_HOST" "$POSTGRES_PORT"; do
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "PostgreSQL started"
|
||||||
|
|
||||||
|
# Only migrate, collectstatic and compilemessages if we are NOT in development
|
||||||
|
if [ -z "$DEBUG" ]; then
|
||||||
|
python src/manage.py migrate;
|
||||||
|
python src/manage.py collectstatic --no-input;
|
||||||
|
python src/manage.py compilemessages;
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$@"
|
|
@ -1,8 +0,0 @@
|
||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
from . import models
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Membership)
|
|
||||||
class MembershipAdmin(admin.ModelAdmin):
|
|
||||||
pass
|
|
|
@ -1,5 +0,0 @@
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class MembershipConfig(AppConfig):
|
|
||||||
name = "membership"
|
|
|
@ -1,227 +0,0 @@
|
||||||
# Generated by Django 2.0.6 on 2018-06-23 19:07
|
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import djmoney.models.fields
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="Membership",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.AutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"modified",
|
|
||||||
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"created",
|
|
||||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"can_vote",
|
|
||||||
models.BooleanField(
|
|
||||||
default=False,
|
|
||||||
help_text="Indicates that the user has a democratic membership of the organization.",
|
|
||||||
verbose_name="can vote",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"verbose_name": "membership",
|
|
||||||
"verbose_name_plural": "memberships",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="Organization",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.AutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"modified",
|
|
||||||
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"created",
|
|
||||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
|
||||||
),
|
|
||||||
("name", models.CharField(max_length=64, verbose_name="name")),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"verbose_name": "organization",
|
|
||||||
"verbose_name_plural": "organizations",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="Subscription",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.AutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"modified",
|
|
||||||
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"created",
|
|
||||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"active",
|
|
||||||
models.BooleanField(
|
|
||||||
default=False,
|
|
||||||
help_text="Automatically set by payment system.",
|
|
||||||
verbose_name="active",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("starts", models.DateField()),
|
|
||||||
("ends", models.DateField()),
|
|
||||||
(
|
|
||||||
"renewed_subscription",
|
|
||||||
models.ForeignKey(
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
|
||||||
to="membership.Subscription",
|
|
||||||
verbose_name="renewed subscription",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"verbose_name": "subscription",
|
|
||||||
"verbose_name_plural": "subscriptions",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="SubscriptionType",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.AutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"modified",
|
|
||||||
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"created",
|
|
||||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
|
||||||
),
|
|
||||||
("name", models.CharField(max_length=64, verbose_name="name")),
|
|
||||||
(
|
|
||||||
"fee_currency",
|
|
||||||
djmoney.models.fields.CurrencyField(
|
|
||||||
choices=[("DKK", "DKK")],
|
|
||||||
default="XYZ",
|
|
||||||
editable=False,
|
|
||||||
max_length=3,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"fee",
|
|
||||||
djmoney.models.fields.MoneyField(
|
|
||||||
decimal_places=2, default=Decimal("0.0"), max_digits=16
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"fee_vat_currency",
|
|
||||||
djmoney.models.fields.CurrencyField(
|
|
||||||
choices=[("DKK", "DKK")],
|
|
||||||
default="XYZ",
|
|
||||||
editable=False,
|
|
||||||
max_length=3,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"fee_vat",
|
|
||||||
djmoney.models.fields.MoneyField(
|
|
||||||
decimal_places=2, default=Decimal("0"), max_digits=16
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"duration",
|
|
||||||
models.PositiveSmallIntegerField(
|
|
||||||
choices=[(1, "annual")], default=1, verbose_name="duration"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"organization",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
|
||||||
to="membership.Organization",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"verbose_name": "subscription type",
|
|
||||||
"verbose_name_plural": "subscription types",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="subscription",
|
|
||||||
name="subscription_type",
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
|
||||||
related_name="memberships",
|
|
||||||
to="membership.SubscriptionType",
|
|
||||||
verbose_name="subscription type",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="subscription",
|
|
||||||
name="user",
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="membership",
|
|
||||||
name="organization",
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
|
||||||
to="membership.Organization",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="membership",
|
|
||||||
name="user",
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,123 +0,0 @@
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from django.db import models
|
|
||||||
from django.utils.translation import gettext as _
|
|
||||||
from djmoney.models.fields import MoneyField
|
|
||||||
|
|
||||||
|
|
||||||
class CreatedModifiedAbstract(models.Model):
|
|
||||||
|
|
||||||
modified = models.DateTimeField(auto_now=True, verbose_name=_("modified"))
|
|
||||||
created = models.DateTimeField(auto_now_add=True, verbose_name=_("created"))
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
|
|
||||||
class Organization(CreatedModifiedAbstract):
|
|
||||||
"""
|
|
||||||
This holds the data of the organization that someone is a member of. It is
|
|
||||||
possible that we'll create more advanced features here.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = models.CharField(verbose_name=_("name"), max_length=64)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("organization")
|
|
||||||
verbose_name_plural = _("organizations")
|
|
||||||
|
|
||||||
|
|
||||||
class Membership(CreatedModifiedAbstract):
|
|
||||||
"""
|
|
||||||
A user remains a member of an organization even though the subscription is
|
|
||||||
unpaid or renewed. This just changes the status/permissions etc. of the
|
|
||||||
membership, thus we need to track subscription creation, expiry, renewals
|
|
||||||
etc. and ensure that the membership is modified accordingly.
|
|
||||||
|
|
||||||
This expresses some
|
|
||||||
"""
|
|
||||||
|
|
||||||
organization = models.ForeignKey(Organization, on_delete=models.PROTECT)
|
|
||||||
user = models.ForeignKey(get_user_model(), on_delete=models.PROTECT)
|
|
||||||
|
|
||||||
can_vote = models.BooleanField(
|
|
||||||
default=False,
|
|
||||||
verbose_name=_("can vote"),
|
|
||||||
help_text=_(
|
|
||||||
"Indicates that the user has a democratic membership of the "
|
|
||||||
"organization."
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
|
|
||||||
return _("{} is a member of {}").format(
|
|
||||||
self.user.get_full_name(), self.organization.name
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("membership")
|
|
||||||
verbose_name_plural = _("memberships")
|
|
||||||
|
|
||||||
|
|
||||||
class SubscriptionType(CreatedModifiedAbstract):
|
|
||||||
"""
|
|
||||||
Properties of subscriptions are stored here. Should of course not be edited
|
|
||||||
after subscriptions are created.
|
|
||||||
"""
|
|
||||||
|
|
||||||
organization = models.ForeignKey(Organization, on_delete=models.PROTECT)
|
|
||||||
|
|
||||||
name = models.CharField(verbose_name=_("name"), max_length=64)
|
|
||||||
|
|
||||||
fee = MoneyField(max_digits=16, decimal_places=2)
|
|
||||||
|
|
||||||
fee_vat = MoneyField(max_digits=16, decimal_places=2, default=0)
|
|
||||||
|
|
||||||
duration = models.PositiveSmallIntegerField(
|
|
||||||
default=1, choices=[(1, _("annual"))], verbose_name=_("duration")
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("subscription type")
|
|
||||||
verbose_name_plural = _("subscription types")
|
|
||||||
|
|
||||||
|
|
||||||
class Subscription(CreatedModifiedAbstract):
|
|
||||||
"""
|
|
||||||
To not confuse other types of subscriptions, one can be a *subscribed*
|
|
||||||
member of an organization, meaning that they are paying etc.
|
|
||||||
|
|
||||||
A subscription does not track payment, this is done in the accounting app.
|
|
||||||
"""
|
|
||||||
|
|
||||||
subscription_type = models.ForeignKey(
|
|
||||||
SubscriptionType,
|
|
||||||
related_name="memberships",
|
|
||||||
verbose_name=_("subscription type"),
|
|
||||||
on_delete=models.PROTECT,
|
|
||||||
)
|
|
||||||
user = models.ForeignKey(get_user_model(), on_delete=models.PROTECT)
|
|
||||||
|
|
||||||
active = models.BooleanField(
|
|
||||||
default=False,
|
|
||||||
verbose_name=_("active"),
|
|
||||||
help_text=_("Automatically set by payment system."),
|
|
||||||
)
|
|
||||||
|
|
||||||
starts = models.DateField()
|
|
||||||
ends = models.DateField()
|
|
||||||
|
|
||||||
renewed_subscription = models.ForeignKey(
|
|
||||||
"self",
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
verbose_name=_("renewed subscription"),
|
|
||||||
on_delete=models.PROTECT,
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("subscription")
|
|
||||||
verbose_name_plural = _("subscriptions")
|
|
653
poetry.lock
generated
653
poetry.lock
generated
|
@ -1,653 +0,0 @@
|
||||||
[[package]]
|
|
||||||
name = "appdirs"
|
|
||||||
version = "1.4.4"
|
|
||||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "asgiref"
|
|
||||||
version = "3.3.1"
|
|
||||||
description = "ASGI specs, helper code, and adapters"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.5"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
tests = ["pytest", "pytest-asyncio"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "atomicwrites"
|
|
||||||
version = "1.4.0"
|
|
||||||
description = "Atomic file writes."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "attrs"
|
|
||||||
version = "20.3.0"
|
|
||||||
description = "Classes Without Boilerplate"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"]
|
|
||||||
docs = ["furo", "sphinx", "zope.interface"]
|
|
||||||
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
|
|
||||||
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "certifi"
|
|
||||||
version = "2020.12.5"
|
|
||||||
description = "Python package for providing Mozilla's CA Bundle."
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfgv"
|
|
||||||
version = "3.2.0"
|
|
||||||
description = "Validate configuration and produce human readable error messages."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6.1"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "chardet"
|
|
||||||
version = "4.0.0"
|
|
||||||
description = "Universal encoding detector for Python 2 and 3"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "colorama"
|
|
||||||
version = "0.4.4"
|
|
||||||
description = "Cross-platform colored terminal text."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "defusedxml"
|
|
||||||
version = "0.6.0"
|
|
||||||
description = "XML bomb protection for Python stdlib modules"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "distlib"
|
|
||||||
version = "0.3.1"
|
|
||||||
description = "Distribution utilities"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "django"
|
|
||||||
version = "3.1.5"
|
|
||||||
description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
asgiref = ">=3.2.10,<4"
|
|
||||||
pytz = "*"
|
|
||||||
sqlparse = ">=0.2.2"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
argon2 = ["argon2-cffi (>=16.1.0)"]
|
|
||||||
bcrypt = ["bcrypt"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "django-allauth"
|
|
||||||
version = "0.40.0"
|
|
||||||
description = "Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication."
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
Django = ">=1.11"
|
|
||||||
python3-openid = ">=3.0.8"
|
|
||||||
requests = "*"
|
|
||||||
requests-oauthlib = ">=0.3.0"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "django-money"
|
|
||||||
version = "1.3"
|
|
||||||
description = "Adds support for using money and currency fields in django models and forms. Uses py-moneyed as the money implementation."
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.5"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
Django = ">=1.11"
|
|
||||||
py-moneyed = ">=0.8,<1.0"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
exchange = ["certifi"]
|
|
||||||
test = ["pytest (>=3.1.0)", "pytest-django", "pytest-pythonpath", "pytest-cov", "django-reversion", "mixer"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "filelock"
|
|
||||||
version = "3.0.12"
|
|
||||||
description = "A platform independent file lock."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "identify"
|
|
||||||
version = "1.5.13"
|
|
||||||
description = "File identification library for Python"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
license = ["editdistance"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "idna"
|
|
||||||
version = "2.10"
|
|
||||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "importlib-metadata"
|
|
||||||
version = "3.4.0"
|
|
||||||
description = "Read metadata from Python packages"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
|
|
||||||
zipp = ">=0.5"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
|
|
||||||
testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "more-itertools"
|
|
||||||
version = "8.6.0"
|
|
||||||
description = "More routines for operating on iterables, beyond itertools"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.5"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nodeenv"
|
|
||||||
version = "1.5.0"
|
|
||||||
description = "Node.js virtual environment builder"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "oauthlib"
|
|
||||||
version = "3.1.0"
|
|
||||||
description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
rsa = ["cryptography"]
|
|
||||||
signals = ["blinker"]
|
|
||||||
signedtoken = ["cryptography", "pyjwt (>=1.0.0)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "packaging"
|
|
||||||
version = "20.8"
|
|
||||||
description = "Core utilities for Python packages"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
pyparsing = ">=2.0.2"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pluggy"
|
|
||||||
version = "0.13.1"
|
|
||||||
description = "plugin and hook calling mechanisms for python"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
dev = ["pre-commit", "tox"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pre-commit"
|
|
||||||
version = "2.9.3"
|
|
||||||
description = "A framework for managing and maintaining multi-language pre-commit hooks."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6.1"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
cfgv = ">=2.0.0"
|
|
||||||
identify = ">=1.0.0"
|
|
||||||
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
|
|
||||||
nodeenv = ">=0.11.1"
|
|
||||||
pyyaml = ">=5.1"
|
|
||||||
toml = "*"
|
|
||||||
virtualenv = ">=20.0.8"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "py"
|
|
||||||
version = "1.10.0"
|
|
||||||
description = "library with cross-python path, ini-parsing, io, code, log facilities"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "py-moneyed"
|
|
||||||
version = "0.8.0"
|
|
||||||
description = "Provides Currency and Money classes for use in your Python code."
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
tests = ["pytest (>=2.3.0)", "tox (>=1.6.0)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pyparsing"
|
|
||||||
version = "2.4.7"
|
|
||||||
description = "Python parsing module"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pytest"
|
|
||||||
version = "5.4.3"
|
|
||||||
description = "pytest: simple powerful testing with Python"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.5"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
|
|
||||||
attrs = ">=17.4.0"
|
|
||||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
|
||||||
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
|
|
||||||
more-itertools = ">=4.0.0"
|
|
||||||
packaging = "*"
|
|
||||||
pluggy = ">=0.12,<1.0"
|
|
||||||
py = ">=1.5.0"
|
|
||||||
wcwidth = "*"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
checkqa-mypy = ["mypy (==v0.761)"]
|
|
||||||
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pytest-django"
|
|
||||||
version = "3.10.0"
|
|
||||||
description = "A Django plugin for pytest."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
pytest = ">=3.6"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
docs = ["sphinx", "sphinx-rtd-theme"]
|
|
||||||
testing = ["django", "django-configurations (>=2.0)", "six"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "python3-openid"
|
|
||||||
version = "3.2.0"
|
|
||||||
description = "OpenID support for modern servers and consumers."
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
defusedxml = "*"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
mysql = ["mysql-connector-python"]
|
|
||||||
postgresql = ["psycopg2"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pytz"
|
|
||||||
version = "2020.5"
|
|
||||||
description = "World timezone definitions, modern and historical"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pyyaml"
|
|
||||||
version = "5.4.1"
|
|
||||||
description = "YAML parser and emitter for Python"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "requests"
|
|
||||||
version = "2.25.1"
|
|
||||||
description = "Python HTTP for Humans."
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
certifi = ">=2017.4.17"
|
|
||||||
chardet = ">=3.0.2,<5"
|
|
||||||
idna = ">=2.5,<3"
|
|
||||||
urllib3 = ">=1.21.1,<1.27"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
|
|
||||||
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "requests-oauthlib"
|
|
||||||
version = "1.3.0"
|
|
||||||
description = "OAuthlib authentication support for Requests."
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
oauthlib = ">=3.0.0"
|
|
||||||
requests = ">=2.0.0"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "six"
|
|
||||||
version = "1.15.0"
|
|
||||||
description = "Python 2 and 3 compatibility utilities"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sqlparse"
|
|
||||||
version = "0.4.1"
|
|
||||||
description = "A non-validating SQL parser."
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.5"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml"
|
|
||||||
version = "0.10.2"
|
|
||||||
description = "Python Library for Tom's Obvious, Minimal Language"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "typing-extensions"
|
|
||||||
version = "3.7.4.3"
|
|
||||||
description = "Backported and Experimental Type Hints for Python 3.5+"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "urllib3"
|
|
||||||
version = "1.26.3"
|
|
||||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
brotli = ["brotlipy (>=0.6.0)"]
|
|
||||||
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
|
|
||||||
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "virtualenv"
|
|
||||||
version = "20.4.0"
|
|
||||||
description = "Virtual Python Environment builder"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
appdirs = ">=1.4.3,<2"
|
|
||||||
distlib = ">=0.3.1,<1"
|
|
||||||
filelock = ">=3.0.0,<4"
|
|
||||||
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
|
|
||||||
six = ">=1.9.0,<2"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"]
|
|
||||||
testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)", "xonsh (>=0.9.16)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wcwidth"
|
|
||||||
version = "0.2.5"
|
|
||||||
description = "Measures the displayed width of unicode strings in a terminal"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zipp"
|
|
||||||
version = "3.4.0"
|
|
||||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
|
|
||||||
testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
|
|
||||||
|
|
||||||
[metadata]
|
|
||||||
lock-version = "1.1"
|
|
||||||
python-versions = "^3.7"
|
|
||||||
content-hash = "0145566bcf7def56887d7b546860c6f0c87477493c0047774e92ef3b69b1d61f"
|
|
||||||
|
|
||||||
[metadata.files]
|
|
||||||
appdirs = [
|
|
||||||
{file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
|
|
||||||
{file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
|
|
||||||
]
|
|
||||||
asgiref = [
|
|
||||||
{file = "asgiref-3.3.1-py3-none-any.whl", hash = "sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17"},
|
|
||||||
{file = "asgiref-3.3.1.tar.gz", hash = "sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0"},
|
|
||||||
]
|
|
||||||
atomicwrites = [
|
|
||||||
{file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
|
|
||||||
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
|
|
||||||
]
|
|
||||||
attrs = [
|
|
||||||
{file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"},
|
|
||||||
{file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"},
|
|
||||||
]
|
|
||||||
certifi = [
|
|
||||||
{file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"},
|
|
||||||
{file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"},
|
|
||||||
]
|
|
||||||
cfgv = [
|
|
||||||
{file = "cfgv-3.2.0-py2.py3-none-any.whl", hash = "sha256:32e43d604bbe7896fe7c248a9c2276447dbef840feb28fe20494f62af110211d"},
|
|
||||||
{file = "cfgv-3.2.0.tar.gz", hash = "sha256:cf22deb93d4bcf92f345a5c3cd39d3d41d6340adc60c78bbbd6588c384fda6a1"},
|
|
||||||
]
|
|
||||||
chardet = [
|
|
||||||
{file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"},
|
|
||||||
{file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"},
|
|
||||||
]
|
|
||||||
colorama = [
|
|
||||||
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
|
||||||
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
|
|
||||||
]
|
|
||||||
defusedxml = [
|
|
||||||
{file = "defusedxml-0.6.0-py2.py3-none-any.whl", hash = "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93"},
|
|
||||||
{file = "defusedxml-0.6.0.tar.gz", hash = "sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"},
|
|
||||||
]
|
|
||||||
distlib = [
|
|
||||||
{file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"},
|
|
||||||
{file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"},
|
|
||||||
]
|
|
||||||
django = [
|
|
||||||
{file = "Django-3.1.5-py3-none-any.whl", hash = "sha256:efa2ab96b33b20c2182db93147a0c3cd7769d418926f9e9f140a60dca7c64ca9"},
|
|
||||||
{file = "Django-3.1.5.tar.gz", hash = "sha256:2d78425ba74c7a1a74b196058b261b9733a8570782f4e2828974777ccca7edf7"},
|
|
||||||
]
|
|
||||||
django-allauth = [
|
|
||||||
{file = "django-allauth-0.40.0.tar.gz", hash = "sha256:6a189fc4d3ee23596c3fd6e9f49c59b5b15618980118171a50675dd6a27cc589"},
|
|
||||||
]
|
|
||||||
django-money = [
|
|
||||||
{file = "django-money-1.3.tar.gz", hash = "sha256:da95f9a7174281eb2ef0f5f1584d5ee2670fc0d67707cd269816a73cae791eb3"},
|
|
||||||
{file = "django_money-1.3-py3-none-any.whl", hash = "sha256:09952d49f998d089b21eb0f552d6dcb40d82626ab674d1caf0535bbd83a1ea01"},
|
|
||||||
]
|
|
||||||
filelock = [
|
|
||||||
{file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"},
|
|
||||||
{file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"},
|
|
||||||
]
|
|
||||||
identify = [
|
|
||||||
{file = "identify-1.5.13-py2.py3-none-any.whl", hash = "sha256:9dfb63a2e871b807e3ba62f029813552a24b5289504f5b071dea9b041aee9fe4"},
|
|
||||||
{file = "identify-1.5.13.tar.gz", hash = "sha256:70b638cf4743f33042bebb3b51e25261a0a10e80f978739f17e7fd4837664a66"},
|
|
||||||
]
|
|
||||||
idna = [
|
|
||||||
{file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
|
|
||||||
{file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
|
|
||||||
]
|
|
||||||
importlib-metadata = [
|
|
||||||
{file = "importlib_metadata-3.4.0-py3-none-any.whl", hash = "sha256:ace61d5fc652dc280e7b6b4ff732a9c2d40db2c0f92bc6cb74e07b73d53a1771"},
|
|
||||||
{file = "importlib_metadata-3.4.0.tar.gz", hash = "sha256:fa5daa4477a7414ae34e95942e4dd07f62adf589143c875c133c1e53c4eff38d"},
|
|
||||||
]
|
|
||||||
more-itertools = [
|
|
||||||
{file = "more-itertools-8.6.0.tar.gz", hash = "sha256:b3a9005928e5bed54076e6e549c792b306fddfe72b2d1d22dd63d42d5d3899cf"},
|
|
||||||
{file = "more_itertools-8.6.0-py3-none-any.whl", hash = "sha256:8e1a2a43b2f2727425f2b5839587ae37093f19153dc26c0927d1048ff6557330"},
|
|
||||||
]
|
|
||||||
nodeenv = [
|
|
||||||
{file = "nodeenv-1.5.0-py2.py3-none-any.whl", hash = "sha256:5304d424c529c997bc888453aeaa6362d242b6b4631e90f3d4bf1b290f1c84a9"},
|
|
||||||
{file = "nodeenv-1.5.0.tar.gz", hash = "sha256:ab45090ae383b716c4ef89e690c41ff8c2b257b85b309f01f3654df3d084bd7c"},
|
|
||||||
]
|
|
||||||
oauthlib = [
|
|
||||||
{file = "oauthlib-3.1.0-py2.py3-none-any.whl", hash = "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea"},
|
|
||||||
{file = "oauthlib-3.1.0.tar.gz", hash = "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889"},
|
|
||||||
]
|
|
||||||
packaging = [
|
|
||||||
{file = "packaging-20.8-py2.py3-none-any.whl", hash = "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858"},
|
|
||||||
{file = "packaging-20.8.tar.gz", hash = "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"},
|
|
||||||
]
|
|
||||||
pluggy = [
|
|
||||||
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
|
|
||||||
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
|
|
||||||
]
|
|
||||||
pre-commit = [
|
|
||||||
{file = "pre_commit-2.9.3-py2.py3-none-any.whl", hash = "sha256:6c86d977d00ddc8a60d68eec19f51ef212d9462937acf3ea37c7adec32284ac0"},
|
|
||||||
{file = "pre_commit-2.9.3.tar.gz", hash = "sha256:ee784c11953e6d8badb97d19bc46b997a3a9eded849881ec587accd8608d74a4"},
|
|
||||||
]
|
|
||||||
py = [
|
|
||||||
{file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"},
|
|
||||||
{file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"},
|
|
||||||
]
|
|
||||||
py-moneyed = [
|
|
||||||
{file = "py-moneyed-0.8.0.tar.gz", hash = "sha256:ec73795171919d537880a33c44d07fcdf0a5225e8368684fe02f0e75a6404742"},
|
|
||||||
{file = "py_moneyed-0.8.0-py2.py3-none-any.whl", hash = "sha256:c6691b914a5e4b5b2335cf113620479a52cc82988c0e143435a7c5c7d60cd4ad"},
|
|
||||||
]
|
|
||||||
pyparsing = [
|
|
||||||
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
|
|
||||||
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
|
|
||||||
]
|
|
||||||
pytest = [
|
|
||||||
{file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"},
|
|
||||||
{file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"},
|
|
||||||
]
|
|
||||||
pytest-django = [
|
|
||||||
{file = "pytest-django-3.10.0.tar.gz", hash = "sha256:4de6dbd077ed8606616958f77655fed0d5e3ee45159475671c7fa67596c6dba6"},
|
|
||||||
{file = "pytest_django-3.10.0-py2.py3-none-any.whl", hash = "sha256:c33e3d3da14d8409b125d825d4e74da17bb252191bf6fc3da6856e27a8b73ea4"},
|
|
||||||
]
|
|
||||||
python3-openid = [
|
|
||||||
{file = "python3-openid-3.2.0.tar.gz", hash = "sha256:33fbf6928f401e0b790151ed2b5290b02545e8775f982485205a066f874aaeaf"},
|
|
||||||
{file = "python3_openid-3.2.0-py3-none-any.whl", hash = "sha256:6626f771e0417486701e0b4daff762e7212e820ca5b29fcc0d05f6f8736dfa6b"},
|
|
||||||
]
|
|
||||||
pytz = [
|
|
||||||
{file = "pytz-2020.5-py2.py3-none-any.whl", hash = "sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4"},
|
|
||||||
{file = "pytz-2020.5.tar.gz", hash = "sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"},
|
|
||||||
]
|
|
||||||
pyyaml = [
|
|
||||||
{file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
|
|
||||||
{file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"},
|
|
||||||
{file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"},
|
|
||||||
{file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"},
|
|
||||||
{file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"},
|
|
||||||
{file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"},
|
|
||||||
{file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"},
|
|
||||||
{file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"},
|
|
||||||
{file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"},
|
|
||||||
{file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"},
|
|
||||||
{file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"},
|
|
||||||
{file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"},
|
|
||||||
{file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"},
|
|
||||||
{file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"},
|
|
||||||
{file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"},
|
|
||||||
{file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"},
|
|
||||||
{file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"},
|
|
||||||
{file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"},
|
|
||||||
{file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"},
|
|
||||||
{file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"},
|
|
||||||
{file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
|
|
||||||
]
|
|
||||||
requests = [
|
|
||||||
{file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"},
|
|
||||||
{file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"},
|
|
||||||
]
|
|
||||||
requests-oauthlib = [
|
|
||||||
{file = "requests-oauthlib-1.3.0.tar.gz", hash = "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"},
|
|
||||||
{file = "requests_oauthlib-1.3.0-py2.py3-none-any.whl", hash = "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d"},
|
|
||||||
{file = "requests_oauthlib-1.3.0-py3.7.egg", hash = "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"},
|
|
||||||
]
|
|
||||||
six = [
|
|
||||||
{file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
|
|
||||||
{file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
|
|
||||||
]
|
|
||||||
sqlparse = [
|
|
||||||
{file = "sqlparse-0.4.1-py3-none-any.whl", hash = "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0"},
|
|
||||||
{file = "sqlparse-0.4.1.tar.gz", hash = "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"},
|
|
||||||
]
|
|
||||||
toml = [
|
|
||||||
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
|
|
||||||
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
|
||||||
]
|
|
||||||
typing-extensions = [
|
|
||||||
{file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"},
|
|
||||||
{file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"},
|
|
||||||
{file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"},
|
|
||||||
]
|
|
||||||
urllib3 = [
|
|
||||||
{file = "urllib3-1.26.3-py2.py3-none-any.whl", hash = "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80"},
|
|
||||||
{file = "urllib3-1.26.3.tar.gz", hash = "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"},
|
|
||||||
]
|
|
||||||
virtualenv = [
|
|
||||||
{file = "virtualenv-20.4.0-py2.py3-none-any.whl", hash = "sha256:227a8fed626f2f20a6cdb0870054989f82dd27b2560a911935ba905a2a5e0034"},
|
|
||||||
{file = "virtualenv-20.4.0.tar.gz", hash = "sha256:219ee956e38b08e32d5639289aaa5bd190cfbe7dafcb8fa65407fca08e808f9c"},
|
|
||||||
]
|
|
||||||
wcwidth = [
|
|
||||||
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
|
|
||||||
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
|
|
||||||
]
|
|
||||||
zipp = [
|
|
||||||
{file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"},
|
|
||||||
{file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"},
|
|
||||||
]
|
|
|
@ -1,7 +0,0 @@
|
||||||
"""Context processors for the membersystem app."""
|
|
||||||
from django.contrib.sites.shortcuts import get_current_site
|
|
||||||
|
|
||||||
|
|
||||||
def current_site(request):
|
|
||||||
"""Include the current site in the context."""
|
|
||||||
return {"site": get_current_site(request)}
|
|
|
@ -1,14 +0,0 @@
|
||||||
import warnings
|
|
||||||
|
|
||||||
from .base import * # noqa
|
|
||||||
|
|
||||||
try:
|
|
||||||
from .local import * # noqa
|
|
||||||
except ImportError:
|
|
||||||
warnings.warn(
|
|
||||||
"No settings.local, using a default SECRET_KEY 'hest'. You should "
|
|
||||||
"write a custom local.py with this setting."
|
|
||||||
)
|
|
||||||
SECRET_KEY = "hest"
|
|
||||||
DEBUG = True
|
|
||||||
pass
|
|
|
@ -1,132 +0,0 @@
|
||||||
"""
|
|
||||||
Django settings for membersystem project.
|
|
||||||
|
|
||||||
Generated by 'django-admin startproject' using Django 2.0.4.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/2.0/topics/settings/
|
|
||||||
|
|
||||||
For the full list of settings and their values, see
|
|
||||||
https://docs.djangoproject.com/en/2.0/ref/settings/
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
|
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
|
||||||
# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/
|
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
|
||||||
DEBUG = False
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = []
|
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
|
||||||
"django.contrib.admin",
|
|
||||||
"django.contrib.auth",
|
|
||||||
"django.contrib.contenttypes",
|
|
||||||
"django.contrib.sessions",
|
|
||||||
"django.contrib.messages",
|
|
||||||
"django.contrib.staticfiles",
|
|
||||||
"django.contrib.sites",
|
|
||||||
"users",
|
|
||||||
"accounting",
|
|
||||||
"membership",
|
|
||||||
]
|
|
||||||
|
|
||||||
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 = "project.urls"
|
|
||||||
|
|
||||||
TEMPLATES = [
|
|
||||||
{
|
|
||||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
|
||||||
"DIRS": [os.path.join("project", "templates")],
|
|
||||||
"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",
|
|
||||||
"project.context_processors.current_site",
|
|
||||||
]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = (
|
|
||||||
"django.contrib.auth.backends.ModelBackend",
|
|
||||||
"allauth.account.auth_backends.AuthenticationBackend",
|
|
||||||
)
|
|
||||||
|
|
||||||
WSGI_APPLICATION = "project.wsgi.application"
|
|
||||||
|
|
||||||
|
|
||||||
# Database
|
|
||||||
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
"default": {
|
|
||||||
"ENGINE": "django.db.backends.sqlite3",
|
|
||||||
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Password validation
|
|
||||||
# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators
|
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
|
||||||
{
|
|
||||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" # noqa
|
|
||||||
},
|
|
||||||
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, # noqa
|
|
||||||
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, # noqa
|
|
||||||
{
|
|
||||||
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator" # noqa
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
AUTH_USER_MODEL = "users.User"
|
|
||||||
|
|
||||||
|
|
||||||
# Internationalization
|
|
||||||
# https://docs.djangoproject.com/en/2.0/topics/i18n/
|
|
||||||
|
|
||||||
LANGUAGE_CODE = "en-us"
|
|
||||||
|
|
||||||
TIME_ZONE = "UTC"
|
|
||||||
|
|
||||||
USE_I18N = True
|
|
||||||
|
|
||||||
USE_L10N = True
|
|
||||||
|
|
||||||
USE_TZ = True
|
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
|
||||||
# https://docs.djangoproject.com/en/2.0/howto/static-files/
|
|
||||||
|
|
||||||
STATIC_URL = "/static/"
|
|
||||||
|
|
||||||
STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")]
|
|
||||||
|
|
||||||
SITE_ID = 1
|
|
||||||
|
|
||||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
|
||||||
|
|
||||||
CURRENCIES = ("DKK",)
|
|
||||||
CURRENCY_CHOICES = [("DKK", "DKK")]
|
|
|
@ -1,79 +0,0 @@
|
||||||
/* General styles */
|
|
||||||
html
|
|
||||||
{
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 2.5vmin;
|
|
||||||
background: #f8f8f8;
|
|
||||||
}
|
|
||||||
|
|
||||||
body
|
|
||||||
{
|
|
||||||
background: #fff;
|
|
||||||
color: #000;
|
|
||||||
margin: 1em auto;
|
|
||||||
max-width: 50em;
|
|
||||||
padding: 0 1em;
|
|
||||||
box-shadow: 0 0 2.5em rgba(0, 0, 0, 20%);
|
|
||||||
}
|
|
||||||
|
|
||||||
header,
|
|
||||||
footer
|
|
||||||
{
|
|
||||||
background: #eee;
|
|
||||||
padding: .5em;
|
|
||||||
margin: 0 -1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer
|
|
||||||
{
|
|
||||||
margin-top: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
header h1
|
|
||||||
{
|
|
||||||
font-size: 1em;
|
|
||||||
float: left;
|
|
||||||
padding: .5em .5em;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
header ul,
|
|
||||||
footer ul
|
|
||||||
{
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
header ul li,
|
|
||||||
footer ul li
|
|
||||||
{
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
header ul li a,
|
|
||||||
footer ul li a
|
|
||||||
{
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0;
|
|
||||||
padding: .5em .5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Forms */
|
|
||||||
label
|
|
||||||
{
|
|
||||||
display: block;
|
|
||||||
padding: .5em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
button,
|
|
||||||
input,
|
|
||||||
textarea
|
|
||||||
{
|
|
||||||
font-size: inherit;
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
{% load static %}
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>{% block head_title %}{% endblock %} – {{ site.name }}</title>
|
|
||||||
{% block extra_head %}{% endblock %}
|
|
||||||
<link rel="stylesheet" href="{% static '/css/membersystem.css' %}" type="text/css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<h1>
|
|
||||||
<a href="/">{{ site.name }}</a>
|
|
||||||
</h1>
|
|
||||||
<ul>
|
|
||||||
{% if user.is_authenticated %}
|
|
||||||
<li><a href="{% url 'users:password_change' %}">Change password</a></li>
|
|
||||||
<li><a href="{% url 'users:logout' %}">Sign out</a></li>
|
|
||||||
{% else %}
|
|
||||||
<li><a href="{% url 'users:login' %}">Sign in</a></li>
|
|
||||||
<li><a href="{% url 'users:signup' %}">Sign up</a></li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</header>
|
|
||||||
{% block body %}
|
|
||||||
{% if messages %}
|
|
||||||
<ul id="messages">
|
|
||||||
{% for message in messages %}
|
|
||||||
<li>{{message}}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% endblock %}
|
|
||||||
{% endblock %}
|
|
||||||
{% block extra_body %}
|
|
||||||
{% endblock %}
|
|
||||||
<footer>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="https://data.coop">data.coop</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://git.data.coop/data.coop/membersystem">source code</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,12 +0,0 @@
|
||||||
"""URLs for the membersystem"""
|
|
||||||
from django.contrib import admin
|
|
||||||
from django.urls import include
|
|
||||||
from django.urls import path
|
|
||||||
|
|
||||||
from . import views
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path("", views.index),
|
|
||||||
path("users/", include("users.urls")),
|
|
||||||
path("admin/", admin.site.urls),
|
|
||||||
]
|
|
|
@ -1,5 +0,0 @@
|
||||||
from django.shortcuts import render
|
|
||||||
|
|
||||||
|
|
||||||
def index(request):
|
|
||||||
return render(request, "index.html")
|
|
123
pyproject.toml
123
pyproject.toml
|
@ -1,20 +1,105 @@
|
||||||
[tool.poetry]
|
|
||||||
name = "membersystem"
|
|
||||||
version = "0.1.0"
|
|
||||||
description = ""
|
|
||||||
authors = ["Your Name <you@example.com>"]
|
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
|
||||||
python = "^3.7"
|
|
||||||
Django = "^3.1"
|
|
||||||
django-money = "^1.3"
|
|
||||||
django-allauth = "^0.40.0"
|
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
|
||||||
pre-commit = "^2.9.3"
|
|
||||||
pytest = "^5.1"
|
|
||||||
pytest-django = "^3.5"
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry>=0.12"]
|
requires = ["hatchling", "hatch-vcs"]
|
||||||
build-backend = "poetry.masonry.api"
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "membersystem"
|
||||||
|
description = ''
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
keywords = []
|
||||||
|
authors = [
|
||||||
|
{ name = "Víðir Valberg Guðmundsson", email = "valberg@orn.li" },
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"Django==5.0.1",
|
||||||
|
"django-money==3.4.1",
|
||||||
|
"django-allauth==0.60.0",
|
||||||
|
"psycopg[binary]==3.1.16",
|
||||||
|
"environs[django]==10.0.0",
|
||||||
|
"uvicorn==0.25.0",
|
||||||
|
"whitenoise==6.6.0",
|
||||||
|
"django-zen-queries==2.1.0",
|
||||||
|
"django-registries==0.0.3",
|
||||||
|
]
|
||||||
|
dynamic = ["version"]
|
||||||
|
|
||||||
|
[tool.hatch.version]
|
||||||
|
source = "vcs"
|
||||||
|
|
||||||
|
[tool.hatch.envs.default]
|
||||||
|
dependencies = [
|
||||||
|
"coverage[toml]==7.3.0",
|
||||||
|
"pytest==7.2.2",
|
||||||
|
"pytest-cov",
|
||||||
|
"pytest-django==4.5.2",
|
||||||
|
"mypy==1.1.1",
|
||||||
|
"django-stubs==1.16.0",
|
||||||
|
"pip-tools==7.3.0",
|
||||||
|
"django-debug-toolbar==4.2.0",
|
||||||
|
"django-browser-reload==1.7.0",
|
||||||
|
"model-bakery==1.17.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[tool.hatch.envs.tests.matrix]]
|
||||||
|
python = ["3.12"]
|
||||||
|
django = ["4.2", "5.0"]
|
||||||
|
|
||||||
|
[tool.hatch.envs.tests.overrides]
|
||||||
|
matrix.django.dependencies = [
|
||||||
|
{ value = "django~={matrix:django}" },
|
||||||
|
]
|
||||||
|
matrix.python.dependencies = [
|
||||||
|
{ value = "typing_extensions==4.5.0", if = ["3.10"]},
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.hatch.envs.default.scripts]
|
||||||
|
cov = "pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=src --cov=tests --cov=append {args}"
|
||||||
|
no-cov = "cov --no-cov {args}"
|
||||||
|
typecheck = "mypy --config-file=pyproject.toml ."
|
||||||
|
requirements = "pip-compile --output-file requirements/base.txt pyproject.toml"
|
||||||
|
server = "./src/manage.py runserver"
|
||||||
|
migrate = "./src/manage.py migrate"
|
||||||
|
makemigrations = "./src/manage.py makemigrations"
|
||||||
|
createsuperuser = "./src/manage.py createsuperuser"
|
||||||
|
shell = "./src/manage.py shell"
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
DJANGO_SETTINGS_MODULE="tests.settings"
|
||||||
|
addopts = "--reuse-db"
|
||||||
|
norecursedirs = "build dist docs .eggs/* *.egg-info htmlcov .git"
|
||||||
|
python_files = "test*.py"
|
||||||
|
testpaths = "tests"
|
||||||
|
pythonpath = ". tests"
|
||||||
|
|
||||||
|
[tool.coverage.run]
|
||||||
|
branch = true
|
||||||
|
parallel = true
|
||||||
|
|
||||||
|
[tool.coverage.report]
|
||||||
|
exclude_lines = [
|
||||||
|
"no cov",
|
||||||
|
"if __name__ == .__main__.:",
|
||||||
|
"if TYPE_CHECKING:",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
mypy_path = "src/"
|
||||||
|
exclude = [
|
||||||
|
"venv/",
|
||||||
|
"dist/",
|
||||||
|
"docs/",
|
||||||
|
]
|
||||||
|
namespace_packages = false
|
||||||
|
show_error_codes = true
|
||||||
|
strict = true
|
||||||
|
warn_unreachable = true
|
||||||
|
follow_imports = "normal"
|
||||||
|
#plugins = ["mypy_django_plugin.main"]
|
||||||
|
|
||||||
|
[tool.django-stubs]
|
||||||
|
#django_settings_module = "tests.settings"
|
||||||
|
|
||||||
|
[[tool.mypy.overrides]]
|
||||||
|
module = "tests.*"
|
||||||
|
allow_untyped_defs = true
|
||||||
|
|
8
requirements/base.in
Normal file
8
requirements/base.in
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
Django==5.0.1
|
||||||
|
django-money==3.4.1
|
||||||
|
django-allauth==0.60.0
|
||||||
|
psycopg[binary]==3.1.16
|
||||||
|
environs[django]==10.0.0
|
||||||
|
uvicorn==0.25.0
|
||||||
|
whitenoise==6.6.0
|
||||||
|
django-zen-queries==2.1.0
|
98
requirements/base.txt
Normal file
98
requirements/base.txt
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
#
|
||||||
|
# This file is autogenerated by pip-compile with Python 3.11
|
||||||
|
# by the following command:
|
||||||
|
#
|
||||||
|
# pip-compile --output-file=requirements/base.txt pyproject.toml
|
||||||
|
#
|
||||||
|
asgiref==3.7.2
|
||||||
|
# via django
|
||||||
|
babel==2.14.0
|
||||||
|
# via py-moneyed
|
||||||
|
certifi==2023.11.17
|
||||||
|
# via requests
|
||||||
|
cffi==1.16.0
|
||||||
|
# via cryptography
|
||||||
|
charset-normalizer==3.3.2
|
||||||
|
# via requests
|
||||||
|
click==8.1.7
|
||||||
|
# via uvicorn
|
||||||
|
cryptography==41.0.7
|
||||||
|
# via pyjwt
|
||||||
|
defusedxml==0.7.1
|
||||||
|
# via python3-openid
|
||||||
|
dj-database-url==2.1.0
|
||||||
|
# via environs
|
||||||
|
dj-email-url==1.0.6
|
||||||
|
# via environs
|
||||||
|
django==5.0.1
|
||||||
|
# via
|
||||||
|
# dj-database-url
|
||||||
|
# django-allauth
|
||||||
|
# django-money
|
||||||
|
# django-registries
|
||||||
|
# django-zen-queries
|
||||||
|
# membersystem (pyproject.toml)
|
||||||
|
django-allauth==0.60.0
|
||||||
|
# via membersystem (pyproject.toml)
|
||||||
|
django-cache-url==3.4.5
|
||||||
|
# via environs
|
||||||
|
django-money==3.4.1
|
||||||
|
# via membersystem (pyproject.toml)
|
||||||
|
django-registries==0.0.3
|
||||||
|
# via membersystem (pyproject.toml)
|
||||||
|
django-zen-queries==2.1.0
|
||||||
|
# via membersystem (pyproject.toml)
|
||||||
|
environs[django]==10.0.0
|
||||||
|
# via
|
||||||
|
# environs
|
||||||
|
# membersystem (pyproject.toml)
|
||||||
|
h11==0.14.0
|
||||||
|
# via uvicorn
|
||||||
|
idna==3.6
|
||||||
|
# via requests
|
||||||
|
marshmallow==3.20.1
|
||||||
|
# via environs
|
||||||
|
oauthlib==3.2.2
|
||||||
|
# via requests-oauthlib
|
||||||
|
packaging==23.2
|
||||||
|
# via marshmallow
|
||||||
|
psycopg[binary]==3.1.16
|
||||||
|
# via
|
||||||
|
# membersystem (pyproject.toml)
|
||||||
|
# psycopg
|
||||||
|
psycopg-binary==3.1.16
|
||||||
|
# via psycopg
|
||||||
|
py-moneyed==3.0
|
||||||
|
# via django-money
|
||||||
|
pycparser==2.21
|
||||||
|
# via cffi
|
||||||
|
pyjwt[crypto]==2.8.0
|
||||||
|
# via
|
||||||
|
# django-allauth
|
||||||
|
# pyjwt
|
||||||
|
python-dotenv==1.0.0
|
||||||
|
# via environs
|
||||||
|
python3-openid==3.2.0
|
||||||
|
# via django-allauth
|
||||||
|
requests==2.31.0
|
||||||
|
# via
|
||||||
|
# django-allauth
|
||||||
|
# requests-oauthlib
|
||||||
|
requests-oauthlib==1.3.1
|
||||||
|
# via django-allauth
|
||||||
|
sqlparse==0.4.4
|
||||||
|
# via django
|
||||||
|
typing-extensions==4.9.0
|
||||||
|
# via
|
||||||
|
# dj-database-url
|
||||||
|
# psycopg
|
||||||
|
# py-moneyed
|
||||||
|
urllib3==2.1.0
|
||||||
|
# via requests
|
||||||
|
uvicorn==0.25.0
|
||||||
|
# via membersystem (pyproject.toml)
|
||||||
|
whitenoise==6.6.0
|
||||||
|
# via membersystem (pyproject.toml)
|
||||||
|
|
||||||
|
# The following packages are considered to be unsafe in a requirements file:
|
||||||
|
# setuptools
|
8
requirements/dev.in
Normal file
8
requirements/dev.in
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
-r test.txt
|
||||||
|
|
||||||
|
django-browser-reload==1.12.1
|
||||||
|
django-debug-toolbar==4.2.0
|
||||||
|
django-extensions==3.2.3
|
||||||
|
django-stubs==4.2.7
|
||||||
|
ipython==8.19.0
|
||||||
|
mypy==1.8.0
|
213
requirements/dev.txt
Normal file
213
requirements/dev.txt
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
#
|
||||||
|
# This file is autogenerated by pip-compile with Python 3.11
|
||||||
|
# by the following command:
|
||||||
|
#
|
||||||
|
# pip-compile --output-file=requirements/dev.txt requirements/dev.in
|
||||||
|
#
|
||||||
|
asgiref==3.7.2
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# django
|
||||||
|
# django-browser-reload
|
||||||
|
asttokens==2.4.1
|
||||||
|
# via stack-data
|
||||||
|
babel==2.14.0
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# py-moneyed
|
||||||
|
certifi==2023.11.17
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# requests
|
||||||
|
cffi==1.16.0
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# cryptography
|
||||||
|
charset-normalizer==3.3.2
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# requests
|
||||||
|
click==8.1.7
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# uvicorn
|
||||||
|
coverage==7.4.0
|
||||||
|
# via -r requirements/test.txt
|
||||||
|
cryptography==41.0.7
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# pyjwt
|
||||||
|
decorator==5.1.1
|
||||||
|
# via ipython
|
||||||
|
defusedxml==0.7.1
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# python3-openid
|
||||||
|
dj-database-url==2.1.0
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# environs
|
||||||
|
dj-email-url==1.0.6
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# environs
|
||||||
|
django==5.0.1
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# dj-database-url
|
||||||
|
# django-allauth
|
||||||
|
# django-browser-reload
|
||||||
|
# django-debug-toolbar
|
||||||
|
# django-extensions
|
||||||
|
# django-money
|
||||||
|
# django-stubs
|
||||||
|
# django-stubs-ext
|
||||||
|
# django-zen-queries
|
||||||
|
django-allauth==0.60.0
|
||||||
|
# via -r requirements/test.txt
|
||||||
|
django-browser-reload==1.12.1
|
||||||
|
# via -r requirements/dev.in
|
||||||
|
django-cache-url==3.4.5
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# environs
|
||||||
|
django-debug-toolbar==4.2.0
|
||||||
|
# via -r requirements/dev.in
|
||||||
|
django-extensions==3.2.3
|
||||||
|
# via -r requirements/dev.in
|
||||||
|
django-money==3.4.1
|
||||||
|
# via -r requirements/test.txt
|
||||||
|
django-stubs==4.2.7
|
||||||
|
# via -r requirements/dev.in
|
||||||
|
django-stubs-ext==4.2.7
|
||||||
|
# via django-stubs
|
||||||
|
django-zen-queries==2.1.0
|
||||||
|
# via -r requirements/test.txt
|
||||||
|
environs[django]==10.0.0
|
||||||
|
# via -r requirements/test.txt
|
||||||
|
executing==2.0.1
|
||||||
|
# via stack-data
|
||||||
|
h11==0.14.0
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# uvicorn
|
||||||
|
idna==3.6
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# requests
|
||||||
|
ipython==8.19.0
|
||||||
|
# via -r requirements/dev.in
|
||||||
|
jedi==0.19.1
|
||||||
|
# via ipython
|
||||||
|
lxml==5.0.1
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# unittest-xml-reporting
|
||||||
|
marshmallow==3.20.1
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# environs
|
||||||
|
matplotlib-inline==0.1.6
|
||||||
|
# via ipython
|
||||||
|
mypy==1.8.0
|
||||||
|
# via -r requirements/dev.in
|
||||||
|
mypy-extensions==1.0.0
|
||||||
|
# via mypy
|
||||||
|
oauthlib==3.2.2
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# requests-oauthlib
|
||||||
|
packaging==23.2
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# marshmallow
|
||||||
|
parso==0.8.3
|
||||||
|
# via jedi
|
||||||
|
pexpect==4.9.0
|
||||||
|
# via ipython
|
||||||
|
prompt-toolkit==3.0.43
|
||||||
|
# via ipython
|
||||||
|
psycopg[binary]==3.1.16
|
||||||
|
# via -r requirements/test.txt
|
||||||
|
psycopg-binary==3.1.16
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# psycopg
|
||||||
|
ptyprocess==0.7.0
|
||||||
|
# via pexpect
|
||||||
|
pure-eval==0.2.2
|
||||||
|
# via stack-data
|
||||||
|
py-moneyed==3.0
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# django-money
|
||||||
|
pycparser==2.21
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# cffi
|
||||||
|
pygments==2.17.2
|
||||||
|
# via ipython
|
||||||
|
pyjwt[crypto]==2.8.0
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# django-allauth
|
||||||
|
python-dotenv==1.0.0
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# environs
|
||||||
|
python3-openid==3.2.0
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# django-allauth
|
||||||
|
requests==2.31.0
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# django-allauth
|
||||||
|
# requests-oauthlib
|
||||||
|
requests-oauthlib==1.3.1
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# django-allauth
|
||||||
|
six==1.16.0
|
||||||
|
# via asttokens
|
||||||
|
sqlparse==0.4.4
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# django
|
||||||
|
# django-debug-toolbar
|
||||||
|
stack-data==0.6.3
|
||||||
|
# via ipython
|
||||||
|
tblib==3.0.0
|
||||||
|
# via -r requirements/test.txt
|
||||||
|
traitlets==5.14.1
|
||||||
|
# via
|
||||||
|
# ipython
|
||||||
|
# matplotlib-inline
|
||||||
|
types-pytz==2023.3.1.1
|
||||||
|
# via django-stubs
|
||||||
|
types-pyyaml==6.0.12.12
|
||||||
|
# via django-stubs
|
||||||
|
typing-extensions==4.9.0
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# dj-database-url
|
||||||
|
# django-stubs
|
||||||
|
# django-stubs-ext
|
||||||
|
# mypy
|
||||||
|
# psycopg
|
||||||
|
# py-moneyed
|
||||||
|
unittest-xml-reporting==3.2.0
|
||||||
|
# via -r requirements/test.txt
|
||||||
|
urllib3==2.1.0
|
||||||
|
# via
|
||||||
|
# -r requirements/test.txt
|
||||||
|
# requests
|
||||||
|
uvicorn==0.25.0
|
||||||
|
# via -r requirements/test.txt
|
||||||
|
wcwidth==0.2.13
|
||||||
|
# via prompt-toolkit
|
||||||
|
whitenoise==6.6.0
|
||||||
|
# via -r requirements/test.txt
|
||||||
|
|
||||||
|
# The following packages are considered to be unsafe in a requirements file:
|
||||||
|
# setuptools
|
5
requirements/test.in
Normal file
5
requirements/test.in
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
-r base.txt
|
||||||
|
|
||||||
|
coverage==7.4.0
|
||||||
|
tblib==3.0.0
|
||||||
|
unittest-xml-reporting==3.2.0
|
149
requirements/test.txt
Normal file
149
requirements/test.txt
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
#
|
||||||
|
# This file is autogenerated by pip-compile with Python 3.11
|
||||||
|
# by the following command:
|
||||||
|
#
|
||||||
|
# pip-compile --output-file=requirements/test.txt requirements/test.in
|
||||||
|
#
|
||||||
|
asgiref==3.7.2
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# django
|
||||||
|
babel==2.14.0
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# py-moneyed
|
||||||
|
certifi==2023.11.17
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# requests
|
||||||
|
cffi==1.16.0
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# cryptography
|
||||||
|
charset-normalizer==3.3.2
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# requests
|
||||||
|
click==8.1.7
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# uvicorn
|
||||||
|
coverage==7.4.0
|
||||||
|
# via -r requirements/test.in
|
||||||
|
cryptography==41.0.7
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# pyjwt
|
||||||
|
defusedxml==0.7.1
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# python3-openid
|
||||||
|
dj-database-url==2.1.0
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# environs
|
||||||
|
dj-email-url==1.0.6
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# environs
|
||||||
|
django==5.0.1
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# dj-database-url
|
||||||
|
# django-allauth
|
||||||
|
# django-money
|
||||||
|
# django-zen-queries
|
||||||
|
django-allauth==0.60.0
|
||||||
|
# via -r requirements/base.txt
|
||||||
|
django-cache-url==3.4.5
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# environs
|
||||||
|
django-money==3.4.1
|
||||||
|
# via -r requirements/base.txt
|
||||||
|
django-zen-queries==2.1.0
|
||||||
|
# via -r requirements/base.txt
|
||||||
|
environs[django]==10.0.0
|
||||||
|
# via -r requirements/base.txt
|
||||||
|
h11==0.14.0
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# uvicorn
|
||||||
|
idna==3.6
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# requests
|
||||||
|
lxml==5.0.1
|
||||||
|
# via unittest-xml-reporting
|
||||||
|
marshmallow==3.20.1
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# environs
|
||||||
|
oauthlib==3.2.2
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# requests-oauthlib
|
||||||
|
packaging==23.2
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# marshmallow
|
||||||
|
psycopg[binary]==3.1.16
|
||||||
|
# via -r requirements/base.txt
|
||||||
|
psycopg-binary==3.1.16
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# psycopg
|
||||||
|
py-moneyed==3.0
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# django-money
|
||||||
|
pycparser==2.21
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# cffi
|
||||||
|
pyjwt[crypto]==2.8.0
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# django-allauth
|
||||||
|
python-dotenv==1.0.0
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# environs
|
||||||
|
python3-openid==3.2.0
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# django-allauth
|
||||||
|
requests==2.31.0
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# django-allauth
|
||||||
|
# requests-oauthlib
|
||||||
|
requests-oauthlib==1.3.1
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# django-allauth
|
||||||
|
sqlparse==0.4.4
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# django
|
||||||
|
tblib==3.0.0
|
||||||
|
# via -r requirements/test.in
|
||||||
|
typing-extensions==4.9.0
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# dj-database-url
|
||||||
|
# psycopg
|
||||||
|
# py-moneyed
|
||||||
|
unittest-xml-reporting==3.2.0
|
||||||
|
# via -r requirements/test.in
|
||||||
|
urllib3==2.1.0
|
||||||
|
# via
|
||||||
|
# -r requirements/base.txt
|
||||||
|
# requests
|
||||||
|
uvicorn==0.25.0
|
||||||
|
# via -r requirements/base.txt
|
||||||
|
whitenoise==6.6.0
|
||||||
|
# via -r requirements/base.txt
|
||||||
|
|
||||||
|
# The following packages are considered to be unsafe in a requirements file:
|
||||||
|
# setuptools
|
|
@ -6,26 +6,21 @@ from . import models
|
||||||
|
|
||||||
@admin.register(models.Order)
|
@admin.register(models.Order)
|
||||||
class OrderAdmin(admin.ModelAdmin):
|
class OrderAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
list_display = ("who", "description", "created", "is_paid")
|
list_display = ("who", "description", "created", "is_paid")
|
||||||
|
|
||||||
|
@admin.display(description=_("Customer"))
|
||||||
def who(self, instance):
|
def who(self, instance):
|
||||||
return instance.user.get_full_name()
|
return instance.user.get_full_name()
|
||||||
|
|
||||||
who.short_description = _("Customer")
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Payment)
|
@admin.register(models.Payment)
|
||||||
class PaymentAdmin(admin.ModelAdmin):
|
class PaymentAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
list_display = ("who", "description", "order_id", "created")
|
list_display = ("who", "description", "order_id", "created")
|
||||||
|
|
||||||
|
@admin.display(description=_("Customer"))
|
||||||
def who(self, instance):
|
def who(self, instance):
|
||||||
return instance.order.user.get_full_name()
|
return instance.order.user.get_full_name()
|
||||||
|
|
||||||
who.short_description = _("Customer")
|
@admin.display(description=_("Order ID"))
|
||||||
|
|
||||||
def order_id(self, instance):
|
def order_id(self, instance):
|
||||||
return instance.order.id
|
return instance.order.id
|
||||||
|
|
||||||
order_id.short_description = _("Order ID")
|
|
|
@ -1,6 +1,4 @@
|
||||||
# Generated by Django 2.0.6 on 2018-06-23 19:51
|
# Generated by Django 3.1.7 on 2021-02-27 20:06
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import djmoney.models.fields
|
import djmoney.models.fields
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -12,7 +10,9 @@ class Migration(migrations.Migration):
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
|
@ -33,7 +33,7 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"created",
|
"created",
|
||||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
models.DateTimeField(auto_now_add=True, verbose_name="oprettet"),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"owner",
|
"owner",
|
||||||
|
@ -43,7 +43,9 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
options={"abstract": False},
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="Order",
|
name="Order",
|
||||||
|
@ -63,7 +65,7 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"created",
|
"created",
|
||||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
models.DateTimeField(auto_now_add=True, verbose_name="oprettet"),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"description",
|
"description",
|
||||||
|
@ -82,7 +84,6 @@ class Migration(migrations.Migration):
|
||||||
"price",
|
"price",
|
||||||
djmoney.models.fields.MoneyField(
|
djmoney.models.fields.MoneyField(
|
||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
default=Decimal("0.0"),
|
|
||||||
max_digits=16,
|
max_digits=16,
|
||||||
verbose_name="price (excl. VAT)",
|
verbose_name="price (excl. VAT)",
|
||||||
),
|
),
|
||||||
|
@ -99,10 +100,7 @@ class Migration(migrations.Migration):
|
||||||
(
|
(
|
||||||
"vat",
|
"vat",
|
||||||
djmoney.models.fields.MoneyField(
|
djmoney.models.fields.MoneyField(
|
||||||
decimal_places=2,
|
decimal_places=2, max_digits=16, verbose_name="VAT"
|
||||||
default=Decimal("0.0"),
|
|
||||||
max_digits=16,
|
|
||||||
verbose_name="VAT",
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
("is_paid", models.BooleanField(default=False, verbose_name="is paid")),
|
("is_paid", models.BooleanField(default=False, verbose_name="is paid")),
|
||||||
|
@ -110,7 +108,7 @@ class Migration(migrations.Migration):
|
||||||
"account",
|
"account",
|
||||||
models.ForeignKey(
|
models.ForeignKey(
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
to="accounting.Account",
|
to="accounting.account",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -121,60 +119,10 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
options={"verbose_name": "Order", "verbose_name_plural": "Orders"},
|
options={
|
||||||
),
|
"verbose_name": "Order",
|
||||||
migrations.CreateModel(
|
"verbose_name_plural": "Orders",
|
||||||
name="Payment",
|
},
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.AutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"modified",
|
|
||||||
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"created",
|
|
||||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"amount_currency",
|
|
||||||
djmoney.models.fields.CurrencyField(
|
|
||||||
choices=[("DKK", "DKK")],
|
|
||||||
default="XYZ",
|
|
||||||
editable=False,
|
|
||||||
max_length=3,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"amount",
|
|
||||||
djmoney.models.fields.MoneyField(
|
|
||||||
decimal_places=2, default=Decimal("0.0"), max_digits=16
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"description",
|
|
||||||
models.CharField(max_length=1024, verbose_name="description"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"stripe_charge_id",
|
|
||||||
models.CharField(blank=True, max_length=255, null=True),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"order",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
|
||||||
to="accounting.Order",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={"verbose_name": "payment", "verbose_name_plural": "payments"},
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="Transaction",
|
name="Transaction",
|
||||||
|
@ -194,7 +142,7 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"created",
|
"created",
|
||||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
models.DateTimeField(auto_now_add=True, verbose_name="oprettet"),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"amount_currency",
|
"amount_currency",
|
||||||
|
@ -209,7 +157,6 @@ class Migration(migrations.Migration):
|
||||||
"amount",
|
"amount",
|
||||||
djmoney.models.fields.MoneyField(
|
djmoney.models.fields.MoneyField(
|
||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
default=Decimal("0.0"),
|
|
||||||
help_text="This will include VAT",
|
help_text="This will include VAT",
|
||||||
max_digits=16,
|
max_digits=16,
|
||||||
verbose_name="amount",
|
verbose_name="amount",
|
||||||
|
@ -224,10 +171,66 @@ class Migration(migrations.Migration):
|
||||||
models.ForeignKey(
|
models.ForeignKey(
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
related_name="transactions",
|
related_name="transactions",
|
||||||
to="accounting.Account",
|
to="accounting.account",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
options={"abstract": False},
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Payment",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"modified",
|
||||||
|
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created",
|
||||||
|
models.DateTimeField(auto_now_add=True, verbose_name="oprettet"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"amount_currency",
|
||||||
|
djmoney.models.fields.CurrencyField(
|
||||||
|
choices=[("DKK", "DKK")],
|
||||||
|
default="XYZ",
|
||||||
|
editable=False,
|
||||||
|
max_length=3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"amount",
|
||||||
|
djmoney.models.fields.MoneyField(decimal_places=2, max_digits=16),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"description",
|
||||||
|
models.CharField(max_length=1024, verbose_name="description"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"stripe_charge_id",
|
||||||
|
models.CharField(blank=True, max_length=255, null=True),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"order",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
to="accounting.order",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "payment",
|
||||||
|
"verbose_name_plural": "payments",
|
||||||
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
|
@ -1,7 +1,6 @@
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.aggregates import Sum
|
from django.db.models.aggregates import Sum
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
@ -10,7 +9,6 @@ from djmoney.models.fields import MoneyField
|
||||||
|
|
||||||
|
|
||||||
class CreatedModifiedAbstract(models.Model):
|
class CreatedModifiedAbstract(models.Model):
|
||||||
|
|
||||||
modified = models.DateTimeField(auto_now=True, verbose_name=_("modified"))
|
modified = models.DateTimeField(auto_now=True, verbose_name=_("modified"))
|
||||||
created = models.DateTimeField(auto_now_add=True, verbose_name=_("created"))
|
created = models.DateTimeField(auto_now_add=True, verbose_name=_("created"))
|
||||||
|
|
||||||
|
@ -24,7 +22,7 @@ class Account(CreatedModifiedAbstract):
|
||||||
can decide which account to use to pay for something.
|
can decide which account to use to pay for something.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
owner = models.ForeignKey(get_user_model(), on_delete=models.PROTECT)
|
owner = models.ForeignKey("auth.User", on_delete=models.PROTECT)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def balance(self):
|
def balance(self):
|
||||||
|
@ -38,7 +36,9 @@ class Transaction(CreatedModifiedAbstract):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
account = models.ForeignKey(
|
account = models.ForeignKey(
|
||||||
Account, on_delete=models.PROTECT, related_name="transactions"
|
Account,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name="transactions",
|
||||||
)
|
)
|
||||||
amount = MoneyField(
|
amount = MoneyField(
|
||||||
verbose_name=_("amount"),
|
verbose_name=_("amount"),
|
||||||
|
@ -56,14 +56,15 @@ class Order(CreatedModifiedAbstract):
|
||||||
invoices at the moment.
|
invoices at the moment.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
user = models.ForeignKey(get_user_model(), on_delete=models.PROTECT)
|
user = models.ForeignKey("auth.User", on_delete=models.PROTECT)
|
||||||
account = models.ForeignKey(Account, on_delete=models.PROTECT)
|
account = models.ForeignKey(Account, on_delete=models.PROTECT)
|
||||||
is_paid = models.BooleanField(default=False)
|
|
||||||
|
|
||||||
description = models.CharField(max_length=1024, verbose_name=_("description"))
|
description = models.CharField(max_length=1024, verbose_name=_("description"))
|
||||||
|
|
||||||
price = MoneyField(
|
price = MoneyField(
|
||||||
verbose_name=_("price (excl. VAT)"), max_digits=16, decimal_places=2
|
verbose_name=_("price (excl. VAT)"),
|
||||||
|
max_digits=16,
|
||||||
|
decimal_places=2,
|
||||||
)
|
)
|
||||||
vat = MoneyField(verbose_name=_("VAT"), max_digits=16, decimal_places=2)
|
vat = MoneyField(verbose_name=_("VAT"), max_digits=16, decimal_places=2)
|
||||||
|
|
||||||
|
@ -91,11 +92,10 @@ class Order(CreatedModifiedAbstract):
|
||||||
verbose_name_plural = pgettext_lazy("accounting term", "Orders")
|
verbose_name_plural = pgettext_lazy("accounting term", "Orders")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Order ID {id}".format(id=self.display_id)
|
return f"Order ID {self.display_id}"
|
||||||
|
|
||||||
|
|
||||||
class Payment(CreatedModifiedAbstract):
|
class Payment(CreatedModifiedAbstract):
|
||||||
|
|
||||||
amount = MoneyField(max_digits=16, decimal_places=2)
|
amount = MoneyField(max_digits=16, decimal_places=2)
|
||||||
order = models.ForeignKey(Order, on_delete=models.PROTECT)
|
order = models.ForeignKey(Order, on_delete=models.PROTECT)
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ class Payment(CreatedModifiedAbstract):
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Payment ID {id}".format(id=self.display_id)
|
return f"Payment ID {self.display_id}"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("payment")
|
verbose_name = _("payment")
|
|
@ -10,6 +10,6 @@ if __name__ == "__main__":
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
"Couldn't import Django. Are you sure it's installed and "
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
"available on your PYTHONPATH environment variable? Did you "
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
"forget to activate a virtual environment?"
|
"forget to activate a virtual environment?",
|
||||||
)
|
)
|
||||||
execute_from_command_line(sys.argv)
|
execute_from_command_line(sys.argv)
|
20
src/membership/admin.py
Normal file
20
src/membership/admin.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models import Membership
|
||||||
|
from .models import MembershipType
|
||||||
|
from .models import SubscriptionPeriod
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Membership)
|
||||||
|
class MembershipAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(MembershipType)
|
||||||
|
class MembershipTypeAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(SubscriptionPeriod)
|
||||||
|
class SubscriptionPeriodAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
11
src/membership/apps.py
Normal file
11
src/membership/apps.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
from django.db.models.signals import post_migrate
|
||||||
|
|
||||||
|
|
||||||
|
class MembershipConfig(AppConfig):
|
||||||
|
name = "membership"
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
from .permissions import persist_permissions
|
||||||
|
|
||||||
|
post_migrate.connect(persist_permissions, sender=self)
|
93
src/membership/migrations/0001_initial.py
Normal file
93
src/membership/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
# Generated by Django 3.1.7 on 2021-02-28 21:09
|
||||||
|
import django.contrib.postgres.fields.ranges
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="MembershipType",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"modified",
|
||||||
|
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created",
|
||||||
|
models.DateTimeField(auto_now_add=True, verbose_name="oprettet"),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=64, verbose_name="navn")),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "membership type",
|
||||||
|
"verbose_name_plural": "membership types",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Membership",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"modified",
|
||||||
|
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created",
|
||||||
|
models.DateTimeField(auto_now_add=True, verbose_name="oprettet"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"period",
|
||||||
|
django.contrib.postgres.fields.ranges.DateTimeRangeField(
|
||||||
|
help_text="The duration this subscription is for. "
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"membership_type",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="memberships",
|
||||||
|
to="membership.membershiptype",
|
||||||
|
verbose_name="subscription type",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "membership",
|
||||||
|
"verbose_name_plural": "memberships",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,63 @@
|
||||||
|
# Generated by Django 4.1 on 2023-01-02 21:05
|
||||||
|
|
||||||
|
import django.contrib.postgres.constraints
|
||||||
|
import django.contrib.postgres.fields.ranges
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("membership", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="SubscriptionPeriod",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"modified",
|
||||||
|
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created",
|
||||||
|
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"period",
|
||||||
|
django.contrib.postgres.fields.ranges.DateRangeField(
|
||||||
|
verbose_name="period"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="membership",
|
||||||
|
name="period",
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="membership",
|
||||||
|
name="created",
|
||||||
|
field=models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="membershiptype",
|
||||||
|
name="created",
|
||||||
|
field=models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name="subscriptionperiod",
|
||||||
|
constraint=django.contrib.postgres.constraints.ExclusionConstraint(
|
||||||
|
expressions=[("period", "&&")], name="exclude_overlapping_periods"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
23
src/membership/migrations/0003_membership_period.py
Normal file
23
src/membership/migrations/0003_membership_period.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 4.1 on 2023-01-02 21:05
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("membership", "0002_subscriptionperiod_remove_membership_period_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="membership",
|
||||||
|
name="period",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
to="membership.subscriptionperiod",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
22
src/membership/migrations/0004_alter_membership_period.py
Normal file
22
src/membership/migrations/0004_alter_membership_period.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# Generated by Django 4.1 on 2023-01-02 21:06
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("membership", "0003_membership_period"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="membership",
|
||||||
|
name="period",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
to="membership.subscriptionperiod",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
25
src/membership/migrations/0005_member.py
Normal file
25
src/membership/migrations/0005_member.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# Generated by Django 4.1.5 on 2023-09-16 14:09
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('auth', '0012_alter_user_first_name_max_length'),
|
||||||
|
('membership', '0004_alter_membership_period'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Member',
|
||||||
|
fields=[
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'proxy': True,
|
||||||
|
'indexes': [],
|
||||||
|
'constraints': [],
|
||||||
|
},
|
||||||
|
bases=('auth.user',),
|
||||||
|
),
|
||||||
|
]
|
121
src/membership/models.py
Normal file
121
src/membership/models.py
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.contrib.postgres.constraints import ExclusionConstraint
|
||||||
|
from django.contrib.postgres.fields import DateRangeField
|
||||||
|
from django.contrib.postgres.fields import RangeOperators
|
||||||
|
from django.db import models
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
from utils.mixins import CreatedModifiedAbstract
|
||||||
|
|
||||||
|
|
||||||
|
class Member(User):
|
||||||
|
class QuerySet(models.QuerySet):
|
||||||
|
def annotate_membership(self):
|
||||||
|
from .selectors import get_current_subscription_period
|
||||||
|
|
||||||
|
current_subscription_period = get_current_subscription_period()
|
||||||
|
|
||||||
|
if not current_subscription_period:
|
||||||
|
raise ValueError("No current subscription period found")
|
||||||
|
|
||||||
|
return self.annotate(
|
||||||
|
active_membership=models.Exists(
|
||||||
|
Membership.objects.filter(
|
||||||
|
user=models.OuterRef("pk"),
|
||||||
|
period=current_subscription_period.id,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
objects = QuerySet.as_manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
proxy = True
|
||||||
|
|
||||||
|
|
||||||
|
class SubscriptionPeriod(CreatedModifiedAbstract):
|
||||||
|
"""
|
||||||
|
Denotes a period for which members should pay their membership fee for.
|
||||||
|
"""
|
||||||
|
|
||||||
|
period = DateRangeField(verbose_name=_("period"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
ExclusionConstraint(
|
||||||
|
name="exclude_overlapping_periods",
|
||||||
|
expressions=[
|
||||||
|
("period", RangeOperators.OVERLAPS),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return (
|
||||||
|
f"{self.period.lower} - {self.period.upper or _('next general assembly')}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Membership(CreatedModifiedAbstract):
|
||||||
|
"""
|
||||||
|
Tracks that a user has membership of a given type for a given period.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class QuerySet(models.QuerySet):
|
||||||
|
def for_member(self, member: Member):
|
||||||
|
return self.filter(user=member)
|
||||||
|
|
||||||
|
def _current(self):
|
||||||
|
return self.filter(period__period__contains=timezone.now())
|
||||||
|
|
||||||
|
def current(self) -> "Membership | None":
|
||||||
|
try:
|
||||||
|
return self._current().get()
|
||||||
|
except self.model.DoesNotExist:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def previous(self) -> list["Membership"]:
|
||||||
|
# A naïve way to get previous by just excluding the current. This
|
||||||
|
# means that there must be some protection against "future"
|
||||||
|
# memberships.
|
||||||
|
return list(self.all().difference(self._current()))
|
||||||
|
|
||||||
|
objects = QuerySet.as_manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("membership")
|
||||||
|
verbose_name_plural = _("memberships")
|
||||||
|
|
||||||
|
user = models.ForeignKey("auth.User", on_delete=models.PROTECT)
|
||||||
|
|
||||||
|
membership_type = models.ForeignKey(
|
||||||
|
"membership.MembershipType",
|
||||||
|
related_name="memberships",
|
||||||
|
verbose_name=_("subscription type"),
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
)
|
||||||
|
|
||||||
|
period = models.ForeignKey(
|
||||||
|
"membership.SubscriptionPeriod",
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.user} - {self.period}"
|
||||||
|
|
||||||
|
|
||||||
|
class MembershipType(CreatedModifiedAbstract):
|
||||||
|
"""
|
||||||
|
Models membership types. Currently only a name, but will in the future
|
||||||
|
possibly contain more information like fees.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("membership type")
|
||||||
|
verbose_name_plural = _("membership types")
|
||||||
|
|
||||||
|
name = models.CharField(verbose_name=_("name"), max_length=64)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
46
src/membership/permissions.py
Normal file
46
src/membership/permissions.py
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from django.contrib.auth.models import Permission as DjangoPermission
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
PERMISSIONS = []
|
||||||
|
|
||||||
|
|
||||||
|
def persist_permissions(sender, **kwargs):
|
||||||
|
for permission in PERMISSIONS:
|
||||||
|
permission.persist_permission()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Permission:
|
||||||
|
name: str
|
||||||
|
codename: str
|
||||||
|
app_label: str
|
||||||
|
model: str
|
||||||
|
|
||||||
|
def __post_init__(self, *args, **kwargs):
|
||||||
|
PERMISSIONS.append(self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self):
|
||||||
|
return f"{self.app_label}.{self.codename}"
|
||||||
|
|
||||||
|
def persist_permission(self):
|
||||||
|
content_type, _ = ContentType.objects.get_or_create(
|
||||||
|
app_label=self.app_label,
|
||||||
|
model=self.model,
|
||||||
|
)
|
||||||
|
DjangoPermission.objects.get_or_create(
|
||||||
|
content_type=content_type,
|
||||||
|
codename=self.codename,
|
||||||
|
defaults={"name": self.name},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
ADMINISTRATE_MEMBERS = Permission(
|
||||||
|
name=_("Can administrate members"),
|
||||||
|
codename="administrate_members",
|
||||||
|
app_label="membership",
|
||||||
|
model="membership",
|
||||||
|
)
|
60
src/membership/selectors.py
Normal file
60
src/membership/selectors.py
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import contextlib
|
||||||
|
|
||||||
|
from django.db.models import Exists
|
||||||
|
from django.db.models import OuterRef
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from membership.models import Member
|
||||||
|
from membership.models import Membership
|
||||||
|
from membership.models import SubscriptionPeriod
|
||||||
|
|
||||||
|
|
||||||
|
def get_subscription_periods(member: Member | None = None) -> list[SubscriptionPeriod]:
|
||||||
|
subscription_periods = SubscriptionPeriod.objects.prefetch_related(
|
||||||
|
"membership_set",
|
||||||
|
"membership_set__user",
|
||||||
|
).all()
|
||||||
|
|
||||||
|
if member:
|
||||||
|
subscription_periods = subscription_periods.annotate(
|
||||||
|
membership_exists=Exists(
|
||||||
|
Membership.objects.filter(
|
||||||
|
user=member,
|
||||||
|
period=OuterRef("pk"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).filter(membership_exists=True)
|
||||||
|
|
||||||
|
return list(subscription_periods)
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_subscription_period() -> SubscriptionPeriod | None:
|
||||||
|
with contextlib.suppress(SubscriptionPeriod.DoesNotExist):
|
||||||
|
return SubscriptionPeriod.objects.prefetch_related(
|
||||||
|
"membership_set",
|
||||||
|
"membership_set__user",
|
||||||
|
).get(period__contains=timezone.now())
|
||||||
|
|
||||||
|
|
||||||
|
def get_memberships(
|
||||||
|
*,
|
||||||
|
member: Member | None = None,
|
||||||
|
period: SubscriptionPeriod | None = None,
|
||||||
|
) -> Membership.QuerySet:
|
||||||
|
memberships = Membership.objects.select_related("membership_type").all()
|
||||||
|
|
||||||
|
if member:
|
||||||
|
memberships = memberships.for_member(member=member)
|
||||||
|
|
||||||
|
if period:
|
||||||
|
memberships = memberships.filter(period=period)
|
||||||
|
|
||||||
|
return memberships
|
||||||
|
|
||||||
|
|
||||||
|
def get_members():
|
||||||
|
return Member.objects.all().annotate_membership().order_by("username")
|
||||||
|
|
||||||
|
|
||||||
|
def get_member(*, member_id: int) -> Member:
|
||||||
|
return get_members().get(id=member_id)
|
|
@ -0,0 +1,45 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block head_title %}
|
||||||
|
{% trans "Member detail" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="content-view">
|
||||||
|
<h1>
|
||||||
|
{{ member.username }}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h3>{% trans "Membership" %}</h3>
|
||||||
|
|
||||||
|
{% if subscription_periods %}
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Start" %}</th>
|
||||||
|
<th>{% trans "End" %}</th>
|
||||||
|
<th>{% trans "Has membership" %}</th>
|
||||||
|
<th>{% trans "Actions" %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for period in subscription_periods %}
|
||||||
|
<tr {% if not period.period.upper %}class="table-active"{% endif %}>
|
||||||
|
<td>{{ period.period.lower }}</td>
|
||||||
|
<td>{{ period.period.upper }}</td>
|
||||||
|
<td>{{ period.membership_exists }}</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
{% trans "No memberships" %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
63
src/membership/templates/membership/membership_overview.html
Normal file
63
src/membership/templates/membership/membership_overview.html
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block head_title %}
|
||||||
|
{% trans "Membership" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="content-view">
|
||||||
|
<h2>Membership settings</h2>
|
||||||
|
{% if not current_membership %}
|
||||||
|
<p>{% trans "You do not have an active membership!" %}</p>
|
||||||
|
|
||||||
|
<p>{% trans "You can become a member by depositing the membership fee to our bank account." %}</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Reg. 8401 (Merkur)</li>
|
||||||
|
<li>Kontonr. 1016866</li>
|
||||||
|
<li>Tekst på overførslen: Your email</li>
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<p>{% trans "You are a member!" %}</p>
|
||||||
|
|
||||||
|
{% trans "next general assembly" as next_general_assembly %}
|
||||||
|
<p>{% trans "Period" %}: {{ current_period.lower|date:"SHORT_DATE_FORMAT" }} to {{ current_period.upper|date:"SHORT_DATE_FORMAT"|default:next_general_assembly }}</p>
|
||||||
|
<p>{% trans "Type" %}: {{ current_membership.membership_type }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-view">
|
||||||
|
<h2>Profile settings</h2>
|
||||||
|
<form>
|
||||||
|
<div>
|
||||||
|
<label for="username">
|
||||||
|
Username
|
||||||
|
</label>
|
||||||
|
<input id="username" type="text" value="{{user}}" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="first_name">
|
||||||
|
First name
|
||||||
|
</label>
|
||||||
|
<input id="first_name" type="text" value="{{user.first_name}}" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="last_name">
|
||||||
|
Last name
|
||||||
|
</label>
|
||||||
|
<input id="last_name" type="text" value="{{user.last_name}}" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button>Update Profile</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="view-list">
|
||||||
|
<h2>Email settings</h2>
|
||||||
|
<button>Update Email</button>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
80
src/membership/views.py
Normal file
80
src/membership/views.py
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.contrib.auth.decorators import permission_required
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from .permissions import ADMINISTRATE_MEMBERS
|
||||||
|
from .selectors import get_member
|
||||||
|
from .selectors import get_members
|
||||||
|
from .selectors import get_memberships
|
||||||
|
from .selectors import get_subscription_periods
|
||||||
|
from utils.view_utils import render
|
||||||
|
from utils.view_utils import render_list
|
||||||
|
from utils.view_utils import RowAction
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def membership_overview(request):
|
||||||
|
memberships = get_memberships(member=request.user)
|
||||||
|
current_membership = memberships.current()
|
||||||
|
previous_memberships = memberships.previous()
|
||||||
|
|
||||||
|
current_period = current_membership.period.period if current_membership else None
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"current_membership": current_membership,
|
||||||
|
"current_period": current_period,
|
||||||
|
"previous_memberships": previous_memberships,
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request=request,
|
||||||
|
template_name="membership/membership_overview.html",
|
||||||
|
context=context,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@permission_required(ADMINISTRATE_MEMBERS.path)
|
||||||
|
def members_admin(request):
|
||||||
|
users = get_members()
|
||||||
|
|
||||||
|
return render_list(
|
||||||
|
entity_name="member",
|
||||||
|
entity_name_plural="members",
|
||||||
|
request=request,
|
||||||
|
paginate_by=20,
|
||||||
|
objects=users,
|
||||||
|
columns=[
|
||||||
|
("username", _("Username")),
|
||||||
|
("first_name", _("First name")),
|
||||||
|
("last_name", _("Last name")),
|
||||||
|
("email", _("Email")),
|
||||||
|
("active_membership", _("Active membership")),
|
||||||
|
],
|
||||||
|
row_actions=[
|
||||||
|
RowAction(
|
||||||
|
label=_("View"),
|
||||||
|
url_name="admin-members-detail",
|
||||||
|
url_kwargs={"member_id": "id"},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@permission_required(ADMINISTRATE_MEMBERS.path)
|
||||||
|
def members_admin_detail(request, member_id):
|
||||||
|
member = get_member(member_id=member_id)
|
||||||
|
subscription_periods = get_subscription_periods(member=member)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"member": member,
|
||||||
|
"subscription_periods": subscription_periods,
|
||||||
|
"base_path": "admin-members",
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request=request,
|
||||||
|
template_name="membership/members_admin_detail.html",
|
||||||
|
context=context,
|
||||||
|
)
|
7
src/project/asgi.py
Normal file
7
src/project/asgi.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
519
src/project/locale/da/LC_MESSAGES/django.po
Normal file
519
src/project/locale/da/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,519 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2021-03-04 09:06+0100\n"
|
||||||
|
"PO-Revision-Date: 2021-03-04 09:06+0100\n"
|
||||||
|
"Last-Translator: \n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"Language: da\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
"X-Generator: Poedit 2.4.1\n"
|
||||||
|
|
||||||
|
#: src/accounting/admin.py:15 src/accounting/admin.py:26
|
||||||
|
msgid "Customer"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/accounting/admin.py:31
|
||||||
|
msgid "Order ID"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/accounting/models.py:13 src/membership/models.py:11
|
||||||
|
msgid "modified"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/accounting/models.py:14 src/membership/models.py:12
|
||||||
|
msgid "created"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/accounting/models.py:43
|
||||||
|
msgid "amount"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/accounting/models.py:46
|
||||||
|
msgid "This will include VAT"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/accounting/models.py:48 src/accounting/models.py:61
|
||||||
|
#: src/accounting/models.py:100
|
||||||
|
msgid "description"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/accounting/models.py:64
|
||||||
|
msgid "price (excl. VAT)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/accounting/models.py:66
|
||||||
|
msgid "VAT"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/accounting/models.py:68
|
||||||
|
msgid "is paid"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/accounting/models.py:88
|
||||||
|
msgctxt "accounting term"
|
||||||
|
msgid "Order"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/accounting/models.py:89
|
||||||
|
msgctxt "accounting term"
|
||||||
|
msgid "Orders"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/accounting/models.py:121
|
||||||
|
msgid "payment"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/accounting/models.py:122
|
||||||
|
msgid "payments"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/membership/models.py:45
|
||||||
|
msgid "membership"
|
||||||
|
msgstr "medlemskab"
|
||||||
|
|
||||||
|
#: src/membership/models.py:46
|
||||||
|
msgid "memberships"
|
||||||
|
msgstr "medlemskaber"
|
||||||
|
|
||||||
|
#: src/membership/models.py:53
|
||||||
|
msgid "subscription type"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/membership/models.py:57
|
||||||
|
msgid "The duration this subscription is for. "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/membership/models.py:70
|
||||||
|
msgid "membership type"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/membership/models.py:71
|
||||||
|
msgid "membership types"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/membership/models.py:73
|
||||||
|
msgid "name"
|
||||||
|
msgstr "navn"
|
||||||
|
|
||||||
|
#: src/membership/templates/membership_overview.html:7
|
||||||
|
msgid "You do not have an active membership!"
|
||||||
|
msgstr "Du har ikke et aktivt medlemskab!"
|
||||||
|
|
||||||
|
#: src/membership/templates/membership_overview.html:9
|
||||||
|
msgid ""
|
||||||
|
"You can become a member by depositing the membership fee to our bank account."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/membership/templates/membership_overview.html:17
|
||||||
|
msgid "You are a member!"
|
||||||
|
msgstr "Du er medlem!"
|
||||||
|
|
||||||
|
#: src/membership/templates/membership_overview.html:19
|
||||||
|
msgid "Period"
|
||||||
|
msgstr "Periode"
|
||||||
|
|
||||||
|
#: src/membership/templates/membership_overview.html:20
|
||||||
|
msgid "Type"
|
||||||
|
msgstr "Type"
|
||||||
|
|
||||||
|
#: src/project/settings.py:131
|
||||||
|
msgid "Danish"
|
||||||
|
msgstr "Dansk"
|
||||||
|
|
||||||
|
#: src/project/settings.py:132
|
||||||
|
msgid "English"
|
||||||
|
msgstr "Engelsk"
|
||||||
|
|
||||||
|
#: src/project/templates/account/account_inactive.html:5
|
||||||
|
#: src/project/templates/account/account_inactive.html:8
|
||||||
|
msgid "Account Inactive"
|
||||||
|
msgstr "Inaktiv konto"
|
||||||
|
|
||||||
|
#: src/project/templates/account/account_inactive.html:10
|
||||||
|
msgid "This account is inactive."
|
||||||
|
msgstr "Denne konto er inaktiv."
|
||||||
|
|
||||||
|
#: src/project/templates/account/email.html:5
|
||||||
|
#: src/project/templates/account/email.html:16
|
||||||
|
msgid "E-mail Addresses"
|
||||||
|
msgstr "E-mail adresser"
|
||||||
|
|
||||||
|
#: src/project/templates/account/email.html:21
|
||||||
|
msgid "The following e-mail addresses are associated with your account:"
|
||||||
|
msgstr "De følgende e-mail adresser er tilknyttet din konto:"
|
||||||
|
|
||||||
|
#: src/project/templates/account/email.html:31
|
||||||
|
msgid "Address"
|
||||||
|
msgstr "Adresse"
|
||||||
|
|
||||||
|
#: src/project/templates/account/email.html:32
|
||||||
|
msgid "Status"
|
||||||
|
msgstr "Status"
|
||||||
|
|
||||||
|
#: src/project/templates/account/email.html:33
|
||||||
|
msgid "Primary"
|
||||||
|
msgstr "Primær"
|
||||||
|
|
||||||
|
#: src/project/templates/account/email.html:90
|
||||||
|
msgid "Warning:"
|
||||||
|
msgstr "Advarsel:"
|
||||||
|
|
||||||
|
#: src/project/templates/account/email.html:91
|
||||||
|
msgid ""
|
||||||
|
"You currently do not have any e-mail address set up. You should really add "
|
||||||
|
"an e-mail address so you can receive notifications, reset your password, etc."
|
||||||
|
msgstr ""
|
||||||
|
"Du har lige nu ingen e-mail adresse tilknyttet. Du burde virkelig tilføje en "
|
||||||
|
"e-mail adresse så du kan modtage notifikationer, nulstille dit kodeord, osv."
|
||||||
|
|
||||||
|
#: src/project/templates/account/email.html:99
|
||||||
|
#: src/project/templates/account/email.html:107
|
||||||
|
msgid "Add E-mail"
|
||||||
|
msgstr "Tilføj e-mail"
|
||||||
|
|
||||||
|
#: src/project/templates/account/email.html:118
|
||||||
|
msgid "Do you really want to remove the selected e-mail address?"
|
||||||
|
msgstr "Vil du virkelig fjerne den valgte e-mail?"
|
||||||
|
|
||||||
|
#: src/project/templates/account/email/base_message.txt:1
|
||||||
|
#, python-format
|
||||||
|
msgid "Hello from %(site_name)s!"
|
||||||
|
msgstr "Hej fra %(site_name)s!"
|
||||||
|
|
||||||
|
#: src/project/templates/account/email/base_message.txt:5
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Thank you for using %(site_name)s!\n"
|
||||||
|
"%(site_domain)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/email/email_confirmation_message.txt:5
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"You're receiving this e-mail because user %(user_display)s has given your e-"
|
||||||
|
"mail address to register an account on %(site_domain)s.\n"
|
||||||
|
"\n"
|
||||||
|
"To confirm this is correct, go to %(activate_url)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/email/email_confirmation_subject.txt:3
|
||||||
|
msgid "Please Confirm Your E-mail Address"
|
||||||
|
msgstr "Venligst bekræft din e-mail adresse"
|
||||||
|
|
||||||
|
#: src/project/templates/account/email/password_reset_key_message.txt:4
|
||||||
|
msgid ""
|
||||||
|
"You're receiving this e-mail because you or someone else has requested a "
|
||||||
|
"password for your user account.\n"
|
||||||
|
"It can be safely ignored if you did not request a password reset. Click the "
|
||||||
|
"link below to reset your password."
|
||||||
|
msgstr ""
|
||||||
|
"Du modtager denne e-mail fordi du, eller nogen anden, har anmodet om "
|
||||||
|
"nulstilling af dit kodeord.\n"
|
||||||
|
"Du kan trygt ignorere dette hvis det ikke var dig der anmodede om "
|
||||||
|
"nulstillingen. Klik på linket herunder for at nulstille dit kodeord."
|
||||||
|
|
||||||
|
#: src/project/templates/account/email/password_reset_key_message.txt:9
|
||||||
|
#, python-format
|
||||||
|
msgid "In case you forgot, your username is %(username)s."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/email/password_reset_key_subject.txt:3
|
||||||
|
msgid "Password Reset E-mail"
|
||||||
|
msgstr "Nulstilling af password"
|
||||||
|
|
||||||
|
#: src/project/templates/account/email_confirm.html:6
|
||||||
|
#: src/project/templates/account/email_confirm.html:10
|
||||||
|
msgid "Confirm E-mail Address"
|
||||||
|
msgstr "Bekræft e-mail adresse"
|
||||||
|
|
||||||
|
#: src/project/templates/account/email_confirm.html:16
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Please confirm that <a href=\"mailto:%(email)s\">%(email)s</a> is an e-mail "
|
||||||
|
"address for user %(user_display)s."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/email_confirm.html:20
|
||||||
|
msgid "Confirm"
|
||||||
|
msgstr "Bekræft"
|
||||||
|
|
||||||
|
#: src/project/templates/account/email_confirm.html:27
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"This e-mail confirmation link expired or is invalid. Please <a href="
|
||||||
|
"\"%(email_url)s\">issue a new e-mail confirmation request</a>."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/login.html:20
|
||||||
|
#: src/project/templates/account/login.html:26
|
||||||
|
#: src/project/templates/account/signup.html:26
|
||||||
|
#: src/project/templates/account/signup.html:32
|
||||||
|
msgid "E-mail"
|
||||||
|
msgstr "E-mail"
|
||||||
|
|
||||||
|
#: src/project/templates/account/login.html:31
|
||||||
|
#: src/project/templates/account/login.html:37
|
||||||
|
#: src/project/templates/account/signup.html:44
|
||||||
|
#: src/project/templates/account/signup.html:50
|
||||||
|
msgid "Password"
|
||||||
|
msgstr "Kodeord"
|
||||||
|
|
||||||
|
#: src/project/templates/account/login.html:41
|
||||||
|
msgid "Sign in"
|
||||||
|
msgstr "Log ind"
|
||||||
|
|
||||||
|
#: src/project/templates/account/login.html:45
|
||||||
|
msgid "Forgot password?"
|
||||||
|
msgstr "Glemt kodeord?"
|
||||||
|
|
||||||
|
#: src/project/templates/account/login.html:48
|
||||||
|
msgid "Or"
|
||||||
|
msgstr "Eller"
|
||||||
|
|
||||||
|
#: src/project/templates/account/login.html:53
|
||||||
|
#: src/project/templates/account/signup.html:6
|
||||||
|
msgid "Become a member"
|
||||||
|
msgstr "Bliv medlem"
|
||||||
|
|
||||||
|
#: src/project/templates/account/logout.html:5
|
||||||
|
#: src/project/templates/account/logout.html:8
|
||||||
|
#: src/project/templates/account/logout.html:17
|
||||||
|
msgid "Sign Out"
|
||||||
|
msgstr "Log ud"
|
||||||
|
|
||||||
|
#: src/project/templates/account/logout.html:10
|
||||||
|
msgid "Are you sure you want to sign out?"
|
||||||
|
msgstr "Er du sikker på at du vil logge ind?"
|
||||||
|
|
||||||
|
#: src/project/templates/account/messages/cannot_delete_primary_email.txt:2
|
||||||
|
#, python-format
|
||||||
|
msgid "You cannot remove your primary e-mail address (%(email)s)."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/messages/email_confirmation_sent.txt:2
|
||||||
|
#, python-format
|
||||||
|
msgid "Confirmation e-mail sent to %(email)s."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/messages/email_confirmed.txt:2
|
||||||
|
#, python-format
|
||||||
|
msgid "You have confirmed %(email)s."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/messages/email_deleted.txt:2
|
||||||
|
#, python-format
|
||||||
|
msgid "Removed e-mail address %(email)s."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/messages/logged_in.txt:4
|
||||||
|
#, python-format
|
||||||
|
msgid "Successfully signed in as %(name)s."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/messages/logged_out.txt:2
|
||||||
|
msgid "You have signed out."
|
||||||
|
msgstr "Du er nu logget ud."
|
||||||
|
|
||||||
|
#: src/project/templates/account/messages/password_changed.txt:2
|
||||||
|
msgid "Password successfully changed."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/messages/password_set.txt:2
|
||||||
|
msgid "Password successfully set."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/messages/primary_email_set.txt:2
|
||||||
|
msgid "Primary e-mail address set."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/messages/unverified_primary_email.txt:2
|
||||||
|
msgid "Your primary e-mail address must be verified."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/password_change.html:5
|
||||||
|
#: src/project/templates/account/password_change.html:8
|
||||||
|
#: src/project/templates/account/password_change.html:13
|
||||||
|
#: src/project/templates/account/password_reset_from_key.html:4
|
||||||
|
#: src/project/templates/account/password_reset_from_key.html:7
|
||||||
|
#: src/project/templates/account/password_reset_from_key_done.html:4
|
||||||
|
#: src/project/templates/account/password_reset_from_key_done.html:7
|
||||||
|
msgid "Change Password"
|
||||||
|
msgstr "Skift kodeord"
|
||||||
|
|
||||||
|
#: src/project/templates/account/password_change.html:14
|
||||||
|
msgid "Forgot Password?"
|
||||||
|
msgstr "Glemt kodeord?"
|
||||||
|
|
||||||
|
#: src/project/templates/account/password_reset.html:6
|
||||||
|
#: src/project/templates/account/password_reset.html:10
|
||||||
|
#: src/project/templates/account/password_reset_done.html:6
|
||||||
|
#: src/project/templates/account/password_reset_done.html:9
|
||||||
|
msgid "Password Reset"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/password_reset.html:16
|
||||||
|
msgid ""
|
||||||
|
"Forgotten your password? Enter your e-mail address below, and we'll send you "
|
||||||
|
"an e-mail allowing you to reset it."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/password_reset.html:21
|
||||||
|
#: src/project/templates/account/password_reset.html:27
|
||||||
|
msgid "E-mail address"
|
||||||
|
msgstr "E-mail adresser"
|
||||||
|
|
||||||
|
#: src/project/templates/account/password_reset.html:38
|
||||||
|
msgid "Reset My Password"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/password_reset.html:41
|
||||||
|
msgid "Please contact us if you have any trouble resetting your password."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/password_reset.html:47
|
||||||
|
#: src/project/templates/account/signup.html:72
|
||||||
|
msgid "To login"
|
||||||
|
msgstr "Til login"
|
||||||
|
|
||||||
|
#: src/project/templates/account/password_reset_done.html:15
|
||||||
|
msgid ""
|
||||||
|
"We have sent you an e-mail. Please contact us if you do not receive it "
|
||||||
|
"within a few minutes."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/password_reset_from_key.html:7
|
||||||
|
msgid "Bad Token"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/password_reset_from_key.html:11
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"The password reset link was invalid, possibly because it has already been "
|
||||||
|
"used. Please request a <a href=\"%(passwd_reset_url)s\">new password reset</"
|
||||||
|
"a>."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/password_reset_from_key.html:17
|
||||||
|
msgid "change password"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/password_reset_from_key.html:20
|
||||||
|
#: src/project/templates/account/password_reset_from_key_done.html:8
|
||||||
|
msgid "Your password is now changed."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/password_set.html:5
|
||||||
|
#: src/project/templates/account/password_set.html:8
|
||||||
|
#: src/project/templates/account/password_set.html:13
|
||||||
|
msgid "Set Password"
|
||||||
|
msgstr "Sæt kodeord"
|
||||||
|
|
||||||
|
#: src/project/templates/account/signup.html:19
|
||||||
|
msgid "To become a member, you need to have an account. Create one here."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/signup.html:66
|
||||||
|
msgid "Sign up"
|
||||||
|
msgstr "Bliv medlem"
|
||||||
|
|
||||||
|
#: src/project/templates/account/signup_closed.html:5
|
||||||
|
#: src/project/templates/account/signup_closed.html:8
|
||||||
|
msgid "Sign Up Closed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/signup_closed.html:10
|
||||||
|
msgid "We are sorry, but the sign up is currently closed."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/snippets/already_logged_in.html:5
|
||||||
|
msgid "Note"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/snippets/already_logged_in.html:5
|
||||||
|
#, python-format
|
||||||
|
msgid "you are already logged in as %(user_display)s."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/verification_sent.html:5
|
||||||
|
#: src/project/templates/account/verification_sent.html:8
|
||||||
|
#: src/project/templates/account/verified_email_required.html:5
|
||||||
|
#: src/project/templates/account/verified_email_required.html:8
|
||||||
|
msgid "Verify Your E-mail Address"
|
||||||
|
msgstr "Verificér din e-mail adresse"
|
||||||
|
|
||||||
|
#: src/project/templates/account/verification_sent.html:10
|
||||||
|
msgid ""
|
||||||
|
"We have sent an e-mail to you for verification. Follow the link provided to "
|
||||||
|
"finalize the signup process. Please contact us if you do not receive it "
|
||||||
|
"within a few minutes."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/verified_email_required.html:12
|
||||||
|
msgid ""
|
||||||
|
"This part of the site requires us to verify that\n"
|
||||||
|
"you are who you claim to be. For this purpose, we require that you\n"
|
||||||
|
"verify ownership of your e-mail address. "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/verified_email_required.html:16
|
||||||
|
msgid ""
|
||||||
|
"We have sent an e-mail to you for\n"
|
||||||
|
"verification. Please click on the link inside this e-mail. Please\n"
|
||||||
|
"contact us if you do not receive it within a few minutes."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/account/verified_email_required.html:20
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"<strong>Note:</strong> you can still <a href=\"%(email_url)s\">change your e-"
|
||||||
|
"mail address</a>."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/base.html:140
|
||||||
|
msgid "Dashboard"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/project/templates/base.html:146
|
||||||
|
msgid "Profile"
|
||||||
|
msgstr "Profil"
|
||||||
|
|
||||||
|
#: src/project/templates/base.html:152
|
||||||
|
msgid "Details"
|
||||||
|
msgstr "Detaljer"
|
||||||
|
|
||||||
|
#: src/project/templates/base.html:158
|
||||||
|
msgid "Emails"
|
||||||
|
msgstr "Emails"
|
||||||
|
|
||||||
|
#: src/project/templates/base.html:164
|
||||||
|
msgid "Membership"
|
||||||
|
msgstr "Medlemskab"
|
||||||
|
|
||||||
|
#: src/project/templates/base.html:171 src/project/templates/base.html:184
|
||||||
|
msgid "Overview"
|
||||||
|
msgstr "Oversigt"
|
||||||
|
|
||||||
|
#: src/project/templates/base.html:177
|
||||||
|
msgid "Services"
|
||||||
|
msgstr "Tjenester"
|
||||||
|
|
||||||
|
#: src/project/templates/base.html:191
|
||||||
|
msgid "Admin"
|
||||||
|
msgstr "Admin"
|
||||||
|
|
||||||
|
#: src/project/templates/base.html:197
|
||||||
|
msgid "Members"
|
||||||
|
msgstr "Medlemmer"
|
||||||
|
|
||||||
|
#~ msgid "OR"
|
||||||
|
#~ msgstr "Eller"
|
167
src/project/settings.py
Normal file
167
src/project/settings.py
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from environs import Env
|
||||||
|
|
||||||
|
env = Env()
|
||||||
|
env.read_env()
|
||||||
|
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
PROJECT_DIR = BASE_DIR / "project"
|
||||||
|
|
||||||
|
SECRET_KEY = env.str("SECRET_KEY", default="something-very-secret")
|
||||||
|
|
||||||
|
DEBUG = env.bool("DEBUG", default=False)
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = env.list("ALLOWED_HOSTS", default=["*"])
|
||||||
|
CSRF_TRUSTED_ORIGINS = env.list(
|
||||||
|
"CSRF_TRUSTED_ORIGINS",
|
||||||
|
default=["http://localhost:8000"],
|
||||||
|
)
|
||||||
|
|
||||||
|
ADMINS = [tuple(x.split(":")) for x in env.list("DJANGO_ADMINS", default=[])]
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
DJANGO_APPS = [
|
||||||
|
"django.contrib.admin",
|
||||||
|
"django.contrib.auth",
|
||||||
|
"django.contrib.contenttypes",
|
||||||
|
"django.contrib.sessions",
|
||||||
|
"django.contrib.messages",
|
||||||
|
"django.contrib.staticfiles",
|
||||||
|
"django.contrib.sites",
|
||||||
|
]
|
||||||
|
|
||||||
|
THIRD_PARTY_APPS = [
|
||||||
|
"allauth",
|
||||||
|
"allauth.account",
|
||||||
|
]
|
||||||
|
|
||||||
|
LOCAL_APPS = [
|
||||||
|
"utils",
|
||||||
|
"accounting",
|
||||||
|
"membership",
|
||||||
|
]
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
*DJANGO_APPS,
|
||||||
|
*THIRD_PARTY_APPS,
|
||||||
|
*LOCAL_APPS,
|
||||||
|
]
|
||||||
|
|
||||||
|
DATABASES = {"default": env.dj_db_url("DATABASE_URL")}
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
"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",
|
||||||
|
"allauth.account.middleware.AccountMiddleware",
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = "project.urls"
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
|
"DIRS": [PROJECT_DIR / "templates"],
|
||||||
|
"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",
|
||||||
|
],
|
||||||
|
"builtins": [
|
||||||
|
"django.templatetags.i18n",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
AUTHENTICATION_BACKENDS = (
|
||||||
|
"django.contrib.auth.backends.ModelBackend",
|
||||||
|
"allauth.account.auth_backends.AuthenticationBackend",
|
||||||
|
)
|
||||||
|
|
||||||
|
WSGI_APPLICATION = "project.wsgi.application"
|
||||||
|
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = []
|
||||||
|
|
||||||
|
LANGUAGE_CODE = "da-dk"
|
||||||
|
|
||||||
|
TIME_ZONE = "Europe/Copenhagen"
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
STATIC_URL = "/static/"
|
||||||
|
STATICFILES_DIRS = [PROJECT_DIR / "static"]
|
||||||
|
STATIC_ROOT = BASE_DIR / "static"
|
||||||
|
|
||||||
|
SITE_ID = 1
|
||||||
|
|
||||||
|
LOGIN_REDIRECT_URL = "/"
|
||||||
|
|
||||||
|
EMAIL_BACKEND = env.str(
|
||||||
|
"EMAIL_BACKEND",
|
||||||
|
default="django.core.mail.backends.console.EmailBackend",
|
||||||
|
)
|
||||||
|
DEFAULT_FROM_EMAIL = env.str("DEFAULT_FROM_EMAIL", default="")
|
||||||
|
# Parse email URLs, e.g. "smtp://"
|
||||||
|
email = env.dj_email_url("EMAIL_URL", default="smtp://")
|
||||||
|
EMAIL_HOST = email["EMAIL_HOST"]
|
||||||
|
EMAIL_PORT = email["EMAIL_PORT"]
|
||||||
|
EMAIL_HOST_PASSWORD = email["EMAIL_HOST_PASSWORD"]
|
||||||
|
EMAIL_HOST_USER = email["EMAIL_HOST_USER"]
|
||||||
|
EMAIL_USE_TLS = email["EMAIL_USE_TLS"]
|
||||||
|
|
||||||
|
# Always show DDT in development for any IP, not just 127.0.0.1 or
|
||||||
|
# settings.INTERNAL_IPS. This is useful in a docker setup where the
|
||||||
|
# requesting IP isn't static.
|
||||||
|
DEBUG_TOOLBAR_CONFIG = {
|
||||||
|
"SHOW_TOOLBAR_CALLBACK": lambda _x: DEBUG,
|
||||||
|
}
|
||||||
|
|
||||||
|
CURRENCIES = ("DKK",)
|
||||||
|
CURRENCY_CHOICES = [("DKK", "DKK")]
|
||||||
|
|
||||||
|
LANGUAGES = [
|
||||||
|
("da", _("Danish")),
|
||||||
|
("en", _("English")),
|
||||||
|
]
|
||||||
|
|
||||||
|
# We store all translations in one location
|
||||||
|
LOCALE_PATHS = [PROJECT_DIR / "locale"]
|
||||||
|
|
||||||
|
# Allauth configuration
|
||||||
|
ACCOUNT_AUTHENTICATION_METHOD = "email"
|
||||||
|
ACCOUNT_EMAIL_REQUIRED = True
|
||||||
|
ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE = False
|
||||||
|
ACCOUNT_USERNAME_REQUIRED = False
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
INSTALLED_APPS += ["debug_toolbar", "django_browser_reload"]
|
||||||
|
MIDDLEWARE += [
|
||||||
|
"debug_toolbar.middleware.DebugToolbarMiddleware",
|
||||||
|
"django_browser_reload.middleware.BrowserReloadMiddleware",
|
||||||
|
]
|
||||||
|
# Always show DDT in development for any IP, not just 127.0.0.1 or
|
||||||
|
# settings.INTERNAL_IPS. This is useful in a docker setup where the
|
||||||
|
# requesting IP isn't static.
|
||||||
|
DEBUG_TOOLBAR_CONFIG = {
|
||||||
|
"SHOW_TOOLBAR_CALLBACK": lambda _x: DEBUG,
|
||||||
|
}
|
2018
src/project/static/css/bootstrap-icons.css
vendored
Normal file
2018
src/project/static/css/bootstrap-icons.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
7
src/project/static/css/bootstrap.min.css
vendored
Normal file
7
src/project/static/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/project/static/css/bootstrap.min.css.map
Normal file
1
src/project/static/css/bootstrap.min.css.map
Normal file
File diff suppressed because one or more lines are too long
BIN
src/project/static/css/fonts/bootstrap-icons.woff
Normal file
BIN
src/project/static/css/fonts/bootstrap-icons.woff
Normal file
Binary file not shown.
BIN
src/project/static/css/fonts/bootstrap-icons.woff2
Normal file
BIN
src/project/static/css/fonts/bootstrap-icons.woff2
Normal file
Binary file not shown.
384
src/project/static/css/style.css
Normal file
384
src/project/static/css/style.css
Normal file
|
@ -0,0 +1,384 @@
|
||||||
|
/* Reset */
|
||||||
|
*, *::before, *::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
img, picture, video, canvas, svg {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
input, button, textarea, select {
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
p, h1, h2, h3, h4, h5, h6 {
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
#root, #__next {
|
||||||
|
isolation: isolate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Variables */
|
||||||
|
:root {
|
||||||
|
/* Colors */
|
||||||
|
--light : #fff;
|
||||||
|
--light-dust : #f6f6f6;
|
||||||
|
--dust : #f1f1f1;
|
||||||
|
--medium-dust : #dadada;
|
||||||
|
--dark-dust : #bfbfbf;
|
||||||
|
--fade : #878787;
|
||||||
|
--twilight : #4a4a4a;
|
||||||
|
--dark : #2a2a2a;
|
||||||
|
--custard : #f0dcac;
|
||||||
|
--water : #a8f3f4;
|
||||||
|
--splash : #4b3aba;
|
||||||
|
|
||||||
|
/* Sizes */
|
||||||
|
--space : 12px;
|
||||||
|
--double-space : calc(var(--space) * 2);
|
||||||
|
--half-space : calc(var(--space) / 2);
|
||||||
|
--quarter-space : calc(var(--space) / 4);
|
||||||
|
--outer-space : var(--double-space);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1380px) {
|
||||||
|
:root {
|
||||||
|
--outer-space : 15%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
height : 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-weight : 600;
|
||||||
|
color : var(--twilight);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-weight : 500;
|
||||||
|
color : var(--splash);
|
||||||
|
text-decoration : none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin : 0;
|
||||||
|
padding : 0;
|
||||||
|
background : var(--custard);
|
||||||
|
font-family : Inter;
|
||||||
|
font-weight : 400;
|
||||||
|
line-height : 1.6;
|
||||||
|
color : var(--dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
display : flex;
|
||||||
|
padding : var(--double-space) var(--outer-space);
|
||||||
|
background : var(--light);
|
||||||
|
justify-content : space-between;
|
||||||
|
align-items : center;
|
||||||
|
}
|
||||||
|
|
||||||
|
header > h1 {
|
||||||
|
font-size : 1.44em;
|
||||||
|
}
|
||||||
|
|
||||||
|
header > a.logout {
|
||||||
|
padding : 6px 12px;
|
||||||
|
border-radius : 6px;
|
||||||
|
background : var(--twilight);
|
||||||
|
text-decoration : none;
|
||||||
|
color : var(--dust);
|
||||||
|
}
|
||||||
|
|
||||||
|
header > a.logout:hover {
|
||||||
|
background : var(--splash);
|
||||||
|
color : var(--light);
|
||||||
|
}
|
||||||
|
|
||||||
|
aside {
|
||||||
|
padding : var(--double-space) var(--outer-space);
|
||||||
|
background : var(--light);
|
||||||
|
}
|
||||||
|
|
||||||
|
aside > div {
|
||||||
|
background : var(--dust);
|
||||||
|
padding : var(--double-space);
|
||||||
|
border-radius : var(--quarter-space);
|
||||||
|
overflow : hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
aside > div > h2 {
|
||||||
|
font-size : 1.22em;
|
||||||
|
margin : 0 0 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
aside > div > figure {
|
||||||
|
width : 100px;
|
||||||
|
height : 100px;
|
||||||
|
border : 1px solid var(--dark-dust);
|
||||||
|
float : left;
|
||||||
|
margin-right : var(--double-space);
|
||||||
|
}
|
||||||
|
|
||||||
|
aside > div > dl {
|
||||||
|
overflow : hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
aside > div > dl > dt {
|
||||||
|
float : left;
|
||||||
|
clear : left;
|
||||||
|
margin : 0 var(--double-space) 0 0;
|
||||||
|
width : 180px;
|
||||||
|
font-weight : 600;
|
||||||
|
color : var(--twilight);
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
display : block;
|
||||||
|
border-bottom : 1px solid var(--dark-dust);
|
||||||
|
background : var(--light);
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ol {
|
||||||
|
margin: 0 calc(var(--outer-space));
|
||||||
|
padding : 0;
|
||||||
|
list-style-type : none;
|
||||||
|
overflow : hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ol li {
|
||||||
|
margin : 0;
|
||||||
|
padding : 0;
|
||||||
|
float : left;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav > ol > li > a {
|
||||||
|
display : block;
|
||||||
|
padding : var(--half-space) var(--half-space) var(--quarter-space);
|
||||||
|
margin : 0 var(--space);
|
||||||
|
border-bottom : var(--half-space) solid transparent;
|
||||||
|
text-decoration : none;
|
||||||
|
color : var(--dark);
|
||||||
|
cursor : pointer;
|
||||||
|
font-weight : 500;
|
||||||
|
letter-spacing : 0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav > ol > li:first-child > a {
|
||||||
|
margin-left : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ol li a:hover {
|
||||||
|
border-color : rgba(0,0,0,0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ol li a.current {
|
||||||
|
font-weight : bold;
|
||||||
|
border-color : var(--splash);
|
||||||
|
color : var(--splash);
|
||||||
|
}
|
||||||
|
|
||||||
|
article {
|
||||||
|
padding : var(--double-space) var(--outer-space);
|
||||||
|
}
|
||||||
|
|
||||||
|
article > div {
|
||||||
|
background : var(--dust);
|
||||||
|
padding : var(--double-space);
|
||||||
|
margin-bottom : var(--double-space);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.content-view > h2 {
|
||||||
|
margin : 0 0 var(--double-space) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.services {
|
||||||
|
display : flex;
|
||||||
|
justify-content : space-between;
|
||||||
|
gap : var(--double-space);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.services > div,
|
||||||
|
div.infobox {
|
||||||
|
background : var(--light);
|
||||||
|
padding : var(--double-space);
|
||||||
|
border-radius : 6px;
|
||||||
|
flex : 240px;
|
||||||
|
max-width : 420px;
|
||||||
|
display : flex;
|
||||||
|
flex-flow : column;
|
||||||
|
justify-content : space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.infobox button {
|
||||||
|
margin-top : var(--double-space);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.services > div > div.description {
|
||||||
|
margin-bottom : var(--double-space);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.services > div > div.description > p {
|
||||||
|
margin-top : var(--half-space);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.services > div > a,
|
||||||
|
a.button,
|
||||||
|
button {
|
||||||
|
display : block;
|
||||||
|
color : var(--light);
|
||||||
|
background : var(--splash);
|
||||||
|
padding : var(--space) var(--double-space);
|
||||||
|
border-radius : 3px;
|
||||||
|
opacity : 0.9;
|
||||||
|
cursor : pointer;
|
||||||
|
text-align : center;
|
||||||
|
border : 0;
|
||||||
|
font-weight : 600;
|
||||||
|
letter-spacing : 0.03em;
|
||||||
|
text-decoration : none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.services > div > a:hover,
|
||||||
|
a.button:hover,
|
||||||
|
button:hover {
|
||||||
|
opacity : 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
article table {
|
||||||
|
width : 100%;
|
||||||
|
border-spacing : 0;
|
||||||
|
margin : var(--space) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
article table thead th {
|
||||||
|
text-align : left;
|
||||||
|
}
|
||||||
|
|
||||||
|
article table tbody tr:nth-child(odd) {
|
||||||
|
background : var(--medium-dust);
|
||||||
|
}
|
||||||
|
|
||||||
|
article table thead th,
|
||||||
|
article table tbody td {
|
||||||
|
padding : var(--half-space);
|
||||||
|
}
|
||||||
|
|
||||||
|
form > div {
|
||||||
|
margin : 0 0 var(--double-space);
|
||||||
|
}
|
||||||
|
|
||||||
|
form > div >label {
|
||||||
|
display : block;
|
||||||
|
margin : 0 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
form > div > input[type="text"],
|
||||||
|
form > div > input[type="password"] {
|
||||||
|
border : 2px solid var(--twilight);
|
||||||
|
border-radius : 6px;
|
||||||
|
padding : 8px;
|
||||||
|
background : var(--light-dust);
|
||||||
|
width : 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#login {
|
||||||
|
height : 100%;
|
||||||
|
display : flex;
|
||||||
|
align-items : center;
|
||||||
|
justify-content : center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loginbox {
|
||||||
|
border-radius : var(--space);
|
||||||
|
border : 6px solid white;
|
||||||
|
width : 800px;
|
||||||
|
height : 500px;
|
||||||
|
|
||||||
|
display : flex;
|
||||||
|
flex-flow : row;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loginbox > div {
|
||||||
|
padding : var(--double-space);
|
||||||
|
flex : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loginbox label {
|
||||||
|
color : var(--twilight);
|
||||||
|
}
|
||||||
|
|
||||||
|
#loginbox > div.login {
|
||||||
|
background : var(--light-dust);
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loginbox > div.signup {
|
||||||
|
background : var(--water);
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loginbox > div:first-child {
|
||||||
|
border-radius : var(--half-space) 0 0 var(--half-space);
|
||||||
|
}
|
||||||
|
|
||||||
|
#loginbox > div:last-child {
|
||||||
|
border-radius : 0 var(--half-space) var(--half-space) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loginbox > div:last-child > * {
|
||||||
|
flex : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loginbox div.new_here {
|
||||||
|
margin-top : var(--double-space);
|
||||||
|
}
|
||||||
|
|
||||||
|
#loginbox div.new_here h2 {
|
||||||
|
margin: var(--double-space) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loginbox button {
|
||||||
|
width : 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loginbox img {
|
||||||
|
padding : 0 var(--double-space);
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin : var(--space) var(--outer-space);
|
||||||
|
padding : var(--space);
|
||||||
|
border-radius : var(--quarter-space);
|
||||||
|
background : var(--dark);
|
||||||
|
color : var(--dust);
|
||||||
|
font-size : 0.78em;
|
||||||
|
opacity : 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.time_remaining {
|
||||||
|
color : var(--fade);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
display : flex;
|
||||||
|
justify-content : center;
|
||||||
|
list-style : none;
|
||||||
|
padding : 0;
|
||||||
|
margin : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination > li {
|
||||||
|
margin : 0 6px;
|
||||||
|
}
|
BIN
src/project/static/fonts/Inter-Black.woff
Normal file
BIN
src/project/static/fonts/Inter-Black.woff
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-Black.woff2
Normal file
BIN
src/project/static/fonts/Inter-Black.woff2
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-BlackItalic.woff
Normal file
BIN
src/project/static/fonts/Inter-BlackItalic.woff
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-BlackItalic.woff2
Normal file
BIN
src/project/static/fonts/Inter-BlackItalic.woff2
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-Bold.woff
Normal file
BIN
src/project/static/fonts/Inter-Bold.woff
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-Bold.woff2
Normal file
BIN
src/project/static/fonts/Inter-Bold.woff2
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-BoldItalic.woff
Normal file
BIN
src/project/static/fonts/Inter-BoldItalic.woff
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-BoldItalic.woff2
Normal file
BIN
src/project/static/fonts/Inter-BoldItalic.woff2
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-ExtraBold.woff
Normal file
BIN
src/project/static/fonts/Inter-ExtraBold.woff
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-ExtraBold.woff2
Normal file
BIN
src/project/static/fonts/Inter-ExtraBold.woff2
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-ExtraBoldItalic.woff
Normal file
BIN
src/project/static/fonts/Inter-ExtraBoldItalic.woff
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-ExtraBoldItalic.woff2
Normal file
BIN
src/project/static/fonts/Inter-ExtraBoldItalic.woff2
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-ExtraLight.woff
Normal file
BIN
src/project/static/fonts/Inter-ExtraLight.woff
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-ExtraLight.woff2
Normal file
BIN
src/project/static/fonts/Inter-ExtraLight.woff2
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-ExtraLightItalic.woff
Normal file
BIN
src/project/static/fonts/Inter-ExtraLightItalic.woff
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-ExtraLightItalic.woff2
Normal file
BIN
src/project/static/fonts/Inter-ExtraLightItalic.woff2
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-Italic.woff
Normal file
BIN
src/project/static/fonts/Inter-Italic.woff
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-Italic.woff2
Normal file
BIN
src/project/static/fonts/Inter-Italic.woff2
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-Light.woff
Normal file
BIN
src/project/static/fonts/Inter-Light.woff
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-Light.woff2
Normal file
BIN
src/project/static/fonts/Inter-Light.woff2
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-LightItalic.woff
Normal file
BIN
src/project/static/fonts/Inter-LightItalic.woff
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-LightItalic.woff2
Normal file
BIN
src/project/static/fonts/Inter-LightItalic.woff2
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-Medium.woff
Normal file
BIN
src/project/static/fonts/Inter-Medium.woff
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-Medium.woff2
Normal file
BIN
src/project/static/fonts/Inter-Medium.woff2
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-MediumItalic.woff
Normal file
BIN
src/project/static/fonts/Inter-MediumItalic.woff
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-MediumItalic.woff2
Normal file
BIN
src/project/static/fonts/Inter-MediumItalic.woff2
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-Regular.woff
Normal file
BIN
src/project/static/fonts/Inter-Regular.woff
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-Regular.woff2
Normal file
BIN
src/project/static/fonts/Inter-Regular.woff2
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-SemiBold.woff
Normal file
BIN
src/project/static/fonts/Inter-SemiBold.woff
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-SemiBold.woff2
Normal file
BIN
src/project/static/fonts/Inter-SemiBold.woff2
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-SemiBoldItalic.woff
Normal file
BIN
src/project/static/fonts/Inter-SemiBoldItalic.woff
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-SemiBoldItalic.woff2
Normal file
BIN
src/project/static/fonts/Inter-SemiBoldItalic.woff2
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-Thin.woff
Normal file
BIN
src/project/static/fonts/Inter-Thin.woff
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-Thin.woff2
Normal file
BIN
src/project/static/fonts/Inter-Thin.woff2
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-ThinItalic.woff
Normal file
BIN
src/project/static/fonts/Inter-ThinItalic.woff
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-ThinItalic.woff2
Normal file
BIN
src/project/static/fonts/Inter-ThinItalic.woff2
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-italic.var.woff2
Normal file
BIN
src/project/static/fonts/Inter-italic.var.woff2
Normal file
Binary file not shown.
BIN
src/project/static/fonts/Inter-roman.var.woff2
Normal file
BIN
src/project/static/fonts/Inter-roman.var.woff2
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue