Use poetry for managing dependencies #9
|
@ -1,14 +1,23 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: git://github.com/pre-commit/pre-commit-hooks
|
- repo: git://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v1.3.0
|
rev: v2.3.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
- id: flake8
|
- id: flake8
|
||||||
|
args: [--max-line-length=120, --exclude=*/migrations/*]
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
- id: check-added-large-files
|
- id: check-added-large-files
|
||||||
- id: debug-statements
|
- id: debug-statements
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
|
- id: check-toml
|
||||||
- repo: https://github.com/asottile/reorder_python_imports
|
- repo: https://github.com/asottile/reorder_python_imports
|
||||||
rev: v1.0.1
|
rev: v1.6.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: reorder-python-imports
|
- id: reorder-python-imports
|
||||||
|
types: [file, python]
|
||||||
|
- repo: https://github.com/psf/black
|
||||||
|
rev: stable
|
||||||
|
hooks:
|
||||||
|
- id: black
|
||||||
|
language: python
|
||||||
|
types: [file, python]
|
||||||
|
|
12
Makefile
12
Makefile
|
@ -2,14 +2,12 @@
|
||||||
# are supposed to be run, but feel free to change them!
|
# are supposed to be run, but feel free to change them!
|
||||||
|
|
||||||
dev-setup:
|
dev-setup:
|
||||||
pip install -r requirements_dev.txt --upgrade
|
poetry run pre-commit install
|
||||||
pip install -r requirements.txt --upgrade
|
poetry run python manage.py migrate
|
||||||
pre-commit install
|
poetry run python manage.py createsuperuser
|
||||||
python manage.py migrate
|
|
||||||
python manage.py createsuperuser
|
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
pre-commit run --all
|
poetry run pre-commit run --all
|
||||||
|
|
||||||
test:
|
test:
|
||||||
pytest
|
poetry run pytest
|
||||||
|
|
24
README.md
Normal file
24
README.md
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# member.data.coop
|
||||||
|
|
||||||
|
To start developing:
|
||||||
|
|
||||||
|
Get poetry
|
||||||
|
|
||||||
|
$ python3 -m pip install --user pipx
|
||||||
|
$ pipx install poetry
|
||||||
|
|
||||||
|
Run poetry to setup environment
|
||||||
|
|
||||||
|
$ poetry install
|
||||||
|
|
||||||
|
Run this make target, which installs all the requirements and sets up a development database.
|
||||||
|
|
||||||
|
$ make dev-setup
|
||||||
|
|
||||||
|
To run the Django development server:
|
||||||
|
|
||||||
|
$ poetry run python manage.py runserver
|
||||||
|
|
||||||
|
Before you push your stuff, run tests:
|
||||||
|
|
||||||
|
$ make test
|
19
README.rst
19
README.rst
|
@ -1,19 +0,0 @@
|
||||||
member.data.coop
|
|
||||||
================
|
|
||||||
|
|
||||||
To start developing:
|
|
||||||
|
|
||||||
# Create a virtualenv with python 3
|
|
||||||
$ mkvirtualenv -p python3 datacoop
|
|
||||||
|
|
||||||
# Run this make target, which installs all the requirements and sets up a
|
|
||||||
# development database.
|
|
||||||
$ make dev-setup
|
|
||||||
|
|
||||||
To run the Django development server:
|
|
||||||
|
|
||||||
$ python manage.py runserver
|
|
||||||
|
|
||||||
Before you push your stuff, run tests:
|
|
||||||
|
|
||||||
$ make test
|
|
|
@ -7,22 +7,25 @@ from . import models
|
||||||
@admin.register(models.Order)
|
@admin.register(models.Order)
|
||||||
class OrderAdmin(admin.ModelAdmin):
|
class OrderAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
list_display = ('who', 'description', 'created', 'is_paid',)
|
list_display = ("who", "description", "created", "is_paid")
|
||||||
|
|
||||||
def who(self, instance):
|
def who(self, instance):
|
||||||
return instance.user.get_full_name()
|
return instance.user.get_full_name()
|
||||||
|
|
||||||
who.short_description = _("Customer")
|
who.short_description = _("Customer")
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Payment)
|
@admin.register(models.Payment)
|
||||||
class PaymentAdmin(admin.ModelAdmin):
|
class PaymentAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
list_display = ('who', 'description', 'order_id', 'created',)
|
list_display = ("who", "description", "order_id", "created")
|
||||||
|
|
||||||
def who(self, instance):
|
def who(self, instance):
|
||||||
return instance.order.user.get_full_name()
|
return instance.order.user.get_full_name()
|
||||||
|
|
||||||
who.short_description = _("Customer")
|
who.short_description = _("Customer")
|
||||||
|
|
||||||
def order_id(self, instance):
|
def order_id(self, instance):
|
||||||
return instance.order.id
|
return instance.order.id
|
||||||
|
|
||||||
order_id.short_description = _("Order ID")
|
order_id.short_description = _("Order ID")
|
||||||
|
|
|
@ -2,4 +2,4 @@ from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class AccountingConfig(AppConfig):
|
class AccountingConfig(AppConfig):
|
||||||
name = 'accounting'
|
name = "accounting"
|
||||||
|
|
|
@ -12,73 +12,222 @@ class Migration(migrations.Migration):
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Account',
|
name="Account",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('modified', models.DateTimeField(auto_now=True, verbose_name='modified')),
|
"id",
|
||||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='created')),
|
models.AutoField(
|
||||||
('owner', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"modified",
|
||||||
|
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created",
|
||||||
|
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"owner",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={"abstract": False},
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Order',
|
name="Order",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('modified', models.DateTimeField(auto_now=True, verbose_name='modified')),
|
"id",
|
||||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='created')),
|
models.AutoField(
|
||||||
('description', models.CharField(max_length=1024, verbose_name='description')),
|
auto_created=True,
|
||||||
('price_currency', djmoney.models.fields.CurrencyField(choices=[('DKK', 'DKK')], default='XYZ', editable=False, max_length=3)),
|
primary_key=True,
|
||||||
('price', djmoney.models.fields.MoneyField(decimal_places=2, default=Decimal('0.0'), max_digits=16, verbose_name='price (excl. VAT)')),
|
serialize=False,
|
||||||
('vat_currency', djmoney.models.fields.CurrencyField(choices=[('DKK', 'DKK')], default='XYZ', editable=False, max_length=3)),
|
verbose_name="ID",
|
||||||
('vat', djmoney.models.fields.MoneyField(decimal_places=2, default=Decimal('0.0'), max_digits=16, verbose_name='VAT')),
|
),
|
||||||
('is_paid', models.BooleanField(default=False, verbose_name='is paid')),
|
),
|
||||||
('account', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='accounting.Account')),
|
(
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
"modified",
|
||||||
|
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created",
|
||||||
|
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"description",
|
||||||
|
models.CharField(max_length=1024, verbose_name="description"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"price_currency",
|
||||||
|
djmoney.models.fields.CurrencyField(
|
||||||
|
choices=[("DKK", "DKK")],
|
||||||
|
default="XYZ",
|
||||||
|
editable=False,
|
||||||
|
max_length=3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"price",
|
||||||
|
djmoney.models.fields.MoneyField(
|
||||||
|
decimal_places=2,
|
||||||
|
default=Decimal("0.0"),
|
||||||
|
max_digits=16,
|
||||||
|
verbose_name="price (excl. VAT)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"vat_currency",
|
||||||
|
djmoney.models.fields.CurrencyField(
|
||||||
|
choices=[("DKK", "DKK")],
|
||||||
|
default="XYZ",
|
||||||
|
editable=False,
|
||||||
|
max_length=3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"vat",
|
||||||
|
djmoney.models.fields.MoneyField(
|
||||||
|
decimal_places=2,
|
||||||
|
default=Decimal("0.0"),
|
||||||
|
max_digits=16,
|
||||||
|
verbose_name="VAT",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("is_paid", models.BooleanField(default=False, verbose_name="is paid")),
|
||||||
|
(
|
||||||
|
"account",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
to="accounting.Account",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={"verbose_name": "Order", "verbose_name_plural": "Orders"},
|
||||||
'verbose_name': 'Order',
|
|
||||||
'verbose_name_plural': 'Orders',
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Payment',
|
name="Payment",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('modified', models.DateTimeField(auto_now=True, verbose_name='modified')),
|
"id",
|
||||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='created')),
|
models.AutoField(
|
||||||
('amount_currency', djmoney.models.fields.CurrencyField(choices=[('DKK', 'DKK')], default='XYZ', editable=False, max_length=3)),
|
auto_created=True,
|
||||||
('amount', djmoney.models.fields.MoneyField(decimal_places=2, default=Decimal('0.0'), max_digits=16)),
|
primary_key=True,
|
||||||
('description', models.CharField(max_length=1024, verbose_name='description')),
|
serialize=False,
|
||||||
('stripe_charge_id', models.CharField(blank=True, max_length=255, null=True)),
|
verbose_name="ID",
|
||||||
('order', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='accounting.Order')),
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"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={
|
options={"verbose_name": "payment", "verbose_name_plural": "payments"},
|
||||||
'verbose_name': 'payment',
|
|
||||||
'verbose_name_plural': 'payments',
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Transaction',
|
name="Transaction",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('modified', models.DateTimeField(auto_now=True, verbose_name='modified')),
|
"id",
|
||||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='created')),
|
models.AutoField(
|
||||||
('amount_currency', djmoney.models.fields.CurrencyField(choices=[('DKK', 'DKK')], default='XYZ', editable=False, max_length=3)),
|
auto_created=True,
|
||||||
('amount', djmoney.models.fields.MoneyField(decimal_places=2, default=Decimal('0.0'), help_text='This will include VAT', max_digits=16, verbose_name='amount')),
|
primary_key=True,
|
||||||
('description', models.CharField(max_length=1024, verbose_name='description')),
|
serialize=False,
|
||||||
('account', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transactions', to='accounting.Account')),
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"modified",
|
||||||
|
models.DateTimeField(auto_now=True, verbose_name="modified"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created",
|
||||||
|
models.DateTimeField(auto_now_add=True, verbose_name="created"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"amount_currency",
|
||||||
|
djmoney.models.fields.CurrencyField(
|
||||||
|
choices=[("DKK", "DKK")],
|
||||||
|
default="XYZ",
|
||||||
|
editable=False,
|
||||||
|
max_length=3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"amount",
|
||||||
|
djmoney.models.fields.MoneyField(
|
||||||
|
decimal_places=2,
|
||||||
|
default=Decimal("0.0"),
|
||||||
|
help_text="This will include VAT",
|
||||||
|
max_digits=16,
|
||||||
|
verbose_name="amount",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"description",
|
||||||
|
models.CharField(max_length=1024, verbose_name="description"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"account",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="transactions",
|
||||||
|
to="accounting.Account",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={"abstract": False},
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -11,14 +11,8 @@ from djmoney.models.fields import MoneyField
|
||||||
|
|
||||||
class CreatedModifiedAbstract(models.Model):
|
class CreatedModifiedAbstract(models.Model):
|
||||||
|
|
||||||
modified = models.DateTimeField(
|
modified = models.DateTimeField(auto_now=True, verbose_name=_("modified"))
|
||||||
auto_now=True,
|
created = models.DateTimeField(auto_now_add=True, verbose_name=_("created"))
|
||||||
verbose_name=_("modified"),
|
|
||||||
)
|
|
||||||
created = models.DateTimeField(
|
|
||||||
auto_now_add=True,
|
|
||||||
verbose_name=_("created"),
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
@ -34,7 +28,7 @@ class Account(CreatedModifiedAbstract):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def balance(self):
|
def balance(self):
|
||||||
return self.transactions.all().aggregate(Sum('amount')).get('amount', 0)
|
return self.transactions.all().aggregate(Sum("amount")).get("amount", 0)
|
||||||
|
|
||||||
|
|
||||||
class Transaction(CreatedModifiedAbstract):
|
class Transaction(CreatedModifiedAbstract):
|
||||||
|
@ -44,20 +38,15 @@ class Transaction(CreatedModifiedAbstract):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
account = models.ForeignKey(
|
account = models.ForeignKey(
|
||||||
Account,
|
Account, on_delete=models.PROTECT, related_name="transactions"
|
||||||
on_delete=models.PROTECT,
|
|
||||||
related_name="transactions",
|
|
||||||
)
|
)
|
||||||
amount = MoneyField(
|
amount = MoneyField(
|
||||||
verbose_name=_("amount"),
|
verbose_name=_("amount"),
|
||||||
max_digits=16,
|
max_digits=16,
|
||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
help_text=_("This will include VAT")
|
help_text=_("This will include VAT"),
|
||||||
)
|
|
||||||
description = models.CharField(
|
|
||||||
max_length=1024,
|
|
||||||
verbose_name=_("description")
|
|
||||||
)
|
)
|
||||||
|
description = models.CharField(max_length=1024, verbose_name=_("description"))
|
||||||
|
|
||||||
|
|
||||||
class Order(CreatedModifiedAbstract):
|
class Order(CreatedModifiedAbstract):
|
||||||
|
@ -71,26 +60,14 @@ class Order(CreatedModifiedAbstract):
|
||||||
account = models.ForeignKey(Account, on_delete=models.PROTECT)
|
account = models.ForeignKey(Account, on_delete=models.PROTECT)
|
||||||
is_paid = models.BooleanField(default=False)
|
is_paid = models.BooleanField(default=False)
|
||||||
|
|
||||||
description = models.CharField(
|
description = models.CharField(max_length=1024, verbose_name=_("description"))
|
||||||
max_length=1024,
|
|
||||||
verbose_name=_("description")
|
|
||||||
)
|
|
||||||
|
|
||||||
price = MoneyField(
|
price = MoneyField(
|
||||||
verbose_name=_("price (excl. VAT)"),
|
verbose_name=_("price (excl. VAT)"), max_digits=16, decimal_places=2
|
||||||
max_digits=16,
|
|
||||||
decimal_places=2,
|
|
||||||
)
|
|
||||||
vat = MoneyField(
|
|
||||||
verbose_name=_("VAT"),
|
|
||||||
max_digits=16,
|
|
||||||
decimal_places=2,
|
|
||||||
)
|
)
|
||||||
|
vat = MoneyField(verbose_name=_("VAT"), max_digits=16, decimal_places=2)
|
||||||
|
|
||||||
is_paid = models.BooleanField(
|
is_paid = models.BooleanField(default=False, verbose_name=_("is paid"))
|
||||||
default=False,
|
|
||||||
verbose_name=_("is paid"),
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def total(self):
|
def total(self):
|
||||||
|
@ -105,7 +82,7 @@ class Order(CreatedModifiedAbstract):
|
||||||
pk = str(self.pk).encode("utf-8")
|
pk = str(self.pk).encode("utf-8")
|
||||||
x = md5()
|
x = md5()
|
||||||
x.update(pk)
|
x.update(pk)
|
||||||
extra_hash = (settings.SECRET_KEY + 'blah').encode('utf-8')
|
extra_hash = (settings.SECRET_KEY + "blah").encode("utf-8")
|
||||||
x.update(extra_hash)
|
x.update(extra_hash)
|
||||||
return x.hexdigest()
|
return x.hexdigest()
|
||||||
|
|
||||||
|
@ -119,22 +96,12 @@ class Order(CreatedModifiedAbstract):
|
||||||
|
|
||||||
class Payment(CreatedModifiedAbstract):
|
class Payment(CreatedModifiedAbstract):
|
||||||
|
|
||||||
amount = MoneyField(
|
amount = MoneyField(max_digits=16, decimal_places=2)
|
||||||
max_digits=16,
|
|
||||||
decimal_places=2,
|
|
||||||
)
|
|
||||||
order = models.ForeignKey(Order, on_delete=models.PROTECT)
|
order = models.ForeignKey(Order, on_delete=models.PROTECT)
|
||||||
|
|
||||||
description = models.CharField(
|
description = models.CharField(max_length=1024, verbose_name=_("description"))
|
||||||
max_length=1024,
|
|
||||||
verbose_name=_("description")
|
|
||||||
)
|
|
||||||
|
|
||||||
stripe_charge_id = models.CharField(
|
stripe_charge_id = models.CharField(max_length=255, null=True, blank=True)
|
||||||
max_length=255,
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def display_id(self):
|
def display_id(self):
|
||||||
|
|
|
@ -7,10 +7,9 @@ from . import models
|
||||||
# def test():
|
# def test():
|
||||||
# do stuff
|
# do stuff
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_balance():
|
def test_balance():
|
||||||
user = User.objects.create_user("test", "lala@adas.com", "1234")
|
user = User.objects.create_user("test", "lala@adas.com", "1234")
|
||||||
account = models.Account.objects.create(
|
account = models.Account.objects.create(owner=user)
|
||||||
owner=user
|
|
||||||
)
|
|
||||||
assert account.balance == 0
|
assert account.balance == 0
|
||||||
|
|
|
@ -6,7 +6,7 @@ if __name__ == "__main__":
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
|
||||||
try:
|
try:
|
||||||
from django.core.management import execute_from_command_line
|
from django.core.management import execute_from_command_line
|
||||||
except ImportError as exc:
|
except ImportError:
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
"Couldn't import Django. Are you sure it's installed and "
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
"available on your PYTHONPATH environment variable? Did you "
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
|
|
@ -2,4 +2,4 @@ from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class MembershipConfig(AppConfig):
|
class MembershipConfig(AppConfig):
|
||||||
name = 'membership'
|
name = "membership"
|
||||||
|
|
|
@ -12,90 +12,216 @@ class Migration(migrations.Migration):
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Membership',
|
name="Membership",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('modified', models.DateTimeField(auto_now=True, verbose_name='modified')),
|
"id",
|
||||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='created')),
|
models.AutoField(
|
||||||
('can_vote', models.BooleanField(default=False, help_text='Indicates that the user has a democratic membership of the organization.', verbose_name='can vote')),
|
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={
|
options={
|
||||||
'verbose_name': 'membership',
|
"verbose_name": "membership",
|
||||||
'verbose_name_plural': 'memberships',
|
"verbose_name_plural": "memberships",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Organization',
|
name="Organization",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('modified', models.DateTimeField(auto_now=True, verbose_name='modified')),
|
"id",
|
||||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='created')),
|
models.AutoField(
|
||||||
('name', models.CharField(max_length=64, verbose_name='name')),
|
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={
|
options={
|
||||||
'verbose_name': 'organization',
|
"verbose_name": "organization",
|
||||||
'verbose_name_plural': 'organizations',
|
"verbose_name_plural": "organizations",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Subscription',
|
name="Subscription",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('modified', models.DateTimeField(auto_now=True, verbose_name='modified')),
|
"id",
|
||||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='created')),
|
models.AutoField(
|
||||||
('active', models.BooleanField(default=False, help_text='Automatically set by payment system.', verbose_name='active')),
|
auto_created=True,
|
||||||
('starts', models.DateField()),
|
primary_key=True,
|
||||||
('ends', models.DateField()),
|
serialize=False,
|
||||||
('renewed_subscription', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='membership.Subscription', verbose_name='renewed subscription')),
|
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={
|
options={
|
||||||
'verbose_name': 'subscription',
|
"verbose_name": "subscription",
|
||||||
'verbose_name_plural': 'subscriptions',
|
"verbose_name_plural": "subscriptions",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='SubscriptionType',
|
name="SubscriptionType",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('modified', models.DateTimeField(auto_now=True, verbose_name='modified')),
|
"id",
|
||||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='created')),
|
models.AutoField(
|
||||||
('name', models.CharField(max_length=64, verbose_name='name')),
|
auto_created=True,
|
||||||
('fee_currency', djmoney.models.fields.CurrencyField(choices=[('DKK', 'DKK')], default='XYZ', editable=False, max_length=3)),
|
primary_key=True,
|
||||||
('fee', djmoney.models.fields.MoneyField(decimal_places=2, default=Decimal('0.0'), max_digits=16)),
|
serialize=False,
|
||||||
('fee_vat_currency', djmoney.models.fields.CurrencyField(choices=[('DKK', 'DKK')], default='XYZ', editable=False, max_length=3)),
|
verbose_name="ID",
|
||||||
('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')),
|
(
|
||||||
|
"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={
|
options={
|
||||||
'verbose_name': 'subscription type',
|
"verbose_name": "subscription type",
|
||||||
'verbose_name_plural': 'subscription types',
|
"verbose_name_plural": "subscription types",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='subscription',
|
model_name="subscription",
|
||||||
name='subscription_type',
|
name="subscription_type",
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='memberships', to='membership.SubscriptionType', verbose_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(
|
migrations.AddField(
|
||||||
model_name='subscription',
|
model_name="subscription",
|
||||||
name='user',
|
name="user",
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='membership',
|
model_name="membership",
|
||||||
name='organization',
|
name="organization",
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='membership.Organization'),
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
to="membership.Organization",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='membership',
|
model_name="membership",
|
||||||
name='user',
|
name="user",
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,14 +6,8 @@ from djmoney.models.fields import MoneyField
|
||||||
|
|
||||||
class CreatedModifiedAbstract(models.Model):
|
class CreatedModifiedAbstract(models.Model):
|
||||||
|
|
||||||
modified = models.DateTimeField(
|
modified = models.DateTimeField(auto_now=True, verbose_name=_("modified"))
|
||||||
auto_now=True,
|
created = models.DateTimeField(auto_now_add=True, verbose_name=_("created"))
|
||||||
verbose_name=_("modified"),
|
|
||||||
)
|
|
||||||
created = models.DateTimeField(
|
|
||||||
auto_now_add=True,
|
|
||||||
verbose_name=_("created"),
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
@ -24,10 +18,8 @@ class Organization(CreatedModifiedAbstract):
|
||||||
This holds the data of the organization that someone is a member of. It is
|
This holds the data of the organization that someone is a member of. It is
|
||||||
possible that we'll create more advanced features here.
|
possible that we'll create more advanced features here.
|
||||||
"""
|
"""
|
||||||
name = models.CharField(
|
|
||||||
verbose_name=_("name"),
|
name = models.CharField(verbose_name=_("name"), max_length=64)
|
||||||
max_length=64,
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -56,14 +48,13 @@ class Membership(CreatedModifiedAbstract):
|
||||||
help_text=_(
|
help_text=_(
|
||||||
"Indicates that the user has a democratic membership of the "
|
"Indicates that the user has a democratic membership of the "
|
||||||
"organization."
|
"organization."
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
||||||
return _("{} is a member of {}").format(
|
return _("{} is a member of {}").format(
|
||||||
self.user.get_full_name(),
|
self.user.get_full_name(), self.organization.name
|
||||||
self.organization.name,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -79,26 +70,14 @@ class SubscriptionType(CreatedModifiedAbstract):
|
||||||
|
|
||||||
organization = models.ForeignKey(Organization, on_delete=models.PROTECT)
|
organization = models.ForeignKey(Organization, on_delete=models.PROTECT)
|
||||||
|
|
||||||
name = models.CharField(
|
name = models.CharField(verbose_name=_("name"), max_length=64)
|
||||||
verbose_name=_("name"),
|
|
||||||
max_length=64,
|
|
||||||
)
|
|
||||||
|
|
||||||
fee = MoneyField(
|
fee = MoneyField(max_digits=16, decimal_places=2)
|
||||||
max_digits=16,
|
|
||||||
decimal_places=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
fee_vat = MoneyField(
|
fee_vat = MoneyField(max_digits=16, decimal_places=2, default=0)
|
||||||
max_digits=16,
|
|
||||||
decimal_places=2,
|
|
||||||
default=0,
|
|
||||||
)
|
|
||||||
|
|
||||||
duration = models.PositiveSmallIntegerField(
|
duration = models.PositiveSmallIntegerField(
|
||||||
default=1,
|
default=1, choices=[(1, _("annual"))], verbose_name=_("duration")
|
||||||
choices=[(1, _("annual"))],
|
|
||||||
verbose_name=_("duration"),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -116,7 +95,7 @@ class Subscription(CreatedModifiedAbstract):
|
||||||
|
|
||||||
subscription_type = models.ForeignKey(
|
subscription_type = models.ForeignKey(
|
||||||
SubscriptionType,
|
SubscriptionType,
|
||||||
related_name='memberships',
|
related_name="memberships",
|
||||||
verbose_name=_("subscription type"),
|
verbose_name=_("subscription type"),
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
)
|
)
|
||||||
|
@ -125,14 +104,14 @@ class Subscription(CreatedModifiedAbstract):
|
||||||
active = models.BooleanField(
|
active = models.BooleanField(
|
||||||
default=False,
|
default=False,
|
||||||
verbose_name=_("active"),
|
verbose_name=_("active"),
|
||||||
help_text=_("Automatically set by payment system.")
|
help_text=_("Automatically set by payment system."),
|
||||||
)
|
)
|
||||||
|
|
||||||
starts = models.DateField()
|
starts = models.DateField()
|
||||||
ends = models.DateField()
|
ends = models.DateField()
|
||||||
|
|
||||||
renewed_subscription = models.ForeignKey(
|
renewed_subscription = models.ForeignKey(
|
||||||
'self',
|
"self",
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_("renewed subscription"),
|
verbose_name=_("renewed subscription"),
|
||||||
|
|
415
poetry.lock
generated
Normal file
415
poetry.lock
generated
Normal file
|
@ -0,0 +1,415 @@
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "A few extensions to pyyaml."
|
||||||
|
name = "aspy.yaml"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "1.3.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
pyyaml = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Atomic file writes."
|
||||||
|
name = "atomicwrites"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "1.3.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Classes Without Boilerplate"
|
||||||
|
name = "attrs"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "19.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Python package for providing Mozilla's CA Bundle."
|
||||||
|
name = "certifi"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "2019.6.16"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Validate configuration and produce human readable error messages."
|
||||||
|
name = "cfgv"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "2.0.1"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
six = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Universal encoding detector for Python 2 and 3"
|
||||||
|
name = "chardet"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "3.0.4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Cross-platform colored terminal text."
|
||||||
|
marker = "sys_platform == \"win32\""
|
||||||
|
name = "colorama"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "0.4.1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "XML bomb protection for Python stdlib modules"
|
||||||
|
name = "defusedxml"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
version = "0.6.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
|
||||||
|
name = "django"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
version = "2.2.4"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
pytz = "*"
|
||||||
|
sqlparse = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication."
|
||||||
|
name = "django-allauth"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.40.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
Django = ">=1.11"
|
||||||
|
python3-openid = ">=3.0.8"
|
||||||
|
requests = "*"
|
||||||
|
requests-oauthlib = ">=0.3.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Adds support for using money and currency fields in django models and forms. Uses py-moneyed as the money implementation."
|
||||||
|
name = "django-money"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.15"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
Django = ">=1.8"
|
||||||
|
py-moneyed = ">=0.8"
|
||||||
|
setuptools = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "File identification library for Python"
|
||||||
|
name = "identify"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "1.4.7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||||
|
name = "idna"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "2.8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Read metadata from Python packages"
|
||||||
|
name = "importlib-metadata"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7,!=3.0,!=3.1,!=3.2,!=3.3"
|
||||||
|
version = "0.19"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
zipp = ">=0.5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "More routines for operating on iterables, beyond itertools"
|
||||||
|
name = "more-itertools"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.4"
|
||||||
|
version = "7.2.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Node.js virtual environment builder"
|
||||||
|
name = "nodeenv"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "1.3.3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
|
||||||
|
name = "oauthlib"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "3.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Core utilities for Python packages"
|
||||||
|
name = "packaging"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "19.1"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
attrs = "*"
|
||||||
|
pyparsing = ">=2.0.2"
|
||||||
|
six = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "plugin and hook calling mechanisms for python"
|
||||||
|
name = "pluggy"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "0.12.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
importlib-metadata = ">=0.12"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "A framework for managing and maintaining multi-language pre-commit hooks."
|
||||||
|
name = "pre-commit"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "1.18.3"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
"aspy.yaml" = "*"
|
||||||
|
cfgv = ">=2.0.0"
|
||||||
|
identify = ">=1.0.0"
|
||||||
|
importlib-metadata = "*"
|
||||||
|
nodeenv = ">=0.11.1"
|
||||||
|
pyyaml = "*"
|
||||||
|
six = "*"
|
||||||
|
toml = "*"
|
||||||
|
virtualenv = ">=15.2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "library with cross-python path, ini-parsing, io, code, log facilities"
|
||||||
|
name = "py"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "1.8.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Provides Currency and Money classes for use in your Python code."
|
||||||
|
name = "py-moneyed"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.8.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Python parsing module"
|
||||||
|
name = "pyparsing"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||||
|
version = "2.4.2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "pytest: simple powerful testing with Python"
|
||||||
|
name = "pytest"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
version = "5.1.2"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
atomicwrites = ">=1.0"
|
||||||
|
attrs = ">=17.4.0"
|
||||||
|
colorama = "*"
|
||||||
|
more-itertools = ">=4.0.0"
|
||||||
|
packaging = "*"
|
||||||
|
pluggy = ">=0.12,<1.0"
|
||||||
|
py = ">=1.5.0"
|
||||||
|
wcwidth = "*"
|
||||||
|
|
||||||
|
[package.dependencies.importlib-metadata]
|
||||||
|
python = "<3.8"
|
||||||
|
version = ">=0.12"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "A Django plugin for pytest."
|
||||||
|
name = "pytest-django"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "3.5.1"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
pytest = ">=3.6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "OpenID support for modern servers and consumers."
|
||||||
|
name = "python3-openid"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "3.1.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
defusedxml = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "World timezone definitions, modern and historical"
|
||||||
|
name = "pytz"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "2019.2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "YAML parser and emitter for Python"
|
||||||
|
name = "pyyaml"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "5.1.2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Python HTTP for Humans."
|
||||||
|
name = "requests"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
version = "2.22.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
certifi = ">=2017.4.17"
|
||||||
|
chardet = ">=3.0.2,<3.1.0"
|
||||||
|
idna = ">=2.5,<2.9"
|
||||||
|
urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "OAuthlib authentication support for Requests."
|
||||||
|
name = "requests-oauthlib"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "1.2.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
oauthlib = ">=3.0.0"
|
||||||
|
requests = ">=2.0.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Python 2 and 3 compatibility utilities"
|
||||||
|
name = "six"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.6, !=3.0.*, !=3.1.*"
|
||||||
|
version = "1.12.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Non-validating SQL parser"
|
||||||
|
name = "sqlparse"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "0.3.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Python Library for Tom's Obvious, Minimal Language"
|
||||||
|
name = "toml"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.10.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||||
|
name = "urllib3"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4"
|
||||||
|
version = "1.25.3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Virtual Python Environment builder"
|
||||||
|
name = "virtualenv"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "16.7.4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Measures number of Terminal column cells of wide-character codes"
|
||||||
|
name = "wcwidth"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.1.7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||||
|
name = "zipp"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7"
|
||||||
|
version = "0.6.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
more-itertools = "*"
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
content-hash = "2ce01170127003b7107bd9d94d7225f3860356ebb3cda1bee98f894e82cf8d99"
|
||||||
|
python-versions = "^3.7"
|
||||||
|
|
||||||
|
[metadata.hashes]
|
||||||
|
"aspy.yaml" = ["463372c043f70160a9ec950c3f1e4c3a82db5fca01d334b6bc89c7164d744bdc", "e7c742382eff2caed61f87a39d13f99109088e5e93f04d76eb8d4b28aa143f45"]
|
||||||
|
atomicwrites = ["03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", "75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"]
|
||||||
|
attrs = ["69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", "f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"]
|
||||||
|
certifi = ["046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", "945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695"]
|
||||||
|
cfgv = ["edb387943b665bf9c434f717bf630fa78aecd53d5900d2e05da6ad6048553144", "fbd93c9ab0a523bf7daec408f3be2ed99a980e20b2d19b50fc184ca6b820d289"]
|
||||||
|
chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"]
|
||||||
|
colorama = ["05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"]
|
||||||
|
defusedxml = ["6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93", "f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"]
|
||||||
|
django = ["16a5d54411599780ac9dfe3b9b38f90f785c51259a584e0b24b6f14a7f69aae8", "9a2f98211ab474c710fcdad29c82f30fc14ce9917c7a70c3682162a624de4035"]
|
||||||
|
django-allauth = ["6a189fc4d3ee23596c3fd6e9f49c59b5b15618980118171a50675dd6a27cc589"]
|
||||||
|
django-money = ["4ce2d6a085739b4982cb6205fedfed4541295405e3e5001c4fad8f486c331997", "791e4454a970c1078db31a4a74a991cd13a75921c51dcaeaebd07fb4daa8114a"]
|
||||||
|
identify = ["4f1fe9a59df4e80fcb0213086fcf502bc1765a01ea4fe8be48da3b65afd2a017", "d8919589bd2a5f99c66302fec0ef9027b12ae150b0b0213999ad3f695fc7296e"]
|
||||||
|
idna = ["c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", "ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"]
|
||||||
|
importlib-metadata = ["23d3d873e008a513952355379d93cbcab874c58f4f034ff657c7a87422fa64e8", "80d2de76188eabfbfcf27e6a37342c2827801e59c4cc14b0371c56fed43820e3"]
|
||||||
|
more-itertools = ["409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", "92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"]
|
||||||
|
nodeenv = ["ad8259494cf1c9034539f6cced78a1da4840a4b157e23640bc4a0c0546b0cb7a"]
|
||||||
|
oauthlib = ["bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889", "df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea"]
|
||||||
|
packaging = ["a7ac867b97fdc07ee80a8058fe4435ccd274ecc3b0ed61d852d7d53055528cf9", "c491ca87294da7cc01902edbe30a5bc6c4c28172b5138ab4e4aa1b9d7bfaeafe"]
|
||||||
|
pluggy = ["0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc", "b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c"]
|
||||||
|
pre-commit = ["1d3c0587bda7c4e537a46c27f2c84aa006acc18facf9970bf947df596ce91f3f", "fa78ff96e8e9ac94c748388597693f18b041a181c94a4f039ad20f45287ba44a"]
|
||||||
|
py = ["64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", "dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"]
|
||||||
|
py-moneyed = ["c6691b914a5e4b5b2335cf113620479a52cc82988c0e143435a7c5c7d60cd4ad", "ec73795171919d537880a33c44d07fcdf0a5225e8368684fe02f0e75a6404742"]
|
||||||
|
pyparsing = ["6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", "d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4"]
|
||||||
|
pytest = ["95d13143cc14174ca1a01ec68e84d76ba5d9d493ac02716fd9706c949a505210", "b78fe2881323bd44fd9bd76e5317173d4316577e7b1cddebae9136a4495ec865"]
|
||||||
|
pytest-django = ["264fb4c506db5d48a6364c311a0b00b7b48a52715bad8839b2d8bee9b99ed6bb", "4adfe5fb3ed47f0ba55506dd3daf688b1f74d5e69148c10ad2dd2f79f40c0d62"]
|
||||||
|
python3-openid = ["0086da6b6ef3161cfe50fb1ee5cceaf2cda1700019fda03c2c5c440ca6abe4fa", "628d365d687e12da12d02c6691170f4451db28d6d68d050007e4a40065868502"]
|
||||||
|
pytz = ["26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32", "c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7"]
|
||||||
|
pyyaml = ["0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9", "01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4", "5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8", "5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696", "7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34", "7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9", "87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73", "9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299", "a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b", "b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae", "b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681", "bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41", "f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8"]
|
||||||
|
requests = ["11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", "9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"]
|
||||||
|
requests-oauthlib = ["bd6533330e8748e94bf0b214775fed487d309b8b8fe823dc45641ebcd9a32f57", "d3ed0c8f2e3bbc6b344fa63d6f933745ab394469da38db16bdddb461c7e25140", "dd5a0499abfefd087c6dd96693cbd5bfd28aa009719a7f85ab3fabe3956ef19a"]
|
||||||
|
six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"]
|
||||||
|
sqlparse = ["40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", "7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873"]
|
||||||
|
toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"]
|
||||||
|
urllib3 = ["b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", "dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232"]
|
||||||
|
virtualenv = ["94a6898293d07f84a98add34c4df900f8ec64a570292279f6d91c781d37fd305", "f6fc312c031f2d2344f885de114f1cb029dfcffd26aa6e57d2ee2296935c4e7d"]
|
||||||
|
wcwidth = ["3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", "f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"]
|
||||||
|
zipp = ["3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", "f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"]
|
|
@ -4,6 +4,4 @@ from django.contrib.sites.shortcuts import get_current_site
|
||||||
|
|
||||||
def current_site(request):
|
def current_site(request):
|
||||||
"""Include the current site in the context."""
|
"""Include the current site in the context."""
|
||||||
return {
|
return {"site": get_current_site(request)}
|
||||||
'site': get_current_site(request)
|
|
||||||
}
|
|
||||||
|
|
|
@ -26,63 +26,62 @@ ALLOWED_HOSTS = []
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'django.contrib.admin',
|
"django.contrib.admin",
|
||||||
'django.contrib.auth',
|
"django.contrib.auth",
|
||||||
'django.contrib.contenttypes',
|
"django.contrib.contenttypes",
|
||||||
'django.contrib.sessions',
|
"django.contrib.sessions",
|
||||||
'django.contrib.messages',
|
"django.contrib.messages",
|
||||||
'django.contrib.staticfiles',
|
"django.contrib.staticfiles",
|
||||||
'django.contrib.sites',
|
"django.contrib.sites",
|
||||||
|
"users",
|
||||||
'users',
|
"accounting",
|
||||||
'accounting',
|
"membership",
|
||||||
'membership',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
'django.middleware.security.SecurityMiddleware',
|
"django.middleware.security.SecurityMiddleware",
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
'django.middleware.common.CommonMiddleware',
|
"django.middleware.common.CommonMiddleware",
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'project.urls'
|
ROOT_URLCONF = "project.urls"
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
'DIRS': [os.path.join("project", "templates")],
|
"DIRS": [os.path.join("project", "templates")],
|
||||||
'APP_DIRS': True,
|
"APP_DIRS": True,
|
||||||
'OPTIONS': {
|
"OPTIONS": {
|
||||||
'context_processors': [
|
"context_processors": [
|
||||||
'django.template.context_processors.debug',
|
"django.template.context_processors.debug",
|
||||||
'django.template.context_processors.request',
|
"django.template.context_processors.request",
|
||||||
'django.contrib.auth.context_processors.auth',
|
"django.contrib.auth.context_processors.auth",
|
||||||
'django.contrib.messages.context_processors.messages',
|
"django.contrib.messages.context_processors.messages",
|
||||||
'project.context_processors.current_site',
|
"project.context_processors.current_site",
|
||||||
],
|
]
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = (
|
AUTHENTICATION_BACKENDS = (
|
||||||
'django.contrib.auth.backends.ModelBackend',
|
"django.contrib.auth.backends.ModelBackend",
|
||||||
'allauth.account.auth_backends.AuthenticationBackend',
|
"allauth.account.auth_backends.AuthenticationBackend",
|
||||||
)
|
)
|
||||||
|
|
||||||
WSGI_APPLICATION = 'project.wsgi.application'
|
WSGI_APPLICATION = "project.wsgi.application"
|
||||||
|
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases
|
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
"default": {
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,28 +91,24 @@ DATABASES = {
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
{
|
{
|
||||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', # noqa
|
"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.MinimumLengthValidator', # noqa
|
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator" # noqa
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', # noqa
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', # noqa
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
AUTH_USER_MODEL = 'users.User'
|
AUTH_USER_MODEL = "users.User"
|
||||||
|
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/2.0/topics/i18n/
|
# https://docs.djangoproject.com/en/2.0/topics/i18n/
|
||||||
|
|
||||||
LANGUAGE_CODE = 'en-us'
|
LANGUAGE_CODE = "en-us"
|
||||||
|
|
||||||
TIME_ZONE = 'UTC'
|
TIME_ZONE = "UTC"
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
|
@ -125,13 +120,13 @@ USE_TZ = True
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/2.0/howto/static-files/
|
# https://docs.djangoproject.com/en/2.0/howto/static-files/
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = "/static/"
|
||||||
|
|
||||||
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
|
STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")]
|
||||||
|
|
||||||
SITE_ID = 1
|
SITE_ID = 1
|
||||||
|
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||||
|
|
||||||
CURRENCIES = ('DKK',)
|
CURRENCIES = ("DKK",)
|
||||||
CURRENCY_CHOICES = [('DKK', 'DKK')]
|
CURRENCY_CHOICES = [("DKK", "DKK")]
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django.urls import path
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.index),
|
path("", views.index),
|
||||||
path("users/", include("users.urls")),
|
path("users/", include("users.urls")),
|
||||||
path('admin/', admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
]
|
]
|
||||||
|
|
20
pyproject.toml
Normal file
20
pyproject.toml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
[tool.poetry]
|
||||||
|
name = "membersystem"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = ""
|
||||||
|
authors = ["Your Name <you@example.com>"]
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.7"
|
||||||
|
Django = "^2.2"
|
||||||
|
django-money = "^0.15.0"
|
||||||
|
django-allauth = "^0.40.0"
|
||||||
|
|
||||||
|
[tool.poetry.dev-dependencies]
|
||||||
|
pre-commit = "^1.18"
|
||||||
|
pytest = "^5.1"
|
||||||
|
pytest-django = "^3.5"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry>=0.12"]
|
||||||
|
build-backend = "poetry.masonry.api"
|
|
@ -1,2 +0,0 @@
|
||||||
Django>=2.2,<2.3
|
|
||||||
django-money==0.15
|
|
|
@ -1,3 +0,0 @@
|
||||||
pytest
|
|
||||||
pytest-django
|
|
||||||
pre-commit
|
|
13
setup.cfg
13
setup.cfg
|
@ -1,13 +0,0 @@
|
||||||
[flake8]
|
|
||||||
ignore = E226,E302,E41
|
|
||||||
max-line-length = 160
|
|
||||||
max-complexity = 10
|
|
||||||
exclude = */migrations/*
|
|
||||||
|
|
||||||
[isort]
|
|
||||||
atomic = true
|
|
||||||
multi_line_output = 5
|
|
||||||
line_length = 160
|
|
||||||
indent = ' '
|
|
||||||
combine_as_imports = true
|
|
||||||
skip = wsgi.py,.eggs,setup.py
|
|
|
@ -1,3 +1 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin # noqa
|
||||||
|
|
||||||
# Register your models here.
|
|
||||||
|
|
|
@ -2,4 +2,4 @@ from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class UsersConfig(AppConfig):
|
class UsersConfig(AppConfig):
|
||||||
name = 'users'
|
name = "users"
|
||||||
|
|
|
@ -9,28 +9,65 @@ class Migration(migrations.Migration):
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("auth", "0011_update_proxy_permissions")]
|
||||||
('auth', '0011_update_proxy_permissions'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='User',
|
name="User",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
"id",
|
||||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
models.AutoField(
|
||||||
('nick', models.CharField(blank=True, max_length=60, null=True)),
|
auto_created=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')),
|
primary_key=True,
|
||||||
('is_active', models.BooleanField(default=True)),
|
serialize=False,
|
||||||
('is_staff', models.BooleanField(default=False)),
|
verbose_name="ID",
|
||||||
('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',
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
("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"},
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
|
@ -20,23 +20,23 @@ class UserManager(BaseUserManager):
|
||||||
user = self.create_user(password=password, **kwargs)
|
user = self.create_user(password=password, **kwargs)
|
||||||
user.is_staff = True
|
user.is_staff = True
|
||||||
user.is_superuser = True
|
user.is_superuser = True
|
||||||
user.save(update_fields=['is_staff', 'is_superuser'])
|
user.save(update_fields=["is_staff", "is_superuser"])
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
class User(PermissionsMixin, AbstractBaseUser):
|
class User(PermissionsMixin, AbstractBaseUser):
|
||||||
|
|
||||||
EMAIL_FIELD = 'email'
|
EMAIL_FIELD = "email"
|
||||||
USERNAME_FIELD = 'email'
|
USERNAME_FIELD = "email"
|
||||||
|
|
||||||
objects = UserManager()
|
objects = UserManager()
|
||||||
|
|
||||||
nick = models.CharField(max_length=60, null=True, blank=True)
|
nick = models.CharField(max_length=60, null=True, blank=True)
|
||||||
email = models.EmailField(
|
email = models.EmailField(
|
||||||
unique=True,
|
unique=True,
|
||||||
verbose_name=_('E-Mail'),
|
verbose_name=_("E-Mail"),
|
||||||
help_text=_(
|
help_text=_(
|
||||||
'Your email address will be used for password resets and notification about your event/submissions.'
|
"Your email address will be used for password resets and notification about your event/submissions."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ class User(PermissionsMixin, AbstractBaseUser):
|
||||||
return self.get_display_name()
|
return self.get_display_name()
|
||||||
|
|
||||||
def get_display_name(self) -> str:
|
def get_display_name(self) -> str:
|
||||||
return self.nick if self.nick else str(_('Unnamed user'))
|
return self.nick if self.nick else str(_("Unnamed user"))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("User")
|
verbose_name = _("User")
|
||||||
|
|
|
@ -3,20 +3,59 @@ from django.urls import path
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
app_name = 'users'
|
app_name = "users"
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('signup/', views.SignupView.as_view(), name='signup'),
|
path("signup/", views.SignupView.as_view(), name="signup"),
|
||||||
path('signup/confirm/', views.SignupConfirmView.as_view(), name='signup_confirm'),
|
path("signup/confirm/", views.SignupConfirmView.as_view(), name="signup_confirm"),
|
||||||
|
path(
|
||||||
path('login/', auth_views.LoginView.as_view(template_name="users/login.html"), name='login'),
|
"login/",
|
||||||
path('logout/', auth_views.LogoutView.as_view(template_name="users/logged_out.html"), name='logout'),
|
auth_views.LoginView.as_view(template_name="users/login.html"),
|
||||||
|
name="login",
|
||||||
path('password_change/', views.PasswordChangeView.as_view(template_name="users/password_change_form.html"), name='password_change'),
|
),
|
||||||
path('password_change/done/', auth_views.PasswordChangeDoneView.as_view(template_name="users/password_change_done.html"), name='password_change_done'),
|
path(
|
||||||
|
"logout/",
|
||||||
path('password_reset/', views.PasswordResetView.as_view(template_name="users/password_reset_form.html"), name='password_reset'),
|
auth_views.LogoutView.as_view(template_name="users/logged_out.html"),
|
||||||
path('password_reset/done/', auth_views.PasswordResetDoneView.as_view(template_name="users/password_reset_done.html"), name='password_reset_done'),
|
name="logout",
|
||||||
path('reset/<uidb64>/<token>/', views.PasswordResetConfirmView.as_view(template_name="users/password_reset_confirm.html"), name='password_reset_confirm'),
|
),
|
||||||
path('reset/done/', auth_views.PasswordResetCompleteView.as_view(template_name="users/password_reset_complete.html"), name='password_reset_complete'),
|
path(
|
||||||
|
"password_change/",
|
||||||
|
views.PasswordChangeView.as_view(
|
||||||
|
template_name="users/password_change_form.html"
|
||||||
|
),
|
||||||
|
name="password_change",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"password_change/done/",
|
||||||
|
auth_views.PasswordChangeDoneView.as_view(
|
||||||
|
template_name="users/password_change_done.html"
|
||||||
|
),
|
||||||
|
name="password_change_done",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"password_reset/",
|
||||||
|
views.PasswordResetView.as_view(template_name="users/password_reset_form.html"),
|
||||||
|
name="password_reset",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"password_reset/done/",
|
||||||
|
auth_views.PasswordResetDoneView.as_view(
|
||||||
|
template_name="users/password_reset_done.html"
|
||||||
|
),
|
||||||
|
name="password_reset_done",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"reset/<uidb64>/<token>/",
|
||||||
|
views.PasswordResetConfirmView.as_view(
|
||||||
|
template_name="users/password_reset_confirm.html"
|
||||||
|
),
|
||||||
|
name="password_reset_confirm",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"reset/done/",
|
||||||
|
auth_views.PasswordResetCompleteView.as_view(
|
||||||
|
template_name="users/password_reset_complete.html"
|
||||||
|
),
|
||||||
|
name="password_reset_complete",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,20 +6,21 @@ from django.views.generic.base import TemplateView
|
||||||
from django.views.generic.edit import FormView
|
from django.views.generic.edit import FormView
|
||||||
|
|
||||||
from . import forms
|
from . import forms
|
||||||
|
|
||||||
# from . import email
|
# from . import email
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetView(auth_views.PasswordResetView):
|
class PasswordResetView(auth_views.PasswordResetView):
|
||||||
email_template_name = 'users/password_reset_email.html'
|
email_template_name = "users/password_reset_email.html"
|
||||||
success_url = reverse_lazy('users:password_reset_done')
|
success_url = reverse_lazy("users:password_reset_done")
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetConfirmView(auth_views.PasswordResetConfirmView):
|
class PasswordResetConfirmView(auth_views.PasswordResetConfirmView):
|
||||||
success_url = reverse_lazy('users:password_reset_complete')
|
success_url = reverse_lazy("users:password_reset_complete")
|
||||||
|
|
||||||
|
|
||||||
class PasswordChangeView(auth_views.PasswordChangeView):
|
class PasswordChangeView(auth_views.PasswordChangeView):
|
||||||
success_url = reverse_lazy('users:password_change_done')
|
success_url = reverse_lazy("users:password_change_done")
|
||||||
|
|
||||||
|
|
||||||
class SignupView(FormView):
|
class SignupView(FormView):
|
||||||
|
@ -31,7 +32,7 @@ class SignupView(FormView):
|
||||||
|
|
||||||
user = form.save(commit=False)
|
user = form.save(commit=False)
|
||||||
user.is_active = False
|
user.is_active = False
|
||||||
user.set_password(form.cleaned_data['password1'])
|
user.set_password(form.cleaned_data["password1"])
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
# mail = email.UserConfirm(user=user)
|
# mail = email.UserConfirm(user=user)
|
||||||
|
@ -48,10 +49,9 @@ class SignupConfirmView(TemplateView):
|
||||||
|
|
||||||
|
|
||||||
class SignupConfirmRedirectView(RedirectView):
|
class SignupConfirmRedirectView(RedirectView):
|
||||||
|
|
||||||
def get_redirect_url(self):
|
def get_redirect_url(self):
|
||||||
|
|
||||||
uuid = self.kwargs['uuid']
|
uuid = self.kwargs["uuid"]
|
||||||
|
|
||||||
if self.kwargs["token"] == forms.get_confirm_code(uuid):
|
if self.kwargs["token"] == forms.get_confirm_code(uuid):
|
||||||
redirect("users:confirmed") # TODO
|
redirect("users:confirmed") # TODO
|
||||||
|
|
Loading…
Reference in a new issue