Compare commits

...

16 Commits

Author SHA1 Message Date
Benjamin Bach 35d8438d66 Renamed membersystem => project (because of tab completion nuisance) and profiles => users 2019-08-31 19:41:35 +02:00
benjaoming 2181f818b1 Merge branch 'membership_stuff' of benjaoming/membersystem into master 2019-08-31 16:55:27 +00:00
Benjamin Bach 66ca59be35 Merge branch 'master' into membership_stuff 2019-08-31 18:54:50 +02:00
Benjamin Bach a22c2587dd Remove allauth 2019-08-31 18:52:12 +02:00
benjaoming 68094b8597 Merge branch 'fix/small-error-in-manage-py' of data.coop/membersystem into master 2018-11-02 14:28:43 +00:00
Benjamin Bach 056ca430ef First pytest stuff 2018-06-24 01:06:35 +02:00
Víðir Valberg Guðmundsson 4c56d7db67 Remove some faulty stray "code". 2018-06-23 23:43:32 +02:00
Benjamin Bach a8321decde Adding accounting and membership apps + pre-commit linting stuff 2018-06-23 23:41:58 +02:00
benjaoming 2f1cc75834 Merge branch 'feature/super-nice-frontend' of data.coop/membersystem into master 2018-06-23 19:28:00 +00:00
Benjamin Bach f46b031a8d django-money dependency 2018-06-23 20:54:37 +02:00
Benjamin Bach 36a4c901a8 Adding pre-commit hooks 2018-06-23 20:54:24 +02:00
Mikkel Munch Mortensen 567babc359
Add a base template and some exquisite styling
Wow!
2018-06-23 20:20:31 +02:00
Mikkel Munch Mortensen 7f41634987
Add a context processor that includes current site 2018-06-23 20:19:20 +02:00
Mikkel Munch Mortensen c8ad58f086
Add django extensions to installed apps 2018-06-23 16:17:41 +02:00
Mikkel Munch Mortensen dfdf065a87
Add django-extensions 2018-06-23 16:09:43 +02:00
decibyte b10ecac1f4 Merge branch 'master' of benjaoming/membersystem into master 2018-06-23 14:04:24 +00:00
40 changed files with 828 additions and 41 deletions

3
.gitignore vendored
View File

@ -2,4 +2,5 @@ __pycache__/
*.pyc
*.sw*
db.sqlite3
membersystem/settings/local.py
project/settings/local.py
.pytest_cache

14
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,14 @@
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

15
Makefile Normal file
View File

@ -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

19
README.rst Normal file
View File

@ -0,0 +1,19 @@
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

28
accounting/admin.py Normal file
View File

@ -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")

5
accounting/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class AccountingConfig(AppConfig):
name = 'accounting'

View File

@ -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,
},
),
]

157
accounting/models.py Normal file
View File

@ -0,0 +1,157 @@
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)
@property
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")

16
accounting/tests.py Normal file
View File

@ -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

View File

@ -3,7 +3,7 @@ import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "membersystem.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
@ -11,5 +11,5 @@ if __name__ == "__main__":
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
)
execute_from_command_line(sys.argv)

8
membership/__init__.py Normal file
View File

@ -0,0 +1,8 @@
"""
Membership application
======================
This application's domain relate to organizational structures and
implementation of statutes, policies etc.
"""

8
membership/admin.py Normal file
View File

@ -0,0 +1,8 @@
from django.contrib import admin
from . import models
@admin.register(models.Membership)
class MembershipAdmin(admin.ModelAdmin):
pass

5
membership/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class MembershipConfig(AppConfig):
name = 'membership'

View File

@ -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),
),
]

144
membership/models.py Normal file
View File

@ -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")

View File

@ -1,10 +0,0 @@
import warnings
from .base import *
try:
from .local import *
except ImportError:
warnings.warn("No settings.local, using a default SECRET_KEY 'hest'")
SECRET_KEY = "hest"
pass

View File

@ -1,5 +0,0 @@
from django.apps import AppConfig
class ProfilesConfig(AppConfig):
name = 'profiles'

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

0
project/__init__.py Normal file
View File

View File

@ -0,0 +1,9 @@
"""Context processors for the membersystem app."""
from django.contrib.sites.shortcuts import get_current_site
def current_site(request):
"""Include the current site in the context."""
return {
'site': get_current_site(request)
}

View File

@ -0,0 +1,14 @@
import warnings
from .base import * # noqa
try:
from .local import * # noqa
except ImportError:
warnings.warn(
"No settings.local, using a default SECRET_KEY 'hest'. You should "
"write a custom local.py with this setting."
)
SECRET_KEY = "hest"
DEBUG = True
pass

View File

@ -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 = []
@ -36,10 +35,9 @@ INSTALLED_APPS = [
'django.contrib.staticfiles',
'django.contrib.sites',
'profiles',
'allauth',
'allauth.account',
'users',
'accounting',
'membership',
]
MIDDLEWARE = [
@ -52,12 +50,12 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'membersystem.urls'
ROOT_URLCONF = 'project.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'DIRS': [os.path.join(BASE_DIR, "project", "templates")],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
@ -65,6 +63,7 @@ TEMPLATES = [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'project.context_processors.current_site',
],
},
},
@ -75,7 +74,7 @@ AUTHENTICATION_BACKENDS = (
'allauth.account.auth_backends.AuthenticationBackend',
)
WSGI_APPLICATION = 'membersystem.wsgi.application'
WSGI_APPLICATION = 'project.wsgi.application'
# Database
@ -94,16 +93,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 +129,6 @@ STATIC_URL = '/static/'
SITE_ID = 1
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
CURRENCIES = ('DKK',)
CURRENCY_CHOICES = [('DKK', 'DKK')]

View File

@ -0,0 +1,79 @@
/* General styles */
html
{
margin: 0;
padding: 0;
font-family: sans-serif;
font-size: 2.5vmin;
background: #f8f8f8;
}
body
{
background: #fff;
color: #000;
margin: 1em auto;
max-width: 50em;
padding: 0 1em;
box-shadow: 0 0 2.5em rgba(0, 0, 0, 20%);
}
header,
footer
{
background: #eee;
padding: .5em;
margin: 0 -1em;
}
footer
{
margin-top: 2em;
}
header h1
{
font-size: 1em;
float: left;
padding: .5em .5em;
margin: 0;
}
header ul,
footer ul
{
list-style-type: none;
padding: 0;
margin: 0;
text-align: right;
}
header ul li,
footer ul li
{
display: inline;
}
header ul li a,
footer ul li a
{
display: inline-block;
margin: 0;
padding: .5em .5em;
}
/* Forms */
label
{
display: block;
padding: .5em 0;
}
button,
input,
textarea
{
font-size: inherit;
}

View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
{% load static %}
<html>
<head>
<title>{% block head_title %}{% endblock %} {{ site.name }}</title>
{% block extra_head %}{% endblock %}
<link rel="stylesheet" href="{% static '/css/membersystem.css' %}" type="text/css" />
</head>
<body>
<header>
<h1>
<a href="/">{{ site.name }}</a>
</h1>
<ul>
{% if user.is_authenticated %}
<li><a href="{% url 'account_email' %}">Change e-mail</a></li>
<li><a href="{% url 'account_logout' %}">Sign out</a></li>
{% else %}
<li><a href="{% url 'account_login' %}">Sign in</a></li>
<li><a href="{% url 'account_signup' %}">Sign up</a></li>
{% endif %}
</ul>
</header>
{% block body %}
{% if messages %}
<ul id="messages">
{% for message in messages %}
<li>{{message}}</li>
{% endfor %}
</ul>
{% endif %}
{% block content %}
{% endblock %}
{% endblock %}
{% block extra_body %}
{% endblock %}
<footer>
<ul>
<li>
<a href="https://data.coop">data.coop</a>
</li>
<li>
<a href="https://git.data.coop/data.coop/membersystem">source code</a>
</li>
</ul>
</footer>
</body>
</html>

View File

@ -1,10 +1,7 @@
"""URLs for the membersystem"""
from django.contrib import admin
from django.conf.urls import url
from django.urls import include, path
from django.urls import path
urlpatterns = [
url(r'^accounts/', include('allauth.urls')),
path('admin/', admin.site.urls),
]

View File

@ -6,11 +6,10 @@ 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
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "membersystem.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
application = get_wsgi_application()

5
pytest.ini Normal file
View File

@ -0,0 +1,5 @@
[pytest]
testpaths = .
python_files = tests.py test_*.py *_tests.py
DJANGO_SETTINGS_MODULE = project.settings
#norecursedirs = dist tmp* .svn .*

View File

@ -1,2 +1,3 @@
Django==2.0.6
django-allauth==0.36.0
django-money==0.14
django-extensions==2.0.7

3
requirements_dev.txt Normal file
View File

@ -0,0 +1,3 @@
pytest
pytest-django
pre-commit

13
setup.cfg Normal file
View File

@ -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

0
users/__init__.py Normal file
View File

5
users/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class UsersConfig(AppConfig):
name = 'users'

View File

@ -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)),
],
),
]

View File