Compare commits
No commits in common. "50e2ba642f6cf3bee207d36ad69f08a09f9a8549" and "2be2755cb6719ec4c013583adb2a4289d49c91d3" have entirely different histories.
50e2ba642f
...
2be2755cb6
33
.drone.yml
33
.drone.yml
|
@ -1,33 +0,0 @@
|
|||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: docker
|
||||
image: plugins/docker
|
||||
environment:
|
||||
DJANGO_ENV: production
|
||||
settings:
|
||||
repo: docker.data.coop/member.data.coop
|
||||
registry: docker.data.coop
|
||||
username:
|
||||
from_secret: DOCKER_USERNAME
|
||||
password:
|
||||
from_secret: DOCKER_PASSWORD
|
||||
tags:
|
||||
- "${DRONE_BUILD_NUMBER}"
|
||||
- "vidir_refactor"
|
||||
when:
|
||||
branch:
|
||||
- vidir_refactor
|
||||
|
||||
# - name: notify
|
||||
# image: plugins/matrix
|
||||
# settings:
|
||||
# homeserver: https://data.coop
|
||||
# roomid: plKSghHbepWeUEtbHE:data.coop
|
||||
# username:
|
||||
# from_secret: matrix_username
|
||||
# password:
|
||||
# from_secret: matrix_password
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -2,6 +2,5 @@ __pycache__/
|
|||
*.pyc
|
||||
*.sw*
|
||||
db.sqlite3
|
||||
project/settings/local.py
|
||||
.pytest_cache
|
||||
.idea/
|
||||
*.mo
|
||||
|
|
42
Makefile
42
Makefile
|
@ -1,39 +1,13 @@
|
|||
DOCKER_COMPOSE = COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose
|
||||
DOCKER_RUN = ${DOCKER_COMPOSE} run -e UID=`id -u` -e GID=`id -g`
|
||||
MANAGE = ${DOCKER_RUN} backend python /app/src/manage.py
|
||||
# These are just some make targets, expressing how things
|
||||
# are supposed to be run, but feel free to change them!
|
||||
|
||||
dev-setup:
|
||||
poetry run pre-commit install
|
||||
poetry run python manage.py migrate
|
||||
poetry run python manage.py createsuperuser
|
||||
|
||||
lint:
|
||||
poetry run pre-commit run --all
|
||||
|
||||
run:
|
||||
${DOCKER_COMPOSE} up --build
|
||||
|
||||
build:
|
||||
${DOCKER_COMPOSE} build
|
||||
|
||||
makemessages:
|
||||
${MANAGE} makemessages -a
|
||||
|
||||
makemigrations:
|
||||
${MANAGE} makemigrations ${EXTRA_ARGS}
|
||||
|
||||
migrate:
|
||||
${MANAGE} migrate ${EXTRA_ARGS}
|
||||
|
||||
createsuperuser:
|
||||
${MANAGE} createsuperuser
|
||||
|
||||
shell:
|
||||
${MANAGE} shell
|
||||
|
||||
manage_command:
|
||||
${MANAGE} ${COMMAND}
|
||||
|
||||
test:
|
||||
${DOCKER_RUN} backend pytest src/
|
||||
|
||||
add_dependency:
|
||||
${DOCKER_RUN} backend poetry add --lock ${DEPENDENCY}
|
||||
|
||||
add_dev_dependency:
|
||||
${DOCKER_RUN} backend poetry add -D --lock ${DEPENDENCY}
|
||||
poetry run pytest
|
||||
|
|
33
README.md
33
README.md
|
@ -1,35 +1,24 @@
|
|||
# member.data.coop
|
||||
|
||||
## Development requirements
|
||||
To start developing:
|
||||
|
||||
- Docker
|
||||
- Docker compose
|
||||
- pre-commit (preferred for contributions)
|
||||
Get poetry
|
||||
|
||||
## Start local server
|
||||
$ python3 -m pip install --user pipx
|
||||
$ pipx install poetry
|
||||
|
||||
Given that the requirements above are installed, it should be as easy as:
|
||||
Run poetry to setup environment
|
||||
|
||||
$ make migrate
|
||||
$ poetry install
|
||||
|
||||
This will setup the database. Next run:
|
||||
Run this make target, which installs all the requirements and sets up a development database.
|
||||
|
||||
$ make run
|
||||
$ make dev-setup
|
||||
|
||||
This will build the docker image and start the member system on http://localhost:8000.
|
||||
To run the Django development server:
|
||||
|
||||
You can create a superuser by running:
|
||||
$ poetry run python manage.py runserver
|
||||
|
||||
$ make createsuperuser
|
||||
|
||||
Make migrations:
|
||||
|
||||
$ make makemigrations
|
||||
|
||||
Make messages:
|
||||
|
||||
$ make makemessages
|
||||
|
||||
Running tests:
|
||||
Before you push your stuff, run tests:
|
||||
|
||||
$ make test
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# Generated by Django 3.1.7 on 2021-02-27 20:06
|
||||
# Generated by Django 2.0.6 on 2018-06-23 19:51
|
||||
from decimal import Decimal
|
||||
|
||||
import django.db.models.deletion
|
||||
import djmoney.models.fields
|
||||
from django.conf import settings
|
||||
|
@ -10,9 +12,7 @@ class Migration(migrations.Migration):
|
|||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
|
@ -33,7 +33,7 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
(
|
||||
"created",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="oprettet"),
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
||||
),
|
||||
(
|
||||
"owner",
|
||||
|
@ -43,9 +43,7 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
options={"abstract": False},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Order",
|
||||
|
@ -65,7 +63,7 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
(
|
||||
"created",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="oprettet"),
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
||||
),
|
||||
(
|
||||
"description",
|
||||
|
@ -84,6 +82,7 @@ class Migration(migrations.Migration):
|
|||
"price",
|
||||
djmoney.models.fields.MoneyField(
|
||||
decimal_places=2,
|
||||
default=Decimal("0.0"),
|
||||
max_digits=16,
|
||||
verbose_name="price (excl. VAT)",
|
||||
),
|
||||
|
@ -100,7 +99,10 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"vat",
|
||||
djmoney.models.fields.MoneyField(
|
||||
decimal_places=2, max_digits=16, verbose_name="VAT"
|
||||
decimal_places=2,
|
||||
default=Decimal("0.0"),
|
||||
max_digits=16,
|
||||
verbose_name="VAT",
|
||||
),
|
||||
),
|
||||
("is_paid", models.BooleanField(default=False, verbose_name="is paid")),
|
||||
|
@ -108,7 +110,7 @@ class Migration(migrations.Migration):
|
|||
"account",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="accounting.account",
|
||||
to="accounting.Account",
|
||||
),
|
||||
),
|
||||
(
|
||||
|
@ -119,65 +121,7 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
),
|
||||
],
|
||||
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,
|
||||
},
|
||||
options={"verbose_name": "Order", "verbose_name_plural": "Orders"},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Payment",
|
||||
|
@ -197,7 +141,7 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
(
|
||||
"created",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="oprettet"),
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
||||
),
|
||||
(
|
||||
"amount_currency",
|
||||
|
@ -210,7 +154,9 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
(
|
||||
"amount",
|
||||
djmoney.models.fields.MoneyField(decimal_places=2, max_digits=16),
|
||||
djmoney.models.fields.MoneyField(
|
||||
decimal_places=2, default=Decimal("0.0"), max_digits=16
|
||||
),
|
||||
),
|
||||
(
|
||||
"description",
|
||||
|
@ -224,13 +170,64 @@ class Migration(migrations.Migration):
|
|||
"order",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="accounting.order",
|
||||
to="accounting.Order",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "payment",
|
||||
"verbose_name_plural": "payments",
|
||||
},
|
||||
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},
|
||||
),
|
||||
]
|
|
@ -1,6 +1,7 @@
|
|||
from hashlib import md5
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db import models
|
||||
from django.db.models.aggregates import Sum
|
||||
from django.utils.translation import gettext as _
|
||||
|
@ -23,7 +24,7 @@ class Account(CreatedModifiedAbstract):
|
|||
can decide which account to use to pay for something.
|
||||
"""
|
||||
|
||||
owner = models.ForeignKey("auth.User", on_delete=models.PROTECT)
|
||||
owner = models.ForeignKey(get_user_model(), on_delete=models.PROTECT)
|
||||
|
||||
@property
|
||||
def balance(self):
|
||||
|
@ -55,8 +56,9 @@ class Order(CreatedModifiedAbstract):
|
|||
invoices at the moment.
|
||||
"""
|
||||
|
||||
user = models.ForeignKey("auth.User", on_delete=models.PROTECT)
|
||||
user = models.ForeignKey(get_user_model(), on_delete=models.PROTECT)
|
||||
account = models.ForeignKey(Account, on_delete=models.PROTECT)
|
||||
is_paid = models.BooleanField(default=False)
|
||||
|
||||
description = models.CharField(max_length=1024, verbose_name=_("description"))
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
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:
|
|
@ -21,7 +21,6 @@ RUN apt-get update \
|
|||
libgdk-pixbuf2.0-0 \
|
||||
libffi-dev \
|
||||
shared-mime-info \
|
||||
gettext \
|
||||
&& pip install "poetry==$POETRY_VERSION"
|
||||
|
||||
WORKDIR /app
|
||||
|
@ -35,13 +34,10 @@ RUN poetry export -f requirements.txt $(test "$DJANGO_ENV" != production && echo
|
|||
RUN groupadd -g 1000 www
|
||||
RUN useradd -u 1000 -ms /bin/bash -g www www
|
||||
COPY --chown=www:www ./ /app/
|
||||
RUN mkdir /app/src/static && chown www:www /app/src/static
|
||||
RUN mkdir /app/static && chown www:www /app/static
|
||||
|
||||
ARG BUILD
|
||||
ENV BUILD ${BUILD}
|
||||
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["uvicorn", "project.asgi:application", "--host", "0.0.0.0", "--port", "8000", "--workers", "3", "--lifespan", "off", "--app-dir", "/app/src"]
|
||||
CMD ["uvicorn", "config.asgi:application", "--host", "0.0.0.0", "--port", "8080", "--workers", "3", "--lifespan", "off"]
|
|
@ -11,11 +11,11 @@ done
|
|||
|
||||
echo "PostgreSQL started"
|
||||
|
||||
# Only migrate, collectstatic and compilemessages if we are NOT in development
|
||||
# Only migrate and collectstatic if we are NOT in development
|
||||
if [ -z "$DEBUG" ]; then
|
||||
python src/manage.py migrate;
|
||||
python src/manage.py collectstatic --no-input;
|
||||
python src/manage.py compilemessages;
|
||||
python manage.py migrate
|
||||
python manage.py collectstatic --no-input;
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
|
||||
|
|
7
env
7
env
|
@ -1,7 +0,0 @@
|
|||
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
|
8
membership/admin.py
Normal file
8
membership/admin.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
@admin.register(models.Membership)
|
||||
class MembershipAdmin(admin.ModelAdmin):
|
||||
pass
|
227
membership/migrations/0001_initial.py
Normal file
227
membership/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,227 @@
|
|||
# Generated by Django 2.0.6 on 2018-06-23 19:07
|
||||
from decimal import Decimal
|
||||
|
||||
import django.db.models.deletion
|
||||
import djmoney.models.fields
|
||||
from django.conf import settings
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Membership",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"modified",
|
||||
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
||||
),
|
||||
(
|
||||
"created",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
||||
),
|
||||
(
|
||||
"can_vote",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="Indicates that the user has a democratic membership of the organization.",
|
||||
verbose_name="can vote",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "membership",
|
||||
"verbose_name_plural": "memberships",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Organization",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"modified",
|
||||
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
||||
),
|
||||
(
|
||||
"created",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
||||
),
|
||||
("name", models.CharField(max_length=64, verbose_name="name")),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "organization",
|
||||
"verbose_name_plural": "organizations",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Subscription",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"modified",
|
||||
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
||||
),
|
||||
(
|
||||
"created",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
||||
),
|
||||
(
|
||||
"active",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="Automatically set by payment system.",
|
||||
verbose_name="active",
|
||||
),
|
||||
),
|
||||
("starts", models.DateField()),
|
||||
("ends", models.DateField()),
|
||||
(
|
||||
"renewed_subscription",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="membership.Subscription",
|
||||
verbose_name="renewed subscription",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "subscription",
|
||||
"verbose_name_plural": "subscriptions",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="SubscriptionType",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"modified",
|
||||
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
||||
),
|
||||
(
|
||||
"created",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
||||
),
|
||||
("name", models.CharField(max_length=64, verbose_name="name")),
|
||||
(
|
||||
"fee_currency",
|
||||
djmoney.models.fields.CurrencyField(
|
||||
choices=[("DKK", "DKK")],
|
||||
default="XYZ",
|
||||
editable=False,
|
||||
max_length=3,
|
||||
),
|
||||
),
|
||||
(
|
||||
"fee",
|
||||
djmoney.models.fields.MoneyField(
|
||||
decimal_places=2, default=Decimal("0.0"), max_digits=16
|
||||
),
|
||||
),
|
||||
(
|
||||
"fee_vat_currency",
|
||||
djmoney.models.fields.CurrencyField(
|
||||
choices=[("DKK", "DKK")],
|
||||
default="XYZ",
|
||||
editable=False,
|
||||
max_length=3,
|
||||
),
|
||||
),
|
||||
(
|
||||
"fee_vat",
|
||||
djmoney.models.fields.MoneyField(
|
||||
decimal_places=2, default=Decimal("0"), max_digits=16
|
||||
),
|
||||
),
|
||||
(
|
||||
"duration",
|
||||
models.PositiveSmallIntegerField(
|
||||
choices=[(1, "annual")], default=1, verbose_name="duration"
|
||||
),
|
||||
),
|
||||
(
|
||||
"organization",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="membership.Organization",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "subscription type",
|
||||
"verbose_name_plural": "subscription types",
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="subscription",
|
||||
name="subscription_type",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="memberships",
|
||||
to="membership.SubscriptionType",
|
||||
verbose_name="subscription type",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="subscription",
|
||||
name="user",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="membership",
|
||||
name="organization",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="membership.Organization",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="membership",
|
||||
name="user",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
]
|
123
membership/models.py
Normal file
123
membership/models.py
Normal file
|
@ -0,0 +1,123 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext as _
|
||||
from djmoney.models.fields import MoneyField
|
||||
|
||||
|
||||
class CreatedModifiedAbstract(models.Model):
|
||||
|
||||
modified = models.DateTimeField(auto_now=True, verbose_name=_("modified"))
|
||||
created = models.DateTimeField(auto_now_add=True, verbose_name=_("created"))
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class Organization(CreatedModifiedAbstract):
|
||||
"""
|
||||
This holds the data of the organization that someone is a member of. It is
|
||||
possible that we'll create more advanced features here.
|
||||
"""
|
||||
|
||||
name = models.CharField(verbose_name=_("name"), max_length=64)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("organization")
|
||||
verbose_name_plural = _("organizations")
|
||||
|
||||
|
||||
class Membership(CreatedModifiedAbstract):
|
||||
"""
|
||||
A user remains a member of an organization even though the subscription is
|
||||
unpaid or renewed. This just changes the status/permissions etc. of the
|
||||
membership, thus we need to track subscription creation, expiry, renewals
|
||||
etc. and ensure that the membership is modified accordingly.
|
||||
|
||||
This expresses some
|
||||
"""
|
||||
|
||||
organization = models.ForeignKey(Organization, on_delete=models.PROTECT)
|
||||
user = models.ForeignKey(get_user_model(), on_delete=models.PROTECT)
|
||||
|
||||
can_vote = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_("can vote"),
|
||||
help_text=_(
|
||||
"Indicates that the user has a democratic membership of the "
|
||||
"organization."
|
||||
),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
|
||||
return _("{} is a member of {}").format(
|
||||
self.user.get_full_name(), self.organization.name
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("membership")
|
||||
verbose_name_plural = _("memberships")
|
||||
|
||||
|
||||
class SubscriptionType(CreatedModifiedAbstract):
|
||||
"""
|
||||
Properties of subscriptions are stored here. Should of course not be edited
|
||||
after subscriptions are created.
|
||||
"""
|
||||
|
||||
organization = models.ForeignKey(Organization, on_delete=models.PROTECT)
|
||||
|
||||
name = models.CharField(verbose_name=_("name"), max_length=64)
|
||||
|
||||
fee = MoneyField(max_digits=16, decimal_places=2)
|
||||
|
||||
fee_vat = MoneyField(max_digits=16, decimal_places=2, default=0)
|
||||
|
||||
duration = models.PositiveSmallIntegerField(
|
||||
default=1, choices=[(1, _("annual"))], verbose_name=_("duration")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("subscription type")
|
||||
verbose_name_plural = _("subscription types")
|
||||
|
||||
|
||||
class Subscription(CreatedModifiedAbstract):
|
||||
"""
|
||||
To not confuse other types of subscriptions, one can be a *subscribed*
|
||||
member of an organization, meaning that they are paying etc.
|
||||
|
||||
A subscription does not track payment, this is done in the accounting app.
|
||||
"""
|
||||
|
||||
subscription_type = models.ForeignKey(
|
||||
SubscriptionType,
|
||||
related_name="memberships",
|
||||
verbose_name=_("subscription type"),
|
||||
on_delete=models.PROTECT,
|
||||
)
|
||||
user = models.ForeignKey(get_user_model(), on_delete=models.PROTECT)
|
||||
|
||||
active = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_("active"),
|
||||
help_text=_("Automatically set by payment system."),
|
||||
)
|
||||
|
||||
starts = models.DateField()
|
||||
ends = models.DateField()
|
||||
|
||||
renewed_subscription = models.ForeignKey(
|
||||
"self",
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name=_("renewed subscription"),
|
||||
on_delete=models.PROTECT,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("subscription")
|
||||
verbose_name_plural = _("subscriptions")
|
378
poetry.lock
generated
378
poetry.lock
generated
|
@ -47,17 +47,6 @@ category = "main"
|
|||
optional = false
|
||||
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]]
|
||||
name = "cfgv"
|
||||
version = "3.2.0"
|
||||
|
@ -74,14 +63,6 @@ category = "main"
|
|||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "7.1.2"
|
||||
description = "Composable command line interface toolkit"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.4"
|
||||
|
@ -90,25 +71,6 @@ category = "dev"
|
|||
optional = false
|
||||
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]]
|
||||
name = "defusedxml"
|
||||
version = "0.6.0"
|
||||
|
@ -125,25 +87,9 @@ category = "dev"
|
|||
optional = false
|
||||
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]]
|
||||
name = "django"
|
||||
version = "3.1.7"
|
||||
version = "3.1.5"
|
||||
description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -160,42 +106,21 @@ bcrypt = ["bcrypt"]
|
|||
|
||||
[[package]]
|
||||
name = "django-allauth"
|
||||
version = "0.44.0"
|
||||
version = "0.40.0"
|
||||
description = "Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
Django = ">=2.0"
|
||||
pyjwt = {version = ">=1.7", extras = ["crypto"]}
|
||||
Django = ">=1.11"
|
||||
python3-openid = ">=3.0.8"
|
||||
requests = "*"
|
||||
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]]
|
||||
name = "django-money"
|
||||
version = "1.3.1"
|
||||
version = "1.3"
|
||||
description = "Adds support for using money and currency fields in django models and forms. Uses py-moneyed as the money implementation."
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -207,28 +132,7 @@ py-moneyed = ">=0.8,<1.0"
|
|||
|
||||
[package.extras]
|
||||
exchange = ["certifi"]
|
||||
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"]
|
||||
test = ["pytest (>=3.1.0)", "pytest-django", "pytest-pythonpath", "pytest-cov", "django-reversion", "mixer"]
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
|
@ -238,21 +142,13 @@ category = "dev"
|
|||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.12.0"
|
||||
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "identify"
|
||||
version = "2.0.0"
|
||||
version = "1.5.13"
|
||||
description = "File identification library for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6.1"
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
|
||||
|
||||
[package.extras]
|
||||
license = ["editdistance"]
|
||||
|
@ -266,22 +162,24 @@ optional = false
|
|||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "marshmallow"
|
||||
version = "3.10.0"
|
||||
description = "A lightweight library for converting complex datatypes to and from native Python datatypes."
|
||||
category = "main"
|
||||
name = "importlib-metadata"
|
||||
version = "3.4.0"
|
||||
description = "Read metadata from Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
|
||||
zipp = ">=0.5"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pytest", "pytz", "simplejson", "mypy (==0.790)", "flake8 (==3.8.4)", "flake8-bugbear (==20.11.1)", "pre-commit (>=2.4,<3.0)", "tox"]
|
||||
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"]
|
||||
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
|
||||
testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "more-itertools"
|
||||
version = "8.7.0"
|
||||
version = "8.6.0"
|
||||
description = "More routines for operating on iterables, beyond itertools"
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -310,7 +208,7 @@ signedtoken = ["cryptography", "pyjwt (>=1.0.0)"]
|
|||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "20.9"
|
||||
version = "20.8"
|
||||
description = "Core utilities for Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -327,12 +225,15 @@ category = "dev"
|
|||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[package.dependencies]
|
||||
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "tox"]
|
||||
|
||||
[[package]]
|
||||
name = "pre-commit"
|
||||
version = "2.10.1"
|
||||
version = "2.9.3"
|
||||
description = "A framework for managing and maintaining multi-language pre-commit hooks."
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -341,6 +242,7 @@ python-versions = ">=3.6.1"
|
|||
[package.dependencies]
|
||||
cfgv = ">=2.0.0"
|
||||
identify = ">=1.0.0"
|
||||
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
|
||||
nodeenv = ">=0.11.1"
|
||||
pyyaml = ">=5.1"
|
||||
toml = "*"
|
||||
|
@ -373,31 +275,6 @@ python-versions = "*"
|
|||
[package.extras]
|
||||
tests = ["pytest (>=2.3.0)", "tox (>=1.6.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "2.20"
|
||||
description = "C parser in Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "pyjwt"
|
||||
version = "2.0.1"
|
||||
description = "JSON Web Token implementation in Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
cryptography = {version = ">=3.3.1,<4.0.0", optional = true, markers = "extra == \"crypto\""}
|
||||
|
||||
[package.extras]
|
||||
crypto = ["cryptography (>=3.3.1,<4.0.0)"]
|
||||
dev = ["sphinx", "sphinx-rtd-theme", "zope.interface", "cryptography (>=3.3.1,<4.0.0)", "pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)", "mypy", "pre-commit"]
|
||||
docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"]
|
||||
tests = ["pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "2.4.7"
|
||||
|
@ -418,6 +295,7 @@ python-versions = ">=3.5"
|
|||
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
|
||||
attrs = ">=17.4.0"
|
||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
|
||||
more-itertools = ">=4.0.0"
|
||||
packaging = "*"
|
||||
pluggy = ">=0.12,<1.0"
|
||||
|
@ -443,17 +321,6 @@ pytest = ">=3.6"
|
|||
docs = ["sphinx", "sphinx-rtd-theme"]
|
||||
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]]
|
||||
name = "python3-openid"
|
||||
version = "3.2.0"
|
||||
|
@ -471,7 +338,7 @@ postgresql = ["psycopg2"]
|
|||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2021.1"
|
||||
version = "2020.5"
|
||||
description = "World timezone definitions, modern and historical"
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -542,6 +409,14 @@ category = "dev"
|
|||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "3.7.4.3"
|
||||
description = "Backported and Experimental Type Hints for Python 3.5+"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "1.26.3"
|
||||
|
@ -555,24 +430,9 @@ brotli = ["brotlipy (>=0.6.0)"]
|
|||
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.13.4"
|
||||
description = "The lightning-fast ASGI server."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=7.0.0,<8.0.0"
|
||||
h11 = ">=0.8"
|
||||
|
||||
[package.extras]
|
||||
standard = ["websockets (>=8.0.0,<9.0.0)", "watchgod (>=0.6)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "httptools (>=0.1.0,<0.2.0)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"]
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "20.4.2"
|
||||
version = "20.4.0"
|
||||
description = "Virtual Python Environment builder"
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -582,6 +442,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
|
|||
appdirs = ">=1.4.3,<2"
|
||||
distlib = ">=0.3.1,<1"
|
||||
filelock = ">=3.0.0,<4"
|
||||
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
|
||||
six = ">=1.9.0,<2"
|
||||
|
||||
[package.extras]
|
||||
|
@ -597,20 +458,21 @@ optional = false
|
|||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "whitenoise"
|
||||
version = "5.2.0"
|
||||
description = "Radically simplified static file serving for WSGI applications"
|
||||
category = "main"
|
||||
name = "zipp"
|
||||
version = "3.4.0"
|
||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5, <4"
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotli"]
|
||||
docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
|
||||
testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "273466dbd53a484027b72a712aba600936507cd2d528c9a245464637c0c54207"
|
||||
python-versions = "^3.7"
|
||||
content-hash = "29ee22cded289d14aaf1015cc00f5fd4b3e441f807c86b325e21c67c2314a274"
|
||||
|
||||
[metadata.files]
|
||||
appdirs = [
|
||||
|
@ -633,45 +495,6 @@ certifi = [
|
|||
{file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"},
|
||||
{file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"},
|
||||
]
|
||||
cffi = [
|
||||
{file = "cffi-1.14.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 = [
|
||||
{file = "cfgv-3.2.0-py2.py3-none-any.whl", hash = "sha256:32e43d604bbe7896fe7c248a9c2276447dbef840feb28fe20494f62af110211d"},
|
||||
{file = "cfgv-3.2.0.tar.gz", hash = "sha256:cf22deb93d4bcf92f345a5c3cd39d3d41d6340adc60c78bbbd6588c384fda6a1"},
|
||||
|
@ -680,28 +503,10 @@ chardet = [
|
|||
{file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"},
|
||||
{file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"},
|
||||
]
|
||||
click = [
|
||||
{file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
|
||||
{file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
|
||||
]
|
||||
colorama = [
|
||||
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
||||
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
|
||||
]
|
||||
cryptography = [
|
||||
{file = "cryptography-3.4.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 = [
|
||||
{file = "defusedxml-0.6.0-py2.py3-none-any.whl", hash = "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93"},
|
||||
{file = "defusedxml-0.6.0.tar.gz", hash = "sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"},
|
||||
|
@ -710,60 +515,36 @@ distlib = [
|
|||
{file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"},
|
||||
{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 = [
|
||||
{file = "Django-3.1.7-py3-none-any.whl", hash = "sha256:baf099db36ad31f970775d0be5587cc58a6256a6771a44eb795b554d45f211b8"},
|
||||
{file = "Django-3.1.7.tar.gz", hash = "sha256:32ce792ee9b6a0cbbec340123e229ac9f765dff8c2a4ae9247a14b2ba3a365a7"},
|
||||
{file = "Django-3.1.5-py3-none-any.whl", hash = "sha256:efa2ab96b33b20c2182db93147a0c3cd7769d418926f9e9f140a60dca7c64ca9"},
|
||||
{file = "Django-3.1.5.tar.gz", hash = "sha256:2d78425ba74c7a1a74b196058b261b9733a8570782f4e2828974777ccca7edf7"},
|
||||
]
|
||||
django-allauth = [
|
||||
{file = "django-allauth-0.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"},
|
||||
{file = "django-allauth-0.40.0.tar.gz", hash = "sha256:6a189fc4d3ee23596c3fd6e9f49c59b5b15618980118171a50675dd6a27cc589"},
|
||||
]
|
||||
django-money = [
|
||||
{file = "django-money-1.3.1.tar.gz", hash = "sha256:a363ce16a23e403befdafa9895b2f538a10f9d390b160f12140094a6dfd55246"},
|
||||
{file = "django_money-1.3.1-py3-none-any.whl", hash = "sha256:3b8fc751c8ae27cf877b8f3770ade1b63af97ee49a32ac08a6a1bc6d8d59f089"},
|
||||
]
|
||||
environs = [
|
||||
{file = "environs-9.3.1-py2.py3-none-any.whl", hash = "sha256:2da44b7c30114415aa858577fa6396ee326fc76a0a60f0f15e8260ba554f19dc"},
|
||||
{file = "environs-9.3.1.tar.gz", hash = "sha256:3f6def554abb5455141b540e6e0b72fda3853404f2b0d31658aab1bf95410db3"},
|
||||
{file = "django-money-1.3.tar.gz", hash = "sha256:da95f9a7174281eb2ef0f5f1584d5ee2670fc0d67707cd269816a73cae791eb3"},
|
||||
{file = "django_money-1.3-py3-none-any.whl", hash = "sha256:09952d49f998d089b21eb0f552d6dcb40d82626ab674d1caf0535bbd83a1ea01"},
|
||||
]
|
||||
filelock = [
|
||||
{file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"},
|
||||
{file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"},
|
||||
]
|
||||
h11 = [
|
||||
{file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"},
|
||||
{file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"},
|
||||
]
|
||||
identify = [
|
||||
{file = "identify-2.0.0-py2.py3-none-any.whl", hash = "sha256:9cdd81e5d2b6e76c3006d5226316dd947bd6324fbeebb881bec489202fa09d3a"},
|
||||
{file = "identify-2.0.0.tar.gz", hash = "sha256:b99aa309329c4fea679463eb35d169f3fbe13e66e9dd6162ad1856cbeb03dcbd"},
|
||||
{file = "identify-1.5.13-py2.py3-none-any.whl", hash = "sha256:9dfb63a2e871b807e3ba62f029813552a24b5289504f5b071dea9b041aee9fe4"},
|
||||
{file = "identify-1.5.13.tar.gz", hash = "sha256:70b638cf4743f33042bebb3b51e25261a0a10e80f978739f17e7fd4837664a66"},
|
||||
]
|
||||
idna = [
|
||||
{file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
|
||||
{file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
|
||||
]
|
||||
marshmallow = [
|
||||
{file = "marshmallow-3.10.0-py2.py3-none-any.whl", hash = "sha256:eca81d53aa4aafbc0e20566973d0d2e50ce8bf0ee15165bb799bec0df1e50177"},
|
||||
{file = "marshmallow-3.10.0.tar.gz", hash = "sha256:4ab2fdb7f36eb61c3665da67a7ce281c8900db08d72ba6bf0e695828253581f7"},
|
||||
importlib-metadata = [
|
||||
{file = "importlib_metadata-3.4.0-py3-none-any.whl", hash = "sha256:ace61d5fc652dc280e7b6b4ff732a9c2d40db2c0f92bc6cb74e07b73d53a1771"},
|
||||
{file = "importlib_metadata-3.4.0.tar.gz", hash = "sha256:fa5daa4477a7414ae34e95942e4dd07f62adf589143c875c133c1e53c4eff38d"},
|
||||
]
|
||||
more-itertools = [
|
||||
{file = "more-itertools-8.7.0.tar.gz", hash = "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713"},
|
||||
{file = "more_itertools-8.7.0-py3-none-any.whl", hash = "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced"},
|
||||
{file = "more-itertools-8.6.0.tar.gz", hash = "sha256:b3a9005928e5bed54076e6e549c792b306fddfe72b2d1d22dd63d42d5d3899cf"},
|
||||
{file = "more_itertools-8.6.0-py3-none-any.whl", hash = "sha256:8e1a2a43b2f2727425f2b5839587ae37093f19153dc26c0927d1048ff6557330"},
|
||||
]
|
||||
nodeenv = [
|
||||
{file = "nodeenv-1.5.0-py2.py3-none-any.whl", hash = "sha256:5304d424c529c997bc888453aeaa6362d242b6b4631e90f3d4bf1b290f1c84a9"},
|
||||
|
@ -774,16 +555,16 @@ oauthlib = [
|
|||
{file = "oauthlib-3.1.0.tar.gz", hash = "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889"},
|
||||
]
|
||||
packaging = [
|
||||
{file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"},
|
||||
{file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"},
|
||||
{file = "packaging-20.8-py2.py3-none-any.whl", hash = "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858"},
|
||||
{file = "packaging-20.8.tar.gz", hash = "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"},
|
||||
]
|
||||
pluggy = [
|
||||
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
|
||||
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
|
||||
]
|
||||
pre-commit = [
|
||||
{file = "pre_commit-2.10.1-py2.py3-none-any.whl", hash = "sha256:16212d1fde2bed88159287da88ff03796863854b04dc9f838a55979325a3d20e"},
|
||||
{file = "pre_commit-2.10.1.tar.gz", hash = "sha256:399baf78f13f4de82a29b649afd74bef2c4e28eb4f021661fc7f29246e8c7a3a"},
|
||||
{file = "pre_commit-2.9.3-py2.py3-none-any.whl", hash = "sha256:6c86d977d00ddc8a60d68eec19f51ef212d9462937acf3ea37c7adec32284ac0"},
|
||||
{file = "pre_commit-2.9.3.tar.gz", hash = "sha256:ee784c11953e6d8badb97d19bc46b997a3a9eded849881ec587accd8608d74a4"},
|
||||
]
|
||||
psycopg2 = [
|
||||
{file = "psycopg2-2.8.6-cp27-cp27m-win32.whl", hash = "sha256:068115e13c70dc5982dfc00c5d70437fe37c014c808acce119b5448361c03725"},
|
||||
|
@ -810,14 +591,6 @@ py-moneyed = [
|
|||
{file = "py-moneyed-0.8.0.tar.gz", hash = "sha256:ec73795171919d537880a33c44d07fcdf0a5225e8368684fe02f0e75a6404742"},
|
||||
{file = "py_moneyed-0.8.0-py2.py3-none-any.whl", hash = "sha256:c6691b914a5e4b5b2335cf113620479a52cc82988c0e143435a7c5c7d60cd4ad"},
|
||||
]
|
||||
pycparser = [
|
||||
{file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"},
|
||||
{file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},
|
||||
]
|
||||
pyjwt = [
|
||||
{file = "PyJWT-2.0.1-py3-none-any.whl", hash = "sha256:b70b15f89dc69b993d8a8d32c299032d5355c82f9b5b7e851d1a6d706dffe847"},
|
||||
{file = "PyJWT-2.0.1.tar.gz", hash = "sha256:a5c70a06e1f33d81ef25eecd50d50bd30e34de1ca8b2b9fa3fe0daaabcf69bf7"},
|
||||
]
|
||||
pyparsing = [
|
||||
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
|
||||
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
|
||||
|
@ -830,17 +603,13 @@ pytest-django = [
|
|||
{file = "pytest-django-3.10.0.tar.gz", hash = "sha256:4de6dbd077ed8606616958f77655fed0d5e3ee45159475671c7fa67596c6dba6"},
|
||||
{file = "pytest_django-3.10.0-py2.py3-none-any.whl", hash = "sha256:c33e3d3da14d8409b125d825d4e74da17bb252191bf6fc3da6856e27a8b73ea4"},
|
||||
]
|
||||
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 = [
|
||||
{file = "python3-openid-3.2.0.tar.gz", hash = "sha256:33fbf6928f401e0b790151ed2b5290b02545e8775f982485205a066f874aaeaf"},
|
||||
{file = "python3_openid-3.2.0-py3-none-any.whl", hash = "sha256:6626f771e0417486701e0b4daff762e7212e820ca5b29fcc0d05f6f8736dfa6b"},
|
||||
]
|
||||
pytz = [
|
||||
{file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"},
|
||||
{file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"},
|
||||
{file = "pytz-2020.5-py2.py3-none-any.whl", hash = "sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4"},
|
||||
{file = "pytz-2020.5.tar.gz", hash = "sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"},
|
||||
]
|
||||
pyyaml = [
|
||||
{file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
|
||||
|
@ -886,23 +655,24 @@ toml = [
|
|||
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
|
||||
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
||||
]
|
||||
typing-extensions = [
|
||||
{file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"},
|
||||
{file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"},
|
||||
{file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"},
|
||||
]
|
||||
urllib3 = [
|
||||
{file = "urllib3-1.26.3-py2.py3-none-any.whl", hash = "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80"},
|
||||
{file = "urllib3-1.26.3.tar.gz", hash = "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"},
|
||||
]
|
||||
uvicorn = [
|
||||
{file = "uvicorn-0.13.4-py3-none-any.whl", hash = "sha256:7587f7b08bd1efd2b9bad809a3d333e972f1d11af8a5e52a9371ee3a5de71524"},
|
||||
{file = "uvicorn-0.13.4.tar.gz", hash = "sha256:3292251b3c7978e8e4a7868f4baf7f7f7bb7e40c759ecc125c37e99cdea34202"},
|
||||
]
|
||||
virtualenv = [
|
||||
{file = "virtualenv-20.4.2-py2.py3-none-any.whl", hash = "sha256:2be72df684b74df0ea47679a7df93fd0e04e72520022c57b479d8f881485dbe3"},
|
||||
{file = "virtualenv-20.4.2.tar.gz", hash = "sha256:147b43894e51dd6bba882cf9c282447f780e2251cd35172403745fc381a0a80d"},
|
||||
{file = "virtualenv-20.4.0-py2.py3-none-any.whl", hash = "sha256:227a8fed626f2f20a6cdb0870054989f82dd27b2560a911935ba905a2a5e0034"},
|
||||
{file = "virtualenv-20.4.0.tar.gz", hash = "sha256:219ee956e38b08e32d5639289aaa5bd190cfbe7dafcb8fa65407fca08e808f9c"},
|
||||
]
|
||||
wcwidth = [
|
||||
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
|
||||
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
|
||||
]
|
||||
whitenoise = [
|
||||
{file = "whitenoise-5.2.0-py2.py3-none-any.whl", hash = "sha256:05d00198c777028d72d8b0bbd234db605ef6d60e9410125124002518a48e515d"},
|
||||
{file = "whitenoise-5.2.0.tar.gz", hash = "sha256:05ce0be39ad85740a78750c86a93485c40f08ad8c62a6006de0233765996e5c7"},
|
||||
zipp = [
|
||||
{file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"},
|
||||
{file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"},
|
||||
]
|
||||
|
|
14
project/settings/__init__.py
Normal file
14
project/settings/__init__.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
import warnings
|
||||
|
||||
from .base import * # noqa
|
||||
|
||||
try:
|
||||
from .local import * # noqa
|
||||
except ImportError:
|
||||
warnings.warn(
|
||||
"No settings.local, using a default SECRET_KEY 'hest'. You should "
|
||||
"write a custom local.py with this setting."
|
||||
)
|
||||
SECRET_KEY = "hest"
|
||||
DEBUG = True
|
||||
pass
|
132
project/settings/base.py
Normal file
132
project/settings/base.py
Normal file
|
@ -0,0 +1,132 @@
|
|||
"""
|
||||
Django settings for membersystem project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 2.0.4.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/2.0/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/2.0/ref/settings/
|
||||
"""
|
||||
import os
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = False
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"django.contrib.sites",
|
||||
"users",
|
||||
"accounting",
|
||||
"membership",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = "project.urls"
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [os.path.join("project", "templates")],
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.template.context_processors.debug",
|
||||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
"project.context_processors.current_site",
|
||||
]
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
"django.contrib.auth.backends.ModelBackend",
|
||||
"allauth.account.auth_backends.AuthenticationBackend",
|
||||
)
|
||||
|
||||
WSGI_APPLICATION = "project.wsgi.application"
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.sqlite3",
|
||||
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" # noqa
|
||||
},
|
||||
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, # noqa
|
||||
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, # noqa
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator" # noqa
|
||||
},
|
||||
]
|
||||
|
||||
AUTH_USER_MODEL = "users.User"
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/2.0/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = "en-us"
|
||||
|
||||
TIME_ZONE = "UTC"
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/2.0/howto/static-files/
|
||||
|
||||
STATIC_URL = "/static/"
|
||||
|
||||
STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")]
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||
|
||||
CURRENCIES = ("DKK",)
|
||||
CURRENCY_CHOICES = [("DKK", "DKK")]
|
79
project/static/css/membersystem.css
Normal file
79
project/static/css/membersystem.css
Normal file
|
@ -0,0 +1,79 @@
|
|||
/* General styles */
|
||||
html
|
||||
{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
font-size: 2.5vmin;
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
body
|
||||
{
|
||||
background: #fff;
|
||||
color: #000;
|
||||
margin: 1em auto;
|
||||
max-width: 50em;
|
||||
padding: 0 1em;
|
||||
box-shadow: 0 0 2.5em rgba(0, 0, 0, 20%);
|
||||
}
|
||||
|
||||
header,
|
||||
footer
|
||||
{
|
||||
background: #eee;
|
||||
padding: .5em;
|
||||
margin: 0 -1em;
|
||||
}
|
||||
|
||||
footer
|
||||
{
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
|
||||
header h1
|
||||
{
|
||||
font-size: 1em;
|
||||
float: left;
|
||||
padding: .5em .5em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
header ul,
|
||||
footer ul
|
||||
{
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
header ul li,
|
||||
footer ul li
|
||||
{
|
||||
display: inline;
|
||||
}
|
||||
|
||||
header ul li a,
|
||||
footer ul li a
|
||||
{
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
padding: .5em .5em;
|
||||
}
|
||||
|
||||
|
||||
/* Forms */
|
||||
label
|
||||
{
|
||||
display: block;
|
||||
padding: .5em 0;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
textarea
|
||||
{
|
||||
font-size: inherit;
|
||||
}
|
49
project/templates/base.html
Normal file
49
project/templates/base.html
Normal file
|
@ -0,0 +1,49 @@
|
|||
<!DOCTYPE html>
|
||||
{% load static %}
|
||||
<html>
|
||||
<head>
|
||||
<title>{% block head_title %}{% endblock %} – {{ site.name }}</title>
|
||||
{% block extra_head %}{% endblock %}
|
||||
<link rel="stylesheet" href="{% static '/css/membersystem.css' %}" type="text/css" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>
|
||||
<a href="/">{{ site.name }}</a>
|
||||
</h1>
|
||||
<ul>
|
||||
{% if user.is_authenticated %}
|
||||
<li><a href="{% url 'users:password_change' %}">Change password</a></li>
|
||||
<li><a href="{% url 'users:logout' %}">Sign out</a></li>
|
||||
{% else %}
|
||||
<li><a href="{% url 'users:login' %}">Sign in</a></li>
|
||||
<li><a href="{% url 'users:signup' %}">Sign up</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</header>
|
||||
{% block body %}
|
||||
{% if messages %}
|
||||
<ul id="messages">
|
||||
{% for message in messages %}
|
||||
<li>{{message}}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
{% block extra_body %}
|
||||
{% endblock %}
|
||||
<footer>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://data.coop">data.coop</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://git.data.coop/data.coop/membersystem">source code</a>
|
||||
</li>
|
||||
</ul>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
12
project/urls.py
Normal file
12
project/urls.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
"""URLs for the membersystem"""
|
||||
from django.contrib import admin
|
||||
from django.urls import include
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path("", views.index),
|
||||
path("users/", include("users.urls")),
|
||||
path("admin/", admin.site.urls),
|
||||
]
|
|
@ -3,7 +3,3 @@ from django.shortcuts import render
|
|||
|
||||
def index(request):
|
||||
return render(request, "index.html")
|
||||
|
||||
|
||||
def services_overview(request):
|
||||
return render(request, "services_overview.html")
|
|
@ -5,20 +5,16 @@ description = ""
|
|||
authors = ["Your Name <you@example.com>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.9"
|
||||
python = "^3.7"
|
||||
Django = "^3.1"
|
||||
django-money = "^1.3"
|
||||
django-allauth = "^0.44.0"
|
||||
django-allauth = "^0.40.0"
|
||||
psycopg2 = "^2.8.6"
|
||||
environs = {extras = ["django"], version = "^9.3.1"}
|
||||
uvicorn = "^0.13.4"
|
||||
whitenoise = "^5.2.0"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
pre-commit = "^2.9.3"
|
||||
pytest = "^5.1"
|
||||
pytest-django = "^3.5"
|
||||
django-debug-toolbar = "^3.2"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry>=0.12"]
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from .models import Membership
|
||||
from .models import MembershipType
|
||||
|
||||
|
||||
@admin.register(Membership)
|
||||
class MembershipAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
@admin.register(MembershipType)
|
||||
class MembershipTypeAdmin(admin.ModelAdmin):
|
||||
pass
|
|
@ -1,93 +0,0 @@
|
|||
# Generated by Django 3.1.7 on 2021-02-28 21:09
|
||||
import django.contrib.postgres.fields.ranges
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="MembershipType",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"modified",
|
||||
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
||||
),
|
||||
(
|
||||
"created",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="oprettet"),
|
||||
),
|
||||
("name", models.CharField(max_length=64, verbose_name="navn")),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "membership type",
|
||||
"verbose_name_plural": "membership types",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Membership",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"modified",
|
||||
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
||||
),
|
||||
(
|
||||
"created",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="oprettet"),
|
||||
),
|
||||
(
|
||||
"period",
|
||||
django.contrib.postgres.fields.ranges.DateTimeRangeField(
|
||||
help_text="The duration this subscription is for. "
|
||||
),
|
||||
),
|
||||
(
|
||||
"membership_type",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="memberships",
|
||||
to="membership.membershiptype",
|
||||
verbose_name="subscription type",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "membership",
|
||||
"verbose_name_plural": "memberships",
|
||||
},
|
||||
),
|
||||
]
|
|
@ -1,76 +0,0 @@
|
|||
from typing import Optional
|
||||
|
||||
from django.contrib.postgres.fields import DateTimeRangeField
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
|
||||
class CreatedModifiedAbstract(models.Model):
|
||||
|
||||
modified = models.DateTimeField(auto_now=True, verbose_name=_("modified"))
|
||||
created = models.DateTimeField(auto_now_add=True, verbose_name=_("created"))
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class Membership(CreatedModifiedAbstract):
|
||||
"""
|
||||
Tracks that a user has membership of a given type for a given period.
|
||||
"""
|
||||
|
||||
class QuerySet(models.QuerySet):
|
||||
def for_user(self, user):
|
||||
return self.filter(user=user)
|
||||
|
||||
def _current(self):
|
||||
return self.filter(period__contains=timezone.now())
|
||||
|
||||
def current(self) -> Optional["Membership"]:
|
||||
try:
|
||||
return self._current().get()
|
||||
except self.model.DoesNotExist:
|
||||
return None
|
||||
|
||||
def previous(self):
|
||||
# A naïve way to get previous by just excluding the current. This
|
||||
# means that there must be some protection against "future"
|
||||
# memberships.
|
||||
return self.all().difference(self._current())
|
||||
|
||||
objects = QuerySet.as_manager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("membership")
|
||||
verbose_name_plural = _("memberships")
|
||||
|
||||
user = models.ForeignKey("auth.User", on_delete=models.PROTECT)
|
||||
|
||||
membership_type = models.ForeignKey(
|
||||
"membership.MembershipType",
|
||||
related_name="memberships",
|
||||
verbose_name=_("subscription type"),
|
||||
on_delete=models.PROTECT,
|
||||
)
|
||||
|
||||
period = DateTimeRangeField(help_text=_("The duration this subscription is for. "))
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user} - {self.period}"
|
||||
|
||||
|
||||
class MembershipType(CreatedModifiedAbstract):
|
||||
"""
|
||||
Models membership types. Currently only a name, but will in the future
|
||||
possibly contain more information like fees.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("membership type")
|
||||
verbose_name_plural = _("membership types")
|
||||
|
||||
name = models.CharField(verbose_name=_("name"), max_length=64)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
|
@ -1,22 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if not current_membership %}
|
||||
<p>{% trans "You do not have an active membership!" %}</p>
|
||||
|
||||
<p>{% trans "You can become a member by depositing the membership fee to our bank account." %}</p>
|
||||
|
||||
<ul>
|
||||
<li>Reg. 8401 (Merkur)</li>
|
||||
<li>Kontonr. 1016866</li>
|
||||
<li>Tekst på overførslen: Your email</li>
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>{% trans "You are a member!" %}</p>
|
||||
|
||||
<p>{% trans "Period" %}: {{ current_membership.period.lower|date:"SHORT_DATE_FORMAT" }} to {{ current_membership.period.upper|date:"SHORT_DATE_FORMAT" }}</p>
|
||||
<p>{% trans "Type" %}: {{ current_membership.membership_type }}</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -1,18 +0,0 @@
|
|||
from django.contrib.auth.decorators import login_required
|
||||
from django.shortcuts import render
|
||||
|
||||
from .models import Membership
|
||||
|
||||
|
||||
@login_required
|
||||
def membership_overview(request):
|
||||
memberships = Membership.objects.for_user(request.user)
|
||||
current_membership = memberships.current()
|
||||
previous_memberships = memberships.previous()
|
||||
|
||||
context = dict(
|
||||
current_membership=current_membership,
|
||||
previous_memberships=previous_memberships,
|
||||
)
|
||||
|
||||
return render(request, "membership_overview.html", context)
|
|
@ -1,7 +0,0 @@
|
|||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
|
||||
|
||||
application = get_asgi_application()
|
|
@ -1,519 +0,0 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-03-04 09:06+0100\n"
|
||||
"PO-Revision-Date: 2021-03-04 09:06+0100\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: da\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 2.4.1\n"
|
||||
|
||||
#: src/accounting/admin.py:15 src/accounting/admin.py:26
|
||||
msgid "Customer"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/admin.py:31
|
||||
msgid "Order ID"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/models.py:13 src/membership/models.py:11
|
||||
msgid "modified"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/models.py:14 src/membership/models.py:12
|
||||
msgid "created"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/models.py:43
|
||||
msgid "amount"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/models.py:46
|
||||
msgid "This will include VAT"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/models.py:48 src/accounting/models.py:61
|
||||
#: src/accounting/models.py:100
|
||||
msgid "description"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/models.py:64
|
||||
msgid "price (excl. VAT)"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/models.py:66
|
||||
msgid "VAT"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/models.py:68
|
||||
msgid "is paid"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/models.py:88
|
||||
msgctxt "accounting term"
|
||||
msgid "Order"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/models.py:89
|
||||
msgctxt "accounting term"
|
||||
msgid "Orders"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/models.py:121
|
||||
msgid "payment"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/models.py:122
|
||||
msgid "payments"
|
||||
msgstr ""
|
||||
|
||||
#: src/membership/models.py:45
|
||||
msgid "membership"
|
||||
msgstr "medlemskab"
|
||||
|
||||
#: src/membership/models.py:46
|
||||
msgid "memberships"
|
||||
msgstr "medlemskaber"
|
||||
|
||||
#: src/membership/models.py:53
|
||||
msgid "subscription type"
|
||||
msgstr ""
|
||||
|
||||
#: src/membership/models.py:57
|
||||
msgid "The duration this subscription is for. "
|
||||
msgstr ""
|
||||
|
||||
#: src/membership/models.py:70
|
||||
msgid "membership type"
|
||||
msgstr ""
|
||||
|
||||
#: src/membership/models.py:71
|
||||
msgid "membership types"
|
||||
msgstr ""
|
||||
|
||||
#: src/membership/models.py:73
|
||||
msgid "name"
|
||||
msgstr "navn"
|
||||
|
||||
#: src/membership/templates/membership_overview.html:7
|
||||
msgid "You do not have an active membership!"
|
||||
msgstr "Du har ikke et aktivt medlemskab!"
|
||||
|
||||
#: src/membership/templates/membership_overview.html:9
|
||||
msgid ""
|
||||
"You can become a member by depositing the membership fee to our bank account."
|
||||
msgstr ""
|
||||
|
||||
#: src/membership/templates/membership_overview.html:17
|
||||
msgid "You are a member!"
|
||||
msgstr "Du er medlem!"
|
||||
|
||||
#: src/membership/templates/membership_overview.html:19
|
||||
msgid "Period"
|
||||
msgstr "Periode"
|
||||
|
||||
#: src/membership/templates/membership_overview.html:20
|
||||
msgid "Type"
|
||||
msgstr "Type"
|
||||
|
||||
#: src/project/settings.py:131
|
||||
msgid "Danish"
|
||||
msgstr "Dansk"
|
||||
|
||||
#: src/project/settings.py:132
|
||||
msgid "English"
|
||||
msgstr "Engelsk"
|
||||
|
||||
#: src/project/templates/account/account_inactive.html:5
|
||||
#: src/project/templates/account/account_inactive.html:8
|
||||
msgid "Account Inactive"
|
||||
msgstr "Inaktiv konto"
|
||||
|
||||
#: src/project/templates/account/account_inactive.html:10
|
||||
msgid "This account is inactive."
|
||||
msgstr "Denne konto er inaktiv."
|
||||
|
||||
#: src/project/templates/account/email.html:5
|
||||
#: src/project/templates/account/email.html:16
|
||||
msgid "E-mail Addresses"
|
||||
msgstr "E-mail adresser"
|
||||
|
||||
#: src/project/templates/account/email.html:21
|
||||
msgid "The following e-mail addresses are associated with your account:"
|
||||
msgstr "De følgende e-mail adresser er tilknyttet din konto:"
|
||||
|
||||
#: src/project/templates/account/email.html:31
|
||||
msgid "Address"
|
||||
msgstr "Adresse"
|
||||
|
||||
#: src/project/templates/account/email.html:32
|
||||
msgid "Status"
|
||||
msgstr "Status"
|
||||
|
||||
#: src/project/templates/account/email.html:33
|
||||
msgid "Primary"
|
||||
msgstr "Primær"
|
||||
|
||||
#: src/project/templates/account/email.html:90
|
||||
msgid "Warning:"
|
||||
msgstr "Advarsel:"
|
||||
|
||||
#: src/project/templates/account/email.html:91
|
||||
msgid ""
|
||||
"You currently do not have any e-mail address set up. You should really add "
|
||||
"an e-mail address so you can receive notifications, reset your password, etc."
|
||||
msgstr ""
|
||||
"Du har lige nu ingen e-mail adresse tilknyttet. Du burde virkelig tilføje en "
|
||||
"e-mail adresse så du kan modtage notifikationer, nulstille dit kodeord, osv."
|
||||
|
||||
#: src/project/templates/account/email.html:99
|
||||
#: src/project/templates/account/email.html:107
|
||||
msgid "Add E-mail"
|
||||
msgstr "Tilføj e-mail"
|
||||
|
||||
#: src/project/templates/account/email.html:118
|
||||
msgid "Do you really want to remove the selected e-mail address?"
|
||||
msgstr "Vil du virkelig fjerne den valgte e-mail?"
|
||||
|
||||
#: src/project/templates/account/email/base_message.txt:1
|
||||
#, python-format
|
||||
msgid "Hello from %(site_name)s!"
|
||||
msgstr "Hej fra %(site_name)s!"
|
||||
|
||||
#: src/project/templates/account/email/base_message.txt:5
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Thank you for using %(site_name)s!\n"
|
||||
"%(site_domain)s"
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/email/email_confirmation_message.txt:5
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You're receiving this e-mail because user %(user_display)s has given your e-"
|
||||
"mail address to register an account on %(site_domain)s.\n"
|
||||
"\n"
|
||||
"To confirm this is correct, go to %(activate_url)s"
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/email/email_confirmation_subject.txt:3
|
||||
msgid "Please Confirm Your E-mail Address"
|
||||
msgstr "Venligst bekræft din e-mail adresse"
|
||||
|
||||
#: src/project/templates/account/email/password_reset_key_message.txt:4
|
||||
msgid ""
|
||||
"You're receiving this e-mail because you or someone else has requested a "
|
||||
"password for your user account.\n"
|
||||
"It can be safely ignored if you did not request a password reset. Click the "
|
||||
"link below to reset your password."
|
||||
msgstr ""
|
||||
"Du modtager denne e-mail fordi du, eller nogen anden, har anmodet om "
|
||||
"nulstilling af dit kodeord.\n"
|
||||
"Du kan trygt ignorere dette hvis det ikke var dig der anmodede om "
|
||||
"nulstillingen. Klik på linket herunder for at nulstille dit kodeord."
|
||||
|
||||
#: src/project/templates/account/email/password_reset_key_message.txt:9
|
||||
#, python-format
|
||||
msgid "In case you forgot, your username is %(username)s."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/email/password_reset_key_subject.txt:3
|
||||
msgid "Password Reset E-mail"
|
||||
msgstr "Nulstilling af password"
|
||||
|
||||
#: src/project/templates/account/email_confirm.html:6
|
||||
#: src/project/templates/account/email_confirm.html:10
|
||||
msgid "Confirm E-mail Address"
|
||||
msgstr "Bekræft e-mail adresse"
|
||||
|
||||
#: src/project/templates/account/email_confirm.html:16
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Please confirm that <a href=\"mailto:%(email)s\">%(email)s</a> is an e-mail "
|
||||
"address for user %(user_display)s."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/email_confirm.html:20
|
||||
msgid "Confirm"
|
||||
msgstr "Bekræft"
|
||||
|
||||
#: src/project/templates/account/email_confirm.html:27
|
||||
#, python-format
|
||||
msgid ""
|
||||
"This e-mail confirmation link expired or is invalid. Please <a href="
|
||||
"\"%(email_url)s\">issue a new e-mail confirmation request</a>."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/login.html:20
|
||||
#: src/project/templates/account/login.html:26
|
||||
#: src/project/templates/account/signup.html:26
|
||||
#: src/project/templates/account/signup.html:32
|
||||
msgid "E-mail"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: src/project/templates/account/login.html:31
|
||||
#: src/project/templates/account/login.html:37
|
||||
#: src/project/templates/account/signup.html:44
|
||||
#: src/project/templates/account/signup.html:50
|
||||
msgid "Password"
|
||||
msgstr "Kodeord"
|
||||
|
||||
#: src/project/templates/account/login.html:41
|
||||
msgid "Sign in"
|
||||
msgstr "Log ind"
|
||||
|
||||
#: src/project/templates/account/login.html:45
|
||||
msgid "Forgot password?"
|
||||
msgstr "Glemt kodeord?"
|
||||
|
||||
#: src/project/templates/account/login.html:48
|
||||
msgid "Or"
|
||||
msgstr "Eller"
|
||||
|
||||
#: src/project/templates/account/login.html:53
|
||||
#: src/project/templates/account/signup.html:6
|
||||
msgid "Become a member"
|
||||
msgstr "Bliv medlem"
|
||||
|
||||
#: src/project/templates/account/logout.html:5
|
||||
#: src/project/templates/account/logout.html:8
|
||||
#: src/project/templates/account/logout.html:17
|
||||
msgid "Sign Out"
|
||||
msgstr "Log ud"
|
||||
|
||||
#: src/project/templates/account/logout.html:10
|
||||
msgid "Are you sure you want to sign out?"
|
||||
msgstr "Er du sikker på at du vil logge ind?"
|
||||
|
||||
#: src/project/templates/account/messages/cannot_delete_primary_email.txt:2
|
||||
#, python-format
|
||||
msgid "You cannot remove your primary e-mail address (%(email)s)."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/messages/email_confirmation_sent.txt:2
|
||||
#, python-format
|
||||
msgid "Confirmation e-mail sent to %(email)s."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/messages/email_confirmed.txt:2
|
||||
#, python-format
|
||||
msgid "You have confirmed %(email)s."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/messages/email_deleted.txt:2
|
||||
#, python-format
|
||||
msgid "Removed e-mail address %(email)s."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/messages/logged_in.txt:4
|
||||
#, python-format
|
||||
msgid "Successfully signed in as %(name)s."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/messages/logged_out.txt:2
|
||||
msgid "You have signed out."
|
||||
msgstr "Du er nu logget ud."
|
||||
|
||||
#: src/project/templates/account/messages/password_changed.txt:2
|
||||
msgid "Password successfully changed."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/messages/password_set.txt:2
|
||||
msgid "Password successfully set."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/messages/primary_email_set.txt:2
|
||||
msgid "Primary e-mail address set."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/messages/unverified_primary_email.txt:2
|
||||
msgid "Your primary e-mail address must be verified."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/password_change.html:5
|
||||
#: src/project/templates/account/password_change.html:8
|
||||
#: src/project/templates/account/password_change.html:13
|
||||
#: src/project/templates/account/password_reset_from_key.html:4
|
||||
#: src/project/templates/account/password_reset_from_key.html:7
|
||||
#: src/project/templates/account/password_reset_from_key_done.html:4
|
||||
#: src/project/templates/account/password_reset_from_key_done.html:7
|
||||
msgid "Change Password"
|
||||
msgstr "Skift kodeord"
|
||||
|
||||
#: src/project/templates/account/password_change.html:14
|
||||
msgid "Forgot Password?"
|
||||
msgstr "Glemt kodeord?"
|
||||
|
||||
#: src/project/templates/account/password_reset.html:6
|
||||
#: src/project/templates/account/password_reset.html:10
|
||||
#: src/project/templates/account/password_reset_done.html:6
|
||||
#: src/project/templates/account/password_reset_done.html:9
|
||||
msgid "Password Reset"
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/password_reset.html:16
|
||||
msgid ""
|
||||
"Forgotten your password? Enter your e-mail address below, and we'll send you "
|
||||
"an e-mail allowing you to reset it."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/password_reset.html:21
|
||||
#: src/project/templates/account/password_reset.html:27
|
||||
msgid "E-mail address"
|
||||
msgstr "E-mail adresser"
|
||||
|
||||
#: src/project/templates/account/password_reset.html:38
|
||||
msgid "Reset My Password"
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/password_reset.html:41
|
||||
msgid "Please contact us if you have any trouble resetting your password."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/password_reset.html:47
|
||||
#: src/project/templates/account/signup.html:72
|
||||
msgid "To login"
|
||||
msgstr "Til login"
|
||||
|
||||
#: src/project/templates/account/password_reset_done.html:15
|
||||
msgid ""
|
||||
"We have sent you an e-mail. Please contact us if you do not receive it "
|
||||
"within a few minutes."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/password_reset_from_key.html:7
|
||||
msgid "Bad Token"
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/password_reset_from_key.html:11
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The password reset link was invalid, possibly because it has already been "
|
||||
"used. Please request a <a href=\"%(passwd_reset_url)s\">new password reset</"
|
||||
"a>."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/password_reset_from_key.html:17
|
||||
msgid "change password"
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/password_reset_from_key.html:20
|
||||
#: src/project/templates/account/password_reset_from_key_done.html:8
|
||||
msgid "Your password is now changed."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/password_set.html:5
|
||||
#: src/project/templates/account/password_set.html:8
|
||||
#: src/project/templates/account/password_set.html:13
|
||||
msgid "Set Password"
|
||||
msgstr "Sæt kodeord"
|
||||
|
||||
#: src/project/templates/account/signup.html:19
|
||||
msgid "To become a member, you need to have an account. Create one here."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/signup.html:66
|
||||
msgid "Sign up"
|
||||
msgstr "Bliv medlem"
|
||||
|
||||
#: src/project/templates/account/signup_closed.html:5
|
||||
#: src/project/templates/account/signup_closed.html:8
|
||||
msgid "Sign Up Closed"
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/signup_closed.html:10
|
||||
msgid "We are sorry, but the sign up is currently closed."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/snippets/already_logged_in.html:5
|
||||
msgid "Note"
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/snippets/already_logged_in.html:5
|
||||
#, python-format
|
||||
msgid "you are already logged in as %(user_display)s."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/verification_sent.html:5
|
||||
#: src/project/templates/account/verification_sent.html:8
|
||||
#: src/project/templates/account/verified_email_required.html:5
|
||||
#: src/project/templates/account/verified_email_required.html:8
|
||||
msgid "Verify Your E-mail Address"
|
||||
msgstr "Verificér din e-mail adresse"
|
||||
|
||||
#: src/project/templates/account/verification_sent.html:10
|
||||
msgid ""
|
||||
"We have sent an e-mail to you for verification. Follow the link provided to "
|
||||
"finalize the signup process. Please contact us if you do not receive it "
|
||||
"within a few minutes."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/verified_email_required.html:12
|
||||
msgid ""
|
||||
"This part of the site requires us to verify that\n"
|
||||
"you are who you claim to be. For this purpose, we require that you\n"
|
||||
"verify ownership of your e-mail address. "
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/verified_email_required.html:16
|
||||
msgid ""
|
||||
"We have sent an e-mail to you for\n"
|
||||
"verification. Please click on the link inside this e-mail. Please\n"
|
||||
"contact us if you do not receive it within a few minutes."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/verified_email_required.html:20
|
||||
#, python-format
|
||||
msgid ""
|
||||
"<strong>Note:</strong> you can still <a href=\"%(email_url)s\">change your e-"
|
||||
"mail address</a>."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/base.html:140
|
||||
msgid "Dashboard"
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/base.html:146
|
||||
msgid "Profile"
|
||||
msgstr "Profil"
|
||||
|
||||
#: src/project/templates/base.html:152
|
||||
msgid "Details"
|
||||
msgstr "Detaljer"
|
||||
|
||||
#: src/project/templates/base.html:158
|
||||
msgid "Emails"
|
||||
msgstr "Emails"
|
||||
|
||||
#: src/project/templates/base.html:164
|
||||
msgid "Membership"
|
||||
msgstr "Medlemskab"
|
||||
|
||||
#: src/project/templates/base.html:171 src/project/templates/base.html:184
|
||||
msgid "Overview"
|
||||
msgstr "Oversigt"
|
||||
|
||||
#: src/project/templates/base.html:177
|
||||
msgid "Services"
|
||||
msgstr "Tjenester"
|
||||
|
||||
#: src/project/templates/base.html:191
|
||||
msgid "Admin"
|
||||
msgstr "Admin"
|
||||
|
||||
#: src/project/templates/base.html:197
|
||||
msgid "Members"
|
||||
msgstr "Medlemmer"
|
||||
|
||||
#~ msgid "OR"
|
||||
#~ msgstr "Eller"
|
|
@ -1,142 +0,0 @@
|
|||
from pathlib import Path
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from environs import Env
|
||||
|
||||
env = Env()
|
||||
env.read_env()
|
||||
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
PROJECT_DIR = BASE_DIR / "project"
|
||||
|
||||
SECRET_KEY = env.str("SECRET_KEY", default="something-very-secret")
|
||||
|
||||
DEBUG = env.bool("DEBUG", default=False)
|
||||
|
||||
ALLOWED_HOSTS = env.list("ALLOWED_HOSTS", default=["*"])
|
||||
|
||||
ADMINS = [tuple(x.split(":")) for x in env.list("DJANGO_ADMINS", default=[])]
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"django.contrib.sites",
|
||||
"debug_toolbar",
|
||||
"allauth",
|
||||
"allauth.account",
|
||||
"utils",
|
||||
"accounting",
|
||||
"membership",
|
||||
]
|
||||
|
||||
DATABASES = {"default": env.dj_db_url("DATABASE_URL")}
|
||||
|
||||
MIDDLEWARE = [
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"whitenoise.middleware.WhiteNoiseMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
"debug_toolbar.middleware.DebugToolbarMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = "project.urls"
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [PROJECT_DIR / "templates"],
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.template.context_processors.debug",
|
||||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
"project.context_processors.current_site",
|
||||
]
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
"django.contrib.auth.backends.ModelBackend",
|
||||
"allauth.account.auth_backends.AuthenticationBackend",
|
||||
)
|
||||
|
||||
WSGI_APPLICATION = "project.wsgi.application"
|
||||
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" # noqa
|
||||
},
|
||||
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, # noqa
|
||||
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, # noqa
|
||||
]
|
||||
|
||||
LANGUAGE_CODE = "da-dk"
|
||||
|
||||
TIME_ZONE = "Europe/Copenhagen"
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
STATIC_URL = "/static/"
|
||||
STATICFILES_DIRS = [PROJECT_DIR / "static"]
|
||||
STATIC_ROOT = BASE_DIR / "static"
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
LOGIN_REDIRECT_URL = "/"
|
||||
|
||||
EMAIL_BACKEND = env.str(
|
||||
"EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend"
|
||||
)
|
||||
DEFAULT_FROM_EMAIL = env.str("DEFAULT_FROM_EMAIL", default="")
|
||||
# Parse email URLs, e.g. "smtp://"
|
||||
email = env.dj_email_url("EMAIL_URL", default="smtp://")
|
||||
EMAIL_HOST = email["EMAIL_HOST"]
|
||||
EMAIL_PORT = email["EMAIL_PORT"]
|
||||
EMAIL_HOST_PASSWORD = email["EMAIL_HOST_PASSWORD"]
|
||||
EMAIL_HOST_USER = email["EMAIL_HOST_USER"]
|
||||
EMAIL_USE_TLS = email["EMAIL_USE_TLS"]
|
||||
|
||||
# Always show DDT in development for any IP, not just 127.0.0.1 or
|
||||
# settings.INTERNAL_IPS. This is useful in a docker setup where the
|
||||
# requesting IP isn't static.
|
||||
DEBUG_TOOLBAR_CONFIG = {
|
||||
"SHOW_TOOLBAR_CALLBACK": lambda _x: DEBUG,
|
||||
}
|
||||
|
||||
CURRENCIES = ("DKK",)
|
||||
CURRENCY_CHOICES = [("DKK", "DKK")]
|
||||
|
||||
LANGUAGES = [
|
||||
("da", _("Danish")),
|
||||
("en", _("English")),
|
||||
]
|
||||
|
||||
# We store all translations in one location
|
||||
LOCALE_PATHS = [PROJECT_DIR / "locale"]
|
||||
|
||||
# Allauth configuration
|
||||
ACCOUNT_AUTHENTICATION_METHOD = "email"
|
||||
ACCOUNT_EMAIL_REQUIRED = True
|
||||
ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE = False
|
||||
ACCOUNT_USERNAME_REQUIRED = False
|
7
src/project/static/css/bootstrap.min.css
vendored
7
src/project/static/css/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,11 +0,0 @@
|
|||
{% extends "account/base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block head_title %}{% trans "Account Inactive" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "Account Inactive" %}</h1>
|
||||
|
||||
<p>{% trans "This account is inactive." %}</p>
|
||||
{% endblock %}
|
|
@ -1 +0,0 @@
|
|||
{% extends "base.html" %}
|
|
@ -1,130 +0,0 @@
|
|||
{% extends 'account/base.html' %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block head_title %}{% trans "E-mail Addresses" %}{% endblock %}
|
||||
|
||||
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4>{% trans "E-mail Addresses" %}</h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
|
||||
{% if user.emailaddress_set.all %}
|
||||
<p>{% trans 'The following e-mail addresses are associated with your account:' %}</p>
|
||||
<form action="{% url 'account_email' %}" class="email_list"
|
||||
method="post">
|
||||
{% csrf_token %}
|
||||
<fieldset class="blockLabels">
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>{% trans "Address" %}</th>
|
||||
<th>{% trans "Status" %}</th>
|
||||
<th>{% trans "Primary" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
{% for emailaddress in user.emailaddress_set.all %}
|
||||
<tr class="ctrlHolder">
|
||||
<label for="email_radio_{{ forloop.counter }}"
|
||||
class="{% if emailaddress.primary %}primary_email{% endif %}">
|
||||
<td>
|
||||
<input
|
||||
id="email_radio_{{ forloop.counter }}"
|
||||
type="radio"
|
||||
name="email"
|
||||
value="{{ emailaddress.email }}"
|
||||
{% if emailaddress.primary or user.emailaddress_set.count == 1 %}
|
||||
checked="checked"
|
||||
{% endif %}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
{{ emailaddress.email }}
|
||||
</td>
|
||||
<td>
|
||||
{% if emailaddress.verified %}
|
||||
<span class="label label-success">Verified</span>
|
||||
{% else %}
|
||||
<span class="label label-danger">Unverified</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if emailaddress.primary %}
|
||||
<span class="label label-primary">Primary</span>{% endif %}
|
||||
</td>
|
||||
</label>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="buttonHolder">
|
||||
<button class="btn btn-success" type="submit"
|
||||
name="action_primary">Make Primary
|
||||
</button>
|
||||
<button class="btn btn-primary" type="submit"
|
||||
name="action_send">Re-send Verification
|
||||
</button>
|
||||
<button class="btn btn-danger" type="submit"
|
||||
name="action_remove">Remove
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
</form>
|
||||
{% else %}
|
||||
<p>
|
||||
<strong>{% trans 'Warning:' %}</strong>
|
||||
{% trans "You currently do not have any e-mail address set up. You should really add an e-mail address so you can receive notifications, reset your password, etc." %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4>{% trans "Add E-mail" %}</h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form method="post" action="{% url 'account_email' %}"
|
||||
class="add_email">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button name="action_add" class="btn btn-success" type="submit">
|
||||
{% trans "Add E-mail" %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function () {
|
||||
let message = "{% trans 'Do you really want to remove the selected e-mail address?' %}";
|
||||
let actions = document.getElementsByName('action_remove');
|
||||
if (actions.length) {
|
||||
actions[0].addEventListener("click", function (e) {
|
||||
if (!confirm(message)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
|
@ -1,7 +0,0 @@
|
|||
{% load i18n %}{% autoescape off %}{% blocktrans with site_name=current_site.name %}Hello from {{ site_name }}!{% endblocktrans %}
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
|
||||
{% blocktrans with site_name=current_site.name site_domain=current_site.domain %}Thank you for using {{ site_name }}!
|
||||
{{ site_domain }}{% endblocktrans %}
|
||||
{% endautoescape %}
|
|
@ -1,7 +0,0 @@
|
|||
{% extends "account/email/base_message.txt" %}
|
||||
{% load account %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}{% autoescape off %}{% user_display user as user_display %}{% blocktrans with site_name=current_site.name site_domain=current_site.domain %}You're receiving this e-mail because user {{ user_display }} has given your e-mail address to register an account on {{ site_domain }}.
|
||||
|
||||
To confirm this is correct, go to {{ activate_url }}{% endblocktrans %}{% endautoescape %}{% endblock %}
|
|
@ -1 +0,0 @@
|
|||
{% include "account/email/email_confirmation_message.txt" %}
|
|
@ -1 +0,0 @@
|
|||
{% include "account/email/email_confirmation_subject.txt" %}
|
|
@ -1,4 +0,0 @@
|
|||
{% load i18n %}
|
||||
{% autoescape off %}
|
||||
{% blocktrans %}Please Confirm Your E-mail Address{% endblocktrans %}
|
||||
{% endautoescape %}
|
|
@ -1,9 +0,0 @@
|
|||
{% extends "account/email/base_message.txt" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}{% autoescape off %}{% blocktrans %}You're receiving this e-mail because you or someone else has requested a password for your user account.
|
||||
It can be safely ignored if you did not request a password reset. Click the link below to reset your password.{% endblocktrans %}
|
||||
|
||||
{{ password_reset_url }}{% if username %}
|
||||
|
||||
{% blocktrans %}In case you forgot, your username is {{ username }}.{% endblocktrans %}{% endif %}{% endautoescape %}{% endblock %}
|
|
@ -1,4 +0,0 @@
|
|||
{% load i18n %}
|
||||
{% autoescape off %}
|
||||
{% blocktrans %}Password Reset E-mail{% endblocktrans %}
|
||||
{% endautoescape %}
|
|
@ -1,31 +0,0 @@
|
|||
{% extends "account/base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load account %}
|
||||
|
||||
{% block head_title %}{% trans "Confirm E-mail Address" %}{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "Confirm E-mail Address" %}</h1>
|
||||
|
||||
{% if confirmation %}
|
||||
|
||||
{% user_display confirmation.email_address.user as user_display %}
|
||||
|
||||
<p>{% blocktrans with confirmation.email_address.email as email %}Please confirm that <a href="mailto:{{ email }}">{{ email }}</a> is an e-mail address for user {{ user_display }}.{% endblocktrans %}</p>
|
||||
|
||||
<form method="post" action="{% url 'account_confirm_email' confirmation.key %}">
|
||||
{% csrf_token %}
|
||||
<button type="submit">{% trans 'Confirm' %}</button>
|
||||
</form>
|
||||
|
||||
{% else %}
|
||||
|
||||
{% url 'account_email' as email_url %}
|
||||
|
||||
<p>{% blocktrans %}This e-mail confirmation link expired or is invalid. Please <a href="{{ email_url }}">issue a new e-mail confirmation request</a>.{% endblocktrans %}</p>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
|
@ -1,56 +0,0 @@
|
|||
{% extends "account/pre_login_base.html" %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block non_login_content %}
|
||||
|
||||
{% 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 "E-mail" %}
|
||||
</label>
|
||||
<input type="text"
|
||||
id="id_username"
|
||||
name="login"
|
||||
class="form-control mb-lg-2"
|
||||
placeholder="{% trans "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 mb-lg-2"
|
||||
placeholder="{% trans "Password" %}"
|
||||
required>
|
||||
|
||||
<button class="w-100 mb-lg-2 btn btn-lg btn-primary"
|
||||
type="submit">{% trans "Sign in" %}</button>
|
||||
</form>
|
||||
<a href="{% url "account_reset_password" %}"
|
||||
class="w-100 btn btn-lg btn-outline-info">
|
||||
{% trans "Forgot password?" %}
|
||||
</a>
|
||||
|
||||
<hr class="hr-text" data-content="{% trans "Or"|upper %}">
|
||||
|
||||
<a class="w-100 btn btn-lg btn-outline-success"
|
||||
type="submit"
|
||||
href="{% url "account_signup" %}">
|
||||
{% trans "Become a member" %}
|
||||
</a>
|
||||
|
||||
{% endblock %}
|
|
@ -1,21 +0,0 @@
|
|||
{% extends "account/base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block head_title %}{% trans "Sign Out" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "Sign Out" %}</h1>
|
||||
|
||||
<p>{% trans 'Are you sure you want to sign out?' %}</p>
|
||||
|
||||
<form method="post" action="{% url 'account_logout' %}">
|
||||
{% csrf_token %}
|
||||
{% if redirect_field_value %}
|
||||
<input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}"/>
|
||||
{% endif %}
|
||||
<button type="submit">{% trans 'Sign Out' %}</button>
|
||||
</form>
|
||||
|
||||
|
||||
{% endblock %}
|
|
@ -1,2 +0,0 @@
|
|||
{% load i18n %}
|
||||
{% blocktrans %}You cannot remove your primary e-mail address ({{email}}).{% endblocktrans %}
|
|
@ -1,2 +0,0 @@
|
|||
{% load i18n %}
|
||||
{% blocktrans %}Confirmation e-mail sent to {{email}}.{% endblocktrans %}
|
|
@ -1,2 +0,0 @@
|
|||
{% load i18n %}
|
||||
{% blocktrans %}You have confirmed {{email}}.{% endblocktrans %}
|
|
@ -1,2 +0,0 @@
|
|||
{% load i18n %}
|
||||
{% blocktrans %}Removed e-mail address {{email}}.{% endblocktrans %}
|
|
@ -1,4 +0,0 @@
|
|||
{% load account %}
|
||||
{% load i18n %}
|
||||
{% user_display user as name %}
|
||||
{% blocktrans %}Successfully signed in as {{name}}.{% endblocktrans %}
|
|
@ -1,2 +0,0 @@
|
|||
{% load i18n %}
|
||||
{% blocktrans %}You have signed out.{% endblocktrans %}
|
|
@ -1,2 +0,0 @@
|
|||
{% load i18n %}
|
||||
{% blocktrans %}Password successfully changed.{% endblocktrans %}
|
|
@ -1,2 +0,0 @@
|
|||
{% load i18n %}
|
||||
{% blocktrans %}Password successfully set.{% endblocktrans %}
|
|
@ -1,2 +0,0 @@
|
|||
{% load i18n %}
|
||||
{% blocktrans %}Primary e-mail address set.{% endblocktrans %}
|
|
@ -1,2 +0,0 @@
|
|||
{% load i18n %}
|
||||
{% blocktrans %}Your primary e-mail address must be verified.{% endblocktrans %}
|
|
@ -1,16 +0,0 @@
|
|||
{% extends "account/base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block head_title %}{% trans "Change Password" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "Change Password" %}</h1>
|
||||
|
||||
<form method="POST" action="{% url 'account_change_password' %}" class="password_change">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit" name="action">{% trans "Change Password" %}</button>
|
||||
<a href="{% url 'account_reset_password' %}">{% trans "Forgot Password?" %}</a>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -1,49 +0,0 @@
|
|||
{% extends "account/pre_login_base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load account %}
|
||||
|
||||
{% block head_title %}{% trans "Password Reset" %}{% endblock %}
|
||||
|
||||
{% block non_login_content %}
|
||||
|
||||
<h1 class="h3 mb-3 fw-normal">{% trans "Password Reset" %}</h1>
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
{% include "account/snippets/already_logged_in.html" %}
|
||||
{% endif %}
|
||||
|
||||
<p>{% trans "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}</p>
|
||||
|
||||
<form method="POST" action="{% url 'account_reset_password' %}" class="password_reset">
|
||||
{% csrf_token %}
|
||||
<label for="id_email" class="visually-hidden">
|
||||
{% trans "E-mail address" %}
|
||||
</label>
|
||||
<input type="email"
|
||||
id="id_email"
|
||||
name="email"
|
||||
class="form-control mb-lg-2 {% if form.email.errors %}is-invalid{% elif form.is_bound %}is-valid{% endif %}"
|
||||
placeholder="{% trans "E-mail address" %}"
|
||||
required>
|
||||
{% if form.email.errors %}
|
||||
{% for error in form.email.errors %}
|
||||
<div class="invalid-feedback mb-lg-2">
|
||||
{{ error }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<input class="w-100 btn btn-lg btn-primary"
|
||||
type="submit" value="{% trans 'Reset My Password' %}" />
|
||||
</form>
|
||||
|
||||
<p>{% blocktrans %}Please contact us if you have any trouble resetting your password.{% endblocktrans %}</p>
|
||||
|
||||
<hr>
|
||||
<a class="w-100"
|
||||
type="submit"
|
||||
href="{% url "account_login" %}">
|
||||
{% trans "To login" %}
|
||||
</a>
|
||||
{% endblock %}
|
|
@ -1,16 +0,0 @@
|
|||
{% extends "account/pre_login_base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load account %}
|
||||
|
||||
{% block head_title %}{% trans "Password Reset" %}{% endblock %}
|
||||
|
||||
{% block non_login_content %}
|
||||
<h1>{% trans "Password Reset" %}</h1>
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
{% include "account/snippets/already_logged_in.html" %}
|
||||
{% endif %}
|
||||
|
||||
<p>{% blocktrans %}We have sent you an e-mail. Please contact us if you do not receive it within a few minutes.{% endblocktrans %}</p>
|
||||
{% endblock %}
|
|
@ -1,24 +0,0 @@
|
|||
{% extends "account/pre_login_base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block head_title %}{% trans "Change Password" %}{% endblock %}
|
||||
|
||||
{% block non_login_content %}
|
||||
<h1>{% if token_fail %}{% trans "Bad Token" %}{% else %}{% trans "Change Password" %}{% endif %}</h1>
|
||||
|
||||
{% if token_fail %}
|
||||
{% url 'account_reset_password' as passwd_reset_url %}
|
||||
<p>{% blocktrans %}The password reset link was invalid, possibly because it has already been used. Please request a <a href="{{ passwd_reset_url }}">new password reset</a>.{% endblocktrans %}</p>
|
||||
{% else %}
|
||||
{% if form %}
|
||||
<form method="POST" action="{{ action_url }}">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" name="action" value="{% trans 'change password' %}"/>
|
||||
</form>
|
||||
{% else %}
|
||||
<p>{% trans 'Your password is now changed.' %}</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -1,9 +0,0 @@
|
|||
{% extends "account/pre_login_base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% block head_title %}{% trans "Change Password" %}{% endblock %}
|
||||
|
||||
{% block non_login_content %}
|
||||
<h1>{% trans "Change Password" %}</h1>
|
||||
<p>{% trans 'Your password is now changed.' %}</p>
|
||||
{% endblock %}
|
|
@ -1,15 +0,0 @@
|
|||
{% extends "account/base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block head_title %}{% trans "Set Password" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "Set Password" %}</h1>
|
||||
|
||||
<form method="POST" action="{% url 'account_set_password' %}" class="password_set">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" name="action" value="{% trans 'Set Password' %}"/>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -1,75 +0,0 @@
|
|||
{% 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-8A6VqJCgKATsNvbzGwXr1Y1kRA+dQzm5znASKSOLMVRJ3s2FgO1h9J1HrtVp9pBF"
|
||||
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;
|
||||
}
|
||||
|
||||
.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">
|
||||
|
||||
{% block non_login_content %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,75 +0,0 @@
|
|||
{% extends "account/pre_login_base.html" %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block non_login_content %}
|
||||
<h1 class="h3 mb-3 fw-normal">{% trans "Become a member" %}</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.email.errors }}
|
||||
{{ form.password1.errors }}
|
||||
|
||||
<p>{% trans "To become a member, you need to have an account. Create one here." %}</p>
|
||||
|
||||
<form method="post" action="">
|
||||
{% csrf_token %}
|
||||
|
||||
<label for="id_email"
|
||||
class="visually-hidden">
|
||||
{% trans "E-mail" %}
|
||||
</label>
|
||||
<input type="email"
|
||||
id="id_email"
|
||||
name="email"
|
||||
class="form-control mb-lg-2 {% if form.email.errors %}is-invalid{% elif form.is_bound %}is-valid{% endif %}"
|
||||
placeholder="{% trans "E-mail" %}"
|
||||
required
|
||||
autofocus>
|
||||
{% if form.email.errors %}
|
||||
{% for error in form.email.errors %}
|
||||
<div class="invalid-feedback">
|
||||
{{ error }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<label for="id_password" class="visually-hidden">
|
||||
{% trans "Password" %}
|
||||
</label>
|
||||
<input type="password"
|
||||
id="id_password"
|
||||
name="password1"
|
||||
class="form-control mb-lg-2 {% if form.password1.errors %}is-invalid{% elif form.is_bound %}is-valid{% endif %}"
|
||||
placeholder="{% trans "Password" %}"
|
||||
required>
|
||||
{% if form.password1.errors %}
|
||||
{% for error in form.password1.errors %}
|
||||
<div class="invalid-feedback">
|
||||
{{ error }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if redirect_field_value %}
|
||||
<input type="hidden" name="{{ redirect_field_name }}"
|
||||
value="{{ redirect_field_value }}"/>
|
||||
{% endif %}
|
||||
|
||||
<button class="w-100 btn btn-lg btn-primary"
|
||||
type="submit">{% trans "Sign up" %}</button>
|
||||
</form>
|
||||
<hr>
|
||||
<a class="w-100"
|
||||
type="submit"
|
||||
href="{% url "account_login" %}">
|
||||
{% trans "To login" %}
|
||||
</a>
|
||||
|
||||
{% endblock %}
|
|
@ -1,11 +0,0 @@
|
|||
{% extends "account/base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block head_title %}{% trans "Sign Up Closed" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "Sign Up Closed" %}</h1>
|
||||
|
||||
<p>{% trans "We are sorry, but the sign up is currently closed." %}</p>
|
||||
{% endblock %}
|
|
@ -1,5 +0,0 @@
|
|||
{% load i18n %}
|
||||
{% load account %}
|
||||
|
||||
{% user_display user as user_display %}
|
||||
<p><strong>{% trans "Note" %}:</strong> {% blocktrans %}you are already logged in as {{ user_display }}.{% endblocktrans %}</p>
|
|
@ -1,12 +0,0 @@
|
|||
{% extends "account/base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block head_title %}{% trans "Verify Your E-mail Address" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "Verify Your E-mail Address" %}</h1>
|
||||
|
||||
<p>{% blocktrans %}We have sent an e-mail to you for verification. Follow the link provided to finalize the signup process. Please contact us if you do not receive it within a few minutes.{% endblocktrans %}</p>
|
||||
|
||||
{% endblock %}
|
|
@ -1,23 +0,0 @@
|
|||
{% extends "account/base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block head_title %}{% trans "Verify Your E-mail Address" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "Verify Your E-mail Address" %}</h1>
|
||||
|
||||
{% url 'account_email' as email_url %}
|
||||
|
||||
<p>{% blocktrans %}This part of the site requires us to verify that
|
||||
you are who you claim to be. For this purpose, we require that you
|
||||
verify ownership of your e-mail address. {% endblocktrans %}</p>
|
||||
|
||||
<p>{% blocktrans %}We have sent an e-mail to you for
|
||||
verification. Please click on the link inside this e-mail. Please
|
||||
contact us if you do not receive it within a few minutes.{% endblocktrans %}</p>
|
||||
|
||||
<p>{% blocktrans %}<strong>Note:</strong> you can still <a href="{{ email_url }}">change your e-mail address</a>.{% endblocktrans %}</p>
|
||||
|
||||
|
||||
{% endblock %}
|
|
@ -1,212 +0,0 @@
|
|||
{% load utils %}
|
||||
{% 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-8A6VqJCgKATsNvbzGwXr1Y1kRA+dQzm5znASKSOLMVRJ3s2FgO1h9J1HrtVp9pBF"
|
||||
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-161MkQeTxmFupfLvH3jbFeSkoKSweR+66Tfp/ot/TuqbbOq0qssz8c91dG8I8y19"
|
||||
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_path "index" "active" %}"
|
||||
href="{% url "index" %}">
|
||||
{% trans "Dashboard" %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
|
||||
<span>{% trans "Profile" %}</span>
|
||||
</h6>
|
||||
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
{% trans "Details" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% active_path "account_email" "active" %}"
|
||||
aria-current="page" href="{% url "account_email" %}">
|
||||
{% trans "Emails" %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
|
||||
<span>{% trans "Membership" %}</span>
|
||||
</h6>
|
||||
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% active_path "membership-overview" "active" %}"
|
||||
aria-current="page" href="{% url "membership-overview" %}">
|
||||
{% trans "Overview" %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
|
||||
<span>{% trans "Services" %}</span>
|
||||
</h6>
|
||||
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% active_path "services-overview" "active" %}"
|
||||
href="{% url "services-overview" %}">
|
||||
{% trans "Overview" %}
|
||||
</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 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<p>
|
||||
Services and signup to these will be
|
||||
</p>
|
||||
<p>
|
||||
This is yet to be implemented.
|
||||
</p>
|
||||
{% endblock %}
|
|
@ -1,19 +0,0 @@
|
|||
"""URLs for the membersystem"""
|
||||
import debug_toolbar
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.urls import include
|
||||
from django.urls import path
|
||||
from membership.views import membership_overview
|
||||
|
||||
from .views import index
|
||||
from .views import services_overview
|
||||
|
||||
urlpatterns = [
|
||||
path("", login_required(index), name="index"),
|
||||
path("services/", login_required(services_overview), name="services-overview"),
|
||||
path("membership/", membership_overview, name="membership-overview"),
|
||||
path("accounts/", include("allauth.urls")),
|
||||
path("admin/", admin.site.urls),
|
||||
path("__debug__/", include(debug_toolbar.urls)),
|
||||
]
|
|
@ -1,13 +0,0 @@
|
|||
from django import template
|
||||
from django.urls import reverse
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def active_path(context, path_name, class_name):
|
||||
path = reverse(path_name)
|
||||
request_path = context.get("request").path
|
||||
|
||||
if path == request_path or ("basepath" in context and context["basepath"] == path):
|
||||
return class_name
|
1
users/admin.py
Normal file
1
users/admin.py
Normal file
|
@ -0,0 +1 @@
|
|||
from django.contrib import admin # noqa
|
5
users/apps.py
Normal file
5
users/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class UsersConfig(AppConfig):
|
||||
name = "users"
|
19
users/forms.py
Normal file
19
users/forms.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
from django import forms
|
||||
from django.contrib.auth.forms import UserCreationForm
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
def get_confirm_code(email):
|
||||
return default_token_generator(email)[:7]
|
||||
|
||||
|
||||
class SignupForm(UserCreationForm):
|
||||
|
||||
username = forms.EmailField(label=_("Email"))
|
||||
|
||||
class Meta:
|
||||
model = models.User
|
||||
fields = ("username",)
|
73
users/migrations/0001_initial.py
Normal file
73
users/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,73 @@
|
|||
# Generated by Django 2.2.4 on 2019-08-31 18:44
|
||||
import uuid
|
||||
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [("auth", "0011_update_proxy_permissions")]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="User",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("password", models.CharField(max_length=128, verbose_name="password")),
|
||||
(
|
||||
"last_login",
|
||||
models.DateTimeField(
|
||||
blank=True, null=True, verbose_name="last login"
|
||||
),
|
||||
),
|
||||
("nick", models.CharField(blank=True, max_length=60, null=True)),
|
||||
(
|
||||
"email",
|
||||
models.EmailField(
|
||||
help_text="Your email address will be used for password resets and notification about your event/submissions.",
|
||||
max_length=254,
|
||||
unique=True,
|
||||
verbose_name="E-Mail",
|
||||
),
|
||||
),
|
||||
("is_active", models.BooleanField(default=True)),
|
||||
("is_staff", models.BooleanField(default=False)),
|
||||
("is_superuser", models.BooleanField(default=False)),
|
||||
("token_uuid", models.UUIDField(default=uuid.uuid4, editable=False)),
|
||||
(
|
||||
"groups",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
|
||||
related_name="user_set",
|
||||
related_query_name="user",
|
||||
to="auth.Group",
|
||||
verbose_name="groups",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user_permissions",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="Specific permissions for this user.",
|
||||
related_name="user_set",
|
||||
related_query_name="user",
|
||||
to="auth.Permission",
|
||||
verbose_name="user permissions",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={"verbose_name": "User"},
|
||||
)
|
||||
]
|
60
users/models.py
Normal file
60
users/models.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
import uuid
|
||||
|
||||
from django.contrib.auth.base_user import BaseUserManager
|
||||
from django.contrib.auth.models import AbstractBaseUser
|
||||
from django.contrib.auth.models import PermissionsMixin
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class UserManager(BaseUserManager):
|
||||
"""The user manager class."""
|
||||
|
||||
def create_user(self, password: str = None, **kwargs):
|
||||
user = self.model(**kwargs)
|
||||
user.set_password(password)
|
||||
user.save()
|
||||
return user
|
||||
|
||||
def create_superuser(self, password: str, **kwargs):
|
||||
user = self.create_user(password=password, **kwargs)
|
||||
user.is_staff = True
|
||||
user.is_superuser = True
|
||||
user.save(update_fields=["is_staff", "is_superuser"])
|
||||
return user
|
||||
|
||||
|
||||
class User(PermissionsMixin, AbstractBaseUser):
|
||||
|
||||
EMAIL_FIELD = "email"
|
||||
USERNAME_FIELD = "email"
|
||||
|
||||
objects = UserManager()
|
||||
|
||||
nick = models.CharField(max_length=60, null=True, blank=True)
|
||||
email = models.EmailField(
|
||||
unique=True,
|
||||
verbose_name=_("E-Mail"),
|
||||
help_text=_(
|
||||
"Your email address will be used for password resets and notification about your event/submissions."
|
||||
),
|
||||
)
|
||||
|
||||
is_active = models.BooleanField(default=True)
|
||||
|
||||
# For the Django admin...
|
||||
is_staff = models.BooleanField(default=False)
|
||||
is_superuser = models.BooleanField(default=False)
|
||||
|
||||
# Used for confirmations and password reminders to NOT disclose email in URL
|
||||
token_uuid = models.UUIDField(default=uuid.uuid4, editable=False)
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Use a useful string representation."""
|
||||
return self.get_display_name()
|
||||
|
||||
def get_display_name(self) -> str:
|
||||
return self.nick if self.nick else str(_("Unnamed user"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("User")
|
10
users/templates/users/logged_out.html
Normal file
10
users/templates/users/logged_out.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<p>{% trans "Thanks for spending some quality time with the Web site today." %}</p>
|
||||
|
||||
<p><a href="{% url 'admin:index' %}">{% trans 'Log in again' %}</a></p>
|
||||
|
||||
{% endblock %}
|
39
users/templates/users/login.html
Normal file
39
users/templates/users/login.html
Normal file
|
@ -0,0 +1,39 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if form.errors %}
|
||||
<p>Your username and password didn't match. Please try again.</p>
|
||||
{% endif %}
|
||||
|
||||
{% if next %}
|
||||
{% if user.is_authenticated %}
|
||||
<p>Your account doesn't have access to this page. To proceed,
|
||||
please login with an account that has access.</p>
|
||||
{% else %}
|
||||
<p>Please login to see this page.</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="{% url 'users:login' %}">
|
||||
{% csrf_token %}
|
||||
<table>
|
||||
<tr>
|
||||
<td>{{ form.username.label_tag }}</td>
|
||||
<td>{{ form.username }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ form.password.label_tag }}</td>
|
||||
<td>{{ form.password }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<input type="submit" value="login">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
</form>
|
||||
|
||||
{# Assumes you setup the password_reset view in your URLconf #}
|
||||
<p><a href="{% url 'users:password_reset' %}">Lost password?</a></p>
|
||||
|
||||
{% endblock %}
|
8
users/templates/users/password_change_done.html
Normal file
8
users/templates/users/password_change_done.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
|
||||
{% block content %}
|
||||
<p>{% trans 'Your password was changed.' %}</p>
|
||||
{% endblock %}
|
52
users/templates/users/password_change_form.html
Normal file
52
users/templates/users/password_change_form.html
Normal file
|
@ -0,0 +1,52 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n static %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
|
||||
|
||||
{% block content %}<div id="content-main">
|
||||
|
||||
<form method="post">{% csrf_token %}
|
||||
<div>
|
||||
{% if form.errors %}
|
||||
<p class="errornote">
|
||||
{% if form.errors.items|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<p>{% trans "Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly." %}</p>
|
||||
|
||||
<fieldset class="module aligned wide">
|
||||
|
||||
<div class="form-row">
|
||||
{{ form.old_password.errors }}
|
||||
{{ form.old_password.label_tag }} {{ form.old_password }}
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
{{ form.new_password1.errors }}
|
||||
{{ form.new_password1.label_tag }} {{ form.new_password1 }}
|
||||
{% if form.new_password1.help_text %}
|
||||
<div class="help">{{ form.new_password1.help_text|safe }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
{{ form.new_password2.errors }}
|
||||
{{ form.new_password2.label_tag }} {{ form.new_password2 }}
|
||||
{% if form.new_password2.help_text %}
|
||||
<div class="help">{{ form.new_password2.help_text|safe }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
|
||||
<div class="submit-row">
|
||||
<input type="submit" value="{% trans 'Change my password' %}" class="default">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form></div>
|
||||
|
||||
{% endblock %}
|
13
users/templates/users/password_reset_complete.html
Normal file
13
users/templates/users/password_reset_complete.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<p>{% trans "Your password has been set. You may go ahead and log in now." %}</p>
|
||||
|
||||
<p><a href="{{ login_url }}">{% trans 'Log in' %}</a></p>
|
||||
|
||||
{% endblock %}
|
34
users/templates/users/password_reset_confirm.html
Normal file
34
users/templates/users/password_reset_confirm.html
Normal file
|
@ -0,0 +1,34 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n static %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
{% if validlink %}
|
||||
|
||||
<p>{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}</p>
|
||||
|
||||
<form method="post">{% csrf_token %}
|
||||
<fieldset class="module aligned">
|
||||
<div class="form-row field-password1">
|
||||
{{ form.new_password1.errors }}
|
||||
<label for="id_new_password1">{% trans 'New password:' %}</label>
|
||||
{{ form.new_password1 }}
|
||||
</div>
|
||||
<div class="form-row field-password2">
|
||||
{{ form.new_password2.errors }}
|
||||
<label for="id_new_password2">{% trans 'Confirm password:' %}</label>
|
||||
{{ form.new_password2 }}
|
||||
</div>
|
||||
<input type="submit" value="{% trans 'Change my password' %}">
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
{% else %}
|
||||
|
||||
<p>{% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}</p>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
12
users/templates/users/password_reset_done.html
Normal file
12
users/templates/users/password_reset_done.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<p>{% trans "We've emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly." %}</p>
|
||||
|
||||
<p>{% trans "If you don't receive an email, please make sure you've entered the address you registered with, and check your spam folder." %}</p>
|
||||
|
||||
{% endblock %}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue