From 36a4c901a8efdc67ca24b5050b6abd9bf14df51c Mon Sep 17 00:00:00 2001 From: Benjamin Bach Date: Sat, 23 Jun 2018 20:54:24 +0200 Subject: [PATCH 1/5] Adding pre-commit hooks --- .pre-commit-config.yaml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b789dfb --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,28 @@ +repos: +- repo: git://github.com/pre-commit/pre-commit-hooks + rev: v1.3.0 + hooks: + - id: trailing-whitespace + - id: flake8 + - id: check-yaml + - id: check-added-large-files + - id: debug-statements + - id: end-of-file-fixer +- repo: https://github.com/asottile/reorder_python_imports + rev: v1.0.1 + hooks: + - id: reorder-python-imports +- repo: local + hooks: + - id: frontend-prettify + name: Prettier Linting of JS and Vue files + description: This hook rewrites JS and the JS portion of Vue files to conform to prettier standards. + entry: yarn run lint-js -- -w + language: system + files: \.(js|vue)$ + - id: frontend-eslint + name: ESLinting of JS and Vue files + description: This hook rewrites JS and the JS portion of Vue files to conform to our ESLint standards. + entry: node node_modules/eslint/bin/eslint.js + language: system + files: \.(js|vue)$ From f46b031a8da9bb8eba87d4407f13a35d5aff4312 Mon Sep 17 00:00:00 2001 From: Benjamin Bach Date: Sat, 23 Jun 2018 20:54:37 +0200 Subject: [PATCH 2/5] django-money dependency --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 869811e..035f111 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ Django==2.0.6 django-allauth==0.36.0 +django-money==0.14 From a8321decde7b65daff290bafb825446d4e6fb5ce Mon Sep 17 00:00:00 2001 From: Benjamin Bach Date: Sat, 23 Jun 2018 21:08:56 +0200 Subject: [PATCH 3/5] Adding accounting and membership apps + pre-commit linting stuff --- .pre-commit-config.yaml | 14 --- Makefile | 15 +++ README.rst | 11 ++ accounting/__init__.py | 0 accounting/admin.py | 28 +++++ accounting/apps.py | 5 + accounting/migrations/0001_initial.py | 84 ++++++++++++++ accounting/migrations/__init__.py | 0 accounting/models.py | 156 ++++++++++++++++++++++++++ accounting/test.py | 0 membership/__init__.py | 8 ++ membership/admin.py | 8 ++ membership/apps.py | 5 + membership/migrations/0001_initial.py | 101 +++++++++++++++++ membership/migrations/__init__.py | 0 membership/models.py | 144 ++++++++++++++++++++++++ membersystem/settings/__init__.py | 12 +- membersystem/settings/base.py | 16 ++- membersystem/urls.py | 6 +- membersystem/wsgi.py | 1 - profiles/migrations/0001_initial.py | 24 ++++ requirements_dev.txt | 2 + setup.cfg | 13 +++ 23 files changed, 625 insertions(+), 28 deletions(-) create mode 100644 Makefile create mode 100644 README.rst create mode 100644 accounting/__init__.py create mode 100644 accounting/admin.py create mode 100644 accounting/apps.py create mode 100644 accounting/migrations/0001_initial.py create mode 100644 accounting/migrations/__init__.py create mode 100644 accounting/models.py create mode 100644 accounting/test.py create mode 100644 membership/__init__.py create mode 100644 membership/admin.py create mode 100644 membership/apps.py create mode 100644 membership/migrations/0001_initial.py create mode 100644 membership/migrations/__init__.py create mode 100644 membership/models.py create mode 100644 profiles/migrations/0001_initial.py create mode 100644 requirements_dev.txt create mode 100644 setup.cfg diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b789dfb..2b36a80 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,17 +12,3 @@ repos: rev: v1.0.1 hooks: - id: reorder-python-imports -- repo: local - hooks: - - id: frontend-prettify - name: Prettier Linting of JS and Vue files - description: This hook rewrites JS and the JS portion of Vue files to conform to prettier standards. - entry: yarn run lint-js -- -w - language: system - files: \.(js|vue)$ - - id: frontend-eslint - name: ESLinting of JS and Vue files - description: This hook rewrites JS and the JS portion of Vue files to conform to our ESLint standards. - entry: node node_modules/eslint/bin/eslint.js - language: system - files: \.(js|vue)$ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d57229e --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +# These are just some make targets, expressing how things +# are supposed to be run, but feel free to change them! + +dev-setup: + pip install -r requirements_dev.txt --upgrade + pip install -r requirements.txt --upgrade + pre-commit install + python manage.py migrate + python manage.py createsuperuser + +lint: + pre-commit run --all + +test: + pytest diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..0d23ef5 --- /dev/null +++ b/README.rst @@ -0,0 +1,11 @@ +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 diff --git a/accounting/__init__.py b/accounting/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/accounting/admin.py b/accounting/admin.py new file mode 100644 index 0000000..41d2755 --- /dev/null +++ b/accounting/admin.py @@ -0,0 +1,28 @@ +from django.contrib import admin +from django.utils.translation import gettext_lazy as _ + +from . import models + + +@admin.register(models.Order) +class OrderAdmin(admin.ModelAdmin): + + list_display = ('who', 'description', 'created', 'is_paid',) + + def who(self, instance): + return instance.user.get_full_name() + who.short_description = _("Customer") + + +@admin.register(models.Payment) +class PaymentAdmin(admin.ModelAdmin): + + list_display = ('who', 'description', 'order_id', 'created',) + + def who(self, instance): + return instance.order.user.get_full_name() + who.short_description = _("Customer") + + def order_id(self, instance): + return instance.order.id + order_id.short_description = _("Order ID") diff --git a/accounting/apps.py b/accounting/apps.py new file mode 100644 index 0000000..bbcd3d0 --- /dev/null +++ b/accounting/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class AccountingConfig(AppConfig): + name = 'accounting' diff --git a/accounting/migrations/0001_initial.py b/accounting/migrations/0001_initial.py new file mode 100644 index 0000000..a226af6 --- /dev/null +++ b/accounting/migrations/0001_initial.py @@ -0,0 +1,84 @@ +# Generated by Django 2.0.6 on 2018-06-23 19:51 +from decimal import Decimal + +import django.db.models.deletion +import djmoney.models.fields +from django.conf import settings +from django.db import migrations +from django.db import models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Account', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('modified', models.DateTimeField(auto_now=True, verbose_name='modified')), + ('created', models.DateTimeField(auto_now_add=True, verbose_name='created')), + ('owner', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Order', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('modified', models.DateTimeField(auto_now=True, verbose_name='modified')), + ('created', models.DateTimeField(auto_now_add=True, verbose_name='created')), + ('description', models.CharField(max_length=1024, verbose_name='description')), + ('price_currency', djmoney.models.fields.CurrencyField(choices=[('DKK', 'DKK')], default='XYZ', editable=False, max_length=3)), + ('price', djmoney.models.fields.MoneyField(decimal_places=2, default=Decimal('0.0'), max_digits=16, verbose_name='price (excl. VAT)')), + ('vat_currency', djmoney.models.fields.CurrencyField(choices=[('DKK', 'DKK')], default='XYZ', editable=False, max_length=3)), + ('vat', djmoney.models.fields.MoneyField(decimal_places=2, default=Decimal('0.0'), max_digits=16, verbose_name='VAT')), + ('is_paid', models.BooleanField(default=False, verbose_name='is paid')), + ('account', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='accounting.Account')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Order', + 'verbose_name_plural': 'Orders', + }, + ), + migrations.CreateModel( + name='Payment', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('modified', models.DateTimeField(auto_now=True, verbose_name='modified')), + ('created', models.DateTimeField(auto_now_add=True, verbose_name='created')), + ('amount_currency', djmoney.models.fields.CurrencyField(choices=[('DKK', 'DKK')], default='XYZ', editable=False, max_length=3)), + ('amount', djmoney.models.fields.MoneyField(decimal_places=2, default=Decimal('0.0'), max_digits=16)), + ('description', models.CharField(max_length=1024, verbose_name='description')), + ('stripe_charge_id', models.CharField(blank=True, max_length=255, null=True)), + ('order', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='accounting.Order')), + ], + options={ + 'verbose_name': 'payment', + 'verbose_name_plural': 'payments', + }, + ), + migrations.CreateModel( + name='Transaction', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('modified', models.DateTimeField(auto_now=True, verbose_name='modified')), + ('created', models.DateTimeField(auto_now_add=True, verbose_name='created')), + ('amount_currency', djmoney.models.fields.CurrencyField(choices=[('DKK', 'DKK')], default='XYZ', editable=False, max_length=3)), + ('amount', djmoney.models.fields.MoneyField(decimal_places=2, default=Decimal('0.0'), help_text='This will include VAT', max_digits=16, verbose_name='amount')), + ('description', models.CharField(max_length=1024, verbose_name='description')), + ('account', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transactions', to='accounting.Account')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/accounting/migrations/__init__.py b/accounting/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/accounting/models.py b/accounting/models.py new file mode 100644 index 0000000..73ddb8d --- /dev/null +++ b/accounting/models.py @@ -0,0 +1,156 @@ +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 _ +from django.utils.translation import pgettext_lazy +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 Account(CreatedModifiedAbstract): + """ + This is the model where we can give access to several users, such that they + can decide which account to use to pay for something. + """ + + owner = models.ForeignKey(get_user_model(), on_delete=models.PROTECT) + + def balance(self): + return self.transactions.all().aggregate(Sum('amount')).get('amount', 0) + + +class Transaction(CreatedModifiedAbstract): + """ + Tracks in and outgoing events of an account. When an order is received, an + amount is subtracted, when a payment is received, an amount is added. + """ + + account = models.ForeignKey( + Account, + on_delete=models.PROTECT, + related_name="transactions", + ) + amount = MoneyField( + verbose_name=_("amount"), + max_digits=16, + decimal_places=2, + help_text=_("This will include VAT") + ) + description = models.CharField( + max_length=1024, + verbose_name=_("description") + ) + + +class Order(CreatedModifiedAbstract): + """ + Scoped out: Contents of invoices will have to be tracked either here or in + a separate Invoice model. This is undecided because we are not generating + invoices at the moment. + """ + + user = models.ForeignKey(get_user_model(), on_delete=models.PROTECT) + account = models.ForeignKey(Account, on_delete=models.PROTECT) + is_paid = models.BooleanField(default=False) + + description = models.CharField( + max_length=1024, + verbose_name=_("description") + ) + + price = MoneyField( + verbose_name=_("price (excl. VAT)"), + max_digits=16, + decimal_places=2, + ) + vat = MoneyField( + verbose_name=_("VAT"), + max_digits=16, + decimal_places=2, + ) + + is_paid = models.BooleanField( + default=False, + verbose_name=_("is paid"), + ) + + @property + def total(self): + return self.price + self.vat + + @property + def display_id(self): + return str(self.id).zfill(6) + + @property + def payment_token(self): + pk = str(self.pk).encode("utf-8") + x = md5() + x.update(pk) + extra_hash = (settings.SECRET_KEY + 'blah').encode('utf-8') + x.update(extra_hash) + return x.hexdigest() + + class Meta: + verbose_name = pgettext_lazy("accounting term", "Order") + verbose_name_plural = pgettext_lazy("accounting term", "Orders") + + def __str__(self): + return "Order ID {id}".format(id=self.display_id) + + +class Payment(CreatedModifiedAbstract): + + amount = MoneyField( + max_digits=16, + decimal_places=2, + ) + order = models.ForeignKey(Order, on_delete=models.PROTECT) + + description = models.CharField( + max_length=1024, + verbose_name=_("description") + ) + + stripe_charge_id = models.CharField( + max_length=255, + null=True, + blank=True, + ) + + @property + def display_id(self): + return str(self.id).zfill(6) + + @classmethod + def from_order(cls, order): + return cls.objects.create( + order=order, + user=order.user, + amount=order.total, + description=order.description, + ) + + def __str__(self): + return "Payment ID {id}".format(id=self.display_id) + + class Meta: + verbose_name = _("payment") + verbose_name_plural = _("payments") diff --git a/accounting/test.py b/accounting/test.py new file mode 100644 index 0000000..e69de29 diff --git a/membership/__init__.py b/membership/__init__.py new file mode 100644 index 0000000..fc35f0b --- /dev/null +++ b/membership/__init__.py @@ -0,0 +1,8 @@ +""" +Membership application +====================== + +This application's domain relate to organizational structures and +implementation of statutes, policies etc. + +""" diff --git a/membership/admin.py b/membership/admin.py new file mode 100644 index 0000000..90e644e --- /dev/null +++ b/membership/admin.py @@ -0,0 +1,8 @@ +from django.contrib import admin + +from . import models + + +@admin.register(models.Membership) +class MembershipAdmin(admin.ModelAdmin): + pass diff --git a/membership/apps.py b/membership/apps.py new file mode 100644 index 0000000..537f521 --- /dev/null +++ b/membership/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class MembershipConfig(AppConfig): + name = 'membership' diff --git a/membership/migrations/0001_initial.py b/membership/migrations/0001_initial.py new file mode 100644 index 0000000..70b3ad8 --- /dev/null +++ b/membership/migrations/0001_initial.py @@ -0,0 +1,101 @@ +# 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), + ), + ] diff --git a/membership/migrations/__init__.py b/membership/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/membership/models.py b/membership/models.py new file mode 100644 index 0000000..aaca0de --- /dev/null +++ b/membership/models.py @@ -0,0 +1,144 @@ +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") diff --git a/membersystem/settings/__init__.py b/membersystem/settings/__init__.py index 166c847..87c4305 100644 --- a/membersystem/settings/__init__.py +++ b/membersystem/settings/__init__.py @@ -1,10 +1,14 @@ import warnings -from .base import * +from .base import * # noqa try: - from .local import * + from .local import * # noqa except ImportError: - warnings.warn("No settings.local, using a default SECRET_KEY 'hest'") + warnings.warn( + "No settings.local, using a default SECRET_KEY 'hest'. You should " + "write a custom local.py with this setting." + ) SECRET_KEY = "hest" - pass \ No newline at end of file + DEBUG = True + pass diff --git a/membersystem/settings/base.py b/membersystem/settings/base.py index f2a31f3..9133d01 100644 --- a/membersystem/settings/base.py +++ b/membersystem/settings/base.py @@ -9,7 +9,6 @@ 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, ...) @@ -20,7 +19,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = False ALLOWED_HOSTS = [] @@ -37,6 +36,8 @@ INSTALLED_APPS = [ 'django.contrib.sites', 'profiles', + 'accounting', + 'membership', 'allauth', 'allauth.account', @@ -94,16 +95,16 @@ DATABASES = { AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', # noqa }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', # noqa }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', # noqa }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', # noqa }, ] @@ -130,3 +131,6 @@ STATIC_URL = '/static/' SITE_ID = 1 EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' + +CURRENCIES = ('DKK',) +CURRENCY_CHOICES = [('DKK', 'DKK')] diff --git a/membersystem/urls.py b/membersystem/urls.py index ef67fa2..bf33cb6 100644 --- a/membersystem/urls.py +++ b/membersystem/urls.py @@ -1,8 +1,8 @@ """URLs for the membersystem""" - -from django.contrib import admin from django.conf.urls import url -from django.urls import include, path +from django.contrib import admin +from django.urls import include +from django.urls import path urlpatterns = [ url(r'^accounts/', include('allauth.urls')), diff --git a/membersystem/wsgi.py b/membersystem/wsgi.py index b9da374..a1d4885 100644 --- a/membersystem/wsgi.py +++ b/membersystem/wsgi.py @@ -6,7 +6,6 @@ It exposes the WSGI callable as a module-level variable named ``application``. For more information on this file, see https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/ """ - import os from django.core.wsgi import get_wsgi_application diff --git a/profiles/migrations/0001_initial.py b/profiles/migrations/0001_initial.py new file mode 100644 index 0000000..16d03e3 --- /dev/null +++ b/profiles/migrations/0001_initial.py @@ -0,0 +1,24 @@ +# Generated by Django 2.0.6 on 2018-06-23 19:45 +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='Profile', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/requirements_dev.txt b/requirements_dev.txt new file mode 100644 index 0000000..4d4a689 --- /dev/null +++ b/requirements_dev.txt @@ -0,0 +1,2 @@ +pytest +pre-commit diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..7235929 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,13 @@ +[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 From 056ca430ef5a6deb494ecda5d3d199001c54e336 Mon Sep 17 00:00:00 2001 From: Benjamin Bach Date: Sun, 24 Jun 2018 01:05:02 +0200 Subject: [PATCH 4/5] First pytest stuff --- .gitignore | 1 + README.rst | 8 ++++++++ accounting/models.py | 1 + accounting/test.py | 0 accounting/tests.py | 16 ++++++++++++++++ pytest.ini | 5 +++++ requirements_dev.txt | 1 + 7 files changed, 32 insertions(+) delete mode 100644 accounting/test.py create mode 100644 accounting/tests.py create mode 100644 pytest.ini diff --git a/.gitignore b/.gitignore index 976eaca..f4b2f47 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ __pycache__/ *.sw* db.sqlite3 membersystem/settings/local.py +.pytest_cache diff --git a/README.rst b/README.rst index 0d23ef5..2ac2ce6 100644 --- a/README.rst +++ b/README.rst @@ -9,3 +9,11 @@ To start developing: # 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 diff --git a/accounting/models.py b/accounting/models.py index 73ddb8d..0b55eb3 100644 --- a/accounting/models.py +++ b/accounting/models.py @@ -32,6 +32,7 @@ class Account(CreatedModifiedAbstract): owner = models.ForeignKey(get_user_model(), on_delete=models.PROTECT) + @property def balance(self): return self.transactions.all().aggregate(Sum('amount')).get('amount', 0) diff --git a/accounting/test.py b/accounting/test.py deleted file mode 100644 index e69de29..0000000 diff --git a/accounting/tests.py b/accounting/tests.py new file mode 100644 index 0000000..b2e28e3 --- /dev/null +++ b/accounting/tests.py @@ -0,0 +1,16 @@ +import pytest +from django.contrib.auth.models import User + +from . import models + +# @pytest.fixture +# def test(): +# do stuff + +@pytest.mark.django_db +def test_balance(): + user = User.objects.create_user("test", "lala@adas.com", "1234") + account = models.Account.objects.create( + owner=user + ) + assert account.balance == 0 diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..1e2ec6a --- /dev/null +++ b/pytest.ini @@ -0,0 +1,5 @@ +[pytest] +testpaths = . +python_files = tests.py test_*.py *_tests.py +DJANGO_SETTINGS_MODULE = membersystem.settings +#norecursedirs = dist tmp* .svn .* diff --git a/requirements_dev.txt b/requirements_dev.txt index 4d4a689..ad11934 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,2 +1,3 @@ pytest +pytest-django pre-commit From a22c2587dd1797b518abf03970d94e613dda7d07 Mon Sep 17 00:00:00 2001 From: Benjamin Bach Date: Sat, 31 Aug 2019 18:52:12 +0200 Subject: [PATCH 5/5] Remove allauth --- membersystem/settings/base.py | 3 --- membersystem/urls.py | 3 --- requirements.txt | 1 - 3 files changed, 7 deletions(-) diff --git a/membersystem/settings/base.py b/membersystem/settings/base.py index 9133d01..92f44da 100644 --- a/membersystem/settings/base.py +++ b/membersystem/settings/base.py @@ -38,9 +38,6 @@ INSTALLED_APPS = [ 'profiles', 'accounting', 'membership', - - 'allauth', - 'allauth.account', ] MIDDLEWARE = [ diff --git a/membersystem/urls.py b/membersystem/urls.py index bf33cb6..0e3265b 100644 --- a/membersystem/urls.py +++ b/membersystem/urls.py @@ -1,10 +1,7 @@ """URLs for the membersystem""" -from django.conf.urls import url from django.contrib import admin -from django.urls import include from django.urls import path urlpatterns = [ - url(r'^accounts/', include('allauth.urls')), path('admin/', admin.site.urls), ] diff --git a/requirements.txt b/requirements.txt index 035f111..d74bd6a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,2 @@ Django==2.0.6 -django-allauth==0.36.0 django-money==0.14