forked from data.coop/membersystem
Compare commits
2 commits
main
...
feature/al
Author | SHA1 | Date | |
---|---|---|---|
Mikkel Munch Mortensen | 4a10c62bf2 | ||
Mikkel Munch Mortensen | 7f01d9a277 |
|
@ -1,7 +0,0 @@
|
||||||
*
|
|
||||||
.*
|
|
||||||
*/.*
|
|
||||||
|
|
||||||
!src/
|
|
||||||
!requirements/
|
|
||||||
!entrypoint.sh
|
|
27
.drone.yml
27
.drone.yml
|
@ -1,27 +0,0 @@
|
||||||
---
|
|
||||||
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
|
|
|
@ -1,9 +0,0 @@
|
||||||
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,9 +2,5 @@ __pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
*.sw*
|
*.sw*
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
|
project/settings/local.py
|
||||||
.pytest_cache
|
.pytest_cache
|
||||||
.idea/
|
|
||||||
*.mo
|
|
||||||
.env
|
|
||||||
venv/
|
|
||||||
.venv/
|
|
||||||
|
|
|
@ -1,60 +1,23 @@
|
||||||
default_language_version:
|
|
||||||
python: python3.11
|
|
||||||
exclude: ^.*\b(migrations)\b.*$
|
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: git://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.5.0
|
rev: v2.3.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-ast
|
- id: trailing-whitespace
|
||||||
- id: check-merge-conflict
|
- id: flake8
|
||||||
- id: check-case-conflict
|
args: [--max-line-length=120, --exclude=*/migrations/*]
|
||||||
- id: detect-private-key
|
- id: check-yaml
|
||||||
- id: check-added-large-files
|
- id: check-added-large-files
|
||||||
- id: check-json
|
- id: debug-statements
|
||||||
- id: check-symlinks
|
- id: end-of-file-fixer
|
||||||
- id: check-toml
|
- id: check-toml
|
||||||
- id: end-of-file-fixer
|
- repo: https://github.com/asottile/reorder_python_imports
|
||||||
- id: trailing-whitespace
|
rev: v1.6.1
|
||||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
hooks:
|
||||||
rev: 'v0.1.11'
|
- id: reorder-python-imports
|
||||||
hooks:
|
types: [file, python]
|
||||||
- id: ruff
|
- repo: https://github.com/psf/black
|
||||||
args:
|
rev: stable
|
||||||
- --fix
|
hooks:
|
||||||
- repo: https://github.com/asottile/reorder_python_imports
|
- id: black
|
||||||
rev: v3.12.0
|
language: python
|
||||||
hooks:
|
types: [file, python]
|
||||||
- id: reorder-python-imports
|
|
||||||
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
|
|
||||||
rev: 23.12.1
|
|
||||||
hooks:
|
|
||||||
- id: black
|
|
||||||
|
|
40
Dockerfile
40
Dockerfile
|
@ -1,40 +0,0 @@
|
||||||
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,58 +1,13 @@
|
||||||
DOCKER_COMPOSE = COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker compose
|
# These are just some make targets, expressing how things
|
||||||
DOCKER_RUN = ${DOCKER_COMPOSE} run -u `id -u`
|
# are supposed to be run, but feel free to change them!
|
||||||
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}
|
|
||||||
|
|
||||||
init: setup_venv pre_commit_install migrate
|
dev-setup:
|
||||||
|
poetry run pre-commit install
|
||||||
|
poetry run python manage.py migrate
|
||||||
|
poetry run python manage.py createsuperuser
|
||||||
|
|
||||||
run:
|
lint:
|
||||||
${DOCKER_COMPOSE} up
|
poetry run pre-commit run --all
|
||||||
|
|
||||||
setup_venv:
|
test:
|
||||||
rm -rf venv
|
poetry run pytest
|
||||||
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,71 +1,24 @@
|
||||||
# member.data.coop
|
# member.data.coop
|
||||||
|
|
||||||
## Development
|
To start developing:
|
||||||
|
|
||||||
### Setup environment
|
Get poetry
|
||||||
|
|
||||||
Copy over the .env.example file to .env and adjust DATABASE_URL accordingly
|
$ python3 -m pip install --user pipx
|
||||||
|
$ pipx install poetry
|
||||||
|
|
||||||
$ cp .env.example .env
|
Run poetry to setup environment
|
||||||
|
|
||||||
### Docker
|
$ poetry install
|
||||||
|
|
||||||
#### Requirements
|
Run this make target, which installs all the requirements and sets up a development database.
|
||||||
|
|
||||||
- Docker
|
$ make dev-setup
|
||||||
- Docker compose
|
|
||||||
- pre-commit (preferred for contributions)
|
|
||||||
|
|
||||||
#### Setup
|
To run the Django development server:
|
||||||
|
|
||||||
Given that the requirements above are installed, it should be as easy as:
|
$ poetry run python manage.py runserver
|
||||||
|
|
||||||
$ make migrate
|
Before you push your stuff, run tests:
|
||||||
|
|
||||||
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
|
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
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:
|
|
|
@ -1,21 +0,0 @@
|
||||||
#!/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 "$@"
|
|
|
@ -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)
|
|
@ -6,21 +6,26 @@ 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()
|
||||||
|
|
||||||
@admin.display(description=_("Order ID"))
|
who.short_description = _("Customer")
|
||||||
|
|
||||||
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,4 +1,6 @@
|
||||||
# Generated by Django 3.1.7 on 2021-02-27 20:06
|
# Generated by Django 2.0.6 on 2018-06-23 19:51
|
||||||
|
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
|
||||||
|
@ -10,9 +12,7 @@ class Migration(migrations.Migration):
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
|
||||||
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="oprettet"),
|
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"owner",
|
"owner",
|
||||||
|
@ -43,9 +43,7 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={"abstract": False},
|
||||||
"abstract": False,
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="Order",
|
name="Order",
|
||||||
|
@ -65,7 +63,7 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"created",
|
"created",
|
||||||
models.DateTimeField(auto_now_add=True, verbose_name="oprettet"),
|
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"description",
|
"description",
|
||||||
|
@ -84,6 +82,7 @@ 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)",
|
||||||
),
|
),
|
||||||
|
@ -100,7 +99,10 @@ class Migration(migrations.Migration):
|
||||||
(
|
(
|
||||||
"vat",
|
"vat",
|
||||||
djmoney.models.fields.MoneyField(
|
djmoney.models.fields.MoneyField(
|
||||||
decimal_places=2, max_digits=16, verbose_name="VAT"
|
decimal_places=2,
|
||||||
|
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")),
|
||||||
|
@ -108,7 +110,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",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -119,65 +121,7 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={"verbose_name": "Order", "verbose_name_plural": "Orders"},
|
||||||
"verbose_name": "Order",
|
|
||||||
"verbose_name_plural": "Orders",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="Transaction",
|
|
||||||
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,
|
|
||||||
help_text="This will include VAT",
|
|
||||||
max_digits=16,
|
|
||||||
verbose_name="amount",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"description",
|
|
||||||
models.CharField(max_length=1024, verbose_name="description"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"account",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
|
||||||
related_name="transactions",
|
|
||||||
to="accounting.account",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"abstract": False,
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="Payment",
|
name="Payment",
|
||||||
|
@ -197,7 +141,7 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"created",
|
"created",
|
||||||
models.DateTimeField(auto_now_add=True, verbose_name="oprettet"),
|
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"amount_currency",
|
"amount_currency",
|
||||||
|
@ -210,7 +154,9 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"amount",
|
"amount",
|
||||||
djmoney.models.fields.MoneyField(decimal_places=2, max_digits=16),
|
djmoney.models.fields.MoneyField(
|
||||||
|
decimal_places=2, default=Decimal("0.0"), max_digits=16
|
||||||
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"description",
|
"description",
|
||||||
|
@ -224,13 +170,64 @@ class Migration(migrations.Migration):
|
||||||
"order",
|
"order",
|
||||||
models.ForeignKey(
|
models.ForeignKey(
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
to="accounting.order",
|
to="accounting.Order",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={"verbose_name": "payment", "verbose_name_plural": "payments"},
|
||||||
"verbose_name": "payment",
|
),
|
||||||
"verbose_name_plural": "payments",
|
migrations.CreateModel(
|
||||||
},
|
name="Transaction",
|
||||||
|
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"),
|
||||||
|
help_text="This will include VAT",
|
||||||
|
max_digits=16,
|
||||||
|
verbose_name="amount",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"description",
|
||||||
|
models.CharField(max_length=1024, verbose_name="description"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"account",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="transactions",
|
||||||
|
to="accounting.Account",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={"abstract": False},
|
||||||
),
|
),
|
||||||
]
|
]
|
|
@ -1,6 +1,7 @@
|
||||||
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 _
|
||||||
|
@ -9,6 +10,7 @@ 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"))
|
||||||
|
|
||||||
|
@ -22,7 +24,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("auth.User", on_delete=models.PROTECT)
|
owner = models.ForeignKey(get_user_model(), on_delete=models.PROTECT)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def balance(self):
|
def balance(self):
|
||||||
|
@ -36,9 +38,7 @@ class Transaction(CreatedModifiedAbstract):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
account = models.ForeignKey(
|
account = models.ForeignKey(
|
||||||
Account,
|
Account, on_delete=models.PROTECT, related_name="transactions"
|
||||||
on_delete=models.PROTECT,
|
|
||||||
related_name="transactions",
|
|
||||||
)
|
)
|
||||||
amount = MoneyField(
|
amount = MoneyField(
|
||||||
verbose_name=_("amount"),
|
verbose_name=_("amount"),
|
||||||
|
@ -56,15 +56,14 @@ class Order(CreatedModifiedAbstract):
|
||||||
invoices at the moment.
|
invoices at the moment.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
user = models.ForeignKey("auth.User", on_delete=models.PROTECT)
|
user = models.ForeignKey(get_user_model(), 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)"),
|
verbose_name=_("price (excl. VAT)"), max_digits=16, decimal_places=2
|
||||||
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)
|
||||||
|
|
||||||
|
@ -92,10 +91,11 @@ 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 f"Order ID {self.display_id}"
|
return "Order ID {id}".format(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 f"Payment ID {self.display_id}"
|
return "Payment ID {id}".format(id=self.display_id)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("payment")
|
verbose_name = _("payment")
|
8
parked_apps/membership/admin.py
Normal file
8
parked_apps/membership/admin.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.Membership)
|
||||||
|
class MembershipAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
5
parked_apps/membership/apps.py
Normal file
5
parked_apps/membership/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class MembershipConfig(AppConfig):
|
||||||
|
name = "membership"
|
227
parked_apps/membership/migrations/0001_initial.py
Normal file
227
parked_apps/membership/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
# 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
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
123
parked_apps/membership/models.py
Normal file
123
parked_apps/membership/models.py
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
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")
|
1
parked_apps/users/admin.py
Normal file
1
parked_apps/users/admin.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
from django.contrib import admin # noqa
|
5
parked_apps/users/apps.py
Normal file
5
parked_apps/users/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class UsersConfig(AppConfig):
|
||||||
|
name = "users"
|
19
parked_apps/users/forms.py
Normal file
19
parked_apps/users/forms.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
from django import forms
|
||||||
|
from django.contrib.auth.forms import UserCreationForm
|
||||||
|
from django.contrib.auth.tokens import default_token_generator
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
def get_confirm_code(email):
|
||||||
|
return default_token_generator(email)[:7]
|
||||||
|
|
||||||
|
|
||||||
|
class SignupForm(UserCreationForm):
|
||||||
|
|
||||||
|
username = forms.EmailField(label=_("Email"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.User
|
||||||
|
fields = ("username",)
|
73
parked_apps/users/migrations/0001_initial.py
Normal file
73
parked_apps/users/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-08-31 18:44
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [("auth", "0011_update_proxy_permissions")]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="User",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("password", models.CharField(max_length=128, verbose_name="password")),
|
||||||
|
(
|
||||||
|
"last_login",
|
||||||
|
models.DateTimeField(
|
||||||
|
blank=True, null=True, verbose_name="last login"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("nick", models.CharField(blank=True, max_length=60, null=True)),
|
||||||
|
(
|
||||||
|
"email",
|
||||||
|
models.EmailField(
|
||||||
|
help_text="Your email address will be used for password resets and notification about your event/submissions.",
|
||||||
|
max_length=254,
|
||||||
|
unique=True,
|
||||||
|
verbose_name="E-Mail",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("is_active", models.BooleanField(default=True)),
|
||||||
|
("is_staff", models.BooleanField(default=False)),
|
||||||
|
("is_superuser", models.BooleanField(default=False)),
|
||||||
|
("token_uuid", models.UUIDField(default=uuid.uuid4, editable=False)),
|
||||||
|
(
|
||||||
|
"groups",
|
||||||
|
models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
|
||||||
|
related_name="user_set",
|
||||||
|
related_query_name="user",
|
||||||
|
to="auth.Group",
|
||||||
|
verbose_name="groups",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user_permissions",
|
||||||
|
models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Specific permissions for this user.",
|
||||||
|
related_name="user_set",
|
||||||
|
related_query_name="user",
|
||||||
|
to="auth.Permission",
|
||||||
|
verbose_name="user permissions",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={"verbose_name": "User"},
|
||||||
|
)
|
||||||
|
]
|
60
parked_apps/users/models.py
Normal file
60
parked_apps/users/models.py
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from django.contrib.auth.base_user import BaseUserManager
|
||||||
|
from django.contrib.auth.models import AbstractBaseUser
|
||||||
|
from django.contrib.auth.models import PermissionsMixin
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class UserManager(BaseUserManager):
|
||||||
|
"""The user manager class."""
|
||||||
|
|
||||||
|
def create_user(self, password: str = None, **kwargs):
|
||||||
|
user = self.model(**kwargs)
|
||||||
|
user.set_password(password)
|
||||||
|
user.save()
|
||||||
|
return user
|
||||||
|
|
||||||
|
def create_superuser(self, password: str, **kwargs):
|
||||||
|
user = self.create_user(password=password, **kwargs)
|
||||||
|
user.is_staff = True
|
||||||
|
user.is_superuser = True
|
||||||
|
user.save(update_fields=["is_staff", "is_superuser"])
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
class User(PermissionsMixin, AbstractBaseUser):
|
||||||
|
|
||||||
|
EMAIL_FIELD = "email"
|
||||||
|
USERNAME_FIELD = "email"
|
||||||
|
|
||||||
|
objects = UserManager()
|
||||||
|
|
||||||
|
nick = models.CharField(max_length=60, null=True, blank=True)
|
||||||
|
email = models.EmailField(
|
||||||
|
unique=True,
|
||||||
|
verbose_name=_("E-Mail"),
|
||||||
|
help_text=_(
|
||||||
|
"Your email address will be used for password resets and notification about your event/submissions."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
is_active = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
# For the Django admin...
|
||||||
|
is_staff = models.BooleanField(default=False)
|
||||||
|
is_superuser = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
# Used for confirmations and password reminders to NOT disclose email in URL
|
||||||
|
token_uuid = models.UUIDField(default=uuid.uuid4, editable=False)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
"""Use a useful string representation."""
|
||||||
|
return self.get_display_name()
|
||||||
|
|
||||||
|
def get_display_name(self) -> str:
|
||||||
|
return self.nick if self.nick else str(_("Unnamed user"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("User")
|
10
parked_apps/users/templates/users/logged_out.html
Normal file
10
parked_apps/users/templates/users/logged_out.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<p>{% trans "Thanks for spending some quality time with the Web site today." %}</p>
|
||||||
|
|
||||||
|
<p><a href="{% url 'admin:index' %}">{% trans 'Log in again' %}</a></p>
|
||||||
|
|
||||||
|
{% endblock %}
|
39
parked_apps/users/templates/users/login.html
Normal file
39
parked_apps/users/templates/users/login.html
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% if form.errors %}
|
||||||
|
<p>Your username and password didn't match. Please try again.</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if next %}
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
<p>Your account doesn't have access to this page. To proceed,
|
||||||
|
please login with an account that has access.</p>
|
||||||
|
{% else %}
|
||||||
|
<p>Please login to see this page.</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form method="post" action="{% url 'users:login' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>{{ form.username.label_tag }}</td>
|
||||||
|
<td>{{ form.username }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ form.password.label_tag }}</td>
|
||||||
|
<td>{{ form.password }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<input type="submit" value="login">
|
||||||
|
<input type="hidden" name="next" value="{{ next }}">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{# Assumes you setup the password_reset view in your URLconf #}
|
||||||
|
<p><a href="{% url 'users:password_reset' %}">Lost password?</a></p>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,8 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<p>{% trans 'Your password was changed.' %}</p>
|
||||||
|
{% endblock %}
|
52
parked_apps/users/templates/users/password_change_form.html
Normal file
52
parked_apps/users/templates/users/password_change_form.html
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n static %}
|
||||||
|
|
||||||
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}<div id="content-main">
|
||||||
|
|
||||||
|
<form method="post">{% csrf_token %}
|
||||||
|
<div>
|
||||||
|
{% if form.errors %}
|
||||||
|
<p class="errornote">
|
||||||
|
{% if form.errors.items|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
<p>{% trans "Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly." %}</p>
|
||||||
|
|
||||||
|
<fieldset class="module aligned wide">
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
{{ form.old_password.errors }}
|
||||||
|
{{ form.old_password.label_tag }} {{ form.old_password }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
{{ form.new_password1.errors }}
|
||||||
|
{{ form.new_password1.label_tag }} {{ form.new_password1 }}
|
||||||
|
{% if form.new_password1.help_text %}
|
||||||
|
<div class="help">{{ form.new_password1.help_text|safe }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
{{ form.new_password2.errors }}
|
||||||
|
{{ form.new_password2.label_tag }} {{ form.new_password2 }}
|
||||||
|
{% if form.new_password2.help_text %}
|
||||||
|
<div class="help">{{ form.new_password2.help_text|safe }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<div class="submit-row">
|
||||||
|
<input type="submit" value="{% trans 'Change my password' %}" class="default">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</form></div>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<p>{% trans "Your password has been set. You may go ahead and log in now." %}</p>
|
||||||
|
|
||||||
|
<p><a href="{{ login_url }}">{% trans 'Log in' %}</a></p>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,34 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n static %}
|
||||||
|
|
||||||
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% if validlink %}
|
||||||
|
|
||||||
|
<p>{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}</p>
|
||||||
|
|
||||||
|
<form method="post">{% csrf_token %}
|
||||||
|
<fieldset class="module aligned">
|
||||||
|
<div class="form-row field-password1">
|
||||||
|
{{ form.new_password1.errors }}
|
||||||
|
<label for="id_new_password1">{% trans 'New password:' %}</label>
|
||||||
|
{{ form.new_password1 }}
|
||||||
|
</div>
|
||||||
|
<div class="form-row field-password2">
|
||||||
|
{{ form.new_password2.errors }}
|
||||||
|
<label for="id_new_password2">{% trans 'Confirm password:' %}</label>
|
||||||
|
{{ form.new_password2 }}
|
||||||
|
</div>
|
||||||
|
<input type="submit" value="{% trans 'Change my password' %}">
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
<p>{% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}</p>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
12
parked_apps/users/templates/users/password_reset_done.html
Normal file
12
parked_apps/users/templates/users/password_reset_done.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<p>{% trans "We've emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly." %}</p>
|
||||||
|
|
||||||
|
<p>{% trans "If you don't receive an email, please make sure you've entered the address you registered with, and check your spam folder." %}</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
14
parked_apps/users/templates/users/password_reset_email.html
Normal file
14
parked_apps/users/templates/users/password_reset_email.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{% load i18n %}{% autoescape off %}
|
||||||
|
{% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %}
|
||||||
|
|
||||||
|
{% trans "Please go to the following page and choose a new password:" %}
|
||||||
|
{% block reset_link %}
|
||||||
|
{{ protocol }}://{{ domain }}{% url 'users:password_reset_confirm' uidb64=uid token=token %}
|
||||||
|
{% endblock %}
|
||||||
|
{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}
|
||||||
|
|
||||||
|
{% trans "Thanks for using our site!" %}
|
||||||
|
|
||||||
|
{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
|
||||||
|
|
||||||
|
{% endautoescape %}
|
19
parked_apps/users/templates/users/password_reset_form.html
Normal file
19
parked_apps/users/templates/users/password_reset_form.html
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n static %}
|
||||||
|
|
||||||
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1>{% trans "Forgotten password?" %}</h1>
|
||||||
|
|
||||||
|
<p>{% trans "Forgotten your password? Enter your email address below, and we'll email instructions for setting a new one." %}</p>
|
||||||
|
|
||||||
|
<form method="post">{% csrf_token %}
|
||||||
|
{{ form.email.errors }}
|
||||||
|
<label for="id_email">{% trans 'Email address:' %}</label>
|
||||||
|
{{ form.email }}
|
||||||
|
<input type="submit" value="{% trans 'Reset my password' %}">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
21
parked_apps/users/templates/users/signup.html
Normal file
21
parked_apps/users/templates/users/signup.html
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1>{% trans "Sign up" %}</h1>
|
||||||
|
|
||||||
|
{% if form.errors %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form method="post" action="{% url 'users:signup' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
|
||||||
|
<p><button type="submit">{% trans "Confirm email..." %}</button></p>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p><a href="{% url 'users:login' %}">{% trans "Already have an account? Log in..." %}</a></p>
|
||||||
|
|
||||||
|
{% endblock %}
|
10
parked_apps/users/templates/users/signup_confirm.html
Normal file
10
parked_apps/users/templates/users/signup_confirm.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1>{% trans "Confirm your email" %}</h1>
|
||||||
|
|
||||||
|
<p>{% trans "You've got mail - click the link or copy paste it to this browser session and you'll be logged in." %}</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
61
parked_apps/users/urls.py
Normal file
61
parked_apps/users/urls.py
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
from django.contrib.auth import views as auth_views
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
app_name = "users"
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("signup/", views.SignupView.as_view(), name="signup"),
|
||||||
|
path("signup/confirm/", views.SignupConfirmView.as_view(), name="signup_confirm"),
|
||||||
|
path(
|
||||||
|
"login/",
|
||||||
|
auth_views.LoginView.as_view(template_name="users/login.html"),
|
||||||
|
name="login",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"logout/",
|
||||||
|
auth_views.LogoutView.as_view(template_name="users/logged_out.html"),
|
||||||
|
name="logout",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"password_change/",
|
||||||
|
views.PasswordChangeView.as_view(
|
||||||
|
template_name="users/password_change_form.html"
|
||||||
|
),
|
||||||
|
name="password_change",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"password_change/done/",
|
||||||
|
auth_views.PasswordChangeDoneView.as_view(
|
||||||
|
template_name="users/password_change_done.html"
|
||||||
|
),
|
||||||
|
name="password_change_done",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"password_reset/",
|
||||||
|
views.PasswordResetView.as_view(template_name="users/password_reset_form.html"),
|
||||||
|
name="password_reset",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"password_reset/done/",
|
||||||
|
auth_views.PasswordResetDoneView.as_view(
|
||||||
|
template_name="users/password_reset_done.html"
|
||||||
|
),
|
||||||
|
name="password_reset_done",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"reset/<uidb64>/<token>/",
|
||||||
|
views.PasswordResetConfirmView.as_view(
|
||||||
|
template_name="users/password_reset_confirm.html"
|
||||||
|
),
|
||||||
|
name="password_reset_confirm",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"reset/done/",
|
||||||
|
auth_views.PasswordResetCompleteView.as_view(
|
||||||
|
template_name="users/password_reset_complete.html"
|
||||||
|
),
|
||||||
|
name="password_reset_complete",
|
||||||
|
),
|
||||||
|
]
|
59
parked_apps/users/views.py
Normal file
59
parked_apps/users/views.py
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
from django.contrib.auth import views as auth_views
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.urls.base import reverse_lazy
|
||||||
|
from django.views.generic.base import RedirectView
|
||||||
|
from django.views.generic.base import TemplateView
|
||||||
|
from django.views.generic.edit import FormView
|
||||||
|
|
||||||
|
from . import forms
|
||||||
|
|
||||||
|
# from . import email
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordResetView(auth_views.PasswordResetView):
|
||||||
|
email_template_name = "users/password_reset_email.html"
|
||||||
|
success_url = reverse_lazy("users:password_reset_done")
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordResetConfirmView(auth_views.PasswordResetConfirmView):
|
||||||
|
success_url = reverse_lazy("users:password_reset_complete")
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordChangeView(auth_views.PasswordChangeView):
|
||||||
|
success_url = reverse_lazy("users:password_change_done")
|
||||||
|
|
||||||
|
|
||||||
|
class SignupView(FormView):
|
||||||
|
|
||||||
|
template_name = "users/signup.html"
|
||||||
|
form_class = forms.SignupForm
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
|
||||||
|
user = form.save(commit=False)
|
||||||
|
user.is_active = False
|
||||||
|
user.set_password(form.cleaned_data["password1"])
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
# mail = email.UserConfirm(user=user)
|
||||||
|
# mail.send_with_feedback(success_msg=_("An email was sent with a confirmation link"))
|
||||||
|
|
||||||
|
self.request.session["user_confirm_pending_id"] = user.id
|
||||||
|
|
||||||
|
return redirect("users:signup_confirm")
|
||||||
|
|
||||||
|
|
||||||
|
class SignupConfirmView(TemplateView):
|
||||||
|
|
||||||
|
template_name = "users/signup_confirm.html"
|
||||||
|
|
||||||
|
|
||||||
|
class SignupConfirmRedirectView(RedirectView):
|
||||||
|
def get_redirect_url(self):
|
||||||
|
|
||||||
|
uuid = self.kwargs["uuid"]
|
||||||
|
|
||||||
|
if self.kwargs["token"] == forms.get_confirm_code(uuid):
|
||||||
|
redirect("users:confirmed") # TODO
|
||||||
|
|
||||||
|
redirect("users:confirm_nope") # TODO
|
765
poetry.lock
generated
Normal file
765
poetry.lock
generated
Normal file
|
@ -0,0 +1,765 @@
|
||||||
|
[[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 = "cffi"
|
||||||
|
version = "1.14.4"
|
||||||
|
description = "Foreign Function Interface for Python calling C code."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
pycparser = "*"
|
||||||
|
|
||||||
|
[[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 = "cryptography"
|
||||||
|
version = "3.4.3"
|
||||||
|
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
cffi = ">=1.12"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
|
||||||
|
docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
|
||||||
|
pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
|
||||||
|
sdist = ["setuptools-rust (>=0.11.4)"]
|
||||||
|
ssh = ["bcrypt (>=3.1.5)"]
|
||||||
|
test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
|
||||||
|
|
||||||
|
[[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.6"
|
||||||
|
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.44.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 = ">=2.0"
|
||||||
|
pyjwt = {version = ">=1.7", extras = ["crypto"]}
|
||||||
|
python3-openid = ">=3.0.8"
|
||||||
|
requests = "*"
|
||||||
|
requests-oauthlib = ">=0.3.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-money"
|
||||||
|
version = "1.3.1"
|
||||||
|
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", "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.7.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.9"
|
||||||
|
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.10.1"
|
||||||
|
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 = "pycparser"
|
||||||
|
version = "2.20"
|
||||||
|
description = "C parser in Python"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyjwt"
|
||||||
|
version = "2.0.1"
|
||||||
|
description = "JSON Web Token implementation in Python"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
cryptography = {version = ">=3.3.1,<4.0.0", optional = true, markers = "extra == \"crypto\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
crypto = ["cryptography (>=3.3.1,<4.0.0)"]
|
||||||
|
dev = ["sphinx", "sphinx-rtd-theme", "zope.interface", "cryptography (>=3.3.1,<4.0.0)", "pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)", "mypy", "pre-commit"]
|
||||||
|
docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"]
|
||||||
|
tests = ["pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)"]
|
||||||
|
|
||||||
|
[[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 = "2021.1"
|
||||||
|
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.2"
|
||||||
|
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 = "da57e323ba2dd8af6797adfc34c1335a21e84c0c967264576e8a465c4b409dc5"
|
||||||
|
|
||||||
|
[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"},
|
||||||
|
]
|
||||||
|
cffi = [
|
||||||
|
{file = "cffi-1.14.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775"},
|
||||||
|
{file = "cffi-1.14.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06"},
|
||||||
|
{file = "cffi-1.14.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26"},
|
||||||
|
{file = "cffi-1.14.4-cp27-cp27m-win32.whl", hash = "sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c"},
|
||||||
|
{file = "cffi-1.14.4-cp27-cp27m-win_amd64.whl", hash = "sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b"},
|
||||||
|
{file = "cffi-1.14.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d"},
|
||||||
|
{file = "cffi-1.14.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca"},
|
||||||
|
{file = "cffi-1.14.4-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698"},
|
||||||
|
{file = "cffi-1.14.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b"},
|
||||||
|
{file = "cffi-1.14.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293"},
|
||||||
|
{file = "cffi-1.14.4-cp35-cp35m-win32.whl", hash = "sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2"},
|
||||||
|
{file = "cffi-1.14.4-cp35-cp35m-win_amd64.whl", hash = "sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7"},
|
||||||
|
{file = "cffi-1.14.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f"},
|
||||||
|
{file = "cffi-1.14.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362"},
|
||||||
|
{file = "cffi-1.14.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec"},
|
||||||
|
{file = "cffi-1.14.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b"},
|
||||||
|
{file = "cffi-1.14.4-cp36-cp36m-win32.whl", hash = "sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668"},
|
||||||
|
{file = "cffi-1.14.4-cp36-cp36m-win_amd64.whl", hash = "sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009"},
|
||||||
|
{file = "cffi-1.14.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb"},
|
||||||
|
{file = "cffi-1.14.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d"},
|
||||||
|
{file = "cffi-1.14.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03"},
|
||||||
|
{file = "cffi-1.14.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:a5ed8c05548b54b998b9498753fb9cadbfd92ee88e884641377d8a8b291bcc01"},
|
||||||
|
{file = "cffi-1.14.4-cp37-cp37m-win32.whl", hash = "sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e"},
|
||||||
|
{file = "cffi-1.14.4-cp37-cp37m-win_amd64.whl", hash = "sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35"},
|
||||||
|
{file = "cffi-1.14.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d"},
|
||||||
|
{file = "cffi-1.14.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b"},
|
||||||
|
{file = "cffi-1.14.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53"},
|
||||||
|
{file = "cffi-1.14.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:d5ff0621c88ce83a28a10d2ce719b2ee85635e85c515f12bac99a95306da4b2e"},
|
||||||
|
{file = "cffi-1.14.4-cp38-cp38-win32.whl", hash = "sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d"},
|
||||||
|
{file = "cffi-1.14.4-cp38-cp38-win_amd64.whl", hash = "sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375"},
|
||||||
|
{file = "cffi-1.14.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909"},
|
||||||
|
{file = "cffi-1.14.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd"},
|
||||||
|
{file = "cffi-1.14.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a"},
|
||||||
|
{file = "cffi-1.14.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:7ef7d4ced6b325e92eb4d3502946c78c5367bc416398d387b39591532536734e"},
|
||||||
|
{file = "cffi-1.14.4-cp39-cp39-win32.whl", hash = "sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3"},
|
||||||
|
{file = "cffi-1.14.4-cp39-cp39-win_amd64.whl", hash = "sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b"},
|
||||||
|
{file = "cffi-1.14.4.tar.gz", hash = "sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c"},
|
||||||
|
]
|
||||||
|
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"},
|
||||||
|
]
|
||||||
|
cryptography = [
|
||||||
|
{file = "cryptography-3.4.3-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3cce61b179ff415ccc67393c6d6fa577aedb23d776779527c79ebf2438d4d25b"},
|
||||||
|
{file = "cryptography-3.4.3-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:bbce8faaaee586d5c84c5eacf77f227710b3bf24f229f6ced6f9fe3e7a662223"},
|
||||||
|
{file = "cryptography-3.4.3-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:1a37179da6d3a67db8e324d60d9e421b3cfd5ca6467b6861d9b1ce86dd191be3"},
|
||||||
|
{file = "cryptography-3.4.3-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:180baf51ad689b3b86bce7c2bf0cfff491fdea16fa2fe640a77e316ee26bad1e"},
|
||||||
|
{file = "cryptography-3.4.3-cp36-abi3-win32.whl", hash = "sha256:965fd6905f188876a49f1b9edadad0847ef8d056cd4995e82b9a4f03ac049bd0"},
|
||||||
|
{file = "cryptography-3.4.3-cp36-abi3-win_amd64.whl", hash = "sha256:c58467c96f3b79cf9eb4628371c235427db2c1ece210c2c539d38d23338875e4"},
|
||||||
|
{file = "cryptography-3.4.3.tar.gz", hash = "sha256:d70065c42de45e15776a53216000283a2a183ae37379badb37f527a2bdfd6221"},
|
||||||
|
]
|
||||||
|
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.6-py3-none-any.whl", hash = "sha256:169e2e7b4839a7910b393eec127fd7cbae62e80fa55f89c6510426abf673fe5f"},
|
||||||
|
{file = "Django-3.1.6.tar.gz", hash = "sha256:c6c0462b8b361f8691171af1fb87eceb4442da28477e12200c40420176206ba7"},
|
||||||
|
]
|
||||||
|
django-allauth = [
|
||||||
|
{file = "django-allauth-0.44.0.tar.gz", hash = "sha256:e51af457466022f52154d74c8523ac69375120fad2acce6e239635d85e610b25"},
|
||||||
|
]
|
||||||
|
django-money = [
|
||||||
|
{file = "django-money-1.3.1.tar.gz", hash = "sha256:a363ce16a23e403befdafa9895b2f538a10f9d390b160f12140094a6dfd55246"},
|
||||||
|
{file = "django_money-1.3.1-py3-none-any.whl", hash = "sha256:3b8fc751c8ae27cf877b8f3770ade1b63af97ee49a32ac08a6a1bc6d8d59f089"},
|
||||||
|
]
|
||||||
|
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.7.0.tar.gz", hash = "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713"},
|
||||||
|
{file = "more_itertools-8.7.0-py3-none-any.whl", hash = "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced"},
|
||||||
|
]
|
||||||
|
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.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"},
|
||||||
|
{file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"},
|
||||||
|
]
|
||||||
|
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.10.1-py2.py3-none-any.whl", hash = "sha256:16212d1fde2bed88159287da88ff03796863854b04dc9f838a55979325a3d20e"},
|
||||||
|
{file = "pre_commit-2.10.1.tar.gz", hash = "sha256:399baf78f13f4de82a29b649afd74bef2c4e28eb4f021661fc7f29246e8c7a3a"},
|
||||||
|
]
|
||||||
|
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"},
|
||||||
|
]
|
||||||
|
pycparser = [
|
||||||
|
{file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"},
|
||||||
|
{file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},
|
||||||
|
]
|
||||||
|
pyjwt = [
|
||||||
|
{file = "PyJWT-2.0.1-py3-none-any.whl", hash = "sha256:b70b15f89dc69b993d8a8d32c299032d5355c82f9b5b7e851d1a6d706dffe847"},
|
||||||
|
{file = "PyJWT-2.0.1.tar.gz", hash = "sha256:a5c70a06e1f33d81ef25eecd50d50bd30e34de1ca8b2b9fa3fe0daaabcf69bf7"},
|
||||||
|
]
|
||||||
|
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-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"},
|
||||||
|
{file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"},
|
||||||
|
]
|
||||||
|
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.2-py2.py3-none-any.whl", hash = "sha256:2be72df684b74df0ea47679a7df93fd0e04e72520022c57b479d8f881485dbe3"},
|
||||||
|
{file = "virtualenv-20.4.2.tar.gz", hash = "sha256:147b43894e51dd6bba882cf9c282447f780e2251cd35172403745fc381a0a80d"},
|
||||||
|
]
|
||||||
|
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"},
|
||||||
|
]
|
7
project/context_processors.py
Normal file
7
project/context_processors.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
"""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)}
|
14
project/settings/__init__.py
Normal file
14
project/settings/__init__.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
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
|
133
project/settings/base.py
Normal file
133
project/settings/base.py
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
"""
|
||||||
|
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 = [
|
||||||
|
# From Django.
|
||||||
|
"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",
|
||||||
|
"allauth.socialaccount",
|
||||||
|
# Our apps.
|
||||||
|
]
|
||||||
|
|
||||||
|
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
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# 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")]
|
79
project/static/css/membersystem.css
Normal file
79
project/static/css/membersystem.css
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
/* 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;
|
||||||
|
}
|
49
project/templates/base.html
Normal file
49
project/templates/base.html
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<!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="">Change password</a></li>
|
||||||
|
<li><a href="">Sign out</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li><a href="">Sign in</a></li>
|
||||||
|
<li><a href="">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>
|
12
project/urls.py
Normal file
12
project/urls.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
"""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("accounts/", include("allauth.urls")),
|
||||||
|
path("admin/", admin.site.urls),
|
||||||
|
]
|
5
project/views.py
Normal file
5
project/views.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
|
||||||
|
def index(request):
|
||||||
|
return render(request, "index.html")
|
117
pyproject.toml
117
pyproject.toml
|
@ -1,105 +1,20 @@
|
||||||
[build-system]
|
[tool.poetry]
|
||||||
requires = ["hatchling", "hatch-vcs"]
|
|
||||||
build-backend = "hatchling.build"
|
|
||||||
|
|
||||||
[project]
|
|
||||||
name = "membersystem"
|
name = "membersystem"
|
||||||
description = ''
|
version = "0.1.0"
|
||||||
readme = "README.md"
|
description = ""
|
||||||
requires-python = ">=3.11"
|
authors = ["Your Name <you@example.com>"]
|
||||||
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]
|
[tool.poetry.dependencies]
|
||||||
source = "vcs"
|
python = "^3.7"
|
||||||
|
Django = "^3.1"
|
||||||
|
django-money = "^1.3"
|
||||||
|
django-allauth = "^0.44.0"
|
||||||
|
|
||||||
[tool.hatch.envs.default]
|
[tool.poetry.dev-dependencies]
|
||||||
dependencies = [
|
pre-commit = "^2.9.3"
|
||||||
"coverage[toml]==7.3.0",
|
pytest = "^5.1"
|
||||||
"pytest==7.2.2",
|
pytest-django = "^3.5"
|
||||||
"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]]
|
[build-system]
|
||||||
python = ["3.12"]
|
requires = ["poetry>=0.12"]
|
||||||
django = ["4.2", "5.0"]
|
build-backend = "poetry.masonry.api"
|
||||||
|
|
||||||
[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
|
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
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
|
|
|
@ -1,98 +0,0 @@
|
||||||
#
|
|
||||||
# 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
|
|
|
@ -1,8 +0,0 @@
|
||||||
-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
|
|
|
@ -1,213 +0,0 @@
|
||||||
#
|
|
||||||
# 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
|
|
|
@ -1,5 +0,0 @@
|
||||||
-r base.txt
|
|
||||||
|
|
||||||
coverage==7.4.0
|
|
||||||
tblib==3.0.0
|
|
||||||
unittest-xml-reporting==3.2.0
|
|
|
@ -1,149 +0,0 @@
|
||||||
#
|
|
||||||
# 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
|
|
|
@ -1,20 +0,0 @@
|
||||||
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
|
|
|
@ -1,11 +0,0 @@
|
||||||
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)
|
|
|
@ -1,93 +0,0 @@
|
||||||
# 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",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,63 +0,0 @@
|
||||||
# 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"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,23 +0,0 @@
|
||||||
# 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",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,22 +0,0 @@
|
||||||
# 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",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,25 +0,0 @@
|
||||||
# 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',),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,121 +0,0 @@
|
||||||
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
|
|
|
@ -1,46 +0,0 @@
|
||||||
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",
|
|
||||||
)
|
|
|
@ -1,60 +0,0 @@
|
||||||
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)
|
|
|
@ -1,45 +0,0 @@
|
||||||
{% 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 %}
|
|
|
@ -1,63 +0,0 @@
|
||||||
{% 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 %}
|
|
|
@ -1,80 +0,0 @@
|
||||||
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,
|
|
||||||
)
|
|
|
@ -1,7 +0,0 @@
|
||||||
import os
|
|
||||||
|
|
||||||
from django.core.asgi import get_asgi_application
|
|
||||||
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
|
|
||||||
|
|
||||||
application = get_asgi_application()
|
|
|
@ -1,519 +0,0 @@
|
||||||
# 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"
|
|
|
@ -1,167 +0,0 @@
|
||||||
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
2018
src/project/static/css/bootstrap-icons.css
vendored
File diff suppressed because it is too large
Load diff
7
src/project/static/css/bootstrap.min.css
vendored
7
src/project/static/css/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
|
@ -1,384 +0,0 @@
|
||||||
/* 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;
|
|
||||||
}
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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