forked from data.coop/membersystem
Merge pull request 'Refactoring things and doing stuff in a MVP way.' (#15) from vidir_refactor into master
Reviewed-on: data.coop/membersystem#15
This commit is contained in:
commit
50e2ba642f
33
.drone.yml
Normal file
33
.drone.yml
Normal file
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
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,5 +2,6 @@ __pycache__/
|
|||
*.pyc
|
||||
*.sw*
|
||||
db.sqlite3
|
||||
project/settings/local.py
|
||||
.pytest_cache
|
||||
.idea/
|
||||
*.mo
|
||||
|
|
|
@ -21,6 +21,7 @@ RUN apt-get update \
|
|||
libgdk-pixbuf2.0-0 \
|
||||
libffi-dev \
|
||||
shared-mime-info \
|
||||
gettext \
|
||||
&& pip install "poetry==$POETRY_VERSION"
|
||||
|
||||
WORKDIR /app
|
||||
|
@ -34,10 +35,13 @@ 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/static && chown www:www /app/static
|
||||
RUN mkdir /app/src/static && chown www:www /app/src/static
|
||||
|
||||
ARG BUILD
|
||||
ENV BUILD ${BUILD}
|
||||
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
||||
CMD ["uvicorn", "config.asgi:application", "--host", "0.0.0.0", "--port", "8080", "--workers", "3", "--lifespan", "off"]
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["uvicorn", "project.asgi:application", "--host", "0.0.0.0", "--port", "8000", "--workers", "3", "--lifespan", "off", "--app-dir", "/app/src"]
|
42
Makefile
42
Makefile
|
@ -1,13 +1,39 @@
|
|||
# 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
|
||||
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
|
||||
|
||||
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:
|
||||
poetry run pytest
|
||||
${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}
|
||||
|
|
33
README.md
33
README.md
|
@ -1,24 +1,35 @@
|
|||
# member.data.coop
|
||||
|
||||
To start developing:
|
||||
## Development requirements
|
||||
|
||||
Get poetry
|
||||
- Docker
|
||||
- Docker compose
|
||||
- pre-commit (preferred for contributions)
|
||||
|
||||
$ python3 -m pip install --user pipx
|
||||
$ pipx install poetry
|
||||
## Start local server
|
||||
|
||||
Run poetry to setup environment
|
||||
Given that the requirements above are installed, it should be as easy as:
|
||||
|
||||
$ poetry install
|
||||
$ make migrate
|
||||
|
||||
Run this make target, which installs all the requirements and sets up a development database.
|
||||
This will setup the database. Next run:
|
||||
|
||||
$ make dev-setup
|
||||
$ make run
|
||||
|
||||
To run the Django development server:
|
||||
This will build the docker image and start the member system on http://localhost:8000.
|
||||
|
||||
$ poetry run python manage.py runserver
|
||||
You can create a superuser by running:
|
||||
|
||||
Before you push your stuff, run tests:
|
||||
$ make createsuperuser
|
||||
|
||||
Make migrations:
|
||||
|
||||
$ make makemigrations
|
||||
|
||||
Make messages:
|
||||
|
||||
$ make makemessages
|
||||
|
||||
Running tests:
|
||||
|
||||
$ make test
|
||||
|
|
37
docker-compose.yml
Normal file
37
docker-compose.yml
Normal file
|
@ -0,0 +1,37 @@
|
|||
version: '3.7'
|
||||
|
||||
services:
|
||||
|
||||
backend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
user: $UID:$GID
|
||||
command: python /app/src/manage.py runserver 0.0.0.0:8000
|
||||
tty: true
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
- ./:/app/
|
||||
links:
|
||||
- redis
|
||||
- postgres
|
||||
env_file:
|
||||
- env
|
||||
|
||||
postgres:
|
||||
image: postgres:13-alpine
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data/
|
||||
ports:
|
||||
- 5432:5432
|
||||
env_file:
|
||||
- env
|
||||
|
||||
redis:
|
||||
image: redis:latest
|
||||
ports:
|
||||
- "6379:6379"
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
|
@ -1,21 +1,21 @@
|
|||
#!/bin/sh
|
||||
|
||||
|
||||
echo "Waiting for postgres..."
|
||||
|
||||
|
||||
POSTGRES_PORT=${POSTGRES_PORT:-5432}
|
||||
POSTGRES_HOST=${POSTGRES_HOST:-localhost}
|
||||
|
||||
while ! nc -z "$POSTGRES_HOST" "$POSTGRES_PORT"; do
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
echo "PostgreSQL started"
|
||||
|
||||
# Only migrate and collectstatic if we are NOT in development
|
||||
if [ -z "$DEBUG" ]; then
|
||||
python manage.py migrate
|
||||
python manage.py collectstatic --no-input;
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
|
||||
echo "PostgreSQL started"
|
||||
|
||||
# Only migrate, collectstatic and compilemessages if we are NOT in development
|
||||
if [ -z "$DEBUG" ]; then
|
||||
python src/manage.py migrate;
|
||||
python src/manage.py collectstatic --no-input;
|
||||
python src/manage.py compilemessages;
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
|
|
7
env
Normal file
7
env
Normal file
|
@ -0,0 +1,7 @@
|
|||
SECRET_KEY=something-very-random
|
||||
POSTGRES_HOST=postgres
|
||||
POSTGRES_PASSWORD=postgres
|
||||
POSTGRES_PORT=5432
|
||||
DATABASE_URL=postgres://postgres:postgres@postgres:5432/postgres
|
||||
DEBUG=True
|
||||
DJANGO_ENV=all
|
|
@ -1,8 +0,0 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
@admin.register(models.Membership)
|
||||
class MembershipAdmin(admin.ModelAdmin):
|
||||
pass
|
|
@ -1,227 +0,0 @@
|
|||
# Generated by Django 2.0.6 on 2018-06-23 19:07
|
||||
from decimal import Decimal
|
||||
|
||||
import django.db.models.deletion
|
||||
import djmoney.models.fields
|
||||
from django.conf import settings
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Membership",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"modified",
|
||||
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
||||
),
|
||||
(
|
||||
"created",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
||||
),
|
||||
(
|
||||
"can_vote",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="Indicates that the user has a democratic membership of the organization.",
|
||||
verbose_name="can vote",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "membership",
|
||||
"verbose_name_plural": "memberships",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Organization",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"modified",
|
||||
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
||||
),
|
||||
(
|
||||
"created",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
||||
),
|
||||
("name", models.CharField(max_length=64, verbose_name="name")),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "organization",
|
||||
"verbose_name_plural": "organizations",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Subscription",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"modified",
|
||||
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
||||
),
|
||||
(
|
||||
"created",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
||||
),
|
||||
(
|
||||
"active",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="Automatically set by payment system.",
|
||||
verbose_name="active",
|
||||
),
|
||||
),
|
||||
("starts", models.DateField()),
|
||||
("ends", models.DateField()),
|
||||
(
|
||||
"renewed_subscription",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="membership.Subscription",
|
||||
verbose_name="renewed subscription",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "subscription",
|
||||
"verbose_name_plural": "subscriptions",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="SubscriptionType",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"modified",
|
||||
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
||||
),
|
||||
(
|
||||
"created",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
||||
),
|
||||
("name", models.CharField(max_length=64, verbose_name="name")),
|
||||
(
|
||||
"fee_currency",
|
||||
djmoney.models.fields.CurrencyField(
|
||||
choices=[("DKK", "DKK")],
|
||||
default="XYZ",
|
||||
editable=False,
|
||||
max_length=3,
|
||||
),
|
||||
),
|
||||
(
|
||||
"fee",
|
||||
djmoney.models.fields.MoneyField(
|
||||
decimal_places=2, default=Decimal("0.0"), max_digits=16
|
||||
),
|
||||
),
|
||||
(
|
||||
"fee_vat_currency",
|
||||
djmoney.models.fields.CurrencyField(
|
||||
choices=[("DKK", "DKK")],
|
||||
default="XYZ",
|
||||
editable=False,
|
||||
max_length=3,
|
||||
),
|
||||
),
|
||||
(
|
||||
"fee_vat",
|
||||
djmoney.models.fields.MoneyField(
|
||||
decimal_places=2, default=Decimal("0"), max_digits=16
|
||||
),
|
||||
),
|
||||
(
|
||||
"duration",
|
||||
models.PositiveSmallIntegerField(
|
||||
choices=[(1, "annual")], default=1, verbose_name="duration"
|
||||
),
|
||||
),
|
||||
(
|
||||
"organization",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="membership.Organization",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "subscription type",
|
||||
"verbose_name_plural": "subscription types",
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="subscription",
|
||||
name="subscription_type",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="memberships",
|
||||
to="membership.SubscriptionType",
|
||||
verbose_name="subscription type",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="subscription",
|
||||
name="user",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="membership",
|
||||
name="organization",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="membership.Organization",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="membership",
|
||||
name="user",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,123 +0,0 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext as _
|
||||
from djmoney.models.fields import MoneyField
|
||||
|
||||
|
||||
class CreatedModifiedAbstract(models.Model):
|
||||
|
||||
modified = models.DateTimeField(auto_now=True, verbose_name=_("modified"))
|
||||
created = models.DateTimeField(auto_now_add=True, verbose_name=_("created"))
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class Organization(CreatedModifiedAbstract):
|
||||
"""
|
||||
This holds the data of the organization that someone is a member of. It is
|
||||
possible that we'll create more advanced features here.
|
||||
"""
|
||||
|
||||
name = models.CharField(verbose_name=_("name"), max_length=64)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("organization")
|
||||
verbose_name_plural = _("organizations")
|
||||
|
||||
|
||||
class Membership(CreatedModifiedAbstract):
|
||||
"""
|
||||
A user remains a member of an organization even though the subscription is
|
||||
unpaid or renewed. This just changes the status/permissions etc. of the
|
||||
membership, thus we need to track subscription creation, expiry, renewals
|
||||
etc. and ensure that the membership is modified accordingly.
|
||||
|
||||
This expresses some
|
||||
"""
|
||||
|
||||
organization = models.ForeignKey(Organization, on_delete=models.PROTECT)
|
||||
user = models.ForeignKey(get_user_model(), on_delete=models.PROTECT)
|
||||
|
||||
can_vote = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_("can vote"),
|
||||
help_text=_(
|
||||
"Indicates that the user has a democratic membership of the "
|
||||
"organization."
|
||||
),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
|
||||
return _("{} is a member of {}").format(
|
||||
self.user.get_full_name(), self.organization.name
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("membership")
|
||||
verbose_name_plural = _("memberships")
|
||||
|
||||
|
||||
class SubscriptionType(CreatedModifiedAbstract):
|
||||
"""
|
||||
Properties of subscriptions are stored here. Should of course not be edited
|
||||
after subscriptions are created.
|
||||
"""
|
||||
|
||||
organization = models.ForeignKey(Organization, on_delete=models.PROTECT)
|
||||
|
||||
name = models.CharField(verbose_name=_("name"), max_length=64)
|
||||
|
||||
fee = MoneyField(max_digits=16, decimal_places=2)
|
||||
|
||||
fee_vat = MoneyField(max_digits=16, decimal_places=2, default=0)
|
||||
|
||||
duration = models.PositiveSmallIntegerField(
|
||||
default=1, choices=[(1, _("annual"))], verbose_name=_("duration")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("subscription type")
|
||||
verbose_name_plural = _("subscription types")
|
||||
|
||||
|
||||
class Subscription(CreatedModifiedAbstract):
|
||||
"""
|
||||
To not confuse other types of subscriptions, one can be a *subscribed*
|
||||
member of an organization, meaning that they are paying etc.
|
||||
|
||||
A subscription does not track payment, this is done in the accounting app.
|
||||
"""
|
||||
|
||||
subscription_type = models.ForeignKey(
|
||||
SubscriptionType,
|
||||
related_name="memberships",
|
||||
verbose_name=_("subscription type"),
|
||||
on_delete=models.PROTECT,
|
||||
)
|
||||
user = models.ForeignKey(get_user_model(), on_delete=models.PROTECT)
|
||||
|
||||
active = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_("active"),
|
||||
help_text=_("Automatically set by payment system."),
|
||||
)
|
||||
|
||||
starts = models.DateField()
|
||||
ends = models.DateField()
|
||||
|
||||
renewed_subscription = models.ForeignKey(
|
||||
"self",
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name=_("renewed subscription"),
|
||||
on_delete=models.PROTECT,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("subscription")
|
||||
verbose_name_plural = _("subscriptions")
|
378
poetry.lock
generated
378
poetry.lock
generated
|
@ -47,6 +47,17 @@ 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"
|
||||
|
@ -63,6 +74,14 @@ 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"
|
||||
|
@ -71,6 +90,25 @@ 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"
|
||||
|
@ -87,9 +125,25 @@ 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.5"
|
||||
version = "3.1.7"
|
||||
description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -106,21 +160,42 @@ bcrypt = ["bcrypt"]
|
|||
|
||||
[[package]]
|
||||
name = "django-allauth"
|
||||
version = "0.40.0"
|
||||
version = "0.44.0"
|
||||
description = "Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
Django = ">=1.11"
|
||||
Django = ">=2.0"
|
||||
pyjwt = {version = ">=1.7", extras = ["crypto"]}
|
||||
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"
|
||||
version = "1.3.1"
|
||||
description = "Adds support for using money and currency fields in django models and forms. Uses py-moneyed as the money implementation."
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -132,7 +207,28 @@ py-moneyed = ">=0.8,<1.0"
|
|||
|
||||
[package.extras]
|
||||
exchange = ["certifi"]
|
||||
test = ["pytest (>=3.1.0)", "pytest-django", "pytest-pythonpath", "pytest-cov", "django-reversion", "mixer"]
|
||||
test = ["pytest (>=3.1.0)", "pytest-django", "pytest-pythonpath", "pytest-cov", "mixer"]
|
||||
|
||||
[[package]]
|
||||
name = "environs"
|
||||
version = "9.3.1"
|
||||
description = "simplified environment variable parsing"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
dj-database-url = {version = "*", optional = true, markers = "extra == \"django\""}
|
||||
dj-email-url = {version = "*", optional = true, markers = "extra == \"django\""}
|
||||
django-cache-url = {version = "*", optional = true, markers = "extra == \"django\""}
|
||||
marshmallow = ">=2.7.0"
|
||||
python-dotenv = "*"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pytest", "dj-database-url", "dj-email-url", "django-cache-url", "flake8 (==3.8.4)", "flake8-bugbear (==20.11.1)", "mypy (==0.800)", "pre-commit (>=2.4,<3.0)", "tox"]
|
||||
django = ["dj-database-url", "dj-email-url", "django-cache-url"]
|
||||
lint = ["flake8 (==3.8.4)", "flake8-bugbear (==20.11.1)", "mypy (==0.800)", "pre-commit (>=2.4,<3.0)"]
|
||||
tests = ["pytest", "dj-database-url", "dj-email-url", "django-cache-url"]
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
|
@ -142,13 +238,21 @@ 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 = "1.5.13"
|
||||
version = "2.0.0"
|
||||
description = "File identification library for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
|
||||
python-versions = ">=3.6.1"
|
||||
|
||||
[package.extras]
|
||||
license = ["editdistance"]
|
||||
|
@ -162,24 +266,22 @@ optional = false
|
|||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
version = "3.4.0"
|
||||
description = "Read metadata from Python packages"
|
||||
category = "dev"
|
||||
name = "marshmallow"
|
||||
version = "3.10.0"
|
||||
description = "A lightweight library for converting complex datatypes to and from native Python datatypes."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
|
||||
zipp = ">=0.5"
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
|
||||
testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
|
||||
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"]
|
||||
|
||||
[[package]]
|
||||
name = "more-itertools"
|
||||
version = "8.6.0"
|
||||
version = "8.7.0"
|
||||
description = "More routines for operating on iterables, beyond itertools"
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -208,7 +310,7 @@ signedtoken = ["cryptography", "pyjwt (>=1.0.0)"]
|
|||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "20.8"
|
||||
version = "20.9"
|
||||
description = "Core utilities for Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -225,15 +327,12 @@ category = "dev"
|
|||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[package.dependencies]
|
||||
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "tox"]
|
||||
|
||||
[[package]]
|
||||
name = "pre-commit"
|
||||
version = "2.9.3"
|
||||
version = "2.10.1"
|
||||
description = "A framework for managing and maintaining multi-language pre-commit hooks."
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -242,7 +341,6 @@ 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 = "*"
|
||||
|
@ -275,6 +373,31 @@ 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"
|
||||
|
@ -295,7 +418,6 @@ 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"
|
||||
|
@ -321,6 +443,17 @@ 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"
|
||||
|
@ -338,7 +471,7 @@ postgresql = ["psycopg2"]
|
|||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2020.5"
|
||||
version = "2021.1"
|
||||
description = "World timezone definitions, modern and historical"
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -409,14 +542,6 @@ 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"
|
||||
|
@ -430,9 +555,24 @@ 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.0"
|
||||
version = "20.4.2"
|
||||
description = "Virtual Python Environment builder"
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -442,7 +582,6 @@ 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]
|
||||
|
@ -458,21 +597,20 @@ optional = false
|
|||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "zipp"
|
||||
version = "3.4.0"
|
||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
category = "dev"
|
||||
name = "whitenoise"
|
||||
version = "5.2.0"
|
||||
description = "Radically simplified static file serving for WSGI applications"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.5, <4"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
|
||||
testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
|
||||
brotli = ["brotli"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.7"
|
||||
content-hash = "29ee22cded289d14aaf1015cc00f5fd4b3e441f807c86b325e21c67c2314a274"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "273466dbd53a484027b72a712aba600936507cd2d528c9a245464637c0c54207"
|
||||
|
||||
[metadata.files]
|
||||
appdirs = [
|
||||
|
@ -495,6 +633,45 @@ 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"},
|
||||
|
@ -503,10 +680,28 @@ 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"},
|
||||
|
@ -515,36 +710,60 @@ 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.5-py3-none-any.whl", hash = "sha256:efa2ab96b33b20c2182db93147a0c3cd7769d418926f9e9f140a60dca7c64ca9"},
|
||||
{file = "Django-3.1.5.tar.gz", hash = "sha256:2d78425ba74c7a1a74b196058b261b9733a8570782f4e2828974777ccca7edf7"},
|
||||
{file = "Django-3.1.7-py3-none-any.whl", hash = "sha256:baf099db36ad31f970775d0be5587cc58a6256a6771a44eb795b554d45f211b8"},
|
||||
{file = "Django-3.1.7.tar.gz", hash = "sha256:32ce792ee9b6a0cbbec340123e229ac9f765dff8c2a4ae9247a14b2ba3a365a7"},
|
||||
]
|
||||
django-allauth = [
|
||||
{file = "django-allauth-0.40.0.tar.gz", hash = "sha256:6a189fc4d3ee23596c3fd6e9f49c59b5b15618980118171a50675dd6a27cc589"},
|
||||
{file = "django-allauth-0.44.0.tar.gz", hash = "sha256:e51af457466022f52154d74c8523ac69375120fad2acce6e239635d85e610b25"},
|
||||
]
|
||||
django-cache-url = [
|
||||
{file = "django-cache-url-3.2.3.tar.gz", hash = "sha256:c1d45626ae8a206267c1263aa7a3461e2e186be2e939bcbd8c660e25851ddac8"},
|
||||
{file = "django_cache_url-3.2.3-py2.py3-none-any.whl", hash = "sha256:5514ca3a2075c6b956b3d0a5c540654d32b004e76340d7bdabf6661135b5f218"},
|
||||
]
|
||||
django-debug-toolbar = [
|
||||
{file = "django-debug-toolbar-3.2.tar.gz", hash = "sha256:84e2607d900dbd571df0a2acf380b47c088efb787dce9805aefeb407341961d2"},
|
||||
{file = "django_debug_toolbar-3.2-py3-none-any.whl", hash = "sha256:9e5a25d0c965f7e686f6a8ba23613ca9ca30184daa26487706d4829f5cfb697a"},
|
||||
]
|
||||
django-money = [
|
||||
{file = "django-money-1.3.tar.gz", hash = "sha256:da95f9a7174281eb2ef0f5f1584d5ee2670fc0d67707cd269816a73cae791eb3"},
|
||||
{file = "django_money-1.3-py3-none-any.whl", hash = "sha256:09952d49f998d089b21eb0f552d6dcb40d82626ab674d1caf0535bbd83a1ea01"},
|
||||
{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"},
|
||||
]
|
||||
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-1.5.13-py2.py3-none-any.whl", hash = "sha256:9dfb63a2e871b807e3ba62f029813552a24b5289504f5b071dea9b041aee9fe4"},
|
||||
{file = "identify-1.5.13.tar.gz", hash = "sha256:70b638cf4743f33042bebb3b51e25261a0a10e80f978739f17e7fd4837664a66"},
|
||||
{file = "identify-2.0.0-py2.py3-none-any.whl", hash = "sha256:9cdd81e5d2b6e76c3006d5226316dd947bd6324fbeebb881bec489202fa09d3a"},
|
||||
{file = "identify-2.0.0.tar.gz", hash = "sha256:b99aa309329c4fea679463eb35d169f3fbe13e66e9dd6162ad1856cbeb03dcbd"},
|
||||
]
|
||||
idna = [
|
||||
{file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
|
||||
{file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
|
||||
]
|
||||
importlib-metadata = [
|
||||
{file = "importlib_metadata-3.4.0-py3-none-any.whl", hash = "sha256:ace61d5fc652dc280e7b6b4ff732a9c2d40db2c0f92bc6cb74e07b73d53a1771"},
|
||||
{file = "importlib_metadata-3.4.0.tar.gz", hash = "sha256:fa5daa4477a7414ae34e95942e4dd07f62adf589143c875c133c1e53c4eff38d"},
|
||||
marshmallow = [
|
||||
{file = "marshmallow-3.10.0-py2.py3-none-any.whl", hash = "sha256:eca81d53aa4aafbc0e20566973d0d2e50ce8bf0ee15165bb799bec0df1e50177"},
|
||||
{file = "marshmallow-3.10.0.tar.gz", hash = "sha256:4ab2fdb7f36eb61c3665da67a7ce281c8900db08d72ba6bf0e695828253581f7"},
|
||||
]
|
||||
more-itertools = [
|
||||
{file = "more-itertools-8.6.0.tar.gz", hash = "sha256:b3a9005928e5bed54076e6e549c792b306fddfe72b2d1d22dd63d42d5d3899cf"},
|
||||
{file = "more_itertools-8.6.0-py3-none-any.whl", hash = "sha256:8e1a2a43b2f2727425f2b5839587ae37093f19153dc26c0927d1048ff6557330"},
|
||||
{file = "more-itertools-8.7.0.tar.gz", hash = "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713"},
|
||||
{file = "more_itertools-8.7.0-py3-none-any.whl", hash = "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced"},
|
||||
]
|
||||
nodeenv = [
|
||||
{file = "nodeenv-1.5.0-py2.py3-none-any.whl", hash = "sha256:5304d424c529c997bc888453aeaa6362d242b6b4631e90f3d4bf1b290f1c84a9"},
|
||||
|
@ -555,16 +774,16 @@ oauthlib = [
|
|||
{file = "oauthlib-3.1.0.tar.gz", hash = "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889"},
|
||||
]
|
||||
packaging = [
|
||||
{file = "packaging-20.8-py2.py3-none-any.whl", hash = "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858"},
|
||||
{file = "packaging-20.8.tar.gz", hash = "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"},
|
||||
{file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"},
|
||||
{file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"},
|
||||
]
|
||||
pluggy = [
|
||||
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
|
||||
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
|
||||
]
|
||||
pre-commit = [
|
||||
{file = "pre_commit-2.9.3-py2.py3-none-any.whl", hash = "sha256:6c86d977d00ddc8a60d68eec19f51ef212d9462937acf3ea37c7adec32284ac0"},
|
||||
{file = "pre_commit-2.9.3.tar.gz", hash = "sha256:ee784c11953e6d8badb97d19bc46b997a3a9eded849881ec587accd8608d74a4"},
|
||||
{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"},
|
||||
]
|
||||
psycopg2 = [
|
||||
{file = "psycopg2-2.8.6-cp27-cp27m-win32.whl", hash = "sha256:068115e13c70dc5982dfc00c5d70437fe37c014c808acce119b5448361c03725"},
|
||||
|
@ -591,6 +810,14 @@ 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"},
|
||||
|
@ -603,13 +830,17 @@ 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-2020.5-py2.py3-none-any.whl", hash = "sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4"},
|
||||
{file = "pytz-2020.5.tar.gz", hash = "sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"},
|
||||
{file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"},
|
||||
{file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"},
|
||||
]
|
||||
pyyaml = [
|
||||
{file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
|
||||
|
@ -655,24 +886,23 @@ 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.0-py2.py3-none-any.whl", hash = "sha256:227a8fed626f2f20a6cdb0870054989f82dd27b2560a911935ba905a2a5e0034"},
|
||||
{file = "virtualenv-20.4.0.tar.gz", hash = "sha256:219ee956e38b08e32d5639289aaa5bd190cfbe7dafcb8fa65407fca08e808f9c"},
|
||||
{file = "virtualenv-20.4.2-py2.py3-none-any.whl", hash = "sha256:2be72df684b74df0ea47679a7df93fd0e04e72520022c57b479d8f881485dbe3"},
|
||||
{file = "virtualenv-20.4.2.tar.gz", hash = "sha256:147b43894e51dd6bba882cf9c282447f780e2251cd35172403745fc381a0a80d"},
|
||||
]
|
||||
wcwidth = [
|
||||
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
|
||||
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
|
||||
]
|
||||
zipp = [
|
||||
{file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"},
|
||||
{file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"},
|
||||
whitenoise = [
|
||||
{file = "whitenoise-5.2.0-py2.py3-none-any.whl", hash = "sha256:05d00198c777028d72d8b0bbd234db605ef6d60e9410125124002518a48e515d"},
|
||||
{file = "whitenoise-5.2.0.tar.gz", hash = "sha256:05ce0be39ad85740a78750c86a93485c40f08ad8c62a6006de0233765996e5c7"},
|
||||
]
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
import warnings
|
||||
|
||||
from .base import * # noqa
|
||||
|
||||
try:
|
||||
from .local import * # noqa
|
||||
except ImportError:
|
||||
warnings.warn(
|
||||
"No settings.local, using a default SECRET_KEY 'hest'. You should "
|
||||
"write a custom local.py with this setting."
|
||||
)
|
||||
SECRET_KEY = "hest"
|
||||
DEBUG = True
|
||||
pass
|
|
@ -1,132 +0,0 @@
|
|||
"""
|
||||
Django settings for membersystem project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 2.0.4.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/2.0/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/2.0/ref/settings/
|
||||
"""
|
||||
import os
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = False
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"django.contrib.sites",
|
||||
"users",
|
||||
"accounting",
|
||||
"membership",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = "project.urls"
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [os.path.join("project", "templates")],
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.template.context_processors.debug",
|
||||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
"project.context_processors.current_site",
|
||||
]
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
"django.contrib.auth.backends.ModelBackend",
|
||||
"allauth.account.auth_backends.AuthenticationBackend",
|
||||
)
|
||||
|
||||
WSGI_APPLICATION = "project.wsgi.application"
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.sqlite3",
|
||||
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" # noqa
|
||||
},
|
||||
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, # noqa
|
||||
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, # noqa
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator" # noqa
|
||||
},
|
||||
]
|
||||
|
||||
AUTH_USER_MODEL = "users.User"
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/2.0/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = "en-us"
|
||||
|
||||
TIME_ZONE = "UTC"
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/2.0/howto/static-files/
|
||||
|
||||
STATIC_URL = "/static/"
|
||||
|
||||
STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")]
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||
|
||||
CURRENCIES = ("DKK",)
|
||||
CURRENCY_CHOICES = [("DKK", "DKK")]
|
|
@ -1,79 +0,0 @@
|
|||
/* General styles */
|
||||
html
|
||||
{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
font-size: 2.5vmin;
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
body
|
||||
{
|
||||
background: #fff;
|
||||
color: #000;
|
||||
margin: 1em auto;
|
||||
max-width: 50em;
|
||||
padding: 0 1em;
|
||||
box-shadow: 0 0 2.5em rgba(0, 0, 0, 20%);
|
||||
}
|
||||
|
||||
header,
|
||||
footer
|
||||
{
|
||||
background: #eee;
|
||||
padding: .5em;
|
||||
margin: 0 -1em;
|
||||
}
|
||||
|
||||
footer
|
||||
{
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
|
||||
header h1
|
||||
{
|
||||
font-size: 1em;
|
||||
float: left;
|
||||
padding: .5em .5em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
header ul,
|
||||
footer ul
|
||||
{
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
header ul li,
|
||||
footer ul li
|
||||
{
|
||||
display: inline;
|
||||
}
|
||||
|
||||
header ul li a,
|
||||
footer ul li a
|
||||
{
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
padding: .5em .5em;
|
||||
}
|
||||
|
||||
|
||||
/* Forms */
|
||||
label
|
||||
{
|
||||
display: block;
|
||||
padding: .5em 0;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
textarea
|
||||
{
|
||||
font-size: inherit;
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
{% load static %}
|
||||
<html>
|
||||
<head>
|
||||
<title>{% block head_title %}{% endblock %} – {{ site.name }}</title>
|
||||
{% block extra_head %}{% endblock %}
|
||||
<link rel="stylesheet" href="{% static '/css/membersystem.css' %}" type="text/css" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>
|
||||
<a href="/">{{ site.name }}</a>
|
||||
</h1>
|
||||
<ul>
|
||||
{% if user.is_authenticated %}
|
||||
<li><a href="{% url 'users:password_change' %}">Change password</a></li>
|
||||
<li><a href="{% url 'users:logout' %}">Sign out</a></li>
|
||||
{% else %}
|
||||
<li><a href="{% url 'users:login' %}">Sign in</a></li>
|
||||
<li><a href="{% url 'users:signup' %}">Sign up</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</header>
|
||||
{% block body %}
|
||||
{% if messages %}
|
||||
<ul id="messages">
|
||||
{% for message in messages %}
|
||||
<li>{{message}}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
{% block extra_body %}
|
||||
{% endblock %}
|
||||
<footer>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://data.coop">data.coop</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://git.data.coop/data.coop/membersystem">source code</a>
|
||||
</li>
|
||||
</ul>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
|
@ -1,12 +0,0 @@
|
|||
"""URLs for the membersystem"""
|
||||
from django.contrib import admin
|
||||
from django.urls import include
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path("", views.index),
|
||||
path("users/", include("users.urls")),
|
||||
path("admin/", admin.site.urls),
|
||||
]
|
|
@ -5,16 +5,20 @@ description = ""
|
|||
authors = ["Your Name <you@example.com>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.7"
|
||||
python = "^3.9"
|
||||
Django = "^3.1"
|
||||
django-money = "^1.3"
|
||||
django-allauth = "^0.40.0"
|
||||
django-allauth = "^0.44.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,6 +1,4 @@
|
|||
# Generated by Django 2.0.6 on 2018-06-23 19:51
|
||||
from decimal import Decimal
|
||||
|
||||
# Generated by Django 3.1.7 on 2021-02-27 20:06
|
||||
import django.db.models.deletion
|
||||
import djmoney.models.fields
|
||||
from django.conf import settings
|
||||
|
@ -12,7 +10,9 @@ 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="created"),
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="oprettet"),
|
||||
),
|
||||
(
|
||||
"owner",
|
||||
|
@ -43,7 +43,9 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
),
|
||||
],
|
||||
options={"abstract": False},
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Order",
|
||||
|
@ -63,7 +65,7 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
(
|
||||
"created",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="oprettet"),
|
||||
),
|
||||
(
|
||||
"description",
|
||||
|
@ -82,7 +84,6 @@ class Migration(migrations.Migration):
|
|||
"price",
|
||||
djmoney.models.fields.MoneyField(
|
||||
decimal_places=2,
|
||||
default=Decimal("0.0"),
|
||||
max_digits=16,
|
||||
verbose_name="price (excl. VAT)",
|
||||
),
|
||||
|
@ -99,10 +100,7 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"vat",
|
||||
djmoney.models.fields.MoneyField(
|
||||
decimal_places=2,
|
||||
default=Decimal("0.0"),
|
||||
max_digits=16,
|
||||
verbose_name="VAT",
|
||||
decimal_places=2, max_digits=16, verbose_name="VAT"
|
||||
),
|
||||
),
|
||||
("is_paid", models.BooleanField(default=False, verbose_name="is paid")),
|
||||
|
@ -110,7 +108,7 @@ class Migration(migrations.Migration):
|
|||
"account",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="accounting.Account",
|
||||
to="accounting.account",
|
||||
),
|
||||
),
|
||||
(
|
||||
|
@ -121,60 +119,10 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
),
|
||||
],
|
||||
options={"verbose_name": "Order", "verbose_name_plural": "Orders"},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Payment",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"modified",
|
||||
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
||||
),
|
||||
(
|
||||
"created",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
||||
),
|
||||
(
|
||||
"amount_currency",
|
||||
djmoney.models.fields.CurrencyField(
|
||||
choices=[("DKK", "DKK")],
|
||||
default="XYZ",
|
||||
editable=False,
|
||||
max_length=3,
|
||||
),
|
||||
),
|
||||
(
|
||||
"amount",
|
||||
djmoney.models.fields.MoneyField(
|
||||
decimal_places=2, default=Decimal("0.0"), max_digits=16
|
||||
),
|
||||
),
|
||||
(
|
||||
"description",
|
||||
models.CharField(max_length=1024, verbose_name="description"),
|
||||
),
|
||||
(
|
||||
"stripe_charge_id",
|
||||
models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
(
|
||||
"order",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="accounting.Order",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={"verbose_name": "payment", "verbose_name_plural": "payments"},
|
||||
options={
|
||||
"verbose_name": "Order",
|
||||
"verbose_name_plural": "Orders",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Transaction",
|
||||
|
@ -194,7 +142,7 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
(
|
||||
"created",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="oprettet"),
|
||||
),
|
||||
(
|
||||
"amount_currency",
|
||||
|
@ -209,7 +157,6 @@ class Migration(migrations.Migration):
|
|||
"amount",
|
||||
djmoney.models.fields.MoneyField(
|
||||
decimal_places=2,
|
||||
default=Decimal("0.0"),
|
||||
help_text="This will include VAT",
|
||||
max_digits=16,
|
||||
verbose_name="amount",
|
||||
|
@ -224,10 +171,66 @@ class Migration(migrations.Migration):
|
|||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="transactions",
|
||||
to="accounting.Account",
|
||||
to="accounting.account",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={"abstract": False},
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Payment",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"modified",
|
||||
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
||||
),
|
||||
(
|
||||
"created",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="oprettet"),
|
||||
),
|
||||
(
|
||||
"amount_currency",
|
||||
djmoney.models.fields.CurrencyField(
|
||||
choices=[("DKK", "DKK")],
|
||||
default="XYZ",
|
||||
editable=False,
|
||||
max_length=3,
|
||||
),
|
||||
),
|
||||
(
|
||||
"amount",
|
||||
djmoney.models.fields.MoneyField(decimal_places=2, max_digits=16),
|
||||
),
|
||||
(
|
||||
"description",
|
||||
models.CharField(max_length=1024, verbose_name="description"),
|
||||
),
|
||||
(
|
||||
"stripe_charge_id",
|
||||
models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
(
|
||||
"order",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="accounting.order",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "payment",
|
||||
"verbose_name_plural": "payments",
|
||||
},
|
||||
),
|
||||
]
|
|
@ -1,7 +1,6 @@
|
|||
from hashlib import md5
|
||||
|
||||
from 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 _
|
||||
|
@ -24,7 +23,7 @@ class Account(CreatedModifiedAbstract):
|
|||
can decide which account to use to pay for something.
|
||||
"""
|
||||
|
||||
owner = models.ForeignKey(get_user_model(), on_delete=models.PROTECT)
|
||||
owner = models.ForeignKey("auth.User", on_delete=models.PROTECT)
|
||||
|
||||
@property
|
||||
def balance(self):
|
||||
|
@ -56,9 +55,8 @@ class Order(CreatedModifiedAbstract):
|
|||
invoices at the moment.
|
||||
"""
|
||||
|
||||
user = models.ForeignKey(get_user_model(), on_delete=models.PROTECT)
|
||||
user = models.ForeignKey("auth.User", on_delete=models.PROTECT)
|
||||
account = models.ForeignKey(Account, on_delete=models.PROTECT)
|
||||
is_paid = models.BooleanField(default=False)
|
||||
|
||||
description = models.CharField(max_length=1024, verbose_name=_("description"))
|
||||
|
14
src/membership/admin.py
Normal file
14
src/membership/admin.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
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
|
93
src/membership/migrations/0001_initial.py
Normal file
93
src/membership/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,93 @@
|
|||
# Generated by Django 3.1.7 on 2021-02-28 21:09
|
||||
import django.contrib.postgres.fields.ranges
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="MembershipType",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"modified",
|
||||
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
||||
),
|
||||
(
|
||||
"created",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="oprettet"),
|
||||
),
|
||||
("name", models.CharField(max_length=64, verbose_name="navn")),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "membership type",
|
||||
"verbose_name_plural": "membership types",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Membership",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"modified",
|
||||
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
||||
),
|
||||
(
|
||||
"created",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="oprettet"),
|
||||
),
|
||||
(
|
||||
"period",
|
||||
django.contrib.postgres.fields.ranges.DateTimeRangeField(
|
||||
help_text="The duration this subscription is for. "
|
||||
),
|
||||
),
|
||||
(
|
||||
"membership_type",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="memberships",
|
||||
to="membership.membershiptype",
|
||||
verbose_name="subscription type",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "membership",
|
||||
"verbose_name_plural": "memberships",
|
||||
},
|
||||
),
|
||||
]
|
76
src/membership/models.py
Normal file
76
src/membership/models.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
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
|
22
src/membership/templates/membership_overview.html
Normal file
22
src/membership/templates/membership_overview.html
Normal file
|
@ -0,0 +1,22 @@
|
|||
{% 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 %}
|
18
src/membership/views.py
Normal file
18
src/membership/views.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
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)
|
7
src/project/asgi.py
Normal file
7
src/project/asgi.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
|
||||
|
||||
application = get_asgi_application()
|
519
src/project/locale/da/LC_MESSAGES/django.po
Normal file
519
src/project/locale/da/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,519 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-03-04 09:06+0100\n"
|
||||
"PO-Revision-Date: 2021-03-04 09:06+0100\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: da\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 2.4.1\n"
|
||||
|
||||
#: src/accounting/admin.py:15 src/accounting/admin.py:26
|
||||
msgid "Customer"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/admin.py:31
|
||||
msgid "Order ID"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/models.py:13 src/membership/models.py:11
|
||||
msgid "modified"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/models.py:14 src/membership/models.py:12
|
||||
msgid "created"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/models.py:43
|
||||
msgid "amount"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/models.py:46
|
||||
msgid "This will include VAT"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/models.py:48 src/accounting/models.py:61
|
||||
#: src/accounting/models.py:100
|
||||
msgid "description"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/models.py:64
|
||||
msgid "price (excl. VAT)"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/models.py:66
|
||||
msgid "VAT"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/models.py:68
|
||||
msgid "is paid"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/models.py:88
|
||||
msgctxt "accounting term"
|
||||
msgid "Order"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/models.py:89
|
||||
msgctxt "accounting term"
|
||||
msgid "Orders"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/models.py:121
|
||||
msgid "payment"
|
||||
msgstr ""
|
||||
|
||||
#: src/accounting/models.py:122
|
||||
msgid "payments"
|
||||
msgstr ""
|
||||
|
||||
#: src/membership/models.py:45
|
||||
msgid "membership"
|
||||
msgstr "medlemskab"
|
||||
|
||||
#: src/membership/models.py:46
|
||||
msgid "memberships"
|
||||
msgstr "medlemskaber"
|
||||
|
||||
#: src/membership/models.py:53
|
||||
msgid "subscription type"
|
||||
msgstr ""
|
||||
|
||||
#: src/membership/models.py:57
|
||||
msgid "The duration this subscription is for. "
|
||||
msgstr ""
|
||||
|
||||
#: src/membership/models.py:70
|
||||
msgid "membership type"
|
||||
msgstr ""
|
||||
|
||||
#: src/membership/models.py:71
|
||||
msgid "membership types"
|
||||
msgstr ""
|
||||
|
||||
#: src/membership/models.py:73
|
||||
msgid "name"
|
||||
msgstr "navn"
|
||||
|
||||
#: src/membership/templates/membership_overview.html:7
|
||||
msgid "You do not have an active membership!"
|
||||
msgstr "Du har ikke et aktivt medlemskab!"
|
||||
|
||||
#: src/membership/templates/membership_overview.html:9
|
||||
msgid ""
|
||||
"You can become a member by depositing the membership fee to our bank account."
|
||||
msgstr ""
|
||||
|
||||
#: src/membership/templates/membership_overview.html:17
|
||||
msgid "You are a member!"
|
||||
msgstr "Du er medlem!"
|
||||
|
||||
#: src/membership/templates/membership_overview.html:19
|
||||
msgid "Period"
|
||||
msgstr "Periode"
|
||||
|
||||
#: src/membership/templates/membership_overview.html:20
|
||||
msgid "Type"
|
||||
msgstr "Type"
|
||||
|
||||
#: src/project/settings.py:131
|
||||
msgid "Danish"
|
||||
msgstr "Dansk"
|
||||
|
||||
#: src/project/settings.py:132
|
||||
msgid "English"
|
||||
msgstr "Engelsk"
|
||||
|
||||
#: src/project/templates/account/account_inactive.html:5
|
||||
#: src/project/templates/account/account_inactive.html:8
|
||||
msgid "Account Inactive"
|
||||
msgstr "Inaktiv konto"
|
||||
|
||||
#: src/project/templates/account/account_inactive.html:10
|
||||
msgid "This account is inactive."
|
||||
msgstr "Denne konto er inaktiv."
|
||||
|
||||
#: src/project/templates/account/email.html:5
|
||||
#: src/project/templates/account/email.html:16
|
||||
msgid "E-mail Addresses"
|
||||
msgstr "E-mail adresser"
|
||||
|
||||
#: src/project/templates/account/email.html:21
|
||||
msgid "The following e-mail addresses are associated with your account:"
|
||||
msgstr "De følgende e-mail adresser er tilknyttet din konto:"
|
||||
|
||||
#: src/project/templates/account/email.html:31
|
||||
msgid "Address"
|
||||
msgstr "Adresse"
|
||||
|
||||
#: src/project/templates/account/email.html:32
|
||||
msgid "Status"
|
||||
msgstr "Status"
|
||||
|
||||
#: src/project/templates/account/email.html:33
|
||||
msgid "Primary"
|
||||
msgstr "Primær"
|
||||
|
||||
#: src/project/templates/account/email.html:90
|
||||
msgid "Warning:"
|
||||
msgstr "Advarsel:"
|
||||
|
||||
#: src/project/templates/account/email.html:91
|
||||
msgid ""
|
||||
"You currently do not have any e-mail address set up. You should really add "
|
||||
"an e-mail address so you can receive notifications, reset your password, etc."
|
||||
msgstr ""
|
||||
"Du har lige nu ingen e-mail adresse tilknyttet. Du burde virkelig tilføje en "
|
||||
"e-mail adresse så du kan modtage notifikationer, nulstille dit kodeord, osv."
|
||||
|
||||
#: src/project/templates/account/email.html:99
|
||||
#: src/project/templates/account/email.html:107
|
||||
msgid "Add E-mail"
|
||||
msgstr "Tilføj e-mail"
|
||||
|
||||
#: src/project/templates/account/email.html:118
|
||||
msgid "Do you really want to remove the selected e-mail address?"
|
||||
msgstr "Vil du virkelig fjerne den valgte e-mail?"
|
||||
|
||||
#: src/project/templates/account/email/base_message.txt:1
|
||||
#, python-format
|
||||
msgid "Hello from %(site_name)s!"
|
||||
msgstr "Hej fra %(site_name)s!"
|
||||
|
||||
#: src/project/templates/account/email/base_message.txt:5
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Thank you for using %(site_name)s!\n"
|
||||
"%(site_domain)s"
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/email/email_confirmation_message.txt:5
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You're receiving this e-mail because user %(user_display)s has given your e-"
|
||||
"mail address to register an account on %(site_domain)s.\n"
|
||||
"\n"
|
||||
"To confirm this is correct, go to %(activate_url)s"
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/email/email_confirmation_subject.txt:3
|
||||
msgid "Please Confirm Your E-mail Address"
|
||||
msgstr "Venligst bekræft din e-mail adresse"
|
||||
|
||||
#: src/project/templates/account/email/password_reset_key_message.txt:4
|
||||
msgid ""
|
||||
"You're receiving this e-mail because you or someone else has requested a "
|
||||
"password for your user account.\n"
|
||||
"It can be safely ignored if you did not request a password reset. Click the "
|
||||
"link below to reset your password."
|
||||
msgstr ""
|
||||
"Du modtager denne e-mail fordi du, eller nogen anden, har anmodet om "
|
||||
"nulstilling af dit kodeord.\n"
|
||||
"Du kan trygt ignorere dette hvis det ikke var dig der anmodede om "
|
||||
"nulstillingen. Klik på linket herunder for at nulstille dit kodeord."
|
||||
|
||||
#: src/project/templates/account/email/password_reset_key_message.txt:9
|
||||
#, python-format
|
||||
msgid "In case you forgot, your username is %(username)s."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/email/password_reset_key_subject.txt:3
|
||||
msgid "Password Reset E-mail"
|
||||
msgstr "Nulstilling af password"
|
||||
|
||||
#: src/project/templates/account/email_confirm.html:6
|
||||
#: src/project/templates/account/email_confirm.html:10
|
||||
msgid "Confirm E-mail Address"
|
||||
msgstr "Bekræft e-mail adresse"
|
||||
|
||||
#: src/project/templates/account/email_confirm.html:16
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Please confirm that <a href=\"mailto:%(email)s\">%(email)s</a> is an e-mail "
|
||||
"address for user %(user_display)s."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/email_confirm.html:20
|
||||
msgid "Confirm"
|
||||
msgstr "Bekræft"
|
||||
|
||||
#: src/project/templates/account/email_confirm.html:27
|
||||
#, python-format
|
||||
msgid ""
|
||||
"This e-mail confirmation link expired or is invalid. Please <a href="
|
||||
"\"%(email_url)s\">issue a new e-mail confirmation request</a>."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/login.html:20
|
||||
#: src/project/templates/account/login.html:26
|
||||
#: src/project/templates/account/signup.html:26
|
||||
#: src/project/templates/account/signup.html:32
|
||||
msgid "E-mail"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: src/project/templates/account/login.html:31
|
||||
#: src/project/templates/account/login.html:37
|
||||
#: src/project/templates/account/signup.html:44
|
||||
#: src/project/templates/account/signup.html:50
|
||||
msgid "Password"
|
||||
msgstr "Kodeord"
|
||||
|
||||
#: src/project/templates/account/login.html:41
|
||||
msgid "Sign in"
|
||||
msgstr "Log ind"
|
||||
|
||||
#: src/project/templates/account/login.html:45
|
||||
msgid "Forgot password?"
|
||||
msgstr "Glemt kodeord?"
|
||||
|
||||
#: src/project/templates/account/login.html:48
|
||||
msgid "Or"
|
||||
msgstr "Eller"
|
||||
|
||||
#: src/project/templates/account/login.html:53
|
||||
#: src/project/templates/account/signup.html:6
|
||||
msgid "Become a member"
|
||||
msgstr "Bliv medlem"
|
||||
|
||||
#: src/project/templates/account/logout.html:5
|
||||
#: src/project/templates/account/logout.html:8
|
||||
#: src/project/templates/account/logout.html:17
|
||||
msgid "Sign Out"
|
||||
msgstr "Log ud"
|
||||
|
||||
#: src/project/templates/account/logout.html:10
|
||||
msgid "Are you sure you want to sign out?"
|
||||
msgstr "Er du sikker på at du vil logge ind?"
|
||||
|
||||
#: src/project/templates/account/messages/cannot_delete_primary_email.txt:2
|
||||
#, python-format
|
||||
msgid "You cannot remove your primary e-mail address (%(email)s)."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/messages/email_confirmation_sent.txt:2
|
||||
#, python-format
|
||||
msgid "Confirmation e-mail sent to %(email)s."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/messages/email_confirmed.txt:2
|
||||
#, python-format
|
||||
msgid "You have confirmed %(email)s."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/messages/email_deleted.txt:2
|
||||
#, python-format
|
||||
msgid "Removed e-mail address %(email)s."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/messages/logged_in.txt:4
|
||||
#, python-format
|
||||
msgid "Successfully signed in as %(name)s."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/messages/logged_out.txt:2
|
||||
msgid "You have signed out."
|
||||
msgstr "Du er nu logget ud."
|
||||
|
||||
#: src/project/templates/account/messages/password_changed.txt:2
|
||||
msgid "Password successfully changed."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/messages/password_set.txt:2
|
||||
msgid "Password successfully set."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/messages/primary_email_set.txt:2
|
||||
msgid "Primary e-mail address set."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/messages/unverified_primary_email.txt:2
|
||||
msgid "Your primary e-mail address must be verified."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/password_change.html:5
|
||||
#: src/project/templates/account/password_change.html:8
|
||||
#: src/project/templates/account/password_change.html:13
|
||||
#: src/project/templates/account/password_reset_from_key.html:4
|
||||
#: src/project/templates/account/password_reset_from_key.html:7
|
||||
#: src/project/templates/account/password_reset_from_key_done.html:4
|
||||
#: src/project/templates/account/password_reset_from_key_done.html:7
|
||||
msgid "Change Password"
|
||||
msgstr "Skift kodeord"
|
||||
|
||||
#: src/project/templates/account/password_change.html:14
|
||||
msgid "Forgot Password?"
|
||||
msgstr "Glemt kodeord?"
|
||||
|
||||
#: src/project/templates/account/password_reset.html:6
|
||||
#: src/project/templates/account/password_reset.html:10
|
||||
#: src/project/templates/account/password_reset_done.html:6
|
||||
#: src/project/templates/account/password_reset_done.html:9
|
||||
msgid "Password Reset"
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/password_reset.html:16
|
||||
msgid ""
|
||||
"Forgotten your password? Enter your e-mail address below, and we'll send you "
|
||||
"an e-mail allowing you to reset it."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/password_reset.html:21
|
||||
#: src/project/templates/account/password_reset.html:27
|
||||
msgid "E-mail address"
|
||||
msgstr "E-mail adresser"
|
||||
|
||||
#: src/project/templates/account/password_reset.html:38
|
||||
msgid "Reset My Password"
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/password_reset.html:41
|
||||
msgid "Please contact us if you have any trouble resetting your password."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/password_reset.html:47
|
||||
#: src/project/templates/account/signup.html:72
|
||||
msgid "To login"
|
||||
msgstr "Til login"
|
||||
|
||||
#: src/project/templates/account/password_reset_done.html:15
|
||||
msgid ""
|
||||
"We have sent you an e-mail. Please contact us if you do not receive it "
|
||||
"within a few minutes."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/password_reset_from_key.html:7
|
||||
msgid "Bad Token"
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/password_reset_from_key.html:11
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The password reset link was invalid, possibly because it has already been "
|
||||
"used. Please request a <a href=\"%(passwd_reset_url)s\">new password reset</"
|
||||
"a>."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/password_reset_from_key.html:17
|
||||
msgid "change password"
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/password_reset_from_key.html:20
|
||||
#: src/project/templates/account/password_reset_from_key_done.html:8
|
||||
msgid "Your password is now changed."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/password_set.html:5
|
||||
#: src/project/templates/account/password_set.html:8
|
||||
#: src/project/templates/account/password_set.html:13
|
||||
msgid "Set Password"
|
||||
msgstr "Sæt kodeord"
|
||||
|
||||
#: src/project/templates/account/signup.html:19
|
||||
msgid "To become a member, you need to have an account. Create one here."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/signup.html:66
|
||||
msgid "Sign up"
|
||||
msgstr "Bliv medlem"
|
||||
|
||||
#: src/project/templates/account/signup_closed.html:5
|
||||
#: src/project/templates/account/signup_closed.html:8
|
||||
msgid "Sign Up Closed"
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/signup_closed.html:10
|
||||
msgid "We are sorry, but the sign up is currently closed."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/snippets/already_logged_in.html:5
|
||||
msgid "Note"
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/snippets/already_logged_in.html:5
|
||||
#, python-format
|
||||
msgid "you are already logged in as %(user_display)s."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/verification_sent.html:5
|
||||
#: src/project/templates/account/verification_sent.html:8
|
||||
#: src/project/templates/account/verified_email_required.html:5
|
||||
#: src/project/templates/account/verified_email_required.html:8
|
||||
msgid "Verify Your E-mail Address"
|
||||
msgstr "Verificér din e-mail adresse"
|
||||
|
||||
#: src/project/templates/account/verification_sent.html:10
|
||||
msgid ""
|
||||
"We have sent an e-mail to you for verification. Follow the link provided to "
|
||||
"finalize the signup process. Please contact us if you do not receive it "
|
||||
"within a few minutes."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/verified_email_required.html:12
|
||||
msgid ""
|
||||
"This part of the site requires us to verify that\n"
|
||||
"you are who you claim to be. For this purpose, we require that you\n"
|
||||
"verify ownership of your e-mail address. "
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/verified_email_required.html:16
|
||||
msgid ""
|
||||
"We have sent an e-mail to you for\n"
|
||||
"verification. Please click on the link inside this e-mail. Please\n"
|
||||
"contact us if you do not receive it within a few minutes."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/account/verified_email_required.html:20
|
||||
#, python-format
|
||||
msgid ""
|
||||
"<strong>Note:</strong> you can still <a href=\"%(email_url)s\">change your e-"
|
||||
"mail address</a>."
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/base.html:140
|
||||
msgid "Dashboard"
|
||||
msgstr ""
|
||||
|
||||
#: src/project/templates/base.html:146
|
||||
msgid "Profile"
|
||||
msgstr "Profil"
|
||||
|
||||
#: src/project/templates/base.html:152
|
||||
msgid "Details"
|
||||
msgstr "Detaljer"
|
||||
|
||||
#: src/project/templates/base.html:158
|
||||
msgid "Emails"
|
||||
msgstr "Emails"
|
||||
|
||||
#: src/project/templates/base.html:164
|
||||
msgid "Membership"
|
||||
msgstr "Medlemskab"
|
||||
|
||||
#: src/project/templates/base.html:171 src/project/templates/base.html:184
|
||||
msgid "Overview"
|
||||
msgstr "Oversigt"
|
||||
|
||||
#: src/project/templates/base.html:177
|
||||
msgid "Services"
|
||||
msgstr "Tjenester"
|
||||
|
||||
#: src/project/templates/base.html:191
|
||||
msgid "Admin"
|
||||
msgstr "Admin"
|
||||
|
||||
#: src/project/templates/base.html:197
|
||||
msgid "Members"
|
||||
msgstr "Medlemmer"
|
||||
|
||||
#~ msgid "OR"
|
||||
#~ msgstr "Eller"
|
142
src/project/settings.py
Normal file
142
src/project/settings.py
Normal file
|
@ -0,0 +1,142 @@
|
|||
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
Normal file
7
src/project/static/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/project/static/css/bootstrap.min.css.map
Normal file
1
src/project/static/css/bootstrap.min.css.map
Normal file
File diff suppressed because one or more lines are too long
7
src/project/static/js/bootstrap.bundle.min.js
vendored
Normal file
7
src/project/static/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/project/static/js/bootstrap.bundle.min.js.map
Normal file
1
src/project/static/js/bootstrap.bundle.min.js.map
Normal file
File diff suppressed because one or more lines are too long
11
src/project/templates/account/account_inactive.html
Normal file
11
src/project/templates/account/account_inactive.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
{% 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 %}
|
130
src/project/templates/account/email.html
Normal file
130
src/project/templates/account/email.html
Normal file
|
@ -0,0 +1,130 @@
|
|||
{% 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 %}
|
7
src/project/templates/account/email/base_message.txt
Normal file
7
src/project/templates/account/email/base_message.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
{% 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 %}
|
|
@ -0,0 +1,7 @@
|
|||
{% 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 %}
|
|
@ -0,0 +1 @@
|
|||
{% include "account/email/email_confirmation_message.txt" %}
|
|
@ -0,0 +1 @@
|
|||
{% include "account/email/email_confirmation_subject.txt" %}
|
|
@ -0,0 +1,4 @@
|
|||
{% load i18n %}
|
||||
{% autoescape off %}
|
||||
{% blocktrans %}Please Confirm Your E-mail Address{% endblocktrans %}
|
||||
{% endautoescape %}
|
|
@ -0,0 +1,9 @@
|
|||
{% 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 %}
|
|
@ -0,0 +1,4 @@
|
|||
{% load i18n %}
|
||||
{% autoescape off %}
|
||||
{% blocktrans %}Password Reset E-mail{% endblocktrans %}
|
||||
{% endautoescape %}
|
31
src/project/templates/account/email_confirm.html
Normal file
31
src/project/templates/account/email_confirm.html
Normal file
|
@ -0,0 +1,31 @@
|
|||
{% 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 %}
|
56
src/project/templates/account/login.html
Normal file
56
src/project/templates/account/login.html
Normal file
|
@ -0,0 +1,56 @@
|
|||
{% 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 %}
|
21
src/project/templates/account/logout.html
Normal file
21
src/project/templates/account/logout.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
{% 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 %}
|
|
@ -0,0 +1,2 @@
|
|||
{% load i18n %}
|
||||
{% blocktrans %}You cannot remove your primary e-mail address ({{email}}).{% endblocktrans %}
|
|
@ -0,0 +1,2 @@
|
|||
{% load i18n %}
|
||||
{% blocktrans %}Confirmation e-mail sent to {{email}}.{% endblocktrans %}
|
|
@ -0,0 +1,2 @@
|
|||
{% load i18n %}
|
||||
{% blocktrans %}You have confirmed {{email}}.{% endblocktrans %}
|
2
src/project/templates/account/messages/email_deleted.txt
Normal file
2
src/project/templates/account/messages/email_deleted.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
{% load i18n %}
|
||||
{% blocktrans %}Removed e-mail address {{email}}.{% endblocktrans %}
|
4
src/project/templates/account/messages/logged_in.txt
Normal file
4
src/project/templates/account/messages/logged_in.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
{% load account %}
|
||||
{% load i18n %}
|
||||
{% user_display user as name %}
|
||||
{% blocktrans %}Successfully signed in as {{name}}.{% endblocktrans %}
|
2
src/project/templates/account/messages/logged_out.txt
Normal file
2
src/project/templates/account/messages/logged_out.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
{% load i18n %}
|
||||
{% blocktrans %}You have signed out.{% endblocktrans %}
|
|
@ -0,0 +1,2 @@
|
|||
{% load i18n %}
|
||||
{% blocktrans %}Password successfully changed.{% endblocktrans %}
|
2
src/project/templates/account/messages/password_set.txt
Normal file
2
src/project/templates/account/messages/password_set.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
{% load i18n %}
|
||||
{% blocktrans %}Password successfully set.{% endblocktrans %}
|
|
@ -0,0 +1,2 @@
|
|||
{% load i18n %}
|
||||
{% blocktrans %}Primary e-mail address set.{% endblocktrans %}
|
|
@ -0,0 +1,2 @@
|
|||
{% load i18n %}
|
||||
{% blocktrans %}Your primary e-mail address must be verified.{% endblocktrans %}
|
16
src/project/templates/account/password_change.html
Normal file
16
src/project/templates/account/password_change.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
{% 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 %}
|
49
src/project/templates/account/password_reset.html
Normal file
49
src/project/templates/account/password_reset.html
Normal file
|
@ -0,0 +1,49 @@
|
|||
{% 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 %}
|
16
src/project/templates/account/password_reset_done.html
Normal file
16
src/project/templates/account/password_reset_done.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
{% 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 %}
|
24
src/project/templates/account/password_reset_from_key.html
Normal file
24
src/project/templates/account/password_reset_from_key.html
Normal file
|
@ -0,0 +1,24 @@
|
|||
{% 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 %}
|
|
@ -0,0 +1,9 @@
|
|||
{% 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 %}
|
15
src/project/templates/account/password_set.html
Normal file
15
src/project/templates/account/password_set.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
{% 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 %}
|
75
src/project/templates/account/pre_login_base.html
Normal file
75
src/project/templates/account/pre_login_base.html
Normal file
|
@ -0,0 +1,75 @@
|
|||
{% 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>
|
75
src/project/templates/account/signup.html
Normal file
75
src/project/templates/account/signup.html
Normal file
|
@ -0,0 +1,75 @@
|
|||
{% 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 %}
|
11
src/project/templates/account/signup_closed.html
Normal file
11
src/project/templates/account/signup_closed.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
{% 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 %}
|
|
@ -0,0 +1,5 @@
|
|||
{% 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>
|
12
src/project/templates/account/verification_sent.html
Normal file
12
src/project/templates/account/verification_sent.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
{% 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 %}
|
23
src/project/templates/account/verified_email_required.html
Normal file
23
src/project/templates/account/verified_email_required.html
Normal file
|
@ -0,0 +1,23 @@
|
|||
{% 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 %}
|
212
src/project/templates/base.html
Normal file
212
src/project/templates/base.html
Normal file
|
@ -0,0 +1,212 @@
|
|||
{% 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
src/project/templates/index.html
Normal file
1
src/project/templates/index.html
Normal file
|
@ -0,0 +1 @@
|
|||
{% extends "base.html" %}
|
10
src/project/templates/services_overview.html
Normal file
10
src/project/templates/services_overview.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<p>
|
||||
Services and signup to these will be
|
||||
</p>
|
||||
<p>
|
||||
This is yet to be implemented.
|
||||
</p>
|
||||
{% endblock %}
|
19
src/project/urls.py
Normal file
19
src/project/urls.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
"""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)),
|
||||
]
|
|
@ -3,3 +3,7 @@ from django.shortcuts import render
|
|||
|
||||
def index(request):
|
||||
return render(request, "index.html")
|
||||
|
||||
|
||||
def services_overview(request):
|
||||
return render(request, "services_overview.html")
|
13
src/utils/templatetags/utils.py
Normal file
13
src/utils/templatetags/utils.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
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 +0,0 @@
|
|||
from django.contrib import admin # noqa
|
|
@ -1,5 +0,0 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class UsersConfig(AppConfig):
|
||||
name = "users"
|
|
@ -1,19 +0,0 @@
|
|||
from django import forms
|
||||
from django.contrib.auth.forms import UserCreationForm
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
def get_confirm_code(email):
|
||||
return default_token_generator(email)[:7]
|
||||
|
||||
|
||||
class SignupForm(UserCreationForm):
|
||||
|
||||
username = forms.EmailField(label=_("Email"))
|
||||
|
||||
class Meta:
|
||||
model = models.User
|
||||
fields = ("username",)
|
|
@ -1,73 +0,0 @@
|
|||
# Generated by Django 2.2.4 on 2019-08-31 18:44
|
||||
import uuid
|
||||
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [("auth", "0011_update_proxy_permissions")]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="User",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("password", models.CharField(max_length=128, verbose_name="password")),
|
||||
(
|
||||
"last_login",
|
||||
models.DateTimeField(
|
||||
blank=True, null=True, verbose_name="last login"
|
||||
),
|
||||
),
|
||||
("nick", models.CharField(blank=True, max_length=60, null=True)),
|
||||
(
|
||||
"email",
|
||||
models.EmailField(
|
||||
help_text="Your email address will be used for password resets and notification about your event/submissions.",
|
||||
max_length=254,
|
||||
unique=True,
|
||||
verbose_name="E-Mail",
|
||||
),
|
||||
),
|
||||
("is_active", models.BooleanField(default=True)),
|
||||
("is_staff", models.BooleanField(default=False)),
|
||||
("is_superuser", models.BooleanField(default=False)),
|
||||
("token_uuid", models.UUIDField(default=uuid.uuid4, editable=False)),
|
||||
(
|
||||
"groups",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
|
||||
related_name="user_set",
|
||||
related_query_name="user",
|
||||
to="auth.Group",
|
||||
verbose_name="groups",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user_permissions",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="Specific permissions for this user.",
|
||||
related_name="user_set",
|
||||
related_query_name="user",
|
||||
to="auth.Permission",
|
||||
verbose_name="user permissions",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={"verbose_name": "User"},
|
||||
)
|
||||
]
|
|
@ -1,60 +0,0 @@
|
|||
import uuid
|
||||
|
||||
from django.contrib.auth.base_user import BaseUserManager
|
||||
from django.contrib.auth.models import AbstractBaseUser
|
||||
from django.contrib.auth.models import PermissionsMixin
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class UserManager(BaseUserManager):
|
||||
"""The user manager class."""
|
||||
|
||||
def create_user(self, password: str = None, **kwargs):
|
||||
user = self.model(**kwargs)
|
||||
user.set_password(password)
|
||||
user.save()
|
||||
return user
|
||||
|
||||
def create_superuser(self, password: str, **kwargs):
|
||||
user = self.create_user(password=password, **kwargs)
|
||||
user.is_staff = True
|
||||
user.is_superuser = True
|
||||
user.save(update_fields=["is_staff", "is_superuser"])
|
||||
return user
|
||||
|
||||
|
||||
class User(PermissionsMixin, AbstractBaseUser):
|
||||
|
||||
EMAIL_FIELD = "email"
|
||||
USERNAME_FIELD = "email"
|
||||
|
||||
objects = UserManager()
|
||||
|
||||
nick = models.CharField(max_length=60, null=True, blank=True)
|
||||
email = models.EmailField(
|
||||
unique=True,
|
||||
verbose_name=_("E-Mail"),
|
||||
help_text=_(
|
||||
"Your email address will be used for password resets and notification about your event/submissions."
|
||||
),
|
||||
)
|
||||
|
||||
is_active = models.BooleanField(default=True)
|
||||
|
||||
# For the Django admin...
|
||||
is_staff = models.BooleanField(default=False)
|
||||
is_superuser = models.BooleanField(default=False)
|
||||
|
||||
# Used for confirmations and password reminders to NOT disclose email in URL
|
||||
token_uuid = models.UUIDField(default=uuid.uuid4, editable=False)
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Use a useful string representation."""
|
||||
return self.get_display_name()
|
||||
|
||||
def get_display_name(self) -> str:
|
||||
return self.nick if self.nick else str(_("Unnamed user"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("User")
|
|
@ -1,10 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<p>{% trans "Thanks for spending some quality time with the Web site today." %}</p>
|
||||
|
||||
<p><a href="{% url 'admin:index' %}">{% trans 'Log in again' %}</a></p>
|
||||
|
||||
{% endblock %}
|
|
@ -1,39 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if form.errors %}
|
||||
<p>Your username and password didn't match. Please try again.</p>
|
||||
{% endif %}
|
||||
|
||||
{% if next %}
|
||||
{% if user.is_authenticated %}
|
||||
<p>Your account doesn't have access to this page. To proceed,
|
||||
please login with an account that has access.</p>
|
||||
{% else %}
|
||||
<p>Please login to see this page.</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="{% url 'users:login' %}">
|
||||
{% csrf_token %}
|
||||
<table>
|
||||
<tr>
|
||||
<td>{{ form.username.label_tag }}</td>
|
||||
<td>{{ form.username }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ form.password.label_tag }}</td>
|
||||
<td>{{ form.password }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<input type="submit" value="login">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
</form>
|
||||
|
||||
{# Assumes you setup the password_reset view in your URLconf #}
|
||||
<p><a href="{% url 'users:password_reset' %}">Lost password?</a></p>
|
||||
|
||||
{% endblock %}
|
|
@ -1,8 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
|
||||
{% block content %}
|
||||
<p>{% trans 'Your password was changed.' %}</p>
|
||||
{% endblock %}
|
|
@ -1,52 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n static %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
|
||||
|
||||
{% block content %}<div id="content-main">
|
||||
|
||||
<form method="post">{% csrf_token %}
|
||||
<div>
|
||||
{% if form.errors %}
|
||||
<p class="errornote">
|
||||
{% if form.errors.items|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<p>{% trans "Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly." %}</p>
|
||||
|
||||
<fieldset class="module aligned wide">
|
||||
|
||||
<div class="form-row">
|
||||
{{ form.old_password.errors }}
|
||||
{{ form.old_password.label_tag }} {{ form.old_password }}
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
{{ form.new_password1.errors }}
|
||||
{{ form.new_password1.label_tag }} {{ form.new_password1 }}
|
||||
{% if form.new_password1.help_text %}
|
||||
<div class="help">{{ form.new_password1.help_text|safe }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
{{ form.new_password2.errors }}
|
||||
{{ form.new_password2.label_tag }} {{ form.new_password2 }}
|
||||
{% if form.new_password2.help_text %}
|
||||
<div class="help">{{ form.new_password2.help_text|safe }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
|
||||
<div class="submit-row">
|
||||
<input type="submit" value="{% trans 'Change my password' %}" class="default">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form></div>
|
||||
|
||||
{% endblock %}
|
|
@ -1,13 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<p>{% trans "Your password has been set. You may go ahead and log in now." %}</p>
|
||||
|
||||
<p><a href="{{ login_url }}">{% trans 'Log in' %}</a></p>
|
||||
|
||||
{% endblock %}
|
|
@ -1,34 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n static %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
{% if validlink %}
|
||||
|
||||
<p>{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}</p>
|
||||
|
||||
<form method="post">{% csrf_token %}
|
||||
<fieldset class="module aligned">
|
||||
<div class="form-row field-password1">
|
||||
{{ form.new_password1.errors }}
|
||||
<label for="id_new_password1">{% trans 'New password:' %}</label>
|
||||
{{ form.new_password1 }}
|
||||
</div>
|
||||
<div class="form-row field-password2">
|
||||
{{ form.new_password2.errors }}
|
||||
<label for="id_new_password2">{% trans 'Confirm password:' %}</label>
|
||||
{{ form.new_password2 }}
|
||||
</div>
|
||||
<input type="submit" value="{% trans 'Change my password' %}">
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
{% else %}
|
||||
|
||||
<p>{% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}</p>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
|
@ -1,12 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<p>{% trans "We've emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly." %}</p>
|
||||
|
||||
<p>{% trans "If you don't receive an email, please make sure you've entered the address you registered with, and check your spam folder." %}</p>
|
||||
|
||||
{% endblock %}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue