Refactoring things and doing stuff in a MVP way. #15
33
Makefile
33
Makefile
|
@ -1,13 +1,30 @@
|
||||||
# These are just some make targets, expressing how things
|
DOCKER_RUN = docker-compose run
|
||||||
# are supposed to be run, but feel free to change them!
|
DOCKER_BUILD = DOCKER_BUILDKIT=1 docker build
|
||||||
|
MANAGE = ${DOCKER_RUN} backend python /app/src/manage.py
|
||||||
dev-setup:
|
|
||||||
poetry run pre-commit install
|
|
||||||
poetry run python manage.py migrate
|
|
||||||
poetry run python manage.py createsuperuser
|
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
poetry run pre-commit run --all
|
poetry run pre-commit run --all
|
||||||
|
|
||||||
|
run:
|
||||||
|
docker-compose up --build
|
||||||
|
|
||||||
|
build:
|
||||||
|
docker-compose build
|
||||||
|
|
||||||
|
makemigrations:
|
||||||
|
${MANAGE} makemigrations ${EXTRA_ARGS}
|
||||||
|
|
||||||
|
migrate:
|
||||||
|
${MANAGE} migrate ${EXTRA_ARGS}
|
||||||
|
|
||||||
|
createsuperuser:
|
||||||
|
${MANAGE} createsuperuser
|
||||||
|
|
||||||
|
shell:
|
||||||
|
${MANAGE} shell
|
||||||
|
|
||||||
|
manage_command:
|
||||||
|
${MANAGE} ${COMMAND}
|
||||||
|
|
||||||
test:
|
test:
|
||||||
poetry run pytest
|
${DOCKER_RUN} backend pytest src/
|
||||||
|
|
|
@ -1,233 +0,0 @@
|
||||||
# Generated by Django 2.0.6 on 2018-06-23 19:51
|
|
||||||
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="Account",
|
|
||||||
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"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"owner",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
|
||||||
to=settings.AUTH_USER_MODEL,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={"abstract": False},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="Order",
|
|
||||||
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"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"description",
|
|
||||||
models.CharField(max_length=1024, verbose_name="description"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"price_currency",
|
|
||||||
djmoney.models.fields.CurrencyField(
|
|
||||||
choices=[("DKK", "DKK")],
|
|
||||||
default="XYZ",
|
|
||||||
editable=False,
|
|
||||||
max_length=3,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"price",
|
|
||||||
djmoney.models.fields.MoneyField(
|
|
||||||
decimal_places=2,
|
|
||||||
default=Decimal("0.0"),
|
|
||||||
max_digits=16,
|
|
||||||
verbose_name="price (excl. VAT)",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"vat_currency",
|
|
||||||
djmoney.models.fields.CurrencyField(
|
|
||||||
choices=[("DKK", "DKK")],
|
|
||||||
default="XYZ",
|
|
||||||
editable=False,
|
|
||||||
max_length=3,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"vat",
|
|
||||||
djmoney.models.fields.MoneyField(
|
|
||||||
decimal_places=2,
|
|
||||||
default=Decimal("0.0"),
|
|
||||||
max_digits=16,
|
|
||||||
verbose_name="VAT",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("is_paid", models.BooleanField(default=False, verbose_name="is paid")),
|
|
||||||
(
|
|
||||||
"account",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
|
||||||
to="accounting.Account",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"user",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
|
||||||
to=settings.AUTH_USER_MODEL,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={"verbose_name": "Order", "verbose_name_plural": "Orders"},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="Payment",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.AutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"modified",
|
|
||||||
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"created",
|
|
||||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"amount_currency",
|
|
||||||
djmoney.models.fields.CurrencyField(
|
|
||||||
choices=[("DKK", "DKK")],
|
|
||||||
default="XYZ",
|
|
||||||
editable=False,
|
|
||||||
max_length=3,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"amount",
|
|
||||||
djmoney.models.fields.MoneyField(
|
|
||||||
decimal_places=2, default=Decimal("0.0"), max_digits=16
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"description",
|
|
||||||
models.CharField(max_length=1024, verbose_name="description"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"stripe_charge_id",
|
|
||||||
models.CharField(blank=True, max_length=255, null=True),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"order",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
|
||||||
to="accounting.Order",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={"verbose_name": "payment", "verbose_name_plural": "payments"},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
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},
|
|
||||||
),
|
|
||||||
]
|
|
37
docker-compose.yml
Normal file
37
docker-compose.yml
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
version: '3.7'
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
user: $UID:$GID
|
||||||
|
command: python /app/src/manage.py runserver 0.0.0.0:8000
|
||||||
|
tty: true
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
volumes:
|
||||||
|
- ./:/app/
|
||||||
|
links:
|
||||||
|
- redis
|
||||||
|
- postgres
|
||||||
|
env_file:
|
||||||
|
- env
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: postgres:13-alpine
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data/
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
env_file:
|
||||||
|
- env
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:latest
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
7
env
Normal file
7
env
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
SECRET_KEY=something-very-random
|
||||||
|
POSTGRES_HOST=postgres
|
||||||
|
POSTGRES_PASSWORD=postgres
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
DATABASE_URL=postgres://postgres:postgres@postgres:5432/postgres
|
||||||
|
DEBUG=True
|
||||||
|
DJANGO_ENV=all
|
|
@ -1,227 +0,0 @@
|
||||||
# Generated by Django 2.0.6 on 2018-06-23 19:07
|
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import djmoney.models.fields
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="Membership",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.AutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"modified",
|
|
||||||
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"created",
|
|
||||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"can_vote",
|
|
||||||
models.BooleanField(
|
|
||||||
default=False,
|
|
||||||
help_text="Indicates that the user has a democratic membership of the organization.",
|
|
||||||
verbose_name="can vote",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"verbose_name": "membership",
|
|
||||||
"verbose_name_plural": "memberships",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="Organization",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.AutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"modified",
|
|
||||||
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"created",
|
|
||||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
|
||||||
),
|
|
||||||
("name", models.CharField(max_length=64, verbose_name="name")),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"verbose_name": "organization",
|
|
||||||
"verbose_name_plural": "organizations",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="Subscription",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.AutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"modified",
|
|
||||||
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"created",
|
|
||||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"active",
|
|
||||||
models.BooleanField(
|
|
||||||
default=False,
|
|
||||||
help_text="Automatically set by payment system.",
|
|
||||||
verbose_name="active",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("starts", models.DateField()),
|
|
||||||
("ends", models.DateField()),
|
|
||||||
(
|
|
||||||
"renewed_subscription",
|
|
||||||
models.ForeignKey(
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
|
||||||
to="membership.Subscription",
|
|
||||||
verbose_name="renewed subscription",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"verbose_name": "subscription",
|
|
||||||
"verbose_name_plural": "subscriptions",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="SubscriptionType",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.AutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"modified",
|
|
||||||
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"created",
|
|
||||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
|
||||||
),
|
|
||||||
("name", models.CharField(max_length=64, verbose_name="name")),
|
|
||||||
(
|
|
||||||
"fee_currency",
|
|
||||||
djmoney.models.fields.CurrencyField(
|
|
||||||
choices=[("DKK", "DKK")],
|
|
||||||
default="XYZ",
|
|
||||||
editable=False,
|
|
||||||
max_length=3,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"fee",
|
|
||||||
djmoney.models.fields.MoneyField(
|
|
||||||
decimal_places=2, default=Decimal("0.0"), max_digits=16
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"fee_vat_currency",
|
|
||||||
djmoney.models.fields.CurrencyField(
|
|
||||||
choices=[("DKK", "DKK")],
|
|
||||||
default="XYZ",
|
|
||||||
editable=False,
|
|
||||||
max_length=3,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"fee_vat",
|
|
||||||
djmoney.models.fields.MoneyField(
|
|
||||||
decimal_places=2, default=Decimal("0"), max_digits=16
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"duration",
|
|
||||||
models.PositiveSmallIntegerField(
|
|
||||||
choices=[(1, "annual")], default=1, verbose_name="duration"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"organization",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
|
||||||
to="membership.Organization",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"verbose_name": "subscription type",
|
|
||||||
"verbose_name_plural": "subscription types",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="subscription",
|
|
||||||
name="subscription_type",
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
|
||||||
related_name="memberships",
|
|
||||||
to="membership.SubscriptionType",
|
|
||||||
verbose_name="subscription type",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="subscription",
|
|
||||||
name="user",
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="membership",
|
|
||||||
name="organization",
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
|
||||||
to="membership.Organization",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="membership",
|
|
||||||
name="user",
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
330
poetry.lock
generated
330
poetry.lock
generated
|
@ -47,6 +47,17 @@ category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cffi"
|
||||||
|
version = "1.14.5"
|
||||||
|
description = "Foreign Function Interface for Python calling C code."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
pycparser = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfgv"
|
name = "cfgv"
|
||||||
version = "3.2.0"
|
version = "3.2.0"
|
||||||
|
@ -71,6 +82,25 @@ category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cryptography"
|
||||||
|
version = "3.4.6"
|
||||||
|
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]]
|
[[package]]
|
||||||
name = "defusedxml"
|
name = "defusedxml"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
|
@ -87,9 +117,25 @@ category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dj-database-url"
|
||||||
|
version = "0.5.0"
|
||||||
|
description = "Use Database URLs in your Django Application."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dj-email-url"
|
||||||
|
version = "1.0.2"
|
||||||
|
description = "Use an URL to configure email backend settings in your Django Application."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django"
|
name = "django"
|
||||||
version = "3.1.5"
|
version = "3.1.7"
|
||||||
description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
|
description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -106,21 +152,42 @@ bcrypt = ["bcrypt"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django-allauth"
|
name = "django-allauth"
|
||||||
version = "0.40.0"
|
version = "0.44.0"
|
||||||
description = "Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication."
|
description = "Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
Django = ">=1.11"
|
Django = ">=2.0"
|
||||||
|
pyjwt = {version = ">=1.7", extras = ["crypto"]}
|
||||||
python3-openid = ">=3.0.8"
|
python3-openid = ">=3.0.8"
|
||||||
requests = "*"
|
requests = "*"
|
||||||
requests-oauthlib = ">=0.3.0"
|
requests-oauthlib = ">=0.3.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-cache-url"
|
||||||
|
version = "3.2.3"
|
||||||
|
description = "Use Cache URLs in your Django application."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-debug-toolbar"
|
||||||
|
version = "3.2"
|
||||||
|
description = "A configurable set of panels that display various debug information about the current request/response."
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
Django = ">=2.2"
|
||||||
|
sqlparse = ">=0.2.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django-money"
|
name = "django-money"
|
||||||
version = "1.3"
|
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."
|
description = "Adds support for using money and currency fields in django models and forms. Uses py-moneyed as the money implementation."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -132,7 +199,28 @@ py-moneyed = ">=0.8,<1.0"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
exchange = ["certifi"]
|
exchange = ["certifi"]
|
||||||
test = ["pytest (>=3.1.0)", "pytest-django", "pytest-pythonpath", "pytest-cov", "django-reversion", "mixer"]
|
test = ["pytest (>=3.1.0)", "pytest-django", "pytest-pythonpath", "pytest-cov", "mixer"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "environs"
|
||||||
|
version = "9.3.1"
|
||||||
|
description = "simplified environment variable parsing"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
dj-database-url = {version = "*", optional = true, markers = "extra == \"django\""}
|
||||||
|
dj-email-url = {version = "*", optional = true, markers = "extra == \"django\""}
|
||||||
|
django-cache-url = {version = "*", optional = true, markers = "extra == \"django\""}
|
||||||
|
marshmallow = ">=2.7.0"
|
||||||
|
python-dotenv = "*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["pytest", "dj-database-url", "dj-email-url", "django-cache-url", "flake8 (==3.8.4)", "flake8-bugbear (==20.11.1)", "mypy (==0.800)", "pre-commit (>=2.4,<3.0)", "tox"]
|
||||||
|
django = ["dj-database-url", "dj-email-url", "django-cache-url"]
|
||||||
|
lint = ["flake8 (==3.8.4)", "flake8-bugbear (==20.11.1)", "mypy (==0.800)", "pre-commit (>=2.4,<3.0)"]
|
||||||
|
tests = ["pytest", "dj-database-url", "dj-email-url", "django-cache-url"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "filelock"
|
name = "filelock"
|
||||||
|
@ -144,7 +232,7 @@ python-versions = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "identify"
|
name = "identify"
|
||||||
version = "1.5.13"
|
version = "1.6.0"
|
||||||
description = "File identification library for Python"
|
description = "File identification library for Python"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -162,24 +250,22 @@ optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "importlib-metadata"
|
name = "marshmallow"
|
||||||
version = "3.4.0"
|
version = "3.10.0"
|
||||||
description = "Read metadata from Python packages"
|
description = "A lightweight library for converting complex datatypes to and from native Python datatypes."
|
||||||
category = "dev"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.5"
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
|
|
||||||
zipp = ">=0.5"
|
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
|
dev = ["pytest", "pytz", "simplejson", "mypy (==0.790)", "flake8 (==3.8.4)", "flake8-bugbear (==20.11.1)", "pre-commit (>=2.4,<3.0)", "tox"]
|
||||||
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)"]
|
docs = ["sphinx (==3.3.1)", "sphinx-issues (==1.2.0)", "alabaster (==0.7.12)", "sphinx-version-warning (==1.1.2)", "autodocsumm (==0.2.2)"]
|
||||||
|
lint = ["mypy (==0.790)", "flake8 (==3.8.4)", "flake8-bugbear (==20.11.1)", "pre-commit (>=2.4,<3.0)"]
|
||||||
|
tests = ["pytest", "pytz", "simplejson"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "more-itertools"
|
name = "more-itertools"
|
||||||
version = "8.6.0"
|
version = "8.7.0"
|
||||||
description = "More routines for operating on iterables, beyond itertools"
|
description = "More routines for operating on iterables, beyond itertools"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -208,7 +294,7 @@ signedtoken = ["cryptography", "pyjwt (>=1.0.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "packaging"
|
name = "packaging"
|
||||||
version = "20.8"
|
version = "20.9"
|
||||||
description = "Core utilities for Python packages"
|
description = "Core utilities for Python packages"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -225,15 +311,12 @@ category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
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]
|
[package.extras]
|
||||||
dev = ["pre-commit", "tox"]
|
dev = ["pre-commit", "tox"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pre-commit"
|
name = "pre-commit"
|
||||||
version = "2.9.3"
|
version = "2.10.1"
|
||||||
description = "A framework for managing and maintaining multi-language pre-commit hooks."
|
description = "A framework for managing and maintaining multi-language pre-commit hooks."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -242,7 +325,6 @@ python-versions = ">=3.6.1"
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
cfgv = ">=2.0.0"
|
cfgv = ">=2.0.0"
|
||||||
identify = ">=1.0.0"
|
identify = ">=1.0.0"
|
||||||
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
|
|
||||||
nodeenv = ">=0.11.1"
|
nodeenv = ">=0.11.1"
|
||||||
pyyaml = ">=5.1"
|
pyyaml = ">=5.1"
|
||||||
toml = "*"
|
toml = "*"
|
||||||
|
@ -275,6 +357,31 @@ python-versions = "*"
|
||||||
[package.extras]
|
[package.extras]
|
||||||
tests = ["pytest (>=2.3.0)", "tox (>=1.6.0)"]
|
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]]
|
[[package]]
|
||||||
name = "pyparsing"
|
name = "pyparsing"
|
||||||
version = "2.4.7"
|
version = "2.4.7"
|
||||||
|
@ -295,7 +402,6 @@ python-versions = ">=3.5"
|
||||||
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
|
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
|
||||||
attrs = ">=17.4.0"
|
attrs = ">=17.4.0"
|
||||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||||
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
|
|
||||||
more-itertools = ">=4.0.0"
|
more-itertools = ">=4.0.0"
|
||||||
packaging = "*"
|
packaging = "*"
|
||||||
pluggy = ">=0.12,<1.0"
|
pluggy = ">=0.12,<1.0"
|
||||||
|
@ -321,6 +427,17 @@ pytest = ">=3.6"
|
||||||
docs = ["sphinx", "sphinx-rtd-theme"]
|
docs = ["sphinx", "sphinx-rtd-theme"]
|
||||||
testing = ["django", "django-configurations (>=2.0)", "six"]
|
testing = ["django", "django-configurations (>=2.0)", "six"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-dotenv"
|
||||||
|
version = "0.15.0"
|
||||||
|
description = "Add .env support to your django/flask apps in development and deployments"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
cli = ["click (>=5.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python3-openid"
|
name = "python3-openid"
|
||||||
version = "3.2.0"
|
version = "3.2.0"
|
||||||
|
@ -338,7 +455,7 @@ postgresql = ["psycopg2"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytz"
|
name = "pytz"
|
||||||
version = "2020.5"
|
version = "2021.1"
|
||||||
description = "World timezone definitions, modern and historical"
|
description = "World timezone definitions, modern and historical"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -409,14 +526,6 @@ category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
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]]
|
[[package]]
|
||||||
name = "urllib3"
|
name = "urllib3"
|
||||||
version = "1.26.3"
|
version = "1.26.3"
|
||||||
|
@ -432,7 +541,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "virtualenv"
|
name = "virtualenv"
|
||||||
version = "20.4.0"
|
version = "20.4.2"
|
||||||
description = "Virtual Python Environment builder"
|
description = "Virtual Python Environment builder"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -442,7 +551,6 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
|
||||||
appdirs = ">=1.4.3,<2"
|
appdirs = ">=1.4.3,<2"
|
||||||
distlib = ">=0.3.1,<1"
|
distlib = ">=0.3.1,<1"
|
||||||
filelock = ">=3.0.0,<4"
|
filelock = ">=3.0.0,<4"
|
||||||
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
|
|
||||||
six = ">=1.9.0,<2"
|
six = ">=1.9.0,<2"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
|
@ -457,22 +565,10 @@ category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
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]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.7"
|
python-versions = "^3.9"
|
||||||
content-hash = "29ee22cded289d14aaf1015cc00f5fd4b3e441f807c86b325e21c67c2314a274"
|
content-hash = "8a01d7c9866e867f833f218d11a56cdebbcb9c5d1d24b636fe44e6393f2652d6"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
appdirs = [
|
appdirs = [
|
||||||
|
@ -495,6 +591,45 @@ certifi = [
|
||||||
{file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"},
|
{file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"},
|
||||||
{file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"},
|
{file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"},
|
||||||
]
|
]
|
||||||
|
cffi = [
|
||||||
|
{file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"},
|
||||||
|
{file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"},
|
||||||
|
{file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"},
|
||||||
|
{file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"},
|
||||||
|
{file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"},
|
||||||
|
{file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"},
|
||||||
|
{file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"},
|
||||||
|
{file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"},
|
||||||
|
{file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"},
|
||||||
|
{file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"},
|
||||||
|
{file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"},
|
||||||
|
{file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"},
|
||||||
|
{file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"},
|
||||||
|
{file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"},
|
||||||
|
{file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"},
|
||||||
|
{file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"},
|
||||||
|
{file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"},
|
||||||
|
{file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"},
|
||||||
|
{file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"},
|
||||||
|
{file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"},
|
||||||
|
{file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"},
|
||||||
|
{file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"},
|
||||||
|
{file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"},
|
||||||
|
{file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"},
|
||||||
|
{file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"},
|
||||||
|
{file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"},
|
||||||
|
{file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"},
|
||||||
|
{file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"},
|
||||||
|
{file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"},
|
||||||
|
{file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"},
|
||||||
|
{file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"},
|
||||||
|
{file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"},
|
||||||
|
{file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"},
|
||||||
|
{file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"},
|
||||||
|
{file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"},
|
||||||
|
{file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"},
|
||||||
|
{file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"},
|
||||||
|
]
|
||||||
cfgv = [
|
cfgv = [
|
||||||
{file = "cfgv-3.2.0-py2.py3-none-any.whl", hash = "sha256:32e43d604bbe7896fe7c248a9c2276447dbef840feb28fe20494f62af110211d"},
|
{file = "cfgv-3.2.0-py2.py3-none-any.whl", hash = "sha256:32e43d604bbe7896fe7c248a9c2276447dbef840feb28fe20494f62af110211d"},
|
||||||
{file = "cfgv-3.2.0.tar.gz", hash = "sha256:cf22deb93d4bcf92f345a5c3cd39d3d41d6340adc60c78bbbd6588c384fda6a1"},
|
{file = "cfgv-3.2.0.tar.gz", hash = "sha256:cf22deb93d4bcf92f345a5c3cd39d3d41d6340adc60c78bbbd6588c384fda6a1"},
|
||||||
|
@ -507,6 +642,20 @@ colorama = [
|
||||||
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
||||||
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
|
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
|
||||||
]
|
]
|
||||||
|
cryptography = [
|
||||||
|
{file = "cryptography-3.4.6-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:57ad77d32917bc55299b16d3b996ffa42a1c73c6cfa829b14043c561288d2799"},
|
||||||
|
{file = "cryptography-3.4.6-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:4169a27b818de4a1860720108b55a2801f32b6ae79e7f99c00d79f2a2822eeb7"},
|
||||||
|
{file = "cryptography-3.4.6-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:93cfe5b7ff006de13e1e89830810ecbd014791b042cbe5eec253be11ac2b28f3"},
|
||||||
|
{file = "cryptography-3.4.6-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:5ecf2bcb34d17415e89b546dbb44e73080f747e504273e4d4987630493cded1b"},
|
||||||
|
{file = "cryptography-3.4.6-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:fec7fb46b10da10d9e1d078d1ff8ed9e05ae14f431fdbd11145edd0550b9a964"},
|
||||||
|
{file = "cryptography-3.4.6-cp36-abi3-win32.whl", hash = "sha256:df186fcbf86dc1ce56305becb8434e4b6b7504bc724b71ad7a3239e0c9d14ef2"},
|
||||||
|
{file = "cryptography-3.4.6-cp36-abi3-win_amd64.whl", hash = "sha256:66b57a9ca4b3221d51b237094b0303843b914b7d5afd4349970bb26518e350b0"},
|
||||||
|
{file = "cryptography-3.4.6-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:066bc53f052dfeda2f2d7c195cf16fb3e5ff13e1b6b7415b468514b40b381a5b"},
|
||||||
|
{file = "cryptography-3.4.6-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:600cf9bfe75e96d965509a4c0b2b183f74a4fa6f5331dcb40fb7b77b7c2484df"},
|
||||||
|
{file = "cryptography-3.4.6-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:0923ba600d00718d63a3976f23cab19aef10c1765038945628cd9be047ad0336"},
|
||||||
|
{file = "cryptography-3.4.6-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:9e98b452132963678e3ac6c73f7010fe53adf72209a32854d55690acac3f6724"},
|
||||||
|
{file = "cryptography-3.4.6.tar.gz", hash = "sha256:2d32223e5b0ee02943f32b19245b61a62db83a882f0e76cc564e1cec60d48f87"},
|
||||||
|
]
|
||||||
defusedxml = [
|
defusedxml = [
|
||||||
{file = "defusedxml-0.6.0-py2.py3-none-any.whl", hash = "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93"},
|
{file = "defusedxml-0.6.0-py2.py3-none-any.whl", hash = "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93"},
|
||||||
{file = "defusedxml-0.6.0.tar.gz", hash = "sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"},
|
{file = "defusedxml-0.6.0.tar.gz", hash = "sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"},
|
||||||
|
@ -515,36 +664,56 @@ distlib = [
|
||||||
{file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"},
|
{file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"},
|
||||||
{file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"},
|
{file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"},
|
||||||
]
|
]
|
||||||
|
dj-database-url = [
|
||||||
|
{file = "dj-database-url-0.5.0.tar.gz", hash = "sha256:4aeaeb1f573c74835b0686a2b46b85990571159ffc21aa57ecd4d1e1cb334163"},
|
||||||
|
{file = "dj_database_url-0.5.0-py2.py3-none-any.whl", hash = "sha256:851785365761ebe4994a921b433062309eb882fedd318e1b0fcecc607ed02da9"},
|
||||||
|
]
|
||||||
|
dj-email-url = [
|
||||||
|
{file = "dj-email-url-1.0.2.tar.gz", hash = "sha256:838fd4ded9deba53ae757debef431e25fa7fca31d3948b3c4808ccdc84fab2b7"},
|
||||||
|
{file = "dj_email_url-1.0.2-py2.py3-none-any.whl", hash = "sha256:15148141c6ef123636e4ca3663e95231ed94ca5ed267e91977e5a4397be8b34c"},
|
||||||
|
]
|
||||||
django = [
|
django = [
|
||||||
{file = "Django-3.1.5-py3-none-any.whl", hash = "sha256:efa2ab96b33b20c2182db93147a0c3cd7769d418926f9e9f140a60dca7c64ca9"},
|
{file = "Django-3.1.7-py3-none-any.whl", hash = "sha256:baf099db36ad31f970775d0be5587cc58a6256a6771a44eb795b554d45f211b8"},
|
||||||
{file = "Django-3.1.5.tar.gz", hash = "sha256:2d78425ba74c7a1a74b196058b261b9733a8570782f4e2828974777ccca7edf7"},
|
{file = "Django-3.1.7.tar.gz", hash = "sha256:32ce792ee9b6a0cbbec340123e229ac9f765dff8c2a4ae9247a14b2ba3a365a7"},
|
||||||
]
|
]
|
||||||
django-allauth = [
|
django-allauth = [
|
||||||
{file = "django-allauth-0.40.0.tar.gz", hash = "sha256:6a189fc4d3ee23596c3fd6e9f49c59b5b15618980118171a50675dd6a27cc589"},
|
{file = "django-allauth-0.44.0.tar.gz", hash = "sha256:e51af457466022f52154d74c8523ac69375120fad2acce6e239635d85e610b25"},
|
||||||
|
]
|
||||||
|
django-cache-url = [
|
||||||
|
{file = "django-cache-url-3.2.3.tar.gz", hash = "sha256:c1d45626ae8a206267c1263aa7a3461e2e186be2e939bcbd8c660e25851ddac8"},
|
||||||
|
{file = "django_cache_url-3.2.3-py2.py3-none-any.whl", hash = "sha256:5514ca3a2075c6b956b3d0a5c540654d32b004e76340d7bdabf6661135b5f218"},
|
||||||
|
]
|
||||||
|
django-debug-toolbar = [
|
||||||
|
{file = "django-debug-toolbar-3.2.tar.gz", hash = "sha256:84e2607d900dbd571df0a2acf380b47c088efb787dce9805aefeb407341961d2"},
|
||||||
|
{file = "django_debug_toolbar-3.2-py3-none-any.whl", hash = "sha256:9e5a25d0c965f7e686f6a8ba23613ca9ca30184daa26487706d4829f5cfb697a"},
|
||||||
]
|
]
|
||||||
django-money = [
|
django-money = [
|
||||||
{file = "django-money-1.3.tar.gz", hash = "sha256:da95f9a7174281eb2ef0f5f1584d5ee2670fc0d67707cd269816a73cae791eb3"},
|
{file = "django-money-1.3.1.tar.gz", hash = "sha256:a363ce16a23e403befdafa9895b2f538a10f9d390b160f12140094a6dfd55246"},
|
||||||
{file = "django_money-1.3-py3-none-any.whl", hash = "sha256:09952d49f998d089b21eb0f552d6dcb40d82626ab674d1caf0535bbd83a1ea01"},
|
{file = "django_money-1.3.1-py3-none-any.whl", hash = "sha256:3b8fc751c8ae27cf877b8f3770ade1b63af97ee49a32ac08a6a1bc6d8d59f089"},
|
||||||
|
]
|
||||||
|
environs = [
|
||||||
|
{file = "environs-9.3.1-py2.py3-none-any.whl", hash = "sha256:2da44b7c30114415aa858577fa6396ee326fc76a0a60f0f15e8260ba554f19dc"},
|
||||||
|
{file = "environs-9.3.1.tar.gz", hash = "sha256:3f6def554abb5455141b540e6e0b72fda3853404f2b0d31658aab1bf95410db3"},
|
||||||
]
|
]
|
||||||
filelock = [
|
filelock = [
|
||||||
{file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"},
|
{file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"},
|
||||||
{file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"},
|
{file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"},
|
||||||
]
|
]
|
||||||
identify = [
|
identify = [
|
||||||
{file = "identify-1.5.13-py2.py3-none-any.whl", hash = "sha256:9dfb63a2e871b807e3ba62f029813552a24b5289504f5b071dea9b041aee9fe4"},
|
{file = "identify-1.6.0-py2.py3-none-any.whl", hash = "sha256:63e8105ec44c16d96beb33a0ae5d5ed1bbefd72415ea7e9d684b2f9b4a1cabf9"},
|
||||||
{file = "identify-1.5.13.tar.gz", hash = "sha256:70b638cf4743f33042bebb3b51e25261a0a10e80f978739f17e7fd4837664a66"},
|
{file = "identify-1.6.0.tar.gz", hash = "sha256:41b6bed56f30150b25ef4c2724688d9d202fcebc448ad243fd9df4e4856c81fc"},
|
||||||
]
|
]
|
||||||
idna = [
|
idna = [
|
||||||
{file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
|
{file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
|
||||||
{file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
|
{file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
|
||||||
]
|
]
|
||||||
importlib-metadata = [
|
marshmallow = [
|
||||||
{file = "importlib_metadata-3.4.0-py3-none-any.whl", hash = "sha256:ace61d5fc652dc280e7b6b4ff732a9c2d40db2c0f92bc6cb74e07b73d53a1771"},
|
{file = "marshmallow-3.10.0-py2.py3-none-any.whl", hash = "sha256:eca81d53aa4aafbc0e20566973d0d2e50ce8bf0ee15165bb799bec0df1e50177"},
|
||||||
{file = "importlib_metadata-3.4.0.tar.gz", hash = "sha256:fa5daa4477a7414ae34e95942e4dd07f62adf589143c875c133c1e53c4eff38d"},
|
{file = "marshmallow-3.10.0.tar.gz", hash = "sha256:4ab2fdb7f36eb61c3665da67a7ce281c8900db08d72ba6bf0e695828253581f7"},
|
||||||
]
|
]
|
||||||
more-itertools = [
|
more-itertools = [
|
||||||
{file = "more-itertools-8.6.0.tar.gz", hash = "sha256:b3a9005928e5bed54076e6e549c792b306fddfe72b2d1d22dd63d42d5d3899cf"},
|
{file = "more-itertools-8.7.0.tar.gz", hash = "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713"},
|
||||||
{file = "more_itertools-8.6.0-py3-none-any.whl", hash = "sha256:8e1a2a43b2f2727425f2b5839587ae37093f19153dc26c0927d1048ff6557330"},
|
{file = "more_itertools-8.7.0-py3-none-any.whl", hash = "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced"},
|
||||||
]
|
]
|
||||||
nodeenv = [
|
nodeenv = [
|
||||||
{file = "nodeenv-1.5.0-py2.py3-none-any.whl", hash = "sha256:5304d424c529c997bc888453aeaa6362d242b6b4631e90f3d4bf1b290f1c84a9"},
|
{file = "nodeenv-1.5.0-py2.py3-none-any.whl", hash = "sha256:5304d424c529c997bc888453aeaa6362d242b6b4631e90f3d4bf1b290f1c84a9"},
|
||||||
|
@ -555,16 +724,16 @@ oauthlib = [
|
||||||
{file = "oauthlib-3.1.0.tar.gz", hash = "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889"},
|
{file = "oauthlib-3.1.0.tar.gz", hash = "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889"},
|
||||||
]
|
]
|
||||||
packaging = [
|
packaging = [
|
||||||
{file = "packaging-20.8-py2.py3-none-any.whl", hash = "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858"},
|
{file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"},
|
||||||
{file = "packaging-20.8.tar.gz", hash = "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"},
|
{file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"},
|
||||||
]
|
]
|
||||||
pluggy = [
|
pluggy = [
|
||||||
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
|
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
|
||||||
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
|
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
|
||||||
]
|
]
|
||||||
pre-commit = [
|
pre-commit = [
|
||||||
{file = "pre_commit-2.9.3-py2.py3-none-any.whl", hash = "sha256:6c86d977d00ddc8a60d68eec19f51ef212d9462937acf3ea37c7adec32284ac0"},
|
{file = "pre_commit-2.10.1-py2.py3-none-any.whl", hash = "sha256:16212d1fde2bed88159287da88ff03796863854b04dc9f838a55979325a3d20e"},
|
||||||
{file = "pre_commit-2.9.3.tar.gz", hash = "sha256:ee784c11953e6d8badb97d19bc46b997a3a9eded849881ec587accd8608d74a4"},
|
{file = "pre_commit-2.10.1.tar.gz", hash = "sha256:399baf78f13f4de82a29b649afd74bef2c4e28eb4f021661fc7f29246e8c7a3a"},
|
||||||
]
|
]
|
||||||
psycopg2 = [
|
psycopg2 = [
|
||||||
{file = "psycopg2-2.8.6-cp27-cp27m-win32.whl", hash = "sha256:068115e13c70dc5982dfc00c5d70437fe37c014c808acce119b5448361c03725"},
|
{file = "psycopg2-2.8.6-cp27-cp27m-win32.whl", hash = "sha256:068115e13c70dc5982dfc00c5d70437fe37c014c808acce119b5448361c03725"},
|
||||||
|
@ -591,6 +760,14 @@ py-moneyed = [
|
||||||
{file = "py-moneyed-0.8.0.tar.gz", hash = "sha256:ec73795171919d537880a33c44d07fcdf0a5225e8368684fe02f0e75a6404742"},
|
{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"},
|
{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 = [
|
pyparsing = [
|
||||||
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
|
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
|
||||||
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
|
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
|
||||||
|
@ -603,13 +780,17 @@ pytest-django = [
|
||||||
{file = "pytest-django-3.10.0.tar.gz", hash = "sha256:4de6dbd077ed8606616958f77655fed0d5e3ee45159475671c7fa67596c6dba6"},
|
{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"},
|
{file = "pytest_django-3.10.0-py2.py3-none-any.whl", hash = "sha256:c33e3d3da14d8409b125d825d4e74da17bb252191bf6fc3da6856e27a8b73ea4"},
|
||||||
]
|
]
|
||||||
|
python-dotenv = [
|
||||||
|
{file = "python-dotenv-0.15.0.tar.gz", hash = "sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0"},
|
||||||
|
{file = "python_dotenv-0.15.0-py2.py3-none-any.whl", hash = "sha256:0c8d1b80d1a1e91717ea7d526178e3882732420b03f08afea0406db6402e220e"},
|
||||||
|
]
|
||||||
python3-openid = [
|
python3-openid = [
|
||||||
{file = "python3-openid-3.2.0.tar.gz", hash = "sha256:33fbf6928f401e0b790151ed2b5290b02545e8775f982485205a066f874aaeaf"},
|
{file = "python3-openid-3.2.0.tar.gz", hash = "sha256:33fbf6928f401e0b790151ed2b5290b02545e8775f982485205a066f874aaeaf"},
|
||||||
{file = "python3_openid-3.2.0-py3-none-any.whl", hash = "sha256:6626f771e0417486701e0b4daff762e7212e820ca5b29fcc0d05f6f8736dfa6b"},
|
{file = "python3_openid-3.2.0-py3-none-any.whl", hash = "sha256:6626f771e0417486701e0b4daff762e7212e820ca5b29fcc0d05f6f8736dfa6b"},
|
||||||
]
|
]
|
||||||
pytz = [
|
pytz = [
|
||||||
{file = "pytz-2020.5-py2.py3-none-any.whl", hash = "sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4"},
|
{file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"},
|
||||||
{file = "pytz-2020.5.tar.gz", hash = "sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"},
|
{file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"},
|
||||||
]
|
]
|
||||||
pyyaml = [
|
pyyaml = [
|
||||||
{file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
|
{file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
|
||||||
|
@ -655,24 +836,15 @@ toml = [
|
||||||
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
|
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
|
||||||
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
{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 = [
|
urllib3 = [
|
||||||
{file = "urllib3-1.26.3-py2.py3-none-any.whl", hash = "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80"},
|
{file = "urllib3-1.26.3-py2.py3-none-any.whl", hash = "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80"},
|
||||||
{file = "urllib3-1.26.3.tar.gz", hash = "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"},
|
{file = "urllib3-1.26.3.tar.gz", hash = "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"},
|
||||||
]
|
]
|
||||||
virtualenv = [
|
virtualenv = [
|
||||||
{file = "virtualenv-20.4.0-py2.py3-none-any.whl", hash = "sha256:227a8fed626f2f20a6cdb0870054989f82dd27b2560a911935ba905a2a5e0034"},
|
{file = "virtualenv-20.4.2-py2.py3-none-any.whl", hash = "sha256:2be72df684b74df0ea47679a7df93fd0e04e72520022c57b479d8f881485dbe3"},
|
||||||
{file = "virtualenv-20.4.0.tar.gz", hash = "sha256:219ee956e38b08e32d5639289aaa5bd190cfbe7dafcb8fa65407fca08e808f9c"},
|
{file = "virtualenv-20.4.2.tar.gz", hash = "sha256:147b43894e51dd6bba882cf9c282447f780e2251cd35172403745fc381a0a80d"},
|
||||||
]
|
]
|
||||||
wcwidth = [
|
wcwidth = [
|
||||||
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
|
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
|
||||||
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
|
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
|
||||||
]
|
]
|
||||||
zipp = [
|
|
||||||
{file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"},
|
|
||||||
{file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"},
|
|
||||||
]
|
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
import warnings
|
|
||||||
|
|
||||||
from .base import * # noqa
|
|
||||||
|
|
||||||
try:
|
|
||||||
from .local import * # noqa
|
|
||||||
except ImportError:
|
|
||||||
warnings.warn(
|
|
||||||
"No settings.local, using a default SECRET_KEY 'hest'. You should "
|
|
||||||
"write a custom local.py with this setting."
|
|
||||||
)
|
|
||||||
SECRET_KEY = "hest"
|
|
||||||
DEBUG = True
|
|
||||||
pass
|
|
|
@ -1,79 +0,0 @@
|
||||||
/* General styles */
|
|
||||||
html
|
|
||||||
{
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 2.5vmin;
|
|
||||||
background: #f8f8f8;
|
|
||||||
}
|
|
||||||
|
|
||||||
body
|
|
||||||
{
|
|
||||||
background: #fff;
|
|
||||||
color: #000;
|
|
||||||
margin: 1em auto;
|
|
||||||
max-width: 50em;
|
|
||||||
padding: 0 1em;
|
|
||||||
box-shadow: 0 0 2.5em rgba(0, 0, 0, 20%);
|
|
||||||
}
|
|
||||||
|
|
||||||
header,
|
|
||||||
footer
|
|
||||||
{
|
|
||||||
background: #eee;
|
|
||||||
padding: .5em;
|
|
||||||
margin: 0 -1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer
|
|
||||||
{
|
|
||||||
margin-top: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
header h1
|
|
||||||
{
|
|
||||||
font-size: 1em;
|
|
||||||
float: left;
|
|
||||||
padding: .5em .5em;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
header ul,
|
|
||||||
footer ul
|
|
||||||
{
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
header ul li,
|
|
||||||
footer ul li
|
|
||||||
{
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
header ul li a,
|
|
||||||
footer ul li a
|
|
||||||
{
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0;
|
|
||||||
padding: .5em .5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Forms */
|
|
||||||
label
|
|
||||||
{
|
|
||||||
display: block;
|
|
||||||
padding: .5em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
button,
|
|
||||||
input,
|
|
||||||
textarea
|
|
||||||
{
|
|
||||||
font-size: inherit;
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
"""URLs for the membersystem"""
|
|
||||||
from django.contrib import admin
|
|
||||||
from django.urls import include
|
|
||||||
from django.urls import path
|
|
||||||
|
|
||||||
from . import views
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path("", views.index),
|
|
||||||
path("users/", include("users.urls")),
|
|
||||||
path("admin/", admin.site.urls),
|
|
||||||
]
|
|
|
@ -5,16 +5,18 @@ description = ""
|
||||||
authors = ["Your Name <you@example.com>"]
|
authors = ["Your Name <you@example.com>"]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.7"
|
python = "^3.9"
|
||||||
Django = "^3.1"
|
Django = "^3.1"
|
||||||
django-money = "^1.3"
|
django-money = "^1.3"
|
||||||
django-allauth = "^0.40.0"
|
django-allauth = "^0.44.0"
|
||||||
psycopg2 = "^2.8.6"
|
psycopg2 = "^2.8.6"
|
||||||
|
environs = {extras = ["django"], version = "^9.3.1"}
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
pre-commit = "^2.9.3"
|
pre-commit = "^2.9.3"
|
||||||
pytest = "^5.1"
|
pytest = "^5.1"
|
||||||
pytest-django = "^3.5"
|
pytest-django = "^3.5"
|
||||||
|
django-debug-toolbar = "^3.2"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry>=0.12"]
|
requires = ["poetry>=0.12"]
|
||||||
|
|
82
src/accounting/migrations/0001_initial.py
Normal file
82
src/accounting/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
# Generated by Django 3.1.7 on 2021-02-27 20:06
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import djmoney.models.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Account',
|
||||||
|
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')),
|
||||||
|
('owner', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Order',
|
||||||
|
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')),
|
||||||
|
('description', models.CharField(max_length=1024, verbose_name='description')),
|
||||||
|
('price_currency', djmoney.models.fields.CurrencyField(choices=[('DKK', 'DKK')], default='XYZ', editable=False, max_length=3)),
|
||||||
|
('price', djmoney.models.fields.MoneyField(decimal_places=2, max_digits=16, verbose_name='price (excl. VAT)')),
|
||||||
|
('vat_currency', djmoney.models.fields.CurrencyField(choices=[('DKK', 'DKK')], default='XYZ', editable=False, max_length=3)),
|
||||||
|
('vat', djmoney.models.fields.MoneyField(decimal_places=2, max_digits=16, verbose_name='VAT')),
|
||||||
|
('is_paid', models.BooleanField(default=False, verbose_name='is paid')),
|
||||||
|
('account', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='accounting.account')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'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(
|
||||||
|
name='Payment',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('modified', models.DateTimeField(auto_now=True, verbose_name='modified')),
|
||||||
|
('created', models.DateTimeField(auto_now_add=True, verbose_name='oprettet')),
|
||||||
|
('amount_currency', djmoney.models.fields.CurrencyField(choices=[('DKK', 'DKK')], default='XYZ', editable=False, max_length=3)),
|
||||||
|
('amount', djmoney.models.fields.MoneyField(decimal_places=2, max_digits=16)),
|
||||||
|
('description', models.CharField(max_length=1024, verbose_name='description')),
|
||||||
|
('stripe_charge_id', models.CharField(blank=True, max_length=255, null=True)),
|
||||||
|
('order', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='accounting.order')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'payment',
|
||||||
|
'verbose_name_plural': 'payments',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,7 +1,6 @@
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.aggregates import Sum
|
from django.db.models.aggregates import Sum
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
@ -24,7 +23,7 @@ class Account(CreatedModifiedAbstract):
|
||||||
can decide which account to use to pay for something.
|
can decide which account to use to pay for something.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
owner = models.ForeignKey(get_user_model(), on_delete=models.PROTECT)
|
owner = models.ForeignKey("auth.User", on_delete=models.PROTECT)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def balance(self):
|
def balance(self):
|
||||||
|
@ -56,9 +55,8 @@ class Order(CreatedModifiedAbstract):
|
||||||
invoices at the moment.
|
invoices at the moment.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
user = models.ForeignKey(get_user_model(), on_delete=models.PROTECT)
|
user = models.ForeignKey("auth.User", on_delete=models.PROTECT)
|
||||||
account = models.ForeignKey(Account, on_delete=models.PROTECT)
|
account = models.ForeignKey(Account, on_delete=models.PROTECT)
|
||||||
is_paid = models.BooleanField(default=False)
|
|
||||||
|
|
||||||
description = models.CharField(max_length=1024, verbose_name=_("description"))
|
description = models.CharField(max_length=1024, verbose_name=_("description"))
|
||||||
|
|
66
src/membership/migrations/0001_initial.py
Normal file
66
src/membership/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
# Generated by Django 3.1.7 on 2021-02-27 20:06
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
from django.conf import settings
|
||||||
|
import django.contrib.postgres.fields.ranges
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import djmoney.models.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
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='oprettet')),
|
||||||
|
('name', models.CharField(max_length=64, verbose_name='navn')),
|
||||||
|
('fee_currency', djmoney.models.fields.CurrencyField(choices=[('DKK', 'DKK')], default='XYZ', editable=False, max_length=3)),
|
||||||
|
('fee', djmoney.models.fields.MoneyField(decimal_places=2, 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)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'subscription type',
|
||||||
|
'verbose_name_plural': 'subscription types',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
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='oprettet')),
|
||||||
|
('active', models.BooleanField(default=False, help_text='Automatically set by payment system.', verbose_name='aktiv')),
|
||||||
|
('duration', django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text='The duration this subscription is for. ')),
|
||||||
|
('subscription_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='memberships', to='membership.subscriptiontype', verbose_name='subscription type')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'subscription',
|
||||||
|
'verbose_name_plural': 'subscriptions',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
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')),
|
||||||
|
('user', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'membership',
|
||||||
|
'verbose_name_plural': 'memberships',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,4 +1,4 @@
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.postgres.fields import DateTimeRangeField
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from djmoney.models.fields import MoneyField
|
from djmoney.models.fields import MoneyField
|
||||||
|
@ -13,49 +13,18 @@ class CreatedModifiedAbstract(models.Model):
|
||||||
abstract = True
|
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):
|
class Membership(CreatedModifiedAbstract):
|
||||||
"""
|
"""
|
||||||
A user remains a member of an organization even though the subscription is
|
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
|
unpaid or renewed. This just changes the status/permissions etc. of the
|
||||||
membership, thus we need to track subscription creation, expiry, renewals
|
membership, thus we need to track subscription creation, expiry, renewals
|
||||||
etc. and ensure that the membership is modified accordingly.
|
etc. and ensure that the membership is modified accordingly.
|
||||||
|
|
||||||
This expresses some
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
organization = models.ForeignKey(Organization, on_delete=models.PROTECT)
|
user = models.OneToOneField("auth.User", 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):
|
def __str__(self):
|
||||||
|
return _(f"{self.user.get_full_name()} is a member")
|
||||||
return _("{} is a member of {}").format(
|
|
||||||
self.user.get_full_name(), self.organization.name
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("membership")
|
verbose_name = _("membership")
|
||||||
|
@ -68,18 +37,12 @@ class SubscriptionType(CreatedModifiedAbstract):
|
||||||
after subscriptions are created.
|
after subscriptions are created.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
organization = models.ForeignKey(Organization, on_delete=models.PROTECT)
|
|
||||||
|
|
||||||
name = models.CharField(verbose_name=_("name"), max_length=64)
|
name = models.CharField(verbose_name=_("name"), max_length=64)
|
||||||
|
|
||||||
fee = MoneyField(max_digits=16, decimal_places=2)
|
fee = MoneyField(max_digits=16, decimal_places=2)
|
||||||
|
|
||||||
fee_vat = MoneyField(max_digits=16, decimal_places=2, default=0)
|
fee_vat = MoneyField(max_digits=16, decimal_places=2, default=0)
|
||||||
|
|
||||||
duration = models.PositiveSmallIntegerField(
|
|
||||||
default=1, choices=[(1, _("annual"))], verbose_name=_("duration")
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("subscription type")
|
verbose_name = _("subscription type")
|
||||||
verbose_name_plural = _("subscription types")
|
verbose_name_plural = _("subscription types")
|
||||||
|
@ -88,7 +51,7 @@ class SubscriptionType(CreatedModifiedAbstract):
|
||||||
class Subscription(CreatedModifiedAbstract):
|
class Subscription(CreatedModifiedAbstract):
|
||||||
"""
|
"""
|
||||||
To not confuse other types of subscriptions, one can be a *subscribed*
|
To not confuse other types of subscriptions, one can be a *subscribed*
|
||||||
member of an organization, meaning that they are paying etc.
|
member, meaning that they are paying etc.
|
||||||
|
|
||||||
A subscription does not track payment, this is done in the accounting app.
|
A subscription does not track payment, this is done in the accounting app.
|
||||||
"""
|
"""
|
||||||
|
@ -99,7 +62,7 @@ class Subscription(CreatedModifiedAbstract):
|
||||||
verbose_name=_("subscription type"),
|
verbose_name=_("subscription type"),
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
)
|
)
|
||||||
user = models.ForeignKey(get_user_model(), on_delete=models.PROTECT)
|
user = models.ForeignKey("auth.User", on_delete=models.PROTECT)
|
||||||
|
|
||||||
active = models.BooleanField(
|
active = models.BooleanField(
|
||||||
default=False,
|
default=False,
|
||||||
|
@ -107,16 +70,7 @@ class Subscription(CreatedModifiedAbstract):
|
||||||
help_text=_("Automatically set by payment system."),
|
help_text=_("Automatically set by payment system."),
|
||||||
)
|
)
|
||||||
|
|
||||||
starts = models.DateField()
|
duration = DateTimeRangeField(help_text=_("The duration this subscription is for. "))
|
||||||
ends = models.DateField()
|
|
||||||
|
|
||||||
renewed_subscription = models.ForeignKey(
|
|
||||||
"self",
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
verbose_name=_("renewed subscription"),
|
|
||||||
on_delete=models.PROTECT,
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("subscription")
|
verbose_name = _("subscription")
|
|
@ -1,26 +1,18 @@
|
||||||
"""
|
from pathlib import Path
|
||||||
Django settings for membersystem project.
|
from environs import Env
|
||||||
|
|
||||||
Generated by 'django-admin startproject' using Django 2.0.4.
|
env = Env()
|
||||||
|
env.read_env()
|
||||||
|
|
||||||
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
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
https://docs.djangoproject.com/en/2.0/ref/settings/
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
"""
|
|
||||||
import os
|
|
||||||
|
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
SECRET_KEY = env.str("SECRET_KEY", default="something-very-secret")
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
DEBUG = env.bool("DEBUG", default=False)
|
||||||
# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/
|
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
ALLOWED_HOSTS = env.list("ALLOWED_HOSTS", default=["*"])
|
||||||
DEBUG = False
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = []
|
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
@ -33,11 +25,15 @@ INSTALLED_APPS = [
|
||||||
"django.contrib.messages",
|
"django.contrib.messages",
|
||||||
"django.contrib.staticfiles",
|
"django.contrib.staticfiles",
|
||||||
"django.contrib.sites",
|
"django.contrib.sites",
|
||||||
"users",
|
"debug_toolbar",
|
||||||
|
"allauth",
|
||||||
|
"allauth.account",
|
||||||
"accounting",
|
"accounting",
|
||||||
"membership",
|
"membership",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
DATABASES = {"default": env.dj_db_url("DATABASE_URL")}
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
"django.middleware.security.SecurityMiddleware",
|
"django.middleware.security.SecurityMiddleware",
|
||||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
|
@ -46,6 +42,7 @@ MIDDLEWARE = [
|
||||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
"django.contrib.messages.middleware.MessageMiddleware",
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
|
"debug_toolbar.middleware.DebugToolbarMiddleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = "project.urls"
|
ROOT_URLCONF = "project.urls"
|
||||||
|
@ -53,7 +50,7 @@ ROOT_URLCONF = "project.urls"
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
"DIRS": [os.path.join("project", "templates")],
|
"DIRS": [BASE_DIR / "project" / "templates"],
|
||||||
"APP_DIRS": True,
|
"APP_DIRS": True,
|
||||||
"OPTIONS": {
|
"OPTIONS": {
|
||||||
"context_processors": [
|
"context_processors": [
|
||||||
|
@ -75,20 +72,6 @@ AUTHENTICATION_BACKENDS = (
|
||||||
WSGI_APPLICATION = "project.wsgi.application"
|
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 = [
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
{
|
{
|
||||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" # noqa
|
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" # noqa
|
||||||
|
@ -100,15 +83,9 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
AUTH_USER_MODEL = "users.User"
|
LANGUAGE_CODE = "da-dk"
|
||||||
|
|
||||||
|
TIME_ZONE = "Europe/Copenhagen"
|
||||||
# Internationalization
|
|
||||||
# https://docs.djangoproject.com/en/2.0/topics/i18n/
|
|
||||||
|
|
||||||
LANGUAGE_CODE = "en-us"
|
|
||||||
|
|
||||||
TIME_ZONE = "UTC"
|
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
|
@ -116,17 +93,22 @@ USE_L10N = True
|
||||||
|
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
|
||||||
# https://docs.djangoproject.com/en/2.0/howto/static-files/
|
|
||||||
|
|
||||||
STATIC_URL = "/static/"
|
STATIC_URL = "/static/"
|
||||||
|
|
||||||
STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")]
|
STATICFILES_DIRS = [BASE_DIR / "project" / "static"]
|
||||||
|
|
||||||
SITE_ID = 1
|
SITE_ID = 1
|
||||||
|
|
||||||
|
LOGIN_REDIRECT_URL = "/"
|
||||||
|
|
||||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||||
|
|
||||||
|
# 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",)
|
CURRENCIES = ("DKK",)
|
||||||
CURRENCY_CHOICES = [("DKK", "DKK")]
|
CURRENCY_CHOICES = [("DKK", "DKK")]
|
7
src/project/static/css/bootstrap.min.css
vendored
Normal file
7
src/project/static/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/project/static/css/bootstrap.min.css.map
Normal file
1
src/project/static/css/bootstrap.min.css.map
Normal file
File diff suppressed because one or more lines are too long
7
src/project/static/js/bootstrap.bundle.min.js
vendored
Normal file
7
src/project/static/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/project/static/js/bootstrap.bundle.min.js.map
Normal file
1
src/project/static/js/bootstrap.bundle.min.js.map
Normal file
File diff suppressed because one or more lines are too long
128
src/project/templates/account/login.html
Normal file
128
src/project/templates/account/login.html
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="description" content="">
|
||||||
|
<title>Login – {{ site.name }}</title>
|
||||||
|
|
||||||
|
<link href="{% static "/css/bootstrap.min.css" %}" rel="stylesheet"
|
||||||
|
integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl"
|
||||||
|
crossorigin="anonymous">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 40px;
|
||||||
|
padding-bottom: 40px;
|
||||||
|
background-color: #a8f3f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 330px;
|
||||||
|
padding: 15px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin .checkbox {
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin .form-control {
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: auto;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin .form-control:focus {
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin input[type="email"] {
|
||||||
|
margin-bottom: -1px;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin input[type="password"] {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hr-text:after {
|
||||||
|
content: attr(data-content);
|
||||||
|
padding: 0 4px;
|
||||||
|
position: relative;
|
||||||
|
top: -13px;
|
||||||
|
background-color: #a8f3f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body class="text-center">
|
||||||
|
|
||||||
|
<main class="form-signin">
|
||||||
|
|
||||||
|
<img class="mb-4" src="https://new.data.coop/static/img/logo_da.svg" alt=""
|
||||||
|
width="260" height="160">
|
||||||
|
<h1 class="h3 mb-3 fw-normal">{% trans "Members only" %}</h1>
|
||||||
|
|
||||||
|
{% if form.non_field_errors %}
|
||||||
|
{% for error in form.non_field_errors %}
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form method="post" action="">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<label for="id_username"
|
||||||
|
class="visually-hidden">
|
||||||
|
{% trans "Username/e-mail" %}
|
||||||
|
</label>
|
||||||
|
<input type="text"
|
||||||
|
id="id_username"
|
||||||
|
name="login"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="{% trans "Username/e-mail" %}"
|
||||||
|
required
|
||||||
|
autofocus>
|
||||||
|
|
||||||
|
<label for="id_password" class="visually-hidden">
|
||||||
|
{% trans "Password" %}
|
||||||
|
</label>
|
||||||
|
<input type="password"
|
||||||
|
id="id_password"
|
||||||
|
name="password"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="{% trans "Password" %}"
|
||||||
|
required>
|
||||||
|
|
||||||
|
<button class="w-100 btn btn-lg btn-primary"
|
||||||
|
type="submit">{% trans "Sign in" %}</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<hr class="hr-text" data-content="OR">
|
||||||
|
|
||||||
|
<a class="w-100 btn btn-lg btn-outline-success"
|
||||||
|
type="submit">{% trans "Become a member" %}</a>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
181
src/project/templates/base.html
Normal file
181
src/project/templates/base.html
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="description" content="">
|
||||||
|
<title>{% block head_title %}{% endblock %} – {{ site.name }}</title>
|
||||||
|
|
||||||
|
<link href="{% static "/css/bootstrap.min.css" %}" rel="stylesheet"
|
||||||
|
integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl"
|
||||||
|
crossorigin="anonymous">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-size: .875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sidebar
|
||||||
|
*/
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 100; /* Behind the navbar */
|
||||||
|
padding: 48px 0 0; /* Height of navbar */
|
||||||
|
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767.98px) {
|
||||||
|
.sidebar {
|
||||||
|
top: 5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-sticky {
|
||||||
|
position: relative;
|
||||||
|
top: 0;
|
||||||
|
height: calc(100vh - 48px);
|
||||||
|
padding-top: .5rem;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .nav-link {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .nav-link .feather {
|
||||||
|
margin-right: 4px;
|
||||||
|
color: #727272;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .nav-link.active {
|
||||||
|
color: #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .nav-link:hover .feather,
|
||||||
|
.sidebar .nav-link.active .feather {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-heading {
|
||||||
|
font-size: .75rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Navbar
|
||||||
|
*/
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
padding-top: .75rem;
|
||||||
|
padding-bottom: .75rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
background-color: rgba(0, 0, 0, .25);
|
||||||
|
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .navbar-toggler {
|
||||||
|
top: .25rem;
|
||||||
|
right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .form-control {
|
||||||
|
padding: .75rem 1rem;
|
||||||
|
border-width: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control-dark {
|
||||||
|
color: #fff;
|
||||||
|
background-color: rgba(255, 255, 255, .1);
|
||||||
|
border-color: rgba(255, 255, 255, .1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control-dark:focus {
|
||||||
|
border-color: transparent;
|
||||||
|
box-shadow: 0 0 0 3px rgba(255, 255, 255, .25);
|
||||||
|
} </style>
|
||||||
|
|
||||||
|
<script src="{% static "js/bootstrap.bundle.min.js" %}"
|
||||||
|
integrity="sha384-b5kHyXgcpbZJO/tY9Ul7kGkf1S0CWuKcCD38l8YkeH8z8QjE0GmW1gYU5S9FOnJ0"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
|
||||||
|
<a class="navbar-brand col-md-3 col-lg-2 me-0 px-3" href="#">{{ site.name }}</a>
|
||||||
|
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button"
|
||||||
|
data-bs-toggle="collapse" data-bs-target="#sidebarMenu"
|
||||||
|
aria-controls="sidebarMenu" aria-expanded="false"
|
||||||
|
aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<ul class="navbar-nav px-3">
|
||||||
|
<li class="nav-item text-nowrap">
|
||||||
|
<a class="nav-link" href="{% url "account_logout" %}">
|
||||||
|
<i class="fas fa-user"></i>
|
||||||
|
Sign out</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<nav id="sidebarMenu"
|
||||||
|
class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
|
||||||
|
<div class="position-sticky pt-3">
|
||||||
|
<ul class="nav flex-column">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" aria-current="page" href="#">
|
||||||
|
{% trans "Overview" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="#">
|
||||||
|
{% trans "Profile" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="#">
|
||||||
|
{% trans "Membership" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="#">
|
||||||
|
{% trans "Services" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% if user.is_staff %}
|
||||||
|
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
|
||||||
|
<span>{% trans "Admin" %}</span>
|
||||||
|
</h6>
|
||||||
|
<ul class="nav flex-column mb-2">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="#">
|
||||||
|
<span data-feather="file-text"></span>
|
||||||
|
{% trans "Members" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,10 +1,10 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>{% block head_title %}{% endblock %} – {{ site.name }}</title>
|
<title>{% block head_title %}{% endblock %} – {{ site.name }}</title>
|
||||||
{% block extra_head %}{% endblock %}
|
{% block extra_head %}{% endblock %}
|
||||||
<link rel="stylesheet" href="{% static '/css/membersystem.css' %}" type="text/css" />
|
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}" type="text/css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
16
src/project/urls.py
Normal file
16
src/project/urls.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
"""URLs for the membersystem"""
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.urls import include
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
import debug_toolbar
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("", login_required(views.index)),
|
||||||
|
path('accounts/', include('allauth.urls')),
|
||||||
|
path("admin/", admin.site.urls),
|
||||||
|
path("__debug__/", include(debug_toolbar.urls)),
|
||||||
|
]
|
|
@ -1 +0,0 @@
|
||||||
from django.contrib import admin # noqa
|
|
|
@ -1,5 +0,0 @@
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class UsersConfig(AppConfig):
|
|
||||||
name = "users"
|
|
|
@ -1,19 +0,0 @@
|
||||||
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",)
|
|
|
@ -1,73 +0,0 @@
|
||||||
# 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"},
|
|
||||||
)
|
|
||||||
]
|
|
|
@ -1,60 +0,0 @@
|
||||||
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")
|
|
|
@ -1,10 +0,0 @@
|
||||||
{% 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 %}
|
|
|
@ -1,39 +0,0 @@
|
||||||
{% 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 %}
|
|
|
@ -1,8 +0,0 @@
|
||||||
{% 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 %}
|
|
|
@ -1,52 +0,0 @@
|
||||||
{% 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 %}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{% 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 %}
|
|
|
@ -1,34 +0,0 @@
|
||||||
{% 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 %}
|
|
|
@ -1,12 +0,0 @@
|
||||||
{% 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 %}
|
|
|
@ -1,14 +0,0 @@
|
||||||
{% 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 %}
|
|
|
@ -1,19 +0,0 @@
|
||||||
{% 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 %}
|
|
|
@ -1,21 +0,0 @@
|
||||||
{% 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 %}
|
|
|
@ -1,10 +0,0 @@
|
||||||
{% 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 %}
|
|
|
@ -1,61 +0,0 @@
|
||||||
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",
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,59 +0,0 @@
|
||||||
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
|
|
Loading…
Reference in a new issue