-__version__ = '0.0.1'
+__version__ = "0.0.1"
class BookkeepingConfig(AppConfig):
- name = 'byro.bookkeeping'
+ name = "byro.bookkeeping"
class ByroPluginMeta:
document_categories = {
- 'byro.bookkeeping.receipt': _('Receipt'),
- 'byro.bookkeeping.invoice': _('Invoice'),
- 'byro.bookkeeping.account.statement': _('Statement'),
+ "byro.bookkeeping.receipt": _("Receipt"),
+ "byro.bookkeeping.invoice": _("Invoice"),
+ "byro.bookkeeping.account.statement": _("Statement"),
}
initial = True
- dependencies = [
- ('members', '0001_initial'),
- ]
+ dependencies = [("members", "0001_initial")]
operations = [
migrations.CreateModel(
- name='Account',
+ name="Account",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('account_category', models.CharField(choices=[('member_donation', 'member_donation'), ('member_fees', 'member_fees')], max_length=15)),
- ('name', models.CharField(max_length=300)),
- ('member', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='members.Member')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "account_category",
+ models.CharField(
+ choices=[
+ ("member_donation", "member_donation"),
+ ("member_fees", "member_fees"),
+ ],
+ max_length=15,
+ ),
+ ),
+ ("name", models.CharField(max_length=300)),
+ (
+ "member",
+ models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.PROTECT,
+ to="members.Member",
+ ),
+ ),
],
bases=(byro.common.models.auditable.Auditable, models.Model),
),
migrations.CreateModel(
- name='RealTransaction',
+ name="RealTransaction",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('channel', models.CharField(choices=[('bank', 'bank'), ('cash', 'cash')], max_length=4)),
- ('booking_datetime', models.DateTimeField(null=True)),
- ('value_datetime', models.DateTimeField()),
- ('amount', models.DecimalField(decimal_places=2, max_digits=8)),
- ('purpose', models.CharField(max_length=200)),
- ('originator', models.CharField(max_length=200)),
- ('importer', models.CharField(max_length=200, null=True)),
- ('data', django.contrib.postgres.fields.jsonb.JSONField(null=True)),
- ('reverses', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='bookkeeping.RealTransaction')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "channel",
+ models.CharField(
+ choices=[("bank", "bank"), ("cash", "cash")], max_length=4
+ ),
+ ),
+ ("booking_datetime", models.DateTimeField(null=True)),
+ ("value_datetime", models.DateTimeField()),
+ ("amount", models.DecimalField(decimal_places=2, max_digits=8)),
+ ("purpose", models.CharField(max_length=200)),
+ ("originator", models.CharField(max_length=200)),
+ ("importer", models.CharField(max_length=200, null=True)),
+ ("data", django.contrib.postgres.fields.jsonb.JSONField(null=True)),
+ (
+ "reverses",
+ models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.PROTECT,
+ to="bookkeeping.RealTransaction",
+ ),
+ ),
],
bases=(byro.common.models.auditable.Auditable, models.Model),
),
migrations.CreateModel(
- name='VirtualTransaction',
+ name="VirtualTransaction",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('amount', models.DecimalField(decimal_places=2, max_digits=8)),
- ('value_datetime', models.DateTimeField(null=True)),
- ('destination_account', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='incoming_transactions', to='bookkeeping.Account')),
- ('real_transaction', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='virtual_transactions', to='bookkeeping.RealTransaction')),
- ('source_account', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='outgoing_transactions', to='bookkeeping.Account')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("amount", models.DecimalField(decimal_places=2, max_digits=8)),
+ ("value_datetime", models.DateTimeField(null=True)),
+ (
+ "destination_account",
+ models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="incoming_transactions",
+ to="bookkeeping.Account",
+ ),
+ ),
+ (
+ "real_transaction",
+ models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="virtual_transactions",
+ to="bookkeeping.RealTransaction",
+ ),
+ ),
+ (
+ "source_account",
+ models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="outgoing_transactions",
+ to="bookkeeping.Account",
+ ),
+ ),
],
bases=(byro.common.models.auditable.Auditable, models.Model),
),
class Migration(migrations.Migration):
- dependencies = [
- ('bookkeeping', '0001_initial'),
- ]
+ dependencies = [("bookkeeping", "0001_initial")]
operations = [
migrations.AlterField(
- model_name='account',
- name='name',
+ model_name="account",
+ name="name",
field=models.CharField(max_length=300, null=True),
- ),
+ )
]
class Migration(migrations.Migration):
dependencies = [
- ('members', '0001_initial'),
- ('bookkeeping', '0002_auto_20170812_1109'),
+ ("members", "0001_initial"),
+ ("bookkeeping", "0002_auto_20170812_1109"),
]
operations = [
migrations.AlterField(
- model_name='account',
- name='member',
- field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='accounts', to='members.Member'),
+ model_name="account",
+ name="member",
+ field=models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="accounts",
+ to="members.Member",
+ ),
),
migrations.AlterUniqueTogether(
- name='account',
- unique_together=set([('member', 'account_category', 'name')]),
+ name="account",
+ unique_together=set([("member", "account_category", "name")]),
),
]
class Migration(migrations.Migration):
dependencies = [
- ('members', '0001_initial'),
- ('bookkeeping', '0003_auto_20170812_1237'),
+ ("members", "0001_initial"),
+ ("bookkeeping", "0003_auto_20170812_1237"),
]
operations = [
migrations.AddField(
- model_name='virtualtransaction',
- name='member',
- field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='transactions', to='members.Member'),
+ model_name="virtualtransaction",
+ name="member",
+ field=models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="transactions",
+ to="members.Member",
+ ),
),
migrations.AlterUniqueTogether(
- name='account',
- unique_together=set([('account_category', 'name')]),
+ name="account", unique_together=set([("account_category", "name")])
),
]
def create_accounts(apps, schema_editor):
from byro.bookkeeping.models import AccountCategory
+
Account = apps.get_model("bookkeeping", "Account")
- for category in ("member_donation", "member_fees"): # Not the constants, because the class got refactored
- Account.objects.create(
- account_category=category
- )
+ for category in (
+ "member_donation",
+ "member_fees",
+ ): # Not the constants, because the class got refactored
+ Account.objects.create(account_category=category)
def delete_accounts(apps, schema_editor):
class Migration(migrations.Migration):
- dependencies = [
- ('bookkeeping', '0004_auto_20170919_1832'),
- ]
+ dependencies = [("bookkeeping", "0004_auto_20170919_1832")]
- operations = [
- migrations.RunPython(create_accounts, delete_accounts),
- ]
+ operations = [migrations.RunPython(create_accounts, delete_accounts)]
class Migration(migrations.Migration):
- dependencies = [
- ('bookkeeping', '0005_auto_20170919_1832'),
- ]
+ dependencies = [("bookkeeping", "0005_auto_20170919_1832")]
- operations = [
- migrations.RemoveField(
- model_name='account',
- name='member',
- ),
- ]
+ operations = [migrations.RemoveField(model_name="account", name="member")]
class Migration(migrations.Migration):
- dependencies = [
- ('bookkeeping', '0006_remove_account_member'),
- ]
+ dependencies = [("bookkeeping", "0006_remove_account_member")]
operations = [
migrations.AlterField(
- model_name='account',
- name='account_category',
- field=models.CharField(choices=[('member_donation', 'Donation account'), ('member_fees', 'Membership fee account'), ('asset', 'Asset account'), ('liability', 'Liability account'), ('income', 'Income account'), ('expense', 'Expense account')], max_length=15),
- ),
+ model_name="account",
+ name="account_category",
+ field=models.CharField(
+ choices=[
+ ("member_donation", "Donation account"),
+ ("member_fees", "Membership fee account"),
+ ("asset", "Asset account"),
+ ("liability", "Liability account"),
+ ("income", "Income account"),
+ ("expense", "Expense account"),
+ ],
+ max_length=15,
+ ),
+ )
]
class Migration(migrations.Migration):
- dependencies = [
- ('bookkeeping', '0007_auto_20171206_1919'),
- ]
+ dependencies = [("bookkeeping", "0007_auto_20171206_1919")]
operations = [
migrations.CreateModel(
- name='RealTransactionSource',
+ name="RealTransactionSource",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('source_file', models.FileField(upload_to='transaction_uploads/')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("source_file", models.FileField(upload_to="transaction_uploads/")),
],
bases=(byro.common.models.auditable.Auditable, models.Model),
),
migrations.AddField(
- model_name='realtransaction',
- name='source',
- field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transactions', to='bookkeeping.RealTransactionSource'),
+ model_name="realtransaction",
+ name="source",
+ field=models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="transactions",
+ to="bookkeeping.RealTransactionSource",
+ ),
),
]
class Migration(migrations.Migration):
- dependencies = [
- ('bookkeeping', '0008_auto_20180111_1807'),
- ]
+ dependencies = [("bookkeeping", "0008_auto_20180111_1807")]
operations = [
migrations.AddField(
- model_name='realtransactionsource',
- name='state',
- field=models.CharField(choices=[('new', 'new'), ('processing', 'processing'), ('processed', 'processed'), ('failed', 'failed')], default='new', max_length=10),
- ),
+ model_name="realtransactionsource",
+ name="state",
+ field=models.CharField(
+ choices=[
+ ("new", "new"),
+ ("processing", "processing"),
+ ("processed", "processed"),
+ ("failed", "failed"),
+ ],
+ default="new",
+ max_length=10,
+ ),
+ )
]
class Migration(migrations.Migration):
- dependencies = [
- ('bookkeeping', '0009_realtransactionsource_state'),
- ]
+ dependencies = [("bookkeeping", "0009_realtransactionsource_state")]
operations = [
migrations.AlterField(
- model_name='realtransaction',
- name='purpose',
+ model_name="realtransaction",
+ name="purpose",
field=models.CharField(max_length=1000),
- ),
+ )
]
class Migration(migrations.Migration):
- dependencies = [
- ('bookkeeping', '0010_auto_20180113_1500'),
- ]
+ dependencies = [("bookkeeping", "0010_auto_20180113_1500")]
operations = [
migrations.AlterField(
- model_name='realtransaction',
- name='importer',
+ model_name="realtransaction",
+ name="importer",
field=models.CharField(max_length=500, null=True),
),
migrations.AlterField(
- model_name='realtransaction',
- name='originator',
+ model_name="realtransaction",
+ name="originator",
field=models.CharField(max_length=500),
),
]
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ('members', '0009_auto_20180512_1810'),
- ('bookkeeping', '0011_auto_20180303_1745'),
+ ("members", "0009_auto_20180512_1810"),
+ ("bookkeeping", "0011_auto_20180303_1745"),
]
operations = [
migrations.CreateModel(
- name='AccountTag',
+ name="AccountTag",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(max_length=300, unique=True)),
- ('description', models.CharField(max_length=1000, null=True)),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("name", models.CharField(max_length=300, unique=True)),
+ ("description", models.CharField(max_length=1000, null=True)),
],
),
migrations.CreateModel(
- name='Booking',
+ name="Booking",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('memo', models.CharField(max_length=1000, null=True)),
- ('booking_datetime', models.DateTimeField(null=True)),
- ('modified', models.DateTimeField(auto_now=True)),
- ('amount', models.DecimalField(decimal_places=2, max_digits=8)),
- ('importer', models.CharField(max_length=500, null=True)),
- ('data', django.contrib.postgres.fields.jsonb.JSONField(null=True)),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("memo", models.CharField(max_length=1000, null=True)),
+ ("booking_datetime", models.DateTimeField(null=True)),
+ ("modified", models.DateTimeField(auto_now=True)),
+ ("amount", models.DecimalField(decimal_places=2, max_digits=8)),
+ ("importer", models.CharField(max_length=500, null=True)),
+ ("data", django.contrib.postgres.fields.jsonb.JSONField(null=True)),
],
),
migrations.CreateModel(
- name='Transaction',
+ name="Transaction",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('memo', models.CharField(max_length=1000, null=True)),
- ('booking_datetime', models.DateTimeField(null=True)),
- ('value_datetime', models.DateTimeField()),
- ('modified', models.DateTimeField(auto_now=True)),
- ('data', django.contrib.postgres.fields.jsonb.JSONField(null=True)),
- ('modified_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL)),
- ('reverses', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='reversed_by', to='bookkeeping.Transaction')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("memo", models.CharField(max_length=1000, null=True)),
+ ("booking_datetime", models.DateTimeField(null=True)),
+ ("value_datetime", models.DateTimeField()),
+ ("modified", models.DateTimeField(auto_now=True)),
+ ("data", django.contrib.postgres.fields.jsonb.JSONField(null=True)),
+ (
+ "modified_by",
+ models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="+",
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ (
+ "reverses",
+ models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="reversed_by",
+ to="bookkeeping.Transaction",
+ ),
+ ),
],
),
migrations.AlterField(
- model_name='account',
- name='account_category',
- field=models.CharField(choices=[('member_donation', 'Donation account'), ('member_fees', 'Membership fee account'), ('asset', 'Asset account'), ('liability', 'Liability account'), ('income', 'Income account'), ('expense', 'Expense account'), ('equity', 'Equity account')], max_length=15),
+ model_name="account",
+ name="account_category",
+ field=models.CharField(
+ choices=[
+ ("member_donation", "Donation account"),
+ ("member_fees", "Membership fee account"),
+ ("asset", "Asset account"),
+ ("liability", "Liability account"),
+ ("income", "Income account"),
+ ("expense", "Expense account"),
+ ("equity", "Equity account"),
+ ],
+ max_length=15,
+ ),
),
migrations.AddField(
- model_name='booking',
- name='credit_account',
- field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='credits', to='bookkeeping.Account'),
+ model_name="booking",
+ name="credit_account",
+ field=models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="credits",
+ to="bookkeeping.Account",
+ ),
),
migrations.AddField(
- model_name='booking',
- name='debit_account',
- field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='debits', to='bookkeeping.Account'),
+ model_name="booking",
+ name="debit_account",
+ field=models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="debits",
+ to="bookkeeping.Account",
+ ),
),
migrations.AddField(
- model_name='booking',
- name='member',
- field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='bookings', to='members.Member'),
+ model_name="booking",
+ name="member",
+ field=models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="bookings",
+ to="members.Member",
+ ),
),
migrations.AddField(
- model_name='booking',
- name='modified_by',
- field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL),
+ model_name="booking",
+ name="modified_by",
+ field=models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="+",
+ to=settings.AUTH_USER_MODEL,
+ ),
),
migrations.AddField(
- model_name='booking',
- name='source',
- field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bookings', to='bookkeeping.RealTransactionSource'),
+ model_name="booking",
+ name="source",
+ field=models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="bookings",
+ to="bookkeeping.RealTransactionSource",
+ ),
),
migrations.AddField(
- model_name='booking',
- name='transaction',
- field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='bookings', to='bookkeeping.Transaction'),
+ model_name="booking",
+ name="transaction",
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="bookings",
+ to="bookkeeping.Transaction",
+ ),
),
migrations.AddField(
- model_name='account',
- name='tags',
- field=models.ManyToManyField(related_name='accounts', to='bookkeeping.AccountTag'),
+ model_name="account",
+ name="tags",
+ field=models.ManyToManyField(
+ related_name="accounts", to="bookkeeping.AccountTag"
+ ),
),
django_db_constraints.operations.AlterConstraints(
- name='Booking',
- db_constraints={'exactly_either_debit_or_credit': 'CHECK (NOT ((debit_account_id IS NULL) = (credit_account_id IS NULL)))'},
+ name="Booking",
+ db_constraints={
+ "exactly_either_debit_or_credit": "CHECK (NOT ((debit_account_id IS NULL) = (credit_account_id IS NULL)))"
+ },
),
]
from django.db import migrations
+
def migrate_bookkeeping_model(apps, schema_editor):
# In the old model, the bank account was implicit. Most VirtualTransaction
# that have "None" set as source or destination account refer to the bank
#
from byro.bookkeeping.special_accounts import SpecialAccounts
- Account = apps.get_model('bookkeeping', 'Account')
- VirtualTransaction = apps.get_model('bookkeeping', 'VirtualTransaction')
- RealTransaction = apps.get_model('bookkeeping', 'RealTransaction')
- Transaction = apps.get_model('bookkeeping', 'Transaction')
- Booking = apps.get_model('bookkeeping', 'Booking')
+ Account = apps.get_model("bookkeeping", "Account")
+ VirtualTransaction = apps.get_model("bookkeeping", "VirtualTransaction")
+ RealTransaction = apps.get_model("bookkeeping", "RealTransaction")
+ Transaction = apps.get_model("bookkeeping", "Transaction")
+ Booking = apps.get_model("bookkeeping", "Booking")
bank = SpecialAccounts.bank
fees_receivable = SpecialAccounts.fees_receivable
fees = SpecialAccounts.fees
donations = SpecialAccounts.donations
- ACCOUNT_MAP = {
- 'member_donation': donations,
- 'member_fees': fees_receivable,
- }
+ ACCOUNT_MAP = {"member_donation": donations, "member_fees": fees_receivable}
map_account = lambda account: ACCOUNT_MAP.get(account.account_category, account)
handled_vts = set()
rt_mapping = {}
for rt in RealTransaction.objects.all():
t = Transaction.objects.create(
- booking_datetime=rt.booking_datetime,
- value_datetime=rt.value_datetime,
+ booking_datetime=rt.booking_datetime, value_datetime=rt.value_datetime
)
rt_mapping[rt.pk] = t.pk
if rt.amount > 0:
booking.source = rt.source
booking.importer = rt.importer
booking.memo = rt.purpose
- booking.data['other_party'] = rt.originator
- if rt.importer.endswith('csv_importer'):
- booking.data['csv_line'] = rt.data
+ booking.data["other_party"] = rt.originator
+ if rt.importer.endswith("csv_importer"):
+ booking.data["csv_line"] = rt.data
elif rt.data:
- booking.data['generic_data'] = rt.data
+ booking.data["generic_data"] = rt.data
booking.save()
for vt in rt.virtual_transactions.all():
params = {
- 'member': vt.member,
- 'amount': vt.amount,
- 'data': {'_migration_value_datetime': str(vt.value_datetime)},
+ "member": vt.member,
+ "amount": vt.amount,
+ "data": {"_migration_value_datetime": str(vt.value_datetime)},
}
if vt.destination_account:
t.bookings.create(
- credit_account_id=map_account(vt.destination_account).id,
- **params
+ credit_account_id=map_account(vt.destination_account).id, **params
)
if vt.source_account:
t.bookings.create(
- debit_account_id=map_account(vt.source_account).id,
- **params
+ debit_account_id=map_account(vt.source_account).id, **params
)
handled_vts.add(vt.pk)
t.save()
for vt in VirtualTransaction.objects.exclude(pk__in=handled_vts).all():
- t = Transaction.objects.create(
- value_datetime=vt.value_datetime
- )
+ t = Transaction.objects.create(value_datetime=vt.value_datetime)
- params = {
- 'member': vt.member,
- 'amount': vt.amount,
- }
+ params = {"member": vt.member, "amount": vt.amount}
if vt.source_account and vt.destination_account:
# Special case: Balanced VirtualTransaction
# (nothing in the code base generates these, handle them anyway)
t.bookings.create(
- credit_account_id=map_account(vt.destination_account).id,
- **params
+ credit_account_id=map_account(vt.destination_account).id, **params
)
t.bookings.create(
- debit_account_id=map_account(vt.source_account).id,
- **params
+ debit_account_id=map_account(vt.source_account).id, **params
)
- elif vt.source_account and vt.source_account.account_category == 'member_fees':
+ elif vt.source_account and vt.source_account.account_category == "member_fees":
# Member fee liability
- t.bookings.create(
- credit_account_id=fees.id,
- **params
- )
- t.bookings.create(
- debit_account_id=fees_receivable.id,
- **params
- )
+ t.bookings.create(credit_account_id=fees.id, **params)
+ t.bookings.create(debit_account_id=fees_receivable.id, **params)
- elif vt.destination_account and vt.destination_account.account_category == 'member_fees':
+ elif (
+ vt.destination_account
+ and vt.destination_account.account_category == "member_fees"
+ ):
# Member fee payment, f.e. from make_testdata
- t.bookings.create(
- credit_account_id=fees_receivable.id,
- **params
- )
- t.bookings.create(
- debit_account_id=bank.id,
- **params
- )
+ t.bookings.create(credit_account_id=fees_receivable.id, **params)
+ t.bookings.create(debit_account_id=bank.id, **params)
elif vt.source_account:
# Something else
t.bookings.create(
- credit_account_id=map_account(vt.source_account).id,
- **params
+ credit_account_id=map_account(vt.source_account).id, **params
)
elif vt.destination_account:
# Something else
t.bookings.create(
- debit_account_id=map_account(vt.source_account).id,
- **params
+ debit_account_id=map_account(vt.source_account).id, **params
)
else:
# A VirtualTransaction with no source or destination account is a sure sign of multiple exclamation marks
- raise Exception("Migration error: {} without either source or destination account!!!!!".format(vt))
-
-
-
+ raise Exception(
+ "Migration error: {} without either source or destination account!!!!!".format(
+ vt
+ )
+ )
class Migration(migrations.Migration):
- dependencies = [
- ('bookkeeping', '0012_auto_20180617_1926'),
- ]
+ dependencies = [("bookkeeping", "0012_auto_20180617_1926")]
operations = [
- migrations.RunPython(migrate_bookkeeping_model, migrations.RunPython.noop),
+ migrations.RunPython(migrate_bookkeeping_model, migrations.RunPython.noop)
]
from django.db import models, migrations
+
def delete_old_accounts(apps, schema_editor):
- Account = apps.get_model('bookkeeping', 'Account')
+ Account = apps.get_model("bookkeeping", "Account")
Account.objects.filter(
- models.Q(account_category='member_donation') | models.Q(account_category='member_fees')
+ models.Q(account_category="member_donation")
+ | models.Q(account_category="member_fees")
).delete()
+
class Migration(migrations.Migration):
- dependencies = [
- ('bookkeeping', '0013_new_data_model'),
- ]
+ dependencies = [("bookkeeping", "0013_new_data_model")]
operations = [
+ migrations.RemoveField(model_name="realtransaction", name="reverses"),
+ migrations.RemoveField(model_name="realtransaction", name="source"),
migrations.RemoveField(
- model_name='realtransaction',
- name='reverses',
- ),
- migrations.RemoveField(
- model_name='realtransaction',
- name='source',
- ),
- migrations.RemoveField(
- model_name='virtualtransaction',
- name='destination_account',
+ model_name="virtualtransaction", name="destination_account"
),
+ migrations.RemoveField(model_name="virtualtransaction", name="member"),
migrations.RemoveField(
- model_name='virtualtransaction',
- name='member',
- ),
- migrations.RemoveField(
- model_name='virtualtransaction',
- name='real_transaction',
- ),
- migrations.RemoveField(
- model_name='virtualtransaction',
- name='source_account',
- ),
- migrations.DeleteModel(
- name='RealTransaction',
- ),
- migrations.DeleteModel(
- name='VirtualTransaction',
+ model_name="virtualtransaction", name="real_transaction"
),
+ migrations.RemoveField(model_name="virtualtransaction", name="source_account"),
+ migrations.DeleteModel(name="RealTransaction"),
+ migrations.DeleteModel(name="VirtualTransaction"),
migrations.RunPython(delete_old_accounts, migrations.RunPython.noop),
]
class Migration(migrations.Migration):
- dependencies = [
- ('bookkeeping', '0014_auto_20180707_1410'),
- ]
+ dependencies = [("bookkeeping", "0014_auto_20180707_1410")]
operations = [
migrations.AlterField(
- model_name='account',
- name='account_category',
- field=models.CharField(choices=[('asset', 'Asset account'), ('liability', 'Liability account'), ('income', 'Income account'), ('expense', 'Expense account'), ('equity', 'Equity account')], max_length=9),
- ),
+ model_name="account",
+ name="account_category",
+ field=models.CharField(
+ choices=[
+ ("asset", "Asset account"),
+ ("liability", "Liability account"),
+ ("income", "Income account"),
+ ("expense", "Expense account"),
+ ("equity", "Equity account"),
+ ],
+ max_length=9,
+ ),
+ )
]
class Migration(migrations.Migration):
dependencies = [
- ('documents', '0004_auto_20181013_1611'),
- ('bookkeeping', '0015_auto_20180707_1522'),
+ ("documents", "0004_auto_20181013_1611"),
+ ("bookkeeping", "0015_auto_20180707_1522"),
]
operations = [
migrations.CreateModel(
- name='DocumentTransactionLink',
+ name="DocumentTransactionLink",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('document', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='documents.Document')),
- ('transaction', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='bookkeeping.Transaction')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "document",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to="documents.Document",
+ ),
+ ),
+ (
+ "transaction",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to="bookkeeping.Transaction",
+ ),
+ ),
],
),
migrations.AddField(
- model_name='transaction',
- name='documents',
- field=models.ManyToManyField(through='bookkeeping.DocumentTransactionLink', to='documents.Document'),
+ model_name="transaction",
+ name="documents",
+ field=models.ManyToManyField(
+ through="bookkeeping.DocumentTransactionLink", to="documents.Document"
+ ),
),
]
from .transaction import Booking, DocumentTransactionLink, Transaction
__all__ = (
- 'Account',
- 'AccountTag',
- 'AccountCategory',
- 'RealTransactionSource',
- 'Transaction',
- 'Booking',
- 'DocumentTransactionLink',
+ "Account",
+ "AccountTag",
+ "AccountCategory",
+ "RealTransactionSource",
+ "Transaction",
+ "Booking",
+ "DocumentTransactionLink",
)
class AccountCategory(Choices):
# Categories for double-entry bookkeeping
- ASSET = 'asset' # de: Aktiva, for example your bank account or cash
- LIABILITY = 'liability' # de: Passiva, for example invoices you have to pay
- INCOME = 'income' # de: Ertragskonten, for example for fees paid
- EXPENSE = 'expense' # de: Aufwandskonten, for example for fees to be paid
- EQUITY = 'equity' # de: Eigenkapital, assets less liabilities
+ ASSET = "asset" # de: Aktiva, for example your bank account or cash
+ LIABILITY = "liability" # de: Passiva, for example invoices you have to pay
+ INCOME = "income" # de: Ertragskonten, for example for fees paid
+ EXPENSE = "expense" # de: Aufwandskonten, for example for fees to be paid
+ EQUITY = "equity" # de: Eigenkapital, assets less liabilities
@classproperty
def choices(cls):
return (
- (cls.ASSET, _('Asset account')),
- (cls.LIABILITY, _('Liability account')),
- (cls.INCOME, _('Income account')),
- (cls.EXPENSE, _('Expense account')),
- (cls.EQUITY, _('Equity account')),
+ (cls.ASSET, _("Asset account")),
+ (cls.LIABILITY, _("Liability account")),
+ (cls.INCOME, _("Income account")),
+ (cls.EXPENSE, _("Expense account")),
+ (cls.EQUITY, _("Equity account")),
)
choices=AccountCategory.choices, max_length=AccountCategory.max_length
)
name = models.CharField(max_length=300, null=True) # e.g. 'Laser donations'
- tags = models.ManyToManyField(AccountTag, related_name='accounts')
+ tags = models.ManyToManyField(AccountTag, related_name="accounts")
class Meta:
- unique_together = (('account_category', 'name'),)
+ unique_together = (("account_category", "name"),)
def __str__(self):
if self.name:
return self.name
- return '{self.account_category} account #{self.id}'.format(self=self)
+ return "{self.account_category} account #{self.id}".format(self=self)
@property
def bookings(self):
qs = self._filter_by_date(self.transactions, start, end)
result = qs.with_balances().aggregate(
- debit=models.functions.Coalesce(models.Sum('balances_debit'), 0),
- credit=models.functions.Coalesce(models.Sum('balances_credit'), 0),
+ debit=models.functions.Coalesce(models.Sum("balances_debit"), 0),
+ credit=models.functions.Coalesce(models.Sum("balances_credit"), 0),
)
# ASSET, EXPENSE: Debit increases balance, credit decreases it
AccountCategory.INCOME,
AccountCategory.EQUITY,
):
- result['net'] = result['credit'] - result['debit']
+ result["net"] = result["credit"] - result["debit"]
else:
- result['net'] = result['debit'] - result['credit']
+ result["net"] = result["debit"] - result["credit"]
- result = {k: Decimal(v).quantize(Decimal('0.01')) for k, v in result.items()}
+ result = {k: Decimal(v).quantize(Decimal("0.01")) for k, v in result.items()}
return result
def get_absolute_url(self):
- return reverse('office:finance.accounts.detail', kwargs={'pk': self.pk})
+ return reverse("office:finance.accounts.detail", kwargs={"pk": self.pk})
def get_object_icon(self):
return mark_safe('<i class="fa fa-bank"></i> ')
class SourceState(Choices):
- NEW = 'new'
- PROCESSING = 'processing'
- PROCESSED = 'processed'
- FAILED = 'failed'
+ NEW = "new"
+ PROCESSING = "processing"
+ PROCESSED = "processed"
+ FAILED = "failed"
class RealTransactionSource(Auditable, models.Model, LogTargetMixin):
- source_file = models.FileField(upload_to='transaction_uploads/')
+ source_file = models.FileField(upload_to="transaction_uploads/")
state = models.CharField(
default=SourceState.NEW,
choices=SourceState.choices,
self.state = SourceState.FAILED
self.save()
raise Exception(
- 'More than one plugin tried to process the CSV upload: {}'.format(
- [r[0].__module__ + '.' + r[0].__name__ for r in responses]
+ "More than one plugin tried to process the CSV upload: {}".format(
+ [r[0].__module__ + "." + r[0].__name__ for r in responses]
)
)
if len(responses) < 1:
self.state = SourceState.FAILED
self.save()
- raise Exception('No plugin tried to process the CSV upload.')
+ raise Exception("No plugin tried to process the CSV upload.")
receiver, response = responses[0]
if isinstance(response, Exception):
return qs
def unbalanced_transactions(self):
- return self.with_balances().exclude(balances_debit=models.F('balances_credit'))
+ return self.with_balances().exclude(balances_debit=models.F("balances_credit"))
class TransactionManager(models.Manager):
def unbalanced_transactions(self):
return self.get_queryset().unbalanced_transactions()
- @log_call('.created')
+ @log_call(".created")
def create(self, *args, **kwargs):
return super().create(*args, **kwargs)
class Transaction(models.Model, LogTargetMixin):
objects = TransactionManager()
- LOG_TARGET_BASE = 'byro.bookkeeping.transaction'
+ LOG_TARGET_BASE = "byro.bookkeeping.transaction"
memo = models.CharField(max_length=1000, null=True)
booking_datetime = models.DateTimeField(null=True)
modified_by = models.ForeignKey(
to=get_user_model(),
on_delete=models.PROTECT,
- related_name='+', # no related lookup
+ related_name="+", # no related lookup
null=True,
)
reverses = models.ForeignKey(
- to='Transaction',
+ to="Transaction",
on_delete=models.PROTECT,
- related_name='reversed_by',
+ related_name="reversed_by",
null=True,
)
data = JSONField(null=True)
- documents = models.ManyToManyField(Document, through='DocumentTransactionLink')
+ documents = models.ManyToManyField(Document, through="DocumentTransactionLink")
- @log_call('.debit.created', log_on='self')
+ @log_call(".debit.created", log_on="self")
def debit(self, account, *args, **kwargs):
return Booking.objects.create(
transaction=self, debit_account=account, *args, **kwargs
)
- @log_call('.credit.created', log_on='self')
+ @log_call(".credit.created", log_on="self")
def credit(self, account, *args, **kwargs):
return Booking.objects.create(
transaction=self, credit_account=account, *args, **kwargs
@transaction.atomic
def reverse(self, value_datetime=None, *args, **kwargs):
- if 'user_or_context' not in kwargs:
+ if "user_or_context" not in kwargs:
raise TypeError(
"You need to provide a 'user_or_context' named parameter which indicates the responsible user (a User model object), request (a View instance or HttpRequest object), or generic context (a str)."
)
- user_or_context = kwargs.pop('user_or_context')
- user = kwargs.pop('user', None)
+ user_or_context = kwargs.pop("user_or_context")
+ user = kwargs.pop("user", None)
t = Transaction.objects.create(
value_datetime=value_datetime or self.value_datetime,
user=user,
)
t.save()
- self.log(user_or_context, '.reversed', user=user, reversed_by=t)
+ self.log(user_or_context, ".reversed", user=user, reversed_by=t)
return t
@property
def balances(self):
balances = {
- 'debit': self.debits.aggregate(total=models.Sum('amount'))['total'] or 0,
- 'credit': self.credits.aggregate(total=models.Sum('amount'))['total'] or 0,
+ "debit": self.debits.aggregate(total=models.Sum("amount"))["total"] or 0,
+ "credit": self.credits.aggregate(total=models.Sum("amount"))["total"] or 0,
}
return balances
@property
def is_balanced(self):
- if hasattr(self, 'balances_debit'):
+ if hasattr(self, "balances_debit"):
return self.balances_debit == self.balances_credit
else:
balances = self.balances
- return balances['debit'] == balances['credit']
+ return balances["debit"] == balances["credit"]
def find_memo(self):
if self.memo:
return self.memo
- if hasattr(self, 'cached_bookings'):
+ if hasattr(self, "cached_bookings"):
for booking in self.cached_bookings:
if booking.memo:
return booking.memo
from byro.bookkeeping.signals import process_transaction
response_counter = Counter()
- this_counter = Counter('dummy')
+ this_counter = Counter("dummy")
while (
not response_counter or response_counter.most_common(1)[0][1] < 5
response_counter += this_counter
if sum(response_counter.values()) < 1:
- raise Exception('No plugin tried to augment the transaction.')
+ raise Exception("No plugin tried to augment the transaction.")
response_counter += Counter() # Remove zero and negative elements
return len(response_counter)
)
def get_absolute_url(self):
- return reverse('office:finance.transactions.detail', kwargs={'pk': self.pk})
+ return reverse("office:finance.transactions.detail", kwargs={"pk": self.pk})
def get_object_icon(self):
return mark_safe('<i class="fa fa-money"></i> ')
def with_transaction_data(self):
qs = self.with_transaction_balances()
qs = qs.select_related(
- 'member', 'transaction', 'credit_account', 'debit_account'
+ "member", "transaction", "credit_account", "debit_account"
)
qs = qs.prefetch_related(
- Prefetch('transaction__bookings', to_attr='cached_bookings'),
- 'transaction__cached_bookings__credit_account',
- 'transaction__cached_bookings__debit_account',
- 'transaction__cached_bookings__member',
+ Prefetch("transaction__bookings", to_attr="cached_bookings"),
+ "transaction__cached_bookings__credit_account",
+ "transaction__cached_bookings__debit_account",
+ "transaction__cached_bookings__member",
)
return qs
modified_by = models.ForeignKey(
to=get_user_model(),
on_delete=models.PROTECT,
- related_name='+', # no related lookup
+ related_name="+", # no related lookup
null=True,
)
transaction = models.ForeignKey(
- to='Transaction', related_name='bookings', on_delete=models.PROTECT
+ to="Transaction", related_name="bookings", on_delete=models.PROTECT
)
amount = models.DecimalField(max_digits=8, decimal_places=2)
debit_account = models.ForeignKey(
- to='bookkeeping.Account',
- related_name='debits',
+ to="bookkeeping.Account",
+ related_name="debits",
on_delete=models.PROTECT,
null=True,
)
credit_account = models.ForeignKey(
- to='bookkeeping.Account',
- related_name='credits',
+ to="bookkeeping.Account",
+ related_name="credits",
on_delete=models.PROTECT,
null=True,
)
member = models.ForeignKey(
- to='members.Member',
- related_name='bookings',
+ to="members.Member",
+ related_name="bookings",
on_delete=models.PROTECT,
null=True,
)
importer = models.CharField(null=True, max_length=500)
data = JSONField(null=True)
source = models.ForeignKey(
- to='bookkeeping.RealTransactionSource',
+ to="bookkeeping.RealTransactionSource",
on_delete=models.SET_NULL,
- related_name='bookings',
+ related_name="bookings",
null=True,
)
def __str__(self):
return "{booking_type} {account} {amount} {memo}".format(
- booking_type='debit' if self.debit_account else 'credit',
+ booking_type="debit" if self.debit_account else "credit",
account=self.debit_account or self.credit_account,
amount=self.amount,
memo=self.memo,
# FUTURE: Should also add a signal or save handler for the same
# constraint in pure python
db_constraints = {
- 'exactly_either_debit_or_credit': 'CHECK (NOT ((debit_account_id IS NULL) = (credit_account_id IS NULL)))'
+ "exactly_either_debit_or_credit": "CHECK (NOT ((debit_account_id IS NULL) = (credit_account_id IS NULL)))"
}
def find_memo(self):
@property
def counter_bookings(self):
- if hasattr(self.transaction, 'cached_bookings'):
+ if hasattr(self.transaction, "cached_bookings"):
# Was prefetched with with_transaction_data()
if self.debit_account:
return [b for b in self.transaction.cached_bookings if b.credit_account]
with transaction.atomic():
account.log(
None,
- 'byro.bookkeeping.account.created',
+ "byro.bookkeeping.account.created",
source="Automatic creation of special account",
)
account.tags.add(tag)
@classproperty
def fees(cls):
- return cls.special_account('fees', AccountCategory.INCOME, _('Member fees'))
+ return cls.special_account("fees", AccountCategory.INCOME, _("Member fees"))
@classproperty
def donations(cls):
- return cls.special_account('donations', AccountCategory.INCOME, _('Donations'))
+ return cls.special_account("donations", AccountCategory.INCOME, _("Donations"))
@classproperty
def fees_receivable(cls):
return cls.special_account(
- 'fees_receivable', AccountCategory.ASSET, _('Member fees receivable')
+ "fees_receivable", AccountCategory.ASSET, _("Member fees receivable")
)
@classproperty
def bank(cls):
- return cls.special_account('bank', AccountCategory.ASSET, _('Bank'))
+ return cls.special_account("bank", AccountCategory.ASSET, _("Bank"))
@classproperty
def opening_balance(cls):
return cls.special_account(
- 'opening_balance', AccountCategory.ASSET, _('Opening balance')
+ "opening_balance", AccountCategory.ASSET, _("Opening balance")
)
@classproperty
def lost_income(cls):
return cls.special_account(
- 'lost_income', AccountCategory.EXPENSE, _('Lost income')
+ "lost_income", AccountCategory.EXPENSE, _("Lost income")
)
from django.conf import settings # noqa
-app = Celery('byro')
-app.config_from_object('django.conf:settings', namespace='CELERY')
+app = Celery("byro")
+app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
class CommonConfig(AppConfig):
- name = 'byro.common'
+ name = "byro.common"
def user_save_receiver(sender, instance, created, **kwargs):
content_object=instance,
action_type="byro.common.user.created",
data={
- 'source': 'User creation checkpoint',
- 'flags': {
- 'active': instance.is_active,
- 'superuser': instance.is_superuser,
- 'staff': instance.is_staff,
+ "source": "User creation checkpoint",
+ "flags": {
+ "active": instance.is_active,
+ "superuser": instance.is_superuser,
+ "staff": instance.is_staff,
},
},
)
-BOLD = '\033[1m'
-RESET = '\033[0m'
+BOLD = "\033[1m"
+RESET = "\033[0m"
def start_box(size):
try:
- print('┏' + '━' * size + '┓')
+ print("┏" + "━" * size + "┓")
except (UnicodeDecodeError, UnicodeEncodeError):
- print('-' * (size + 2))
+ print("-" * (size + 2))
def end_box(size):
try:
- print('┗' + '━' * size + '┛')
+ print("┗" + "━" * size + "┛")
except (UnicodeDecodeError, UnicodeEncodeError):
- print('-' * (size + 2))
+ print("-" * (size + 2))
def print_line(string, box=False, bold=False, color=None, size=None):
text_length = len(string)
alt_string = string
if bold:
- string = '{BOLD}{string}{RESET}'.format(BOLD=BOLD, string=string, RESET=RESET)
+ string = "{BOLD}{string}{RESET}".format(BOLD=BOLD, string=string, RESET=RESET)
if color:
- string = '{color}{string}{RESET}'.format(
+ string = "{color}{string}{RESET}".format(
color=color, string=string, RESET=RESET
)
if box:
if size:
if text_length + 2 < size:
- string += ' ' * (size - text_length - 2)
- alt_string += ' ' * (size - text_length - 2)
- string = '┃ {string} ┃'.format(string=string)
- alt_string = '| {string} |'.format(string=string)
+ string += " " * (size - text_length - 2)
+ alt_string += " " * (size - text_length - 2)
+ string = "┃ {string} ┃".format(string=string)
+ alt_string = "| {string} |".format(string=string)
try:
print(string)
except (UnicodeDecodeError, UnicodeEncodeError):
try:
print(alt_string)
except (UnicodeDecodeError, UnicodeEncodeError):
- print('unprintable setting')
+ print("unprintable setting")
def byro_information(request):
ctx = {
- 'config': Configuration.get_solo(),
- 'pending_mails': EMail.objects.filter(sent__isnull=True).count(),
- 'pending_transactions': Transaction.objects.unbalanced_transactions().count(),
- 'log_end': LogEntry.objects.get_chain_end(),
+ "config": Configuration.get_solo(),
+ "pending_mails": EMail.objects.filter(sent__isnull=True).count(),
+ "pending_transactions": Transaction.objects.unbalanced_transactions().count(),
+ "log_end": LogEntry.objects.get_chain_end(),
}
try:
- ctx['url_name'] = resolve(request.path_info).url_name
+ ctx["url_name"] = resolve(request.path_info).url_name
except Http404:
- ctx['url_name'] = ''
+ ctx["url_name"] = ""
if settings.DEBUG:
- ctx['development_warning'] = True
+ ctx["development_warning"] = True
- ctx['byro_version'] = get_version()
+ ctx["byro_version"] = get_version()
return ctx
else:
_nav_event.extend(response)
- return {'nav_event': _nav_event}
+ return {"nav_event": _nav_event}
# Date according to https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date
-SHORT_DATE_FORMAT = 'Y-m-d'
-SHORT_DATETIME_FORMAT = 'Y-m-d H:i'
-TIME_FORMAT = 'H:i'
+SHORT_DATE_FORMAT = "Y-m-d"
+SHORT_DATETIME_FORMAT = "Y-m-d H:i"
+TIME_FORMAT = "H:i"
from .configuration import ConfigurationForm, InitialForm
from .registration import RegistrationConfigForm
-__all__ = ['ConfigurationForm', 'InitialForm', 'RegistrationConfigForm']
+__all__ = ["ConfigurationForm", "InitialForm", "RegistrationConfigForm"]
class Meta:
model = Configuration
- fields = ('name', 'backoffice_mail', 'mail_from')
+ fields = ("name", "backoffice_mail", "mail_from")
class ConfigurationForm(forms.ModelForm):
class Meta:
model = Configuration
fields = (
- 'name',
- 'address',
- 'url',
- 'language',
- 'currency',
- 'mail_from',
- 'liability_interval',
+ "name",
+ "address",
+ "url",
+ "language",
+ "currency",
+ "mail_from",
+ "liability_interval",
)
class DefaultDates:
- TODAY = 'today'
- BEGINNING_MONTH = 'beginning_month'
- BEGINNING_MONTH_NEXT = 'beginning_month_next'
- BEGINNING_YEAR = 'beginning_year'
- BEGINNING_YEAR_NEXT = 'beginning_year_next'
- FIXED_DATE = 'fixed_date'
+ TODAY = "today"
+ BEGINNING_MONTH = "beginning_month"
+ BEGINNING_MONTH_NEXT = "beginning_month_next"
+ BEGINNING_YEAR = "beginning_year"
+ BEGINNING_YEAR_NEXT = "beginning_year_next"
+ FIXED_DATE = "fixed_date"
@classproperty
def choices(cls):
return (
- (None, '------------'),
- (cls.TODAY, _('Current day')),
- (cls.BEGINNING_MONTH, _('Beginning of current month')),
- (cls.BEGINNING_MONTH_NEXT, _('Beginning of next month')),
- (cls.BEGINNING_YEAR, _('Beginning of current year')),
- (cls.BEGINNING_YEAR_NEXT, _('Beginning of next year')),
- (cls.FIXED_DATE, _('Other/fixed date')),
+ (None, "------------"),
+ (cls.TODAY, _("Current day")),
+ (cls.BEGINNING_MONTH, _("Beginning of current month")),
+ (cls.BEGINNING_MONTH_NEXT, _("Beginning of next month")),
+ (cls.BEGINNING_YEAR, _("Beginning of current year")),
+ (cls.BEGINNING_YEAR_NEXT, _("Beginning of next year")),
+ (cls.FIXED_DATE, _("Other/fixed date")),
)
class DefaultBoolean:
@classproperty
def choices(cls):
- return ((None, '------------'), (False, _('False')), (True, _('True')))
+ return ((None, "------------"), (False, _("False")), (True, _("True")))
-SPECIAL_NAMES = {Member: 'member', Membership: 'membership'}
+SPECIAL_NAMES = {Member: "member", Membership: "membership"}
SPECIAL_ORDER = [
- 'member__number',
- 'member__name',
- 'member__address',
- 'member__email',
- 'membership__start',
- 'membership__interval',
- 'membership__amount',
+ "member__number",
+ "member__name",
+ "member__address",
+ "member__email",
+ "membership__start",
+ "membership__interval",
+ "membership__amount",
]
self.fields_extra = OrderedDict()
fieldsets = []
config = Configuration.get_solo().registration_form or []
- data = {entry['name']: entry for entry in config if 'name' in entry}
+ data = {entry["name"]: entry for entry in config if "name" in entry}
for model, field in self.get_form_fields():
- key = '{}__{}'.format(SPECIAL_NAMES.get(model, model.__name__), field.name)
+ key = "{}__{}".format(SPECIAL_NAMES.get(model, model.__name__), field.name)
entry = data.get(key, {})
verbose_name = field.verbose_name or field.name
if model not in SPECIAL_NAMES:
- verbose_name = '{verbose_name} ({model.__name__})'.format(
+ verbose_name = "{verbose_name} ({model.__name__})".format(
verbose_name=verbose_name, model=model
)
fields = OrderedDict()
- fields['position'] = forms.IntegerField(
+ fields["position"] = forms.IntegerField(
required=False, label=_("Position in form")
)
if isinstance(field, models.DateField):
- fields['default_date'] = forms.ChoiceField(
+ fields["default_date"] = forms.ChoiceField(
required=False,
- label=_('Default date'),
+ label=_("Default date"),
choices=DefaultDates.choices,
)
if isinstance(field, models.BooleanField):
- fields['default_boolean'] = forms.ChoiceField(
+ fields["default_boolean"] = forms.ChoiceField(
required=False,
- label=_('Default value'),
+ label=_("Default value"),
choices=DefaultBoolean.choices,
)
default_field = self.build_default_field(field, model)
if default_field:
- fields['default'] = default_field
+ fields["default"] = default_field
for name, form_field in fields.items():
form_field.initial = entry.get(name, form_field.initial)
fieldsets.append(
(
( # This part is responsible for sorting the model fields:
- data.get(key, {}).get('position', None)
+ data.get(key, {}).get("position", None)
or 998, # Position in form, if set (or 998)
SPECIAL_ORDER.index(key)
if key in SPECIAL_ORDER
def get_form_fields():
for model in [Member, Membership] + Member.profile_classes:
for field in model._meta.fields:
- if field.name in ('id', 'member') or (
- model is Member and field.name == 'membership_type'
+ if field.name in ("id", "member") or (
+ model is Member and field.name == "membership_type"
):
continue
yield (model, field)
def build_default_field(self, field, model):
- choices = getattr(field, 'choices', None)
+ choices = getattr(field, "choices", None)
if choices:
return forms.ChoiceField(
required=False,
- label=_('Default value'),
- choices=[(None, '-----------')] + list(choices),
+ label=_("Default value"),
+ choices=[(None, "-----------")] + list(choices),
)
- if not (model is Member and field.name == 'number'):
+ if not (model is Member and field.name == "number"):
if isinstance(field, models.CharField):
- return forms.CharField(required=False, label=_('Default value'))
+ return forms.CharField(required=False, label=_("Default value"))
elif isinstance(field, models.DecimalField):
return forms.DecimalField(
required=False,
- label=_('Default value'),
+ label=_("Default value"),
max_digits=field.max_digits,
decimal_places=field.decimal_places,
)
elif isinstance(field, models.DateField):
- return forms.CharField(required=False, label=_('Other/fixed date'))
+ return forms.CharField(required=False, label=_("Other/fixed date"))
def clean(self):
ret = super().clean()
if key.endswith("__position") and value is not None
]
if not len(list(positions)) == len(set(positions)):
- raise forms.ValidationError('Every position must be unique!')
+ raise forms.ValidationError("Every position must be unique!")
return ret
def save(self):
if not (value == "" or value is None):
if isinstance(value, Decimal):
value = str(value)
- if key == 'default_boolean':
- value = bool(value == 'True')
+ if key == "default_boolean":
+ value = bool(value == "True")
data.setdefault(name, {})[key] = value
data = [dict(name=key, **value) for (key, value) in data.items()]
config = Configuration.get_solo()
def add_arguments(self, parser):
parser.add_argument(
- '-a',
- '--data-include-actions',
- default=r'^.*$',
+ "-a",
+ "--data-include-actions",
+ default=r"^.*$",
type=str,
help="action_types for which to include the log 'data' member (Regex)",
)
parser.add_argument(
- '-A',
- '--data-exclude-actions',
+ "-A",
+ "--data-exclude-actions",
default=None,
type=str,
help="action_types for which to exclude the log 'data' member (Regex)",
)
def handle(self, *args, **options):
- include_re = re.compile(options['data_include_actions'])
+ include_re = re.compile(options["data_include_actions"])
exclude_re = (
- re.compile(options['data_exclude_actions'])
- if options['data_exclude_actions']
+ re.compile(options["data_exclude_actions"])
+ if options["data_exclude_actions"]
else None
)
outstream = sys.stdout
- outstream.write('[')
+ outstream.write("[")
is_first = True
last = None
current = LogEntry.objects.get_chain_end()
while current and current != last:
data = {
- 'hash': current.auth_hash,
- 'entry': current.get_authenticated_dict(),
+ "hash": current.auth_hash,
+ "entry": current.get_authenticated_dict(),
}
if include_re.search(current.action_type) and (
exclude_re is None or not exclude_re.search(current.action_type)
):
- data['data'] = current.data
+ data["data"] = current.data
- data_pretty = canonicaljson.encode_pretty_printed_json(data).decode('utf-8')
+ data_pretty = canonicaljson.encode_pretty_printed_json(data).decode("utf-8")
if not is_first:
- outstream.write(',\n ')
+ outstream.write(",\n ")
else:
- outstream.write('\n ')
+ outstream.write("\n ")
- outstream.write('\n '.join(data_pretty.split('\n')))
+ outstream.write("\n ".join(data_pretty.split("\n")))
is_first = False
last = current
current = current.auth_prev
- outstream.write('\n]\n')
+ outstream.write("\n]\n")
from byro.common.models.configuration import Configuration
from byro.members.models import FeeIntervals, Member, Membership, MembershipType
-SOURCE_TEST_DATA = 'Import of test data'
+SOURCE_TEST_DATA = "Import of test data"
def make_date(delta, end=False):
def create_configs(self):
config = Configuration.get_solo()
- config.name = 'Der Verein e.V.'
- config.address = 'Erich-Weinert-Straße 53\n39104 Magdeburg'
- config.url = 'https://dervereindervere.in'
- config.language = 'de'
- config.currency = 'EUR'
- config.mail_from = 'verein@dervereindervere.in'
- config.backoffice_mail = 'vorstanz@dervereindervere.in'
+ config.name = "Der Verein e.V."
+ config.address = "Erich-Weinert-Straße 53\n39104 Magdeburg"
+ config.url = "https://dervereindervere.in"
+ config.language = "de"
+ config.currency = "EUR"
+ config.mail_from = "verein@dervereindervere.in"
+ config.backoffice_mail = "vorstanz@dervereindervere.in"
config.registration_form = [
{"name": "member__number", "position": 1},
{"name": "member__name", "position": 2},
{"name": "membership__amount", "default": "23", "position": 7},
]
config.save()
- config.log(SOURCE_TEST_DATA, '.changed')
+ config.log(SOURCE_TEST_DATA, ".changed")
def make_paid(self, member, vaguely=False, overly=False, donates=0, pays_for=None):
member.update_liabilites()
t.save()
def create_membership_types(self):
- MembershipType.objects.create(name='Standard membership', amount=120)
+ MembershipType.objects.create(name="Standard membership", amount=120)
def create_members(self):
has_left = Member.objects.create(
- number='1',
- name='Francis Foundingmember',
- address='Foo St 1\nSome Place',
- email='francis@group.org',
+ number="1",
+ name="Francis Foundingmember",
+ address="Foo St 1\nSome Place",
+ email="francis@group.org",
)
Membership.objects.create(
member=has_left,
interval=FeeIntervals.MONTHLY,
amount=10,
)
- has_left.log(SOURCE_TEST_DATA, '.created')
+ has_left.log(SOURCE_TEST_DATA, ".created")
self.make_paid(has_left)
does_not_pay = Member.objects.create(
- number='2',
- name='Yohnny Yolo',
- address='Bar St 1\nSome Distant Place',
- email='yolo@group.org',
+ number="2",
+ name="Yohnny Yolo",
+ address="Bar St 1\nSome Distant Place",
+ email="yolo@group.org",
)
Membership.objects.create(
member=does_not_pay,
interval=FeeIntervals.MONTHLY,
amount=10,
)
- does_not_pay.log(SOURCE_TEST_DATA, '.created')
+ does_not_pay.log(SOURCE_TEST_DATA, ".created")
does_not_pay.update_liabilites()
pays_occasionally = Member.objects.create(
- number='3',
- name='Olga Occasional',
- address='Currently unknown',
- email='olga@group.org',
+ number="3",
+ name="Olga Occasional",
+ address="Currently unknown",
+ email="olga@group.org",
)
Membership.objects.create(
member=pays_occasionally,
interval=FeeIntervals.MONTHLY,
amount=10,
)
- pays_occasionally.log(SOURCE_TEST_DATA, '.created')
+ pays_occasionally.log(SOURCE_TEST_DATA, ".created")
self.make_paid(pays_occasionally, vaguely=True)
pays_regularly = Member.objects.create(
- number='4',
- name='Dennis Diligent',
- address='Best St 3\nFoo Town\nMy Country\nEarth\nUniverse',
- email='dennis@group.org',
+ number="4",
+ name="Dennis Diligent",
+ address="Best St 3\nFoo Town\nMy Country\nEarth\nUniverse",
+ email="dennis@group.org",
)
Membership.objects.create(
member=pays_regularly,
interval=FeeIntervals.MONTHLY,
amount=10,
)
- pays_regularly.log(SOURCE_TEST_DATA, '.created')
+ pays_regularly.log(SOURCE_TEST_DATA, ".created")
self.make_paid(pays_regularly)
pays_too_much = Member.objects.create(
- number='5',
- name='Omar Overachiever',
- address='SuperBest St 3\nSuperFoo Town',
- email='omar@group.org',
+ number="5",
+ name="Omar Overachiever",
+ address="SuperBest St 3\nSuperFoo Town",
+ email="omar@group.org",
)
Membership.objects.create(
member=pays_too_much,
interval=FeeIntervals.MONTHLY,
amount=10,
)
- pays_too_much.log(SOURCE_TEST_DATA, '.created')
+ pays_too_much.log(SOURCE_TEST_DATA, ".created")
self.make_paid(pays_too_much, overly=True)
will_join = Member.objects.create(
- number='6',
- name='Francine Futuremember',
- address='Future St 3\nFuture Town',
- email='francine@group.org',
+ number="6",
+ name="Francine Futuremember",
+ address="Future St 3\nFuture Town",
+ email="francine@group.org",
)
Membership.objects.create(
member=will_join,
interval=FeeIntervals.MONTHLY,
amount=10,
)
- will_join.log(SOURCE_TEST_DATA, '.created')
+ will_join.log(SOURCE_TEST_DATA, ".created")
giver = Member.objects.create(
- number='7',
- name='George Giver',
- address='Generous St 3\nEnd-of-the-rainbow Hearth',
- email='george@group.org',
+ number="7",
+ name="George Giver",
+ address="Generous St 3\nEnd-of-the-rainbow Hearth",
+ email="george@group.org",
)
Membership.objects.create(
member=giver,
interval=FeeIntervals.MONTHLY,
amount=10,
)
- giver.log(SOURCE_TEST_DATA, '.created')
+ giver.log(SOURCE_TEST_DATA, ".created")
self.make_paid(giver, donates=5)
is_payed_for = Member.objects.create(
- number='8',
- name='Peter Partner',
- address='Commune St 3\nFamily Shire',
- email='peter@group.org',
+ number="8",
+ name="Peter Partner",
+ address="Commune St 3\nFamily Shire",
+ email="peter@group.org",
)
Membership.objects.create(
member=is_payed_for,
interval=FeeIntervals.MONTHLY,
amount=10,
)
- is_payed_for.log(SOURCE_TEST_DATA, '.created')
+ is_payed_for.log(SOURCE_TEST_DATA, ".created")
is_payed_for.update_liabilites()
pays_other = Member.objects.create(
- number='9',
- name='Aaron Alsopayer',
- address='Commune St 3\nFamily Shire',
- email='aaron@group.org',
+ number="9",
+ name="Aaron Alsopayer",
+ address="Commune St 3\nFamily Shire",
+ email="aaron@group.org",
)
Membership.objects.create(
member=pays_other,
interval=FeeIntervals.MONTHLY,
amount=10,
)
- pays_other.log(SOURCE_TEST_DATA, '.created')
+ pays_other.log(SOURCE_TEST_DATA, ".created")
self.make_paid(pays_other, pays_for=is_payed_for)
def create_bank_chaff(self):
class SettingsMiddleware:
- ALLOWED_URLS = ('settings.registration', 'settings.initial', 'settings.plugins')
+ ALLOWED_URLS = ("settings.registration", "settings.initial", "settings.plugins")
def __init__(self, get_response):
self.get_response = get_response
url = resolve(request.path_info)
if not request.user.is_anonymous and url.url_name not in self.ALLOWED_URLS:
config = Configuration.get_solo()
- values = ('name', 'backoffice_mail', 'mail_from')
+ values = ("name", "backoffice_mail", "mail_from")
if not all(getattr(config, value, None) for value in values):
- return redirect('office:settings.initial')
+ return redirect("office:settings.initial")
return self.get_response(request)
class PermissionMiddleware:
- UNAUTHENTICATED_URLS = ('login', 'logout', 'log.info')
+ UNAUTHENTICATED_URLS = ("login", "logout", "log.info")
def __init__(self, get_response):
self.get_response = get_response
if not allow:
return redirect(
- reverse('common:login') + '?next={request.path}'.format(request=request)
+ reverse("common:login") + "?next={request.path}".format(request=request)
)
else:
return self.get_response(request)
initial = True
- dependencies = [
- ]
+ dependencies = []
operations = [
migrations.CreateModel(
- name='Configuration',
+ name="Configuration",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(blank=True, max_length=100, null=True, verbose_name='name')),
- ('address', models.CharField(blank=True, max_length=500, null=True, verbose_name='address')),
- ('url', models.CharField(blank=True, max_length=200, null=True, verbose_name='url')),
- ('language', models.CharField(blank=True, max_length=5, null=True, verbose_name='language')),
- ('currency', models.CharField(blank=True, max_length=3, null=True, verbose_name='currency')),
- ('registration_form', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, verbose_name='registration form configuration')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "name",
+ models.CharField(
+ blank=True, max_length=100, null=True, verbose_name="name"
+ ),
+ ),
+ (
+ "address",
+ models.CharField(
+ blank=True, max_length=500, null=True, verbose_name="address"
+ ),
+ ),
+ (
+ "url",
+ models.CharField(
+ blank=True, max_length=200, null=True, verbose_name="url"
+ ),
+ ),
+ (
+ "language",
+ models.CharField(
+ blank=True, max_length=5, null=True, verbose_name="language"
+ ),
+ ),
+ (
+ "currency",
+ models.CharField(
+ blank=True, max_length=3, null=True, verbose_name="currency"
+ ),
+ ),
+ (
+ "registration_form",
+ django.contrib.postgres.fields.jsonb.JSONField(
+ blank=True,
+ null=True,
+ verbose_name="registration form configuration",
+ ),
+ ),
],
- options={
- 'abstract': False,
- },
- ),
+ options={"abstract": False},
+ )
]
class Migration(migrations.Migration):
- dependencies = [
- ('common', '0001_initial'),
- ]
+ dependencies = [("common", "0001_initial")]
operations = [
migrations.AddField(
- model_name='configuration',
- name='mail_from',
- field=models.EmailField(blank=True, max_length=100, null=True, verbose_name='e-mail sender address'),
- ),
+ model_name="configuration",
+ name="mail_from",
+ field=models.EmailField(
+ blank=True,
+ max_length=100,
+ null=True,
+ verbose_name="e-mail sender address",
+ ),
+ )
]
class Migration(migrations.Migration):
- dependencies = [
- ('common', '0002_configuration_mail_from'),
- ]
+ dependencies = [("common", "0002_configuration_mail_from")]
operations = [
migrations.AlterField(
- model_name='configuration',
- name='address',
- field=models.TextField(blank=True, max_length=500, null=True, verbose_name='address'),
- ),
+ model_name="configuration",
+ name="address",
+ field=models.TextField(
+ blank=True, max_length=500, null=True, verbose_name="address"
+ ),
+ )
]
class Migration(migrations.Migration):
- dependencies = [
- ('common', '0003_auto_20171013_1436'),
- ]
+ dependencies = [("common", "0003_auto_20171013_1436")]
operations = [
migrations.AlterField(
- model_name='configuration',
- name='language',
- field=models.CharField(blank=True, choices=[('af', 'Afrikaans'), ('ar', 'Arabic'), ('ast', 'Asturian'), ('az', 'Azerbaijani'), ('bg', 'Bulgarian'), ('be', 'Belarusian'), ('bn', 'Bengali'), ('br', 'Breton'), ('bs', 'Bosnian'), ('ca', 'Catalan'), ('cs', 'Czech'), ('cy', 'Welsh'), ('da', 'Danish'), ('de', 'German'), ('dsb', 'Lower Sorbian'), ('el', 'Greek'), ('en', 'English'), ('en-au', 'Australian English'), ('en-gb', 'British English'), ('eo', 'Esperanto'), ('es', 'Spanish'), ('es-ar', 'Argentinian Spanish'), ('es-co', 'Colombian Spanish'), ('es-mx', 'Mexican Spanish'), ('es-ni', 'Nicaraguan Spanish'), ('es-ve', 'Venezuelan Spanish'), ('et', 'Estonian'), ('eu', 'Basque'), ('fa', 'Persian'), ('fi', 'Finnish'), ('fr', 'French'), ('fy', 'Frisian'), ('ga', 'Irish'), ('gd', 'Scottish Gaelic'), ('gl', 'Galician'), ('he', 'Hebrew'), ('hi', 'Hindi'), ('hr', 'Croatian'), ('hsb', 'Upper Sorbian'), ('hu', 'Hungarian'), ('ia', 'Interlingua'), ('id', 'Indonesian'), ('io', 'Ido'), ('is', 'Icelandic'), ('it', 'Italian'), ('ja', 'Japanese'), ('ka', 'Georgian'), ('kab', 'Kabyle'), ('kk', 'Kazakh'), ('km', 'Khmer'), ('kn', 'Kannada'), ('ko', 'Korean'), ('lb', 'Luxembourgish'), ('lt', 'Lithuanian'), ('lv', 'Latvian'), ('mk', 'Macedonian'), ('ml', 'Malayalam'), ('mn', 'Mongolian'), ('mr', 'Marathi'), ('my', 'Burmese'), ('nb', 'Norwegian Bokmål'), ('ne', 'Nepali'), ('nl', 'Dutch'), ('nn', 'Norwegian Nynorsk'), ('os', 'Ossetic'), ('pa', 'Punjabi'), ('pl', 'Polish'), ('pt', 'Portuguese'), ('pt-br', 'Brazilian Portuguese'), ('ro', 'Romanian'), ('ru', 'Russian'), ('sk', 'Slovak'), ('sl', 'Slovenian'), ('sq', 'Albanian'), ('sr', 'Serbian'), ('sr-latn', 'Serbian Latin'), ('sv', 'Swedish'), ('sw', 'Swahili'), ('ta', 'Tamil'), ('te', 'Telugu'), ('th', 'Thai'), ('tr', 'Turkish'), ('tt', 'Tatar'), ('udm', 'Udmurt'), ('uk', 'Ukrainian'), ('ur', 'Urdu'), ('vi', 'Vietnamese'), ('zh-hans', 'Simplified Chinese'), ('zh-hant', 'Traditional Chinese')], max_length=5, null=True, verbose_name='language'),
- ),
+ model_name="configuration",
+ name="language",
+ field=models.CharField(
+ blank=True,
+ choices=[
+ ("af", "Afrikaans"),
+ ("ar", "Arabic"),
+ ("ast", "Asturian"),
+ ("az", "Azerbaijani"),
+ ("bg", "Bulgarian"),
+ ("be", "Belarusian"),
+ ("bn", "Bengali"),
+ ("br", "Breton"),
+ ("bs", "Bosnian"),
+ ("ca", "Catalan"),
+ ("cs", "Czech"),
+ ("cy", "Welsh"),
+ ("da", "Danish"),
+ ("de", "German"),
+ ("dsb", "Lower Sorbian"),
+ ("el", "Greek"),
+ ("en", "English"),
+ ("en-au", "Australian English"),
+ ("en-gb", "British English"),
+ ("eo", "Esperanto"),
+ ("es", "Spanish"),
+ ("es-ar", "Argentinian Spanish"),
+ ("es-co", "Colombian Spanish"),
+ ("es-mx", "Mexican Spanish"),
+ ("es-ni", "Nicaraguan Spanish"),
+ ("es-ve", "Venezuelan Spanish"),
+ ("et", "Estonian"),
+ ("eu", "Basque"),
+ ("fa", "Persian"),
+ ("fi", "Finnish"),
+ ("fr", "French"),
+ ("fy", "Frisian"),
+ ("ga", "Irish"),
+ ("gd", "Scottish Gaelic"),
+ ("gl", "Galician"),
+ ("he", "Hebrew"),
+ ("hi", "Hindi"),
+ ("hr", "Croatian"),
+ ("hsb", "Upper Sorbian"),
+ ("hu", "Hungarian"),
+ ("ia", "Interlingua"),
+ ("id", "Indonesian"),
+ ("io", "Ido"),
+ ("is", "Icelandic"),
+ ("it", "Italian"),
+ ("ja", "Japanese"),
+ ("ka", "Georgian"),
+ ("kab", "Kabyle"),
+ ("kk", "Kazakh"),
+ ("km", "Khmer"),
+ ("kn", "Kannada"),
+ ("ko", "Korean"),
+ ("lb", "Luxembourgish"),
+ ("lt", "Lithuanian"),
+ ("lv", "Latvian"),
+ ("mk", "Macedonian"),
+ ("ml", "Malayalam"),
+ ("mn", "Mongolian"),
+ ("mr", "Marathi"),
+ ("my", "Burmese"),
+ ("nb", "Norwegian Bokmål"),
+ ("ne", "Nepali"),
+ ("nl", "Dutch"),
+ ("nn", "Norwegian Nynorsk"),
+ ("os", "Ossetic"),
+ ("pa", "Punjabi"),
+ ("pl", "Polish"),
+ ("pt", "Portuguese"),
+ ("pt-br", "Brazilian Portuguese"),
+ ("ro", "Romanian"),
+ ("ru", "Russian"),
+ ("sk", "Slovak"),
+ ("sl", "Slovenian"),
+ ("sq", "Albanian"),
+ ("sr", "Serbian"),
+ ("sr-latn", "Serbian Latin"),
+ ("sv", "Swedish"),
+ ("sw", "Swahili"),
+ ("ta", "Tamil"),
+ ("te", "Telugu"),
+ ("th", "Thai"),
+ ("tr", "Turkish"),
+ ("tt", "Tatar"),
+ ("udm", "Udmurt"),
+ ("uk", "Ukrainian"),
+ ("ur", "Urdu"),
+ ("vi", "Vietnamese"),
+ ("zh-hans", "Simplified Chinese"),
+ ("zh-hant", "Traditional Chinese"),
+ ],
+ max_length=5,
+ null=True,
+ verbose_name="language",
+ ),
+ )
]
class Migration(migrations.Migration):
- dependencies = [
- ('common', '0004_auto_20180111_1807'),
- ]
+ dependencies = [("common", "0004_auto_20180111_1807")]
operations = [
migrations.AddField(
- model_name='configuration',
- name='liability_interval',
- field=models.IntegerField(default=36, help_text='For which interval should remaining fees be calculated?', verbose_name='Liability interval'),
- ),
+ model_name="configuration",
+ name="liability_interval",
+ field=models.IntegerField(
+ default=36,
+ help_text="For which interval should remaining fees be calculated?",
+ verbose_name="Liability interval",
+ ),
+ )
]
class Migration(migrations.Migration):
dependencies = [
- ('mails', '0003_mailtemplate_reply_to'),
- ('common', '0005_configuration_liability_interval'),
+ ("mails", "0003_mailtemplate_reply_to"),
+ ("common", "0005_configuration_liability_interval"),
]
operations = [
migrations.AddField(
- model_name='configuration',
- name='leave_member_template',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='mails.MailTemplate'),
+ model_name="configuration",
+ name="leave_member_template",
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="+",
+ to="mails.MailTemplate",
+ ),
),
migrations.AddField(
- model_name='configuration',
- name='leave_office_template',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='mails.MailTemplate'),
+ model_name="configuration",
+ name="leave_office_template",
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="+",
+ to="mails.MailTemplate",
+ ),
),
migrations.AddField(
- model_name='configuration',
- name='welcome_member_template',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='mails.MailTemplate'),
+ model_name="configuration",
+ name="welcome_member_template",
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="+",
+ to="mails.MailTemplate",
+ ),
),
migrations.AddField(
- model_name='configuration',
- name='welcome_office_template',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='mails.MailTemplate'),
+ model_name="configuration",
+ name="welcome_office_template",
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="+",
+ to="mails.MailTemplate",
+ ),
),
]
def init_templates(apps, schema_editor):
from byro.mails import default
- MailTemplate = apps.get_model('mails', 'MailTemplate')
- Configuration = apps.get_model('common', 'Configuration')
+
+ MailTemplate = apps.get_model("mails", "MailTemplate")
+ Configuration = apps.get_model("common", "Configuration")
config, _ = Configuration.objects.get_or_create()
if not config.welcome_member_template:
welcome_member = MailTemplate.objects.create(
- subject=default.WELCOME_MEMBER_SUBJECT,
- text=default.WELCOME_MEMBER_TEXT,
+ subject=default.WELCOME_MEMBER_SUBJECT, text=default.WELCOME_MEMBER_TEXT
)
config.welcome_member_template = welcome_member
if not config.welcome_office_template:
welcome_office = MailTemplate.objects.create(
- subject=default.WELCOME_OFFICE_SUBJECT,
- text=default.WELCOME_OFFICE_TEXT,
+ subject=default.WELCOME_OFFICE_SUBJECT, text=default.WELCOME_OFFICE_TEXT
)
config.welcome_office_template = welcome_office
config.save()
class Migration(migrations.Migration):
- dependencies = [
- ('common', '0006_auto_20180224_2114'),
- ]
+ dependencies = [("common", "0006_auto_20180224_2114")]
- operations = [
- migrations.RunPython(init_templates, migrations.RunPython.noop)
- ]
+ operations = [migrations.RunPython(init_templates, migrations.RunPython.noop)]
class Migration(migrations.Migration):
- dependencies = [
- ('common', '0007_auto_20180224_2114'),
- ]
+ dependencies = [("common", "0007_auto_20180224_2114")]
operations = [
migrations.AddField(
- model_name='configuration',
- name='backoffice_mail',
- field=models.EmailField(blank=True, max_length=100, null=True, verbose_name='e-mail of backoffice'),
- ),
+ model_name="configuration",
+ name="backoffice_mail",
+ field=models.EmailField(
+ blank=True,
+ max_length=100,
+ null=True,
+ verbose_name="e-mail of backoffice",
+ ),
+ )
]
def init_templates(apps, schema_editor):
from byro.mails import default
- MailTemplate = apps.get_model('mails', 'MailTemplate')
- Configuration = apps.get_model('common', 'Configuration')
+
+ MailTemplate = apps.get_model("mails", "MailTemplate")
+ Configuration = apps.get_model("common", "Configuration")
config, _ = Configuration.objects.get_or_create()
if not config.leave_member_template:
leave_member = MailTemplate.objects.create(
- subject=default.LEAVE_MEMBER_SUBJECT,
- text=default.LEAVE_MEMBER_TEXT,
+ subject=default.LEAVE_MEMBER_SUBJECT, text=default.LEAVE_MEMBER_TEXT
)
config.leave_member_template = leave_member
if not config.leave_office_template:
class Migration(migrations.Migration):
- dependencies = [
- ('common', '0008_configuration_backoffice_mail'),
- ]
+ dependencies = [("common", "0008_configuration_backoffice_mail")]
- operations = [
- migrations.RunPython(init_templates, migrations.RunPython.noop)
- ]
+ operations = [migrations.RunPython(init_templates, migrations.RunPython.noop)]
class Migration(migrations.Migration):
- dependencies = [
- ('common', '0009_mail_templates'),
- ]
+ dependencies = [("common", "0009_mail_templates")]
operations = [
migrations.AlterField(
- model_name='configuration',
- name='address',
- field=models.TextField(blank=True, max_length=500, null=True, verbose_name='Association address'),
+ model_name="configuration",
+ name="address",
+ field=models.TextField(
+ blank=True,
+ max_length=500,
+ null=True,
+ verbose_name="Association address",
+ ),
),
migrations.AlterField(
- model_name='configuration',
- name='backoffice_mail',
- field=models.EmailField(blank=True, max_length=100, null=True, verbose_name='E-mail address for notifications'),
+ model_name="configuration",
+ name="backoffice_mail",
+ field=models.EmailField(
+ blank=True,
+ max_length=100,
+ null=True,
+ verbose_name="E-mail address for notifications",
+ ),
),
migrations.AlterField(
- model_name='configuration',
- name='currency',
- field=models.CharField(blank=True, help_text='E.g. EUR', max_length=3, null=True, verbose_name='Currency'),
+ model_name="configuration",
+ name="currency",
+ field=models.CharField(
+ blank=True,
+ help_text="E.g. EUR",
+ max_length=3,
+ null=True,
+ verbose_name="Currency",
+ ),
),
migrations.AlterField(
- model_name='configuration',
- name='language',
- field=models.CharField(blank=True, choices=[('af', 'Afrikaans'), ('ar', 'Arabic'), ('ast', 'Asturian'), ('az', 'Azerbaijani'), ('bg', 'Bulgarian'), ('be', 'Belarusian'), ('bn', 'Bengali'), ('br', 'Breton'), ('bs', 'Bosnian'), ('ca', 'Catalan'), ('cs', 'Czech'), ('cy', 'Welsh'), ('da', 'Danish'), ('de', 'German'), ('dsb', 'Lower Sorbian'), ('el', 'Greek'), ('en', 'English'), ('en-au', 'Australian English'), ('en-gb', 'British English'), ('eo', 'Esperanto'), ('es', 'Spanish'), ('es-ar', 'Argentinian Spanish'), ('es-co', 'Colombian Spanish'), ('es-mx', 'Mexican Spanish'), ('es-ni', 'Nicaraguan Spanish'), ('es-ve', 'Venezuelan Spanish'), ('et', 'Estonian'), ('eu', 'Basque'), ('fa', 'Persian'), ('fi', 'Finnish'), ('fr', 'French'), ('fy', 'Frisian'), ('ga', 'Irish'), ('gd', 'Scottish Gaelic'), ('gl', 'Galician'), ('he', 'Hebrew'), ('hi', 'Hindi'), ('hr', 'Croatian'), ('hsb', 'Upper Sorbian'), ('hu', 'Hungarian'), ('ia', 'Interlingua'), ('id', 'Indonesian'), ('io', 'Ido'), ('is', 'Icelandic'), ('it', 'Italian'), ('ja', 'Japanese'), ('ka', 'Georgian'), ('kab', 'Kabyle'), ('kk', 'Kazakh'), ('km', 'Khmer'), ('kn', 'Kannada'), ('ko', 'Korean'), ('lb', 'Luxembourgish'), ('lt', 'Lithuanian'), ('lv', 'Latvian'), ('mk', 'Macedonian'), ('ml', 'Malayalam'), ('mn', 'Mongolian'), ('mr', 'Marathi'), ('my', 'Burmese'), ('nb', 'Norwegian Bokmål'), ('ne', 'Nepali'), ('nl', 'Dutch'), ('nn', 'Norwegian Nynorsk'), ('os', 'Ossetic'), ('pa', 'Punjabi'), ('pl', 'Polish'), ('pt', 'Portuguese'), ('pt-br', 'Brazilian Portuguese'), ('ro', 'Romanian'), ('ru', 'Russian'), ('sk', 'Slovak'), ('sl', 'Slovenian'), ('sq', 'Albanian'), ('sr', 'Serbian'), ('sr-latn', 'Serbian Latin'), ('sv', 'Swedish'), ('sw', 'Swahili'), ('ta', 'Tamil'), ('te', 'Telugu'), ('th', 'Thai'), ('tr', 'Turkish'), ('tt', 'Tatar'), ('udm', 'Udmurt'), ('uk', 'Ukrainian'), ('ur', 'Urdu'), ('vi', 'Vietnamese'), ('zh-hans', 'Simplified Chinese'), ('zh-hant', 'Traditional Chinese')], max_length=5, null=True, verbose_name='Language'),
+ model_name="configuration",
+ name="language",
+ field=models.CharField(
+ blank=True,
+ choices=[
+ ("af", "Afrikaans"),
+ ("ar", "Arabic"),
+ ("ast", "Asturian"),
+ ("az", "Azerbaijani"),
+ ("bg", "Bulgarian"),
+ ("be", "Belarusian"),
+ ("bn", "Bengali"),
+ ("br", "Breton"),
+ ("bs", "Bosnian"),
+ ("ca", "Catalan"),
+ ("cs", "Czech"),
+ ("cy", "Welsh"),
+ ("da", "Danish"),
+ ("de", "German"),
+ ("dsb", "Lower Sorbian"),
+ ("el", "Greek"),
+ ("en", "English"),
+ ("en-au", "Australian English"),
+ ("en-gb", "British English"),
+ ("eo", "Esperanto"),
+ ("es", "Spanish"),
+ ("es-ar", "Argentinian Spanish"),
+ ("es-co", "Colombian Spanish"),
+ ("es-mx", "Mexican Spanish"),
+ ("es-ni", "Nicaraguan Spanish"),
+ ("es-ve", "Venezuelan Spanish"),
+ ("et", "Estonian"),
+ ("eu", "Basque"),
+ ("fa", "Persian"),
+ ("fi", "Finnish"),
+ ("fr", "French"),
+ ("fy", "Frisian"),
+ ("ga", "Irish"),
+ ("gd", "Scottish Gaelic"),
+ ("gl", "Galician"),
+ ("he", "Hebrew"),
+ ("hi", "Hindi"),
+ ("hr", "Croatian"),
+ ("hsb", "Upper Sorbian"),
+ ("hu", "Hungarian"),
+ ("ia", "Interlingua"),
+ ("id", "Indonesian"),
+ ("io", "Ido"),
+ ("is", "Icelandic"),
+ ("it", "Italian"),
+ ("ja", "Japanese"),
+ ("ka", "Georgian"),
+ ("kab", "Kabyle"),
+ ("kk", "Kazakh"),
+ ("km", "Khmer"),
+ ("kn", "Kannada"),
+ ("ko", "Korean"),
+ ("lb", "Luxembourgish"),
+ ("lt", "Lithuanian"),
+ ("lv", "Latvian"),
+ ("mk", "Macedonian"),
+ ("ml", "Malayalam"),
+ ("mn", "Mongolian"),
+ ("mr", "Marathi"),
+ ("my", "Burmese"),
+ ("nb", "Norwegian Bokmål"),
+ ("ne", "Nepali"),
+ ("nl", "Dutch"),
+ ("nn", "Norwegian Nynorsk"),
+ ("os", "Ossetic"),
+ ("pa", "Punjabi"),
+ ("pl", "Polish"),
+ ("pt", "Portuguese"),
+ ("pt-br", "Brazilian Portuguese"),
+ ("ro", "Romanian"),
+ ("ru", "Russian"),
+ ("sk", "Slovak"),
+ ("sl", "Slovenian"),
+ ("sq", "Albanian"),
+ ("sr", "Serbian"),
+ ("sr-latn", "Serbian Latin"),
+ ("sv", "Swedish"),
+ ("sw", "Swahili"),
+ ("ta", "Tamil"),
+ ("te", "Telugu"),
+ ("th", "Thai"),
+ ("tr", "Turkish"),
+ ("tt", "Tatar"),
+ ("udm", "Udmurt"),
+ ("uk", "Ukrainian"),
+ ("ur", "Urdu"),
+ ("vi", "Vietnamese"),
+ ("zh-hans", "Simplified Chinese"),
+ ("zh-hant", "Traditional Chinese"),
+ ],
+ max_length=5,
+ null=True,
+ verbose_name="Language",
+ ),
),
migrations.AlterField(
- model_name='configuration',
- name='liability_interval',
- field=models.IntegerField(default=36, help_text='For which interval can you make members pay their outstanding fees?', verbose_name='Statute of limitations'),
+ model_name="configuration",
+ name="liability_interval",
+ field=models.IntegerField(
+ default=36,
+ help_text="For which interval can you make members pay their outstanding fees?",
+ verbose_name="Statute of limitations",
+ ),
),
migrations.AlterField(
- model_name='configuration',
- name='mail_from',
- field=models.EmailField(blank=True, max_length=100, null=True, verbose_name='E-mail address used as sender'),
+ model_name="configuration",
+ name="mail_from",
+ field=models.EmailField(
+ blank=True,
+ max_length=100,
+ null=True,
+ verbose_name="E-mail address used as sender",
+ ),
),
migrations.AlterField(
- model_name='configuration',
- name='name',
- field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Association name'),
+ model_name="configuration",
+ name="name",
+ field=models.CharField(
+ blank=True, max_length=100, null=True, verbose_name="Association name"
+ ),
),
migrations.AlterField(
- model_name='configuration',
- name='registration_form',
+ model_name="configuration",
+ name="registration_form",
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True),
),
migrations.AlterField(
- model_name='configuration',
- name='url',
- field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Association URL'),
+ model_name="configuration",
+ name="url",
+ field=models.CharField(
+ blank=True, max_length=200, null=True, verbose_name="Association URL"
+ ),
),
]
class Migration(migrations.Migration):
- dependencies = [
- ('mails', '0007_auto_20180929_0915'),
- ('common', '0011_logentry'),
- ]
+ dependencies = [("mails", "0007_auto_20180929_0915"), ("common", "0011_logentry")]
operations = [
migrations.AddField(
- model_name='configuration',
- name='record_disclosure_template',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='mails.MailTemplate'),
- ),
+ model_name="configuration",
+ name="record_disclosure_template",
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="+",
+ to="mails.MailTemplate",
+ ),
+ )
]
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ('contenttypes', '0002_remove_content_type_name'),
- ('common', '0010_auto_20180929_1052'),
+ ("contenttypes", "0002_remove_content_type_name"),
+ ("common", "0010_auto_20180929_1052"),
]
operations = [
migrations.CreateModel(
- name='LogEntry',
+ name="LogEntry",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('object_id', models.PositiveIntegerField(db_index=True)),
- ('datetime', models.DateTimeField(auto_now_add=True, db_index=True)),
- ('action_type', models.CharField(max_length=255)),
- ('data', django.contrib.postgres.fields.jsonb.JSONField(null=True)),
- ('content_type', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='contenttypes.ContentType')),
- ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("object_id", models.PositiveIntegerField(db_index=True)),
+ ("datetime", models.DateTimeField(auto_now_add=True, db_index=True)),
+ ("action_type", models.CharField(max_length=255)),
+ ("data", django.contrib.postgres.fields.jsonb.JSONField(null=True)),
+ (
+ "content_type",
+ models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ to="contenttypes.ContentType",
+ ),
+ ),
+ (
+ "user",
+ models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
],
- options={
- 'ordering': ('-datetime', '-id'),
- },
- ),
+ options={"ordering": ("-datetime", "-id")},
+ )
]
def init_templates(apps, schema_editor):
from byro.mails import default
- MailTemplate = apps.get_model('mails', 'MailTemplate')
- Configuration = apps.get_model('common', 'Configuration')
+
+ MailTemplate = apps.get_model("mails", "MailTemplate")
+ Configuration = apps.get_model("common", "Configuration")
config, _ = Configuration.objects.get_or_create()
if not config.record_disclosure_template:
template = MailTemplate.objects.create(
class Migration(migrations.Migration):
- dependencies = [
- ('common', '0011_configuration_record_disclosure_template'),
- ]
+ dependencies = [("common", "0011_configuration_record_disclosure_template")]
- operations = [
- migrations.RunPython(init_templates, migrations.RunPython.noop)
- ]
+ operations = [migrations.RunPython(init_templates, migrations.RunPython.noop)]
from uuid import uuid4
+
def fill_hash(apps, schema_editor):
- LogEntry = apps.get_model('common', 'LogEntry')
+ LogEntry = apps.get_model("common", "LogEntry")
for entry in LogEntry.objects.filter(auth_hash__isnull=True).all():
entry.auth_hash = "random:{}".format(uuid4().hex)
- entry.save(update_fields=['auth_hash'])
+ entry.save(update_fields=["auth_hash"])
+
def fill_prev(apps, schema_editor):
- LogEntry = apps.get_model('common', 'LogEntry')
+ LogEntry = apps.get_model("common", "LogEntry")
prev = None
- for entry in LogEntry.objects.filter(auth_prev='undefined:0').order_by('datetime', 'id').all():
+ for entry in (
+ LogEntry.objects.filter(auth_prev="undefined:0")
+ .order_by("datetime", "id")
+ .all()
+ ):
entry.auth_prev = prev or entry
prev = entry
- entry.save(update_fields=['auth_prev'])
+ entry.save(update_fields=["auth_prev"])
+
class Migration(migrations.Migration):
- dependencies = [
- ('common', '0012_auto_20180929_1317'),
- ]
+ dependencies = [("common", "0012_auto_20180929_1317")]
operations = [
# https://stackoverflow.com/questions/28429933/django-migrations-using-runpython-to-commit-changes/39541048#39541048
- migrations.RunSQL('SET CONSTRAINTS ALL IMMEDIATE',
- reverse_sql=migrations.RunSQL.noop),
+ migrations.RunSQL(
+ "SET CONSTRAINTS ALL IMMEDIATE", reverse_sql=migrations.RunSQL.noop
+ ),
migrations.AlterField(
- model_name='logentry',
- name='datetime',
- field=models.DateTimeField(db_index=True, default=django.utils.timezone.now),
+ model_name="logentry",
+ name="datetime",
+ field=models.DateTimeField(
+ db_index=True, default=django.utils.timezone.now
+ ),
),
migrations.AddField(
- model_name='logentry',
- name='auth_data',
+ model_name="logentry",
+ name="auth_data",
field=django.contrib.postgres.fields.jsonb.JSONField(default={}),
preserve_default=False,
),
migrations.AddField(
- model_name='logentry',
- name='auth_hash',
+ model_name="logentry",
+ name="auth_hash",
field=models.CharField(null=True, max_length=70),
),
migrations.RunPython(fill_hash, migrations.RunPython.noop),
migrations.AlterField(
- model_name='logentry',
- name='auth_hash',
+ model_name="logentry",
+ name="auth_hash",
field=models.CharField(max_length=140, unique=True, null=False),
),
migrations.AddField(
- model_name='logentry',
- name='auth_prev',
- field=models.ForeignKey(default='undefined:0', on_delete=django.db.models.deletion.PROTECT, related_name='auth_next', to='common.LogEntry', to_field='auth_hash', null=False, blank=False),
+ model_name="logentry",
+ name="auth_prev",
+ field=models.ForeignKey(
+ default="undefined:0",
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="auth_next",
+ to="common.LogEntry",
+ to_field="auth_hash",
+ null=False,
+ blank=False,
+ ),
preserve_default=False,
),
migrations.RunPython(fill_prev, migrations.RunPython.noop),
migrations.AddIndex(
- model_name='logentry',
- index=models.Index(fields=['content_type', 'object_id'], name='common_loge_content_43b532_idx'),
+ model_name="logentry",
+ index=models.Index(
+ fields=["content_type", "object_id"],
+ name="common_loge_content_43b532_idx",
+ ),
),
migrations.AddIndex(
- model_name='logentry',
- index=models.Index(fields=['action_type'], name='common_loge_action__1810d9_idx'),
+ model_name="logentry",
+ index=models.Index(
+ fields=["action_type"], name="common_loge_action__1810d9_idx"
+ ),
+ ),
+ migrations.RunSQL(
+ migrations.RunSQL.noop, reverse_sql="SET CONSTRAINTS ALL IMMEDIATE"
),
- migrations.RunSQL(migrations.RunSQL.noop,
- reverse_sql='SET CONSTRAINTS ALL IMMEDIATE'),
]
class Migration(migrations.Migration):
- dependencies = [
- ('common', '0013_logentry_logchain'),
- ]
+ dependencies = [("common", "0013_logentry_logchain")]
operations = [
migrations.AddField(
- model_name='configuration',
- name='can_see_other_members',
- field=models.CharField(choices=[('no', 'no'), ('name-only', 'name-only'), ('name-contact', 'name-contact')], default='no', max_length=12, verbose_name='Members can see other members'),
+ model_name="configuration",
+ name="can_see_other_members",
+ field=models.CharField(
+ choices=[
+ ("no", "no"),
+ ("name-only", "name-only"),
+ ("name-contact", "name-contact"),
+ ],
+ default="no",
+ max_length=12,
+ verbose_name="Members can see other members",
+ ),
),
migrations.AddField(
- model_name='configuration',
- name='public_base_url',
- field=models.URLField(blank=True, help_text="This field is used to generate the absolute URL for public pages. Leave it empty if it is the same as this page's base URL.", max_length=512, null=True, verbose_name='External base URL of byro installation'),
+ model_name="configuration",
+ name="public_base_url",
+ field=models.URLField(
+ blank=True,
+ help_text="This field is used to generate the absolute URL for public pages. Leave it empty if it is the same as this page's base URL.",
+ max_length=512,
+ null=True,
+ verbose_name="External base URL of byro installation",
+ ),
),
]
from .configuration import Configuration
from .log import LogEntry, LogTargetMixin, log_call
-__all__ = ['Configuration', 'LogEntry', 'LogTargetMixin', 'log_call']
+__all__ = ["Configuration", "LogEntry", "LogTargetMixin", "log_call"]
created_by = models.ForeignKey(
to=get_user_model(),
on_delete=models.PROTECT,
- related_name='+', # no related lookup
+ related_name="+", # no related lookup
null=True,
)
modified_by = models.ForeignKey(
to=get_user_model(),
on_delete=models.PROTECT,
- related_name='+', # no related lookup
+ related_name="+", # no related lookup
null=True,
)
return OrderedDict()
def __new__(cls, name, parents, dct):
- if 'valid_choices' not in dct:
- dct['valid_choices'] = [
+ if "valid_choices" not in dct:
+ dct["valid_choices"] = [
dct[key] for key in dct if isinstance(key, str) and key.upper() == key
]
return super(ChoicesMeta, cls).__new__(cls, name, parents, dct)
@classproperty
def max_length(cls):
- if hasattr(cls, 'valid_choices'):
+ if hasattr(cls, "valid_choices"):
return max([len(val) for val in cls.valid_choices])
else:
return max([len(val) for val, _ in cls.choices])
class MemberViewLevel(Choices):
- NO = 'no'
- NAME_ONLY = 'name-only'
- NAME_AND_CONTACT = 'name-contact'
+ NO = "no"
+ NAME_ONLY = "name-only"
+ NAME_AND_CONTACT = "name-contact"
class Configuration(ByroConfiguration):
- LOG_TARGET_BASE = 'byro.settings'
+ LOG_TARGET_BASE = "byro.settings"
name = models.CharField(
- null=True, blank=True, max_length=100, verbose_name=_('Association name')
+ null=True, blank=True, max_length=100, verbose_name=_("Association name")
)
address = models.TextField(
- null=True, blank=True, max_length=500, verbose_name=_('Association address')
+ null=True, blank=True, max_length=500, verbose_name=_("Association address")
)
url = models.CharField(
- null=True, blank=True, max_length=200, verbose_name=_('Association URL')
+ null=True, blank=True, max_length=200, verbose_name=_("Association URL")
)
liability_interval = models.IntegerField(
default=36,
- verbose_name=_('Statute of limitations'),
+ verbose_name=_("Statute of limitations"),
help_text=_(
- 'For which interval can you make members pay their outstanding fees?'
+ "For which interval can you make members pay their outstanding fees?"
),
)
null=True,
blank=True,
max_length=5,
- verbose_name=_('Language'),
+ verbose_name=_("Language"),
)
currency = models.CharField(
null=True,
blank=True,
max_length=3,
- verbose_name=_('Currency'),
- help_text=_('E.g. EUR'),
+ verbose_name=_("Currency"),
+ help_text=_("E.g. EUR"),
)
# Registration form configuration, contains settings for the fields to include when adding a new member
registration_form = JSONField(null=True, blank=True)
blank=True,
verbose_name=_("External base URL of byro installation"),
help_text=_(
- "This field is used to generate the absolute URL for public pages. Leave it empty if it is the same as this page\'s base URL."
+ "This field is used to generate the absolute URL for public pages. Leave it empty if it is the same as this page's base URL."
),
)
can_see_other_members = models.CharField(
max_length=MemberViewLevel.max_length,
- verbose_name=_('Members can see other members'),
+ verbose_name=_("Members can see other members"),
choices=MemberViewLevel.choices,
default=MemberViewLevel.NO,
)
null=True,
blank=True,
max_length=100,
- verbose_name=_('E-mail address used as sender'),
+ verbose_name=_("E-mail address used as sender"),
)
backoffice_mail = models.EmailField(
null=True,
blank=True,
max_length=100,
- verbose_name=_('E-mail address for notifications'),
+ verbose_name=_("E-mail address for notifications"),
)
welcome_member_template = models.ForeignKey(
- to='mails.MailTemplate',
+ to="mails.MailTemplate",
null=True,
blank=True,
on_delete=models.SET_NULL,
- related_name='+',
+ related_name="+",
)
welcome_office_template = models.ForeignKey(
- to='mails.MailTemplate',
+ to="mails.MailTemplate",
null=True,
blank=True,
on_delete=models.SET_NULL,
- related_name='+',
+ related_name="+",
)
leave_member_template = models.ForeignKey(
- to='mails.MailTemplate',
+ to="mails.MailTemplate",
null=True,
blank=True,
on_delete=models.SET_NULL,
- related_name='+',
+ related_name="+",
)
leave_office_template = models.ForeignKey(
- to='mails.MailTemplate',
+ to="mails.MailTemplate",
null=True,
blank=True,
on_delete=models.SET_NULL,
- related_name='+',
+ related_name="+",
)
record_disclosure_template = models.ForeignKey(
- to='mails.MailTemplate',
+ to="mails.MailTemplate",
null=True,
blank=True,
on_delete=models.SET_NULL,
- related_name='+',
+ related_name="+",
)
- form_title = _('General settings')
+ form_title = _("General settings")
def __str__(self):
return "Settings"
def get_absolute_url(self):
- return reverse('office:settings.base')
+ return reverse("office:settings.base")
"An object manager that can handle filter(content_object=...)"
def filter(self, *args, **kwargs):
- if 'content_object' in kwargs:
- content_object = kwargs.pop('content_object')
- kwargs['content_type'] = ContentType.objects.get_for_model(
+ if "content_object" in kwargs:
+ content_object = kwargs.pop("content_object")
+ kwargs["content_type"] = ContentType.objects.get_for_model(
type(content_object)
)
- kwargs['object_id'] = content_object.pk
+ kwargs["object_id"] = content_object.pk
return super().filter(*args, **kwargs)
"Manager that is linking the log chain on .create()"
def create(self, *args, **kwargs):
- kwargs['auth_prev'] = self.get_chain_end()
+ kwargs["auth_prev"] = self.get_chain_end()
return super().create(*args, **kwargs)
def get_chain_end(self):
- return self.filter(Q(auth_next=None) | Q(auth_prev_id=F('auth_hash'))).first()
+ return self.filter(Q(auth_next=None) | Q(auth_prev_id=F("auth_hash"))).first()
-PERSON_BYTES = b'byro logchain v1'
+PERSON_BYTES = b"byro logchain v1"
class LogEntry(models.Model):
content_type = models.ForeignKey(ContentType, null=True, on_delete=models.SET_NULL)
object_id = models.PositiveIntegerField(db_index=True)
- content_object = GenericForeignKey('content_type', 'object_id')
+ content_object = GenericForeignKey("content_type", "object_id")
datetime = models.DateTimeField(default=now, db_index=True)
user = models.ForeignKey(get_user_model(), null=True, on_delete=models.SET_NULL)
auth_hash = models.CharField(max_length=140, null=False, unique=True)
auth_prev = models.ForeignKey(
- 'self',
+ "self",
on_delete=models.PROTECT,
- related_name='auth_next',
- to_field='auth_hash',
+ related_name="auth_next",
+ to_field="auth_hash",
null=False,
blank=False,
)
objects = LogEntryManager()
class Meta:
- ordering = ('-datetime', '-id')
+ ordering = ("-datetime", "-id")
indexes = [
- models.Index(fields=['content_type', 'object_id']),
- models.Index(fields=['action_type']),
+ models.Index(fields=["content_type", "object_id"]),
+ models.Index(fields=["action_type"]),
]
def delete(self, using=None, keep_parents=False):
raise TypeError("Logs cannot be deleted.")
def save(self, *args, **kwargs):
- if kwargs.get('update_fields', None) == [
- 'auth_hash'
- ] and self.auth_hash.startswith('random:'):
- self._OVERRIDE_SAVE = ['auth_hash']
+ if kwargs.get("update_fields", None) == [
+ "auth_hash"
+ ] and self.auth_hash.startswith("random:"):
+ self._OVERRIDE_SAVE = ["auth_hash"]
elif (
- kwargs.get('update_fields', None) == ['auth_prev']
- and self.auth_prev == 'undefined:0'
+ kwargs.get("update_fields", None) == ["auth_prev"]
+ and self.auth_prev == "undefined:0"
):
- self._OVERRIDE_SAVE = ['auth_prev']
+ self._OVERRIDE_SAVE = ["auth_prev"]
else:
- if getattr(self, 'pk', None):
+ if getattr(self, "pk", None):
raise TypeError("Logs cannot be modified.")
if not self.data:
self.data = {}
if self.user:
- self.data.setdefault('source', str(self.user))
+ self.data.setdefault("source", str(self.user))
- if not self.data.get('source'):
+ if not self.data.get("source"):
raise ValueError("Need to provide at least user or data['source']")
self._compute_logchain()
self.datetime = now()
self.auth_data = {
- 'hash_ver': 1,
- 'nonce': base64.b64encode(hdd_nonce).decode('us-ascii'),
- 'data_mac': 'blake2b:{}'.format(hdd_mac.decode('us-ascii')),
- 'orig_content_type': '{}.{}'.format(
+ "hash_ver": 1,
+ "nonce": base64.b64encode(hdd_nonce).decode("us-ascii"),
+ "data_mac": "blake2b:{}".format(hdd_mac.decode("us-ascii")),
+ "orig_content_type": "{}.{}".format(
self.content_type.app_label, self.content_type.model
)
if self.content_type
else None,
- 'orig_user_id': self.user_id if self.user_id else None,
- 'software_version': ", ".join(get_installed_software()),
+ "orig_user_id": self.user_id if self.user_id else None,
+ "software_version": ", ".join(get_installed_software()),
}
authenticated_dict = self.get_authenticated_dict()
ad_encoded = canonicaljson.encode_canonical_json(authenticated_dict)
self.auth_hash = "blake2b:{}".format(
nacl.hash.blake2b(ad_encoded, digest_size=64, person=PERSON_BYTES).decode(
- 'us-ascii'
+ "us-ascii"
)
)
def get_authenticated_dict(self):
return {
- 'object_id': self.object_id,
- 'datetime': self.datetime.isoformat(),
- 'action_type': self.action_type,
- 'prev_hash': self.auth_prev_id
+ "object_id": self.object_id,
+ "datetime": self.datetime.isoformat(),
+ "action_type": self.action_type,
+ "prev_hash": self.auth_prev_id
if self.auth_prev_id and self.auth_prev_id != self.auth_hash
- else 'initial:0',
- 'auth_data': self.auth_data,
- 'source': self.data['source'],
+ else "initial:0",
+ "auth_data": self.auth_data,
+ "source": self.data["source"],
}
def verify(self):
- if self.auth_data['hash_ver'] != 1:
+ if self.auth_data["hash_ver"] != 1:
return False
if (
self.content_type is not None
- and '{}.{}'.format(self.content_type.app_label, self.content_type.model)
- != self.auth_data['orig_content_type']
+ and "{}.{}".format(self.content_type.app_label, self.content_type.model)
+ != self.auth_data["orig_content_type"]
):
return False
- if self.user_id is not None and self.user_id != self.auth_data['orig_user_id']:
+ if self.user_id is not None and self.user_id != self.auth_data["orig_user_id"]:
return False
hashed_data_dict = dict(self.data)
hdd_encoded = canonicaljson.encode_canonical_json(hashed_data_dict)
- hdd_nonce = base64.b64decode(self.auth_data['nonce'])
+ hdd_nonce = base64.b64decode(self.auth_data["nonce"])
hdd_mac = nacl.hash.blake2b(hdd_encoded, digest_size=64, salt=hdd_nonce)
if (
- 'blake2b:{}'.format(hdd_mac.decode('us-ascii'))
- != self.auth_data['data_mac']
+ "blake2b:{}".format(hdd_mac.decode("us-ascii"))
+ != self.auth_data["data_mac"]
): # FIXME Maybe fixed time comparison?
return False
ad_encoded = canonicaljson.encode_canonical_json(authenticated_dict)
auth_hash = "blake2b:{}".format(
nacl.hash.blake2b(ad_encoded, digest_size=64, person=PERSON_BYTES).decode(
- 'us-ascii'
+ "us-ascii"
)
)
@receiver(pre_save, sender=LogEntry)
def log_entry_pre_save(sender, instance, *args, **kwargs):
- if getattr(instance, '_OVERRIDE_SAVE', None) == [
- 'auth_hash'
- ] and instance.auth_hash.startswith('random:'):
- delattr(instance, '_OVERRIDE_SAVE')
+ if getattr(instance, "_OVERRIDE_SAVE", None) == [
+ "auth_hash"
+ ] and instance.auth_hash.startswith("random:"):
+ delattr(instance, "_OVERRIDE_SAVE")
elif (
- getattr(instance, '_OVERRIDE_SAVE', None) == ['auth_prev']
- and instance.auth_prev == 'undefined:0'
+ getattr(instance, "_OVERRIDE_SAVE", None) == ["auth_prev"]
+ and instance.auth_prev == "undefined:0"
):
- delattr(instance, '_OVERRIDE_SAVE')
+ delattr(instance, "_OVERRIDE_SAVE")
elif instance.pk:
raise TypeError("Logs cannot be modified.")
elif isinstance(inobj, (tuple, list)):
return [flatten_objects(v) for v in inobj]
elif isinstance(inobj, datetime.datetime):
- return inobj.strftime('%Y-%m-%d %H:%M:%S %Z')
+ return inobj.strftime("%Y-%m-%d %H:%M:%S %Z")
elif isinstance(inobj, datetime.date):
- return inobj.strftime('%Y-%m-%d')
+ return inobj.strftime("%Y-%m-%d")
elif isinstance(inobj, decimal.Decimal) or (
- key_was == 'amount' and isinstance(inobj, (int, float))
+ key_was == "amount" and isinstance(inobj, (int, float))
):
return "{:.2f}".format(inobj)
else:
try:
content_type = ContentType.objects.get_for_model(type(inobj))
return {
- 'object': content_type.name,
- 'ref': (content_type.app_label, content_type.model, inobj.pk),
- 'value': str(inobj),
+ "object": content_type.name,
+ "ref": (content_type.app_label, content_type.model, inobj.pk),
+ "value": str(inobj),
}
except Exception:
return str(inobj)
LOG_TARGET_BASE = None
def log(self, context, action, user=None, **kwargs):
- if hasattr(context, 'request'):
+ if hasattr(context, "request"):
context = context.request
- user = user or getattr(context, 'user', None)
+ user = user or getattr(context, "user", None)
- if isinstance(context, str) and 'source' not in kwargs:
- kwargs['source'] = context
+ if isinstance(context, str) and "source" not in kwargs:
+ kwargs["source"] = context
- if self.LOG_TARGET_BASE and action.startswith('.'):
+ if self.LOG_TARGET_BASE and action.startswith("."):
action = self.LOG_TARGET_BASE + action
kwargs = flatten_objects(kwargs)
return LogEntry.objects.filter(content_object=self)
-def log_call(action, log_on='retval'):
+def log_call(action, log_on="retval"):
def outer_decorator(f):
@wraps(f)
def decorator(*args, **kwargs):
- if 'user_or_context' not in kwargs:
+ if "user_or_context" not in kwargs:
raise TypeError(
"You need to provide a 'user_or_context' named parameter which indicates the responsible user (a User model object), request (a View instance or HttpRequest object), or generic context (a str)."
)
- user_or_context = kwargs.pop('user_or_context')
- user = kwargs.pop('user', None)
+ user_or_context = kwargs.pop("user_or_context")
+ user = kwargs.pop("user", None)
retval = f(*args, **kwargs)
1:
] # Warning: we assume that args[0] is 'self'. Only works correctly with calls to bound methods
if log_args:
- log_kwargs['_args'] = [v for v in log_args]
+ log_kwargs["_args"] = [v for v in log_args]
- if log_on == 'retval':
+ if log_on == "retval":
retval.log(user_or_context, action, user=user, **log_kwargs)
else:
args[0].log(user_or_context, action, user=user, **log_kwargs)
from byro.common.settings.utils import reduce_dict
CONFIG = {
- 'filesystem': {
- 'base': {
- 'default': os.path.dirname(
+ "filesystem": {
+ "base": {
+ "default": os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
)
},
- 'logs': {'default': None, 'env': os.getenv('BYRO_FILESYSTEM_LOGS')},
- 'media': {'default': None, 'env': os.getenv('BYRO_FILESYSTEM_MEDIA')},
- 'static': {'default': None, 'env': os.getenv('BYRO_FILESYSTEM_STATIC')},
+ "logs": {"default": None, "env": os.getenv("BYRO_FILESYSTEM_LOGS")},
+ "media": {"default": None, "env": os.getenv("BYRO_FILESYSTEM_MEDIA")},
+ "static": {"default": None, "env": os.getenv("BYRO_FILESYSTEM_STATIC")},
},
- 'site': {
- 'debug': {'default': 'runserver' in sys.argv, 'env': os.getenv('BYRO_DEBUG')},
- 'url': {'default': 'http://localhost', 'env': os.getenv('BYRO_SITE_URL')},
- 'https': {'env': os.getenv('BYRO_HTTPS')},
+ "site": {
+ "debug": {"default": "runserver" in sys.argv, "env": os.getenv("BYRO_DEBUG")},
+ "url": {"default": "http://localhost", "env": os.getenv("BYRO_SITE_URL")},
+ "https": {"env": os.getenv("BYRO_HTTPS")},
},
- 'database': {
- 'name': {'env': os.getenv('BYRO_DB_NAME')},
- 'user': {'default': '', 'env': os.getenv('BYRO_DB_USER')},
- 'password': {'default': '', 'env': os.getenv('BYRO_DB_PASS')},
- 'host': {'default': '', 'env': os.getenv('BYRO_DB_HOST')},
- 'port': {'default': '', 'env': os.getenv('BYRO_DB_PORT')},
+ "database": {
+ "name": {"env": os.getenv("BYRO_DB_NAME")},
+ "user": {"default": "", "env": os.getenv("BYRO_DB_USER")},
+ "password": {"default": "", "env": os.getenv("BYRO_DB_PASS")},
+ "host": {"default": "", "env": os.getenv("BYRO_DB_HOST")},
+ "port": {"default": "", "env": os.getenv("BYRO_DB_PORT")},
},
- 'mail': {
- 'from': {'default': 'admin@localhost', 'env': os.getenv('BYRO_MAIL_FROM')},
- 'host': {'default': 'localhost', 'env': os.getenv('BYRO_MAIL_HOST')},
- 'port': {'default': '25', 'env': os.getenv('BYRO_MAIL_PORT')},
- 'user': {'default': '', 'env': os.getenv('BYRO_MAIL_USER')},
- 'password': {'default': '', 'env': os.getenv('BYRO_MAIL_PASSWORD')},
- 'tls': {'default': 'False', 'env': os.getenv('BYRO_MAIL_TLS')},
- 'ssl': {'default': 'False', 'env': os.getenv('BYRO_MAIL_SSL')},
+ "mail": {
+ "from": {"default": "admin@localhost", "env": os.getenv("BYRO_MAIL_FROM")},
+ "host": {"default": "localhost", "env": os.getenv("BYRO_MAIL_HOST")},
+ "port": {"default": "25", "env": os.getenv("BYRO_MAIL_PORT")},
+ "user": {"default": "", "env": os.getenv("BYRO_MAIL_USER")},
+ "password": {"default": "", "env": os.getenv("BYRO_MAIL_PASSWORD")},
+ "tls": {"default": "False", "env": os.getenv("BYRO_MAIL_TLS")},
+ "ssl": {"default": "False", "env": os.getenv("BYRO_MAIL_SSL")},
},
- 'logging': {
- 'email': {'default': '', 'env': os.getenv('BYRO_LOGGING_EMAIL')},
- 'email_level': {'default': '', 'env': os.getenv('BYRO_LOGGING_EMAIL_LEVEL')},
+ "logging": {
+ "email": {"default": "", "env": os.getenv("BYRO_LOGGING_EMAIL")},
+ "email_level": {"default": "", "env": os.getenv("BYRO_LOGGING_EMAIL_LEVEL")},
},
- 'locale': {
- 'language_code': {'default': 'en', 'env': os.getenv('BYRO_LANGUAGE_CODE')},
- 'time_zone': {'default': 'UTC', 'env': os.getenv('BYRO_TIME_ZONE')},
+ "locale": {
+ "language_code": {"default": "en", "env": os.getenv("BYRO_LANGUAGE_CODE")},
+ "time_zone": {"default": "UTC", "env": os.getenv("BYRO_TIME_ZONE")},
},
}
def read_config_files(config):
- if 'BYRO_CONFIG_FILE' in os.environ:
+ if "BYRO_CONFIG_FILE" in os.environ:
config_files = config.read_file(
- open(os.environ.get('BYRO_CONFIG_FILE'), encoding='utf-8')
+ open(os.environ.get("BYRO_CONFIG_FILE"), encoding="utf-8")
)
else:
config_files = config.read(
- ['/etc/byro/byro.cfg', os.path.expanduser('~/.byro.cfg'), 'byro.cfg'],
- encoding='utf-8',
+ ["/etc/byro/byro.cfg", os.path.expanduser("~/.byro.cfg"), "byro.cfg"],
+ encoding="utf-8",
)
return (
config,
def build_config():
config = configparser.RawConfigParser()
- config = read_layer('default', config)
+ config = read_layer("default", config)
config, config_files = read_config_files(config)
- config = read_layer('env', config)
+ config = read_layer("env", config)
return config, config_files
from byro.settings import * # noqa
tmpdir = tempfile.TemporaryDirectory()
-os.environ.setdefault('DATA_DIR', tmpdir.name)
+os.environ.setdefault("DATA_DIR", tmpdir.name)
BASE_DIR = tmpdir.name
DATA_DIR = tmpdir.name
-LOG_DIR = os.path.join(DATA_DIR, 'logs')
-MEDIA_ROOT = os.path.join(DATA_DIR, 'media')
-STATIC_ROOT = os.path.join(DATA_DIR, 'static')
-HTMLEXPORT_ROOT = os.path.join(DATA_DIR, 'htmlexport')
+LOG_DIR = os.path.join(DATA_DIR, "logs")
+MEDIA_ROOT = os.path.join(DATA_DIR, "media")
+STATIC_ROOT = os.path.join(DATA_DIR, "static")
+HTMLEXPORT_ROOT = os.path.join(DATA_DIR, "htmlexport")
for directory in (BASE_DIR, DATA_DIR, LOG_DIR, MEDIA_ROOT, HTMLEXPORT_ROOT):
os.makedirs(directory, exist_ok=True)
atexit.register(tmpdir.cleanup)
-EMAIL_BACKEND = 'django.core.mail.outbox'
-MAIL_FROM = 'orga@orga.org'
+EMAIL_BACKEND = "django.core.mail.outbox"
+MAIL_FROM = "orga@orga.org"
COMPRESS_ENABLED = COMPRESS_OFFLINE = False
-STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage'
-GET_SOLO_TEMPLATE_TAG_NAME = 'get_solo'
+STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage"
+GET_SOLO_TEMPLATE_TAG_NAME = "get_solo"
DEBUG = True
DEBUG_PROPAGATE_EXCEPTIONS = True
with suppress(ValueError):
- INSTALLED_APPS.remove('debug_toolbar.apps.DebugToolbarConfig') # noqa
- MIDDLEWARE.remove('debug_toolbar.middleware.DebugToolbarMiddleware') # noqa
+ INSTALLED_APPS.remove("debug_toolbar.apps.DebugToolbarConfig") # noqa
+ MIDDLEWARE.remove("debug_toolbar.middleware.DebugToolbarMiddleware") # noqa
from byro import __version__
if os.geteuid() == 0:
- print_line('You are running pretalx as root, why?', bold=True)
+ print_line("You are running pretalx as root, why?", bold=True)
- mode = 'development' if debug else 'production'
+ mode = "development" if debug else "production"
lines = [
(
- 'This is byro v{__version__} calling, running in {mode} mode.'.format(
+ "This is byro v{__version__} calling, running in {mode} mode.".format(
__version__=__version__, mode=mode
),
True,
),
- ('', False),
- ('Settings:', True),
- ('Read from: ' + ", ".join(config_files), False),
- ('Logging: {LOG_DIR}'.format(LOG_DIR=LOG_DIR), False),
+ ("", False),
+ ("Settings:", True),
+ ("Read from: " + ", ".join(config_files), False),
+ ("Logging: {LOG_DIR}".format(LOG_DIR=LOG_DIR), False),
]
if plugins:
- lines += [('Plugins: ' + ",".join(plugins), False)]
+ lines += [("Plugins: " + ",".join(plugins), False)]
else:
- lines += [('', False)]
- image = '''
+ lines += [("", False)]
+ image = """
┏━o━━━━o━━━┓
┣━━━o━━━o━━┫
┣━━━━━━━━━━┫
┃ byro ┃
┗━━━━━━━━━━┛
- '''.strip().split(
- '\n'
+ """.strip().split(
+ "\n"
)
img_width = len(image[0])
- image[-1] += ' ' * (img_width - len(image[-1]))
- image += [' ' * img_width for _ in repeat(None, (len(lines) - len(image)))]
+ image[-1] += " " * (img_width - len(image[-1]))
+ image += [" " * img_width for _ in repeat(None, (len(lines) - len(image)))]
- lines = [(image[n] + ' ' + line[0], line[1]) for n, line in enumerate(lines)]
+ lines = [(image[n] + " " + line[0], line[1]) for n, line in enumerate(lines)]
size = max(len(line[0]) for line in lines) + 4
start_box(size)
def default_formatter(entry):
with suppress(TemplateDoesNotExist):
tmpl = get_template("log_entry/{}.html".format(entry.action_type))
- return tmpl.render({'log_entry': entry})
+ return tmpl.render({"log_entry": entry})
data = dict(entry.data or {})
- data.pop('source', None)
+ data.pop("source", None)
co = entry.content_object
related_object = ""
if co:
url = None
- if hasattr(co, 'get_absolute_url'):
+ if hasattr(co, "get_absolute_url"):
url = co.get_absolute_url()
elif isinstance(co, get_user_model()):
- url = reverse('office:settings.users.detail', kwargs={'pk': co.pk})
+ url = reverse("office:settings.users.detail", kwargs={"pk": co.pk})
if url:
related_object = mark_safe(
' (<a href="{}">{}</a>)'.format(escape(url), escape(str(co)))
)
else:
- related_object = mark_safe(' ({})'.format(escape(co)))
+ related_object = mark_safe(" ({})".format(escape(co)))
extra_data = ""
if data:
)
-@register.filter(name='format_log_entry')
+@register.filter(name="format_log_entry")
def format_log_entry(entry):
if not FORMATTER_REGISTRY:
for module, response in log_formatters.send_robust(sender=__name__):
return FORMATTER_REGISTRY.get(entry.action_type, default_formatter)(entry)
-@register.filter(name='format_log_source')
+@register.filter(name="format_log_source")
def format_log_source(entry):
user = ""
if entry.user:
'<span class="fa fa-user"></span> {}'.format(escape(entry.user))
)
- source = entry.data.get('source', "")
- if source.startswith('internal: '):
+ source = entry.data.get("source", "")
+ if source.startswith("internal: "):
source = mark_safe(
'<span class="fa fa-gears"></span> {}'.format(escape(source[10:]))
)
if entry.user:
- if entry.data.get('source', None) == str(entry.user):
+ if entry.data.get("source", None) == str(entry.user):
return user
else:
- return mark_safe('{} (via {})'.format(source, user))
+ return mark_safe("{} (via {})".format(source, user))
else:
return source
-@register.filter(name='format_log_object')
+@register.filter(name="format_log_object")
def format_log_object(obj, key=None):
with suppress(Exception):
- if 'object' in obj and 'ref' in obj and 'value' in obj:
+ if "object" in obj and "ref" in obj and "value" in obj:
with suppress(Exception):
content_object = ContentType.objects.get(
- app_label=obj['ref'][0], model=obj['ref'][1]
- ).get_object_for_this_type(pk=obj['ref'][2])
+ app_label=obj["ref"][0], model=obj["ref"][1]
+ ).get_object_for_this_type(pk=obj["ref"][2])
- if obj['value'] == str(content_object):
+ if obj["value"] == str(content_object):
url = content_object.get_absolute_url()
- str_val = mark_safe(escape(str(obj['value'])))
+ str_val = mark_safe(escape(str(obj["value"])))
- if hasattr(content_object, 'get_object_icon'):
+ if hasattr(content_object, "get_object_icon"):
icon = content_object.get_object_icon()
else:
icon = ""
return mark_safe(
"<i>{} object</i>: {!r}".format(
- escape(str(obj['object'])), escape(str(obj['value']))
+ escape(str(obj["object"])), escape(str(obj["value"]))
)
)
- if key == 'category' and '.' in obj:
+ if key == "category" and "." in obj:
cats = get_document_category_names()
if obj in cats:
return "{} ({})".format(cats[obj], obj)
- if key == 'content_hash':
- parts = str(obj).split(':', 1)
+ if key == "content_hash":
+ parts = str(obj).split(":", 1)
if len(parts) == 2:
return mark_safe(
'{}: <tt title="{}">{} … {}</tt>'.format(
escape(parts[0]),
escape(parts[1]),
escape(parts[1][: (6 * 2)]),
- escape(parts[1][-(6 * 2):]),
+ escape(parts[1][-(6 * 2) :]),
)
)
return obj
-@register.filter(name='items_sorted')
+@register.filter(name="items_sorted")
def items_sorted(data):
return sorted(data)
with io.BytesIO() as output:
img.save(output, format="PNG")
return "data:image/png;base64,{}".format(
- base64.b64encode(output.getvalue()).decode('us-ascii')
+ base64.b64encode(output.getvalue()).decode("us-ascii")
)
from .views import LogInfoView, LoginView, logout_view
-app_name = 'common'
+app_name = "common"
urlpatterns = [
- url('^login/$', LoginView.as_view(), name='login'),
- url('^logout/$', logout_view, name='logout'),
- url('^log/info$', LogInfoView.as_view(), name='log.info'),
+ url("^login/$", LoginView.as_view(), name="login"),
+ url("^logout/$", logout_view, name="logout"),
+ url("^log/info$", LogInfoView.as_view(), name="log.info"),
]
def get_plugins():
result = []
for app in apps.get_app_configs():
- if hasattr(app, 'ByroPluginMeta'):
- if getattr(app, 'name', '').startswith("byro.") and not hasattr(
- app.ByroPluginMeta, 'version'
+ if hasattr(app, "ByroPluginMeta"):
+ if getattr(app, "name", "").startswith("byro.") and not hasattr(
+ app.ByroPluginMeta, "version"
):
continue
result.append(app)
# FIXME: In a release this should return the version
with suppress(Exception):
- retval = getattr(sys.modules[__name__], '_byro_git_version', None)
+ retval = getattr(sys.modules[__name__], "_byro_git_version", None)
if retval:
return retval
retval = (
subprocess.check_output(
- ['git', 'describe', '--always', '--dirty', '--abbrev=40']
+ ["git", "describe", "--always", "--dirty", "--abbrev=40"]
)
.decode()
.strip()
for plugin in get_plugins():
retval.append(
"{} {}".format(
- plugin.name, getattr(plugin.ByroPluginMeta, 'version', '')
+ plugin.name, getattr(plugin.ByroPluginMeta, "version", "")
).strip()
)
return retval
class LoginView(TemplateView):
- template_name = 'common/auth/login.html'
+ template_name = "common/auth/login.html"
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponseRedirect:
- username = request.POST.get('username')
- password = request.POST.get('password')
+ username = request.POST.get("username")
+ password = request.POST.get("password")
user = authenticate(username=username, password=password)
if user is None:
messages.error(
- request, _('No user account matches the entered credentials.')
+ request, _("No user account matches the entered credentials.")
)
- return redirect('common:login')
+ return redirect("common:login")
if not user.is_active:
- messages.error(request, _('User account is deactivated.'))
+ messages.error(request, _("User account is deactivated."))
LogEntry.objects.create(
content_object=user,
user=user,
action_type="byro.common.login.deactivated",
)
- return redirect('common:login')
+ return redirect("common:login")
login(request, user)
LogEntry.objects.create(
content_object=user, user=user, action_type="byro.common.login.success"
)
- url = urllib.parse.unquote(request.GET.get('next', ''))
+ url = urllib.parse.unquote(request.GET.get("next", ""))
if url and is_safe_url(url, request.get_host()):
return redirect(url)
- return redirect('/')
+ return redirect("/")
def logout_view(request: HttpRequest) -> HttpResponseRedirect:
action_type="byro.common.logout",
)
logout(request)
- return redirect('/')
+ return redirect("/")
class LogInfoView(TemplateView):
- template_name = 'common/log/info.html'
+ template_name = "common/log/info.html"
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
- context['log_head'] = LogEntry.objects.get_chain_end()
- context['now'] = now()
+ context["log_head"] = LogEntry.objects.get_chain_end()
+ context["now"] = now()
return context
class DocumentsConfig(AppConfig):
- name = 'byro.documents'
+ name = "byro.documents"
class ByroPluginMeta:
document_categories = {
- 'byro.documents.misc': _('Miscellaneous document'),
- 'byro.documents.registration_form': _('Registration form'),
+ "byro.documents.misc": _("Miscellaneous document"),
+ "byro.documents.registration_form": _("Registration form"),
}
initial = True
- dependencies = [
- ('members', '0006_auto_20180113_1849'),
- ]
+ dependencies = [("members", "0006_auto_20180113_1849")]
operations = [
migrations.CreateModel(
- name='Document',
+ name="Document",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('document', models.FileField(upload_to='documents/')),
- ('category', models.CharField(max_length=300, null=True)),
- ('direction', models.CharField(choices=[('incoming', 'incoming'), ('outgoing', 'outgoing')], default='outgoing', max_length=8)),
- ('member', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='documents', to='members.Member')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("document", models.FileField(upload_to="documents/")),
+ ("category", models.CharField(max_length=300, null=True)),
+ (
+ "direction",
+ models.CharField(
+ choices=[("incoming", "incoming"), ("outgoing", "outgoing")],
+ default="outgoing",
+ max_length=8,
+ ),
+ ),
+ (
+ "member",
+ models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="documents",
+ to="members.Member",
+ ),
+ ),
],
bases=(byro.common.models.auditable.Auditable, models.Model),
- ),
+ )
]
class Migration(migrations.Migration):
- dependencies = [
- ('documents', '0001_initial'),
- ]
+ dependencies = [("documents", "0001_initial")]
operations = [
migrations.AddField(
- model_name='document',
- name='title',
+ model_name="document",
+ name="title",
field=models.CharField(max_length=300, null=True),
- ),
+ )
]
class Migration(migrations.Migration):
- dependencies = [
- ('documents', '0002_document_title'),
- ]
+ dependencies = [("documents", "0002_document_title")]
operations = [
migrations.AlterModelOptions(
- name='document',
- options={'ordering': ('-date', 'title', '-id')},
+ name="document", options={"ordering": ("-date", "title", "-id")}
),
migrations.AddField(
- model_name='document',
- name='date',
+ model_name="document",
+ name="date",
field=models.DateField(default=django.utils.timezone.now, null=True),
),
migrations.AddField(
- model_name='document',
- name='content_hash',
+ model_name="document",
+ name="content_hash",
field=models.CharField(max_length=300, null=True),
),
migrations.AlterField(
- model_name='document',
- name='document',
- field=models.FileField(max_length=1000, upload_to='documents/%Y/%m/'),
+ model_name="document",
+ name="document",
+ field=models.FileField(max_length=1000, upload_to="documents/%Y/%m/"),
),
]
class Migration(migrations.Migration):
- dependencies = [
- ('documents', '0003_auto_20181009_1816'),
- ]
+ dependencies = [("documents", "0003_auto_20181009_1816")]
operations = [
migrations.AlterField(
- model_name='document',
- name='direction',
- field=models.CharField(choices=[('incoming', 'incoming'), ('outgoing', 'outgoing'), ('other', 'other')], default='outgoing', max_length=8),
- ),
+ model_name="document",
+ name="direction",
+ field=models.CharField(
+ choices=[
+ ("incoming", "incoming"),
+ ("outgoing", "outgoing"),
+ ("other", "other"),
+ ],
+ default="outgoing",
+ max_length=8,
+ ),
+ )
]
class DocumentDirection(Choices):
- INCOMING = 'incoming'
- OUTGOING = 'outgoing'
- OTHER = 'other'
+ INCOMING = "incoming"
+ OUTGOING = "outgoing"
+ OTHER = "other"
class Document(models.Model, LogTargetMixin):
- LOG_TARGET_BASE = 'byro.documents.document'
+ LOG_TARGET_BASE = "byro.documents.document"
class Meta:
- ordering = ('-date', 'title', '-id')
+ ordering = ("-date", "title", "-id")
- document = models.FileField(upload_to='documents/%Y/%m/', max_length=1000)
+ document = models.FileField(upload_to="documents/%Y/%m/", max_length=1000)
date = models.DateField(null=True, default=now)
title = models.CharField(max_length=300, null=True)
category = models.CharField(max_length=300, null=True)
default=DocumentDirection.OUTGOING,
)
member = models.ForeignKey(
- to='members.Member',
- related_name='documents',
+ to="members.Member",
+ related_name="documents",
on_delete=models.SET_NULL,
null=True,
blank=True,
content_hash = models.CharField(max_length=300, null=True)
template = _(
- '''
+ """
Hi, {name},
Please find attached a document we wanted to send you/that you requested.
Thank you,
{association}
-'''
+"""
).strip()
def _get_log_properties(self):
return {
f.name: getattr(self, f.name)
for f in self._meta.get_fields()
- if f.name not in ('id', 'mails') and hasattr(self, f.name)
+ if f.name not in ("id", "mails") and hasattr(self, f.name)
}
@cached_property
def content_hash_ok(self):
h = sha512()
- with self.document.open(mode='rb') as f:
+ with self.document.open(mode="rb") as f:
for chunk in f.chunks():
h.update(chunk)
- return self.content_hash == 'sha512:{}'.format(h.hexdigest())
+ return self.content_hash == "sha512:{}".format(h.hexdigest())
@cached_property
def mime_type_guessed(self):
- with self.document.open(mode='rb') as f:
+ with self.document.open(mode="rb") as f:
chunk = next(f.chunks())
return magic.from_buffer(chunk, mime=True)
# the API allows adding file content after the fact
if self.document and not self.content_hash:
h = sha512()
- with self.document.open(mode='rb') as f:
+ with self.document.open(mode="rb") as f:
for chunk in f.chunks():
h.update(chunk)
- self.content_hash = 'sha512:{}'.format(h.hexdigest())
- super().save(update_fields=['content_hash'])
+ self.content_hash = "sha512:{}".format(h.hexdigest())
+ super().save(update_fields=["content_hash"])
self.log(
- 'internal: automatic checkpoint',
- '.stored',
+ "internal: automatic checkpoint",
+ ".stored",
**self._get_log_properties()
)
or self.template.format(
name=self.member.name if self.member else email, association=us
),
- subject=_('[{association}] Your document').format(association=us),
+ subject=_("[{association}] Your document").format(association=us),
)
mail.attachments.add(self)
mail.save()
return mail
def get_display(self):
- return '{} Document: {}'.format(
+ return "{} Document: {}".format(
self.get_direction_display().capitalize(), self.category, self.title
)
def get_absolute_url(self):
- return reverse('office:documents.detail', kwargs={'pk': self.pk})
+ return reverse("office:documents.detail", kwargs={"pk": self.pk})
def get_object_icon(self):
return mark_safe('<i class="fa fa-file-o"></i> ')
-@receiver(pre_delete, sender=Document, dispatch_uid='documents_models__log_deletion')
+@receiver(pre_delete, sender=Document, dispatch_uid="documents_models__log_deletion")
def log_deletion(sender, instance, using, **kwargs):
instance.log(
- 'internal: automatic checkpoint', '.deleted', **instance._get_log_properties()
+ "internal: automatic checkpoint", ".deleted", **instance._get_log_properties()
)
categories = {}
for app in apps.get_app_configs():
- if hasattr(app, 'ByroPluginMeta'):
- categories.update(getattr(app.ByroPluginMeta, 'document_categories', {}))
+ if hasattr(app, "ByroPluginMeta"):
+ categories.update(getattr(app.ByroPluginMeta, "document_categories", {}))
return categories
class MailsConfig(AppConfig):
- name = 'byro.mails'
+ name = "byro.mails"
from django.utils.translation import ugettext_lazy as _
from i18nfield.strings import LazyI18nString
-WELCOME_MEMBER_SUBJECT = LazyI18nString.from_gettext(_('Welcome, latest member!'))
+WELCOME_MEMBER_SUBJECT = LazyI18nString.from_gettext(_("Welcome, latest member!"))
WELCOME_MEMBER_TEXT = LazyI18nString.from_gettext(
_(
- '''Hi,
+ """Hi,
welcome to {name}! You're now officially our latest member. Your member ID
is {number}. If you have any questions relating to your member fees or
{additional_information}
Thanks,
-the robo clerk'''
+the robo clerk"""
)
)
-WELCOME_OFFICE_SUBJECT = LazyI18nString.from_gettext(_('[byro] New member'))
+WELCOME_OFFICE_SUBJECT = LazyI18nString.from_gettext(_("[byro] New member"))
WELCOME_OFFICE_TEXT = LazyI18nString.from_gettext(
_(
- '''Hi,
+ """Hi,
we have a new member: {member_name}
{additional_information}
Thanks,
-the robo clerk'''
+the robo clerk"""
)
)
-LEAVE_MEMBER_SUBJECT = LazyI18nString.from_gettext(_('Goodbye!'))
+LEAVE_MEMBER_SUBJECT = LazyI18nString.from_gettext(_("Goodbye!"))
LEAVE_MEMBER_TEXT = LazyI18nString.from_gettext(
_(
- '''Hi,
+ """Hi,
we are sorry that you will leave us at {end}.
{additional_information}
Thanks,
-the robo clerk'''
+the robo clerk"""
)
)
LEAVE_MEMBER_OFFICE_SUBJECT = LazyI18nString.from_gettext(
- _('[byro] Membership termination')
+ _("[byro] Membership termination")
)
LEAVE_MEMBER_OFFICE_TEXT = LazyI18nString.from_gettext(
_(
- '''Hi,
+ """Hi,
{member_name} will leave us at {end}.
{additional_information}
Thanks,
-the robo clerk'''
+the robo clerk"""
)
)
RECORD_DISCLOSURE_SUBJECT = LazyI18nString.from_gettext(
- (_('Your {association_name} data record (#{number})'))
+ (_("Your {association_name} data record (#{number})"))
)
RECORD_DISCLOSURE_TEXT = LazyI18nString.from_gettext(
_(
- '''Hi,
+ """Hi,
We're writing you to let you know about the data we have currently stored about
yourself and your membership.
this email.
Thanks,
-the robo clerk'''
+the robo clerk"""
)
)
initial = True
- dependencies = [
- ]
+ dependencies = []
operations = [
migrations.CreateModel(
- name='EMail',
+ name="EMail",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('to', models.CharField(help_text='One email address or several addresses separated by commas.', max_length=1000, verbose_name='To')),
- ('reply_to', models.CharField(blank=True, max_length=1000, null=True, verbose_name='Reply-To')),
- ('cc', models.CharField(blank=True, help_text='One email address or several addresses separated by commas.', max_length=1000, null=True, verbose_name='CC')),
- ('bcc', models.CharField(blank=True, help_text='One email address or several addresses separated by commas.', max_length=1000, null=True, verbose_name='BCC')),
- ('subject', models.CharField(max_length=200, verbose_name='Subject')),
- ('text', models.TextField(verbose_name='Text')),
- ('sent', models.DateTimeField(blank=True, null=True, verbose_name='Sent at')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "to",
+ models.CharField(
+ help_text="One email address or several addresses separated by commas.",
+ max_length=1000,
+ verbose_name="To",
+ ),
+ ),
+ (
+ "reply_to",
+ models.CharField(
+ blank=True, max_length=1000, null=True, verbose_name="Reply-To"
+ ),
+ ),
+ (
+ "cc",
+ models.CharField(
+ blank=True,
+ help_text="One email address or several addresses separated by commas.",
+ max_length=1000,
+ null=True,
+ verbose_name="CC",
+ ),
+ ),
+ (
+ "bcc",
+ models.CharField(
+ blank=True,
+ help_text="One email address or several addresses separated by commas.",
+ max_length=1000,
+ null=True,
+ verbose_name="BCC",
+ ),
+ ),
+ ("subject", models.CharField(max_length=200, verbose_name="Subject")),
+ ("text", models.TextField(verbose_name="Text")),
+ (
+ "sent",
+ models.DateTimeField(blank=True, null=True, verbose_name="Sent at"),
+ ),
],
bases=(byro.common.models.auditable.Auditable, models.Model),
),
migrations.CreateModel(
- name='MailTemplate',
+ name="MailTemplate",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('subject', i18nfield.fields.I18nCharField(max_length=200, verbose_name='Subject')),
- ('text', i18nfield.fields.I18nTextField(verbose_name='Text')),
- ('bcc', models.CharField(blank=True, help_text='Enter comma separated addresses. Will receive a blind copy of every mail sent from this template. This may be a LOT!', max_length=1000, null=True, verbose_name='BCC')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "subject",
+ i18nfield.fields.I18nCharField(
+ max_length=200, verbose_name="Subject"
+ ),
+ ),
+ ("text", i18nfield.fields.I18nTextField(verbose_name="Text")),
+ (
+ "bcc",
+ models.CharField(
+ blank=True,
+ help_text="Enter comma separated addresses. Will receive a blind copy of every mail sent from this template. This may be a LOT!",
+ max_length=1000,
+ null=True,
+ verbose_name="BCC",
+ ),
+ ),
],
bases=(byro.common.models.auditable.Auditable, models.Model),
),
class Migration(migrations.Migration):
- dependencies = [
- ('documents', '0001_initial'),
- ('mails', '0001_initial'),
- ]
+ dependencies = [("documents", "0001_initial"), ("mails", "0001_initial")]
operations = [
migrations.AddField(
- model_name='email',
- name='attachments',
- field=models.ManyToManyField(related_name='mails', to='documents.Document'),
- ),
+ model_name="email",
+ name="attachments",
+ field=models.ManyToManyField(related_name="mails", to="documents.Document"),
+ )
]
class Migration(migrations.Migration):
- dependencies = [
- ('mails', '0002_email_attachments'),
- ]
+ dependencies = [("mails", "0002_email_attachments")]
operations = [
migrations.AddField(
- model_name='mailtemplate',
- name='reply_to',
- field=models.EmailField(blank=True, help_text='Change the Reply-To address if you do not want to use the default orga address', max_length=200, null=True, verbose_name='Reply-To'),
- ),
+ model_name="mailtemplate",
+ name="reply_to",
+ field=models.EmailField(
+ blank=True,
+ help_text="Change the Reply-To address if you do not want to use the default orga address",
+ max_length=200,
+ null=True,
+ verbose_name="Reply-To",
+ ),
+ )
]
class Migration(migrations.Migration):
- dependencies = [
- ('mails', '0003_mailtemplate_reply_to'),
- ]
+ dependencies = [("mails", "0003_mailtemplate_reply_to")]
operations = [
migrations.AddField(
- model_name='email',
- name='template',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='mails.MailTemplate'),
- ),
+ model_name="email",
+ name="template",
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ to="mails.MailTemplate",
+ ),
+ )
]
class Migration(migrations.Migration):
dependencies = [
- ('members', '0009_auto_20180512_1810'),
- ('mails', '0004_email_template'),
+ ("members", "0009_auto_20180512_1810"),
+ ("mails", "0004_email_template"),
]
operations = [
migrations.AddField(
- model_name='email',
- name='members',
- field=models.ManyToManyField(null=True, related_name='emails', to='members.Member'),
- ),
+ model_name="email",
+ name="members",
+ field=models.ManyToManyField(
+ null=True, related_name="emails", to="members.Member"
+ ),
+ )
]
def add_members_to_emails(apps, schema_editor):
- Member = apps.get_model('members', 'Member')
- EMail = apps.get_model('mails', 'EMail')
+ Member = apps.get_model("members", "Member")
+ EMail = apps.get_model("mails", "EMail")
for email in EMail.objects.all():
- for address in email.to.split(','):
+ for address in email.to.split(","):
members = Member.objects.filter(email__iexact=address.strip().lower())
if members.count() == 1:
email.members.add(members.first())
class Migration(migrations.Migration):
- dependencies = [
- ('mails', '0005_email_members'),
- ]
+ dependencies = [("mails", "0005_email_members")]
operations = [
- migrations.RunPython(add_members_to_emails, migrations.RunPython.noop),
+ migrations.RunPython(add_members_to_emails, migrations.RunPython.noop)
]
class Migration(migrations.Migration):
- dependencies = [
- ('mails', '0006_auto_20180911_1956'),
- ]
+ dependencies = [("mails", "0006_auto_20180911_1956")]
operations = [
migrations.AlterField(
- model_name='email',
- name='members',
- field=models.ManyToManyField(related_name='emails', to='members.Member'),
- ),
+ model_name="email",
+ name="members",
+ field=models.ManyToManyField(related_name="emails", to="members.Member"),
+ )
]
class Migration(migrations.Migration):
dependencies = [
- ('members', '0010_memberbalance'),
- ('mails', '0007_auto_20180929_0915'),
+ ("members", "0010_memberbalance"),
+ ("mails", "0007_auto_20180929_0915"),
]
operations = [
migrations.AddField(
- model_name='email',
- name='balance',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reminder_mails', to='members.MemberBalance'),
- ),
+ model_name="email",
+ name="balance",
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="reminder_mails",
+ to="members.MemberBalance",
+ ),
+ )
]
class MailTemplate(Auditable, models.Model):
- subject = I18nCharField(max_length=200, verbose_name=_('Subject'))
- text = I18nTextField(verbose_name=_('Text'))
+ subject = I18nCharField(max_length=200, verbose_name=_("Subject"))
+ text = I18nTextField(verbose_name=_("Text"))
reply_to = models.EmailField(
max_length=200,
blank=True,
null=True,
- verbose_name=_('Reply-To'),
+ verbose_name=_("Reply-To"),
help_text=_(
- 'Change the Reply-To address if you do not want to use the default orga address'
+ "Change the Reply-To address if you do not want to use the default orga address"
),
)
bcc = models.CharField(
max_length=1000,
blank=True,
null=True,
- verbose_name=_('BCC'),
+ verbose_name=_("BCC"),
help_text=_(
- 'Enter comma separated addresses. Will receive a blind copy of every mail sent from this template. This may be a LOT!'
+ "Enter comma separated addresses. Will receive a blind copy of every mail sent from this template. This may be a LOT!"
),
)
def __str__(self):
- return '{self.subject}'.format(self=self)
+ return "{self.subject}".format(self=self)
def to_mail(
self,
text = str(self.text).format(**context)
except KeyError as e:
raise SendMailException(
- 'Experienced KeyError when rendering Text: {e}'.format(e=e)
+ "Experienced KeyError when rendering Text: {e}".format(e=e)
)
mail = EMail(
return mail
def get_absolute_url(self):
- return reverse('office:mails.templates.view', kwargs={'pk': self.pk})
+ return reverse("office:mails.templates.view", kwargs={"pk": self.pk})
def get_object_icon(self):
return mark_safe('<i class="fa fa-envelope-o"></i> ')
class EMail(Auditable, models.Model):
to = models.CharField(
max_length=1000,
- verbose_name=_('To'),
- help_text=_('One email address or several addresses separated by commas.'),
+ verbose_name=_("To"),
+ help_text=_("One email address or several addresses separated by commas."),
)
reply_to = models.CharField(
- max_length=1000, null=True, blank=True, verbose_name=_('Reply-To')
+ max_length=1000, null=True, blank=True, verbose_name=_("Reply-To")
)
cc = models.CharField(
max_length=1000,
null=True,
blank=True,
- verbose_name=_('CC'),
- help_text=_('One email address or several addresses separated by commas.'),
+ verbose_name=_("CC"),
+ help_text=_("One email address or several addresses separated by commas."),
)
bcc = models.CharField(
max_length=1000,
null=True,
blank=True,
- verbose_name=_('BCC'),
- help_text=_('One email address or several addresses separated by commas.'),
+ verbose_name=_("BCC"),
+ help_text=_("One email address or several addresses separated by commas."),
)
- subject = models.CharField(max_length=200, verbose_name=_('Subject'))
- members = models.ManyToManyField(to='members.Member', related_name='emails')
- text = models.TextField(verbose_name=_('Text'))
- sent = models.DateTimeField(null=True, blank=True, verbose_name=_('Sent at'))
+ subject = models.CharField(max_length=200, verbose_name=_("Subject"))
+ members = models.ManyToManyField(to="members.Member", related_name="emails")
+ text = models.TextField(verbose_name=_("Text"))
+ sent = models.DateTimeField(null=True, blank=True, verbose_name=_("Sent at"))
template = models.ForeignKey(
to=MailTemplate, null=True, blank=True, on_delete=models.SET_NULL
)
- attachments = models.ManyToManyField(to='documents.Document', related_name='mails')
+ attachments = models.ManyToManyField(to="documents.Document", related_name="mails")
balance = models.ForeignKey(
- to='members.MemberBalance',
+ to="members.MemberBalance",
on_delete=models.CASCADE,
null=True,
blank=True,
- related_name='reminder_mails',
+ related_name="reminder_mails",
)
@property
def attachment_ids(self):
- if hasattr(self, 'attachments'):
- return list(self.attachments.all().values_list('pk', flat=True))
+ if hasattr(self, "attachments"):
+ return list(self.attachments.all().values_list("pk", flat=True))
return []
def process_special_to(self):
member = Member.all_objects.get(pk=self.to.split(":", 2)[2])
self.to = member.email
self.members.add(member)
- self.save(update_fields=['to'])
+ self.save(update_fields=["to"])
def send(self):
if self.sent:
- raise Exception('This mail has been sent already. It cannot be sent again.')
+ raise Exception("This mail has been sent already. It cannot be sent again.")
self.process_special_to()
self.members.add(member)
else:
- to_addrs = self.to.split(',')
+ to_addrs = self.to.split(",")
send_tos.append(to_addrs)
for addr in to_addrs:
subject=self.subject,
body=self.text,
sender=config.mail_from,
- cc=(self.cc or '').split(','),
- bcc=(self.bcc or '').split(','),
+ cc=(self.cc or "").split(","),
+ bcc=(self.bcc or "").split(","),
attachments=self.attachment_ids,
headers=headers,
)
self.sent = now()
- self.save(update_fields=['sent'])
+ self.save(update_fields=["sent"])
def copy_to_draft(self):
new_mail = deepcopy(self)
(code, resp) = self.connection.mail(from_addr, [])
if code != 250:
logger.warning(
- 'Error testing mail settings, code %d, resp: %s' % (code, resp)
+ "Error testing mail settings, code %d, resp: %s" % (code, resp)
)
raise SMTPSenderRefused(code, resp, from_addr)
senderrs = {}
- (code, resp) = self.connection.rcpt('test@example.com')
+ (code, resp) = self.connection.rcpt("test@example.com")
if (code != 250) and (code != 251):
logger.warning(
- 'Error testing mail settings, code %d, resp: %s' % (code, resp)
+ "Error testing mail settings, code %d, resp: %s" % (code, resp)
)
raise SMTPRecipientsRefused(senderrs)
finally:
try:
backend.send_messages([email])
except Exception:
- logger.exception('Error sending email')
- raise SendMailException('Failed to send an email to {}.'.format(to))
+ logger.exception("Error sending email")
+ raise SendMailException("Failed to send an email to {}.".format(to))
class MemberConfig(AppConfig):
- name = 'byro.members'
+ name = "byro.members"
def ready(self):
from . import signals # noqa
from byro.common.models import Configuration
from byro.members.models import Member, Membership, get_next_member_number
-MAPPING = {'member': Member, 'membership': Membership}
+MAPPING = {"member": Member, "membership": Membership}
class CreateMemberForm(forms.Form):
config = Configuration.get_solo().registration_form or []
config = sorted(
- [field for field in config if field['position'] is not None],
- key=lambda field: field['position'],
+ [field for field in config if field["position"] is not None],
+ key=lambda field: field["position"],
)
profiles = {
profile.related_model.__name__: profile.related_model
for profile in Member._meta.related_objects
- if isinstance(profile, OneToOneRel) and profile.name.startswith('profile_')
+ if isinstance(profile, OneToOneRel) and profile.name.startswith("profile_")
}
for field in config:
self.build_field(field, profiles)
- if 'member__number' in self.fields:
- self.fields['member__number'].initial = get_next_member_number()
+ if "member__number" in self.fields:
+ self.fields["member__number"].initial = get_next_member_number()
def get_date_initial(self, field):
today = now().date()
- default = field['default_date']
+ default = field["default_date"]
if default == DefaultDates.TODAY:
return today
elif default == DefaultDates.BEGINNING_MONTH:
day=1, month=1
)
elif default == DefaultDates.FIXED_DATE:
- return field.get('default', None)
+ return field.get("default", None)
def build_field(self, field, profiles):
- model_name = field['name'].split('__')[0]
+ model_name = field["name"].split("__")[0]
if model_name in MAPPING:
model = MAPPING[model_name]
else:
- model = profiles[field['name'].split('__')[0]]
- form_field = model._meta.get_field(field['name'].split('__')[-1]).formfield()
+ model = profiles[field["name"].split("__")[0]]
+ form_field = model._meta.get_field(field["name"].split("__")[-1]).formfield()
form_field.model = model
- self.fields[field['name']] = form_field
- if 'default_date' in field:
+ self.fields[field["name"]] = form_field
+ if "default_date" in field:
form_field.initial = self.get_date_initial(field=field)
- elif 'default_boolean' in field:
- form_field.initial = field['default_boolean']
- elif 'default' in field:
- form_field.initial = field['default']
+ elif "default_boolean" in field:
+ form_field.initial = field["default_boolean"]
+ elif "default" in field:
+ form_field.initial = field["default"]
def save(self):
profiles = {
profile.related_model.__name__: profile.related_model()
for profile in Member._meta.related_objects
- if isinstance(profile, OneToOneRel) and profile.name.startswith('profile_')
+ if isinstance(profile, OneToOneRel) and profile.name.startswith("profile_")
}
member = Member()
membership = Membership()
for key, value in self.cleaned_data.items():
- model_name = key.split('__')[0]
- if model_name == 'member':
+ model_name = key.split("__")[0]
+ if model_name == "member":
obj = member
- elif model_name == 'membership':
+ elif model_name == "membership":
obj = membership
else:
- obj = profiles[key.split('__')[0]]
- setattr(obj, key.split('__', maxsplit=1)[-1], value)
+ obj = profiles[key.split("__")[0]]
+ setattr(obj, key.split("__", maxsplit=1)[-1], value)
member.save()
member.refresh_from_db()
membership.member = self.instance = member
initial = True
- dependencies = [
- ]
+ dependencies = []
operations = [
migrations.CreateModel(
- name='Member',
+ name="Member",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('email', models.EmailField(max_length=254)),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("email", models.EmailField(max_length=254)),
],
bases=(byro.common.models.auditable.Auditable, models.Model),
- ),
+ )
]
class Migration(migrations.Migration):
- dependencies = [
- ('members', '0001_initial'),
- ]
+ dependencies = [("members", "0001_initial")]
operations = [
migrations.AddField(
- model_name='member',
- name='address',
+ model_name="member",
+ name="address",
field=models.CharField(blank=True, max_length=300, null=True),
),
migrations.AddField(
- model_name='member',
- name='name',
+ model_name="member",
+ name="name",
field=models.CharField(blank=True, max_length=100, null=True),
),
migrations.AddField(
- model_name='member',
- name='number',
+ model_name="member",
+ name="number",
field=models.CharField(blank=True, max_length=100, null=True),
),
migrations.AlterField(
- model_name='member',
- name='email',
+ model_name="member",
+ name="email",
field=models.EmailField(blank=True, max_length=200, null=True),
),
]
class Migration(migrations.Migration):
- dependencies = [
- ('members', '0002_auto_20171012_1857'),
- ]
+ dependencies = [("members", "0002_auto_20171012_1857")]
operations = [
migrations.CreateModel(
- name='Membership',
+ name="Membership",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('start', models.DateField(verbose_name='start')),
- ('end', models.DateField(blank=True, null=True, verbose_name='end')),
- ('amount', models.DecimalField(decimal_places=2, help_text='The amount to be paid in the chosen interval', max_digits=8, verbose_name='amount')),
- ('interval', models.IntegerField(choices=[(1, 'monthly'), (3, 'quarterly'), (6, 'biannually'), (12, 'annually')], help_text='How often does the member pay their fees?', verbose_name='interval')),
- ('member', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='memberships', to='members.Member')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("start", models.DateField(verbose_name="start")),
+ ("end", models.DateField(blank=True, null=True, verbose_name="end")),
+ (
+ "amount",
+ models.DecimalField(
+ decimal_places=2,
+ help_text="The amount to be paid in the chosen interval",
+ max_digits=8,
+ verbose_name="amount",
+ ),
+ ),
+ (
+ "interval",
+ models.IntegerField(
+ choices=[
+ (1, "monthly"),
+ (3, "quarterly"),
+ (6, "biannually"),
+ (12, "annually"),
+ ],
+ help_text="How often does the member pay their fees?",
+ verbose_name="interval",
+ ),
+ ),
+ (
+ "member",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="memberships",
+ to="members.Member",
+ ),
+ ),
],
bases=(byro.common.models.auditable.Auditable, models.Model),
),
migrations.CreateModel(
- name='MembershipType',
+ name="MembershipType",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(max_length=200, verbose_name='name')),
- ('amount', models.DecimalField(decimal_places=2, help_text='Please enter the yearly fee for this membership type.', max_digits=8, verbose_name='amount')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("name", models.CharField(max_length=200, verbose_name="name")),
+ (
+ "amount",
+ models.DecimalField(
+ decimal_places=2,
+ help_text="Please enter the yearly fee for this membership type.",
+ max_digits=8,
+ verbose_name="amount",
+ ),
+ ),
],
bases=(byro.common.models.auditable.Auditable, models.Model),
),
class Migration(migrations.Migration):
- dependencies = [
- ('members', '0003_membership_membershiptype'),
- ]
+ dependencies = [("members", "0003_membership_membershiptype")]
operations = [
migrations.AlterField(
- model_name='member',
- name='address',
+ model_name="member",
+ name="address",
field=models.TextField(blank=True, max_length=300, null=True),
- ),
+ )
]
class Migration(migrations.Migration):
- dependencies = [
- ('members', '0004_auto_20171013_1436'),
- ]
+ dependencies = [("members", "0004_auto_20171013_1436")]
operations = [
migrations.AlterField(
- model_name='member',
- name='address',
- field=models.TextField(blank=True, max_length=300, null=True, verbose_name='Address'),
+ model_name="member",
+ name="address",
+ field=models.TextField(
+ blank=True, max_length=300, null=True, verbose_name="Address"
+ ),
),
migrations.AlterField(
- model_name='member',
- name='email',
- field=models.EmailField(blank=True, max_length=200, null=True, verbose_name='E-Mail'),
+ model_name="member",
+ name="email",
+ field=models.EmailField(
+ blank=True, max_length=200, null=True, verbose_name="E-Mail"
+ ),
),
migrations.AlterField(
- model_name='member',
- name='name',
- field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Name'),
+ model_name="member",
+ name="name",
+ field=models.CharField(
+ blank=True, max_length=100, null=True, verbose_name="Name"
+ ),
),
migrations.AlterField(
- model_name='member',
- name='number',
- field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Membership number/ID'),
+ model_name="member",
+ name="number",
+ field=models.CharField(
+ blank=True,
+ max_length=100,
+ null=True,
+ verbose_name="Membership number/ID",
+ ),
),
migrations.AlterField(
- model_name='membership',
- name='amount',
- field=models.DecimalField(decimal_places=2, help_text='The amount to be paid in the chosen interval', max_digits=8, verbose_name='membership fee'),
+ model_name="membership",
+ name="amount",
+ field=models.DecimalField(
+ decimal_places=2,
+ help_text="The amount to be paid in the chosen interval",
+ max_digits=8,
+ verbose_name="membership fee",
+ ),
),
migrations.AlterField(
- model_name='membership',
- name='interval',
- field=models.IntegerField(choices=[(1, 'monthly'), (3, 'quarterly'), (6, 'biannually'), (12, 'annually')], help_text='How often does the member pay their fees?', verbose_name='payment interval'),
+ model_name="membership",
+ name="interval",
+ field=models.IntegerField(
+ choices=[
+ (1, "monthly"),
+ (3, "quarterly"),
+ (6, "biannually"),
+ (12, "annually"),
+ ],
+ help_text="How often does the member pay their fees?",
+ verbose_name="payment interval",
+ ),
),
]
class Migration(migrations.Migration):
- dependencies = [
- ('members', '0005_auto_20171206_1919'),
- ]
+ dependencies = [("members", "0005_auto_20171206_1919")]
operations = [
migrations.AlterField(
- model_name='member',
- name='number',
- field=models.CharField(blank=True, db_index=True, max_length=100, null=True, verbose_name='Membership number/ID'),
- ),
+ model_name="member",
+ name="number",
+ field=models.CharField(
+ blank=True,
+ db_index=True,
+ max_length=100,
+ null=True,
+ verbose_name="Membership number/ID",
+ ),
+ )
]
class Migration(migrations.Migration):
- dependencies = [
- ('members', '0006_auto_20180113_1849'),
- ]
+ dependencies = [("members", "0006_auto_20180113_1849")]
operations = [
migrations.AddField(
- model_name='member',
- name='membership_type',
- field=models.CharField(default='member', max_length=40),
- ),
+ model_name="member",
+ name="membership_type",
+ field=models.CharField(default="member", max_length=40),
+ )
]
class Migration(migrations.Migration):
- dependencies = [
- ('members', '0007_member_membership_type'),
- ]
+ dependencies = [("members", "0007_member_membership_type")]
operations = [
migrations.AddField(
- model_name='member',
- name='member_contact_type',
- field=models.CharField(choices=[('organization', 'organization'), ('person', 'person'), ('role', 'role')], default='person', max_length=12),
- ),
+ model_name="member",
+ name="member_contact_type",
+ field=models.CharField(
+ choices=[
+ ("organization", "organization"),
+ ("person", "person"),
+ ("role", "role"),
+ ],
+ default="person",
+ max_length=12,
+ ),
+ )
]
class Migration(migrations.Migration):
- dependencies = [
- ('members', '0008_member_member_contact_type'),
- ]
+ dependencies = [("members", "0008_member_member_contact_type")]
operations = [
migrations.AlterField(
- model_name='member',
- name='member_contact_type',
- field=models.CharField(choices=[('organization', 'organization'), ('person', 'person'), ('role', 'role')], default='person', max_length=12, verbose_name='Contact type'),
- ),
+ model_name="member",
+ name="member_contact_type",
+ field=models.CharField(
+ choices=[
+ ("organization", "organization"),
+ ("person", "person"),
+ ("role", "role"),
+ ],
+ default="person",
+ max_length=12,
+ verbose_name="Contact type",
+ ),
+ )
]
class Migration(migrations.Migration):
- dependencies = [
- ('members', '0009_auto_20180512_1810'),
- ]
+ dependencies = [("members", "0009_auto_20180512_1810")]
operations = [
migrations.CreateModel(
- name='MemberBalance',
+ name="MemberBalance",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('reference', models.CharField(blank=True, help_text='For example an invoice number or a payment reference', max_length=50, null=True, unique=True, verbose_name='Reference')),
- ('amount', models.DecimalField(decimal_places=2, max_digits=8, verbose_name='Amount')),
- ('start', models.DateTimeField(verbose_name='Start')),
- ('end', models.DateTimeField(verbose_name='End')),
- ('state', models.CharField(choices=[('paid', 'paid'), ('partial', 'partially paid'), ('unpaid', 'unpaid')], default='unpaid', max_length=7)),
- ('member', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='balances', to='members.Member')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "reference",
+ models.CharField(
+ blank=True,
+ help_text="For example an invoice number or a payment reference",
+ max_length=50,
+ null=True,
+ unique=True,
+ verbose_name="Reference",
+ ),
+ ),
+ (
+ "amount",
+ models.DecimalField(
+ decimal_places=2, max_digits=8, verbose_name="Amount"
+ ),
+ ),
+ ("start", models.DateTimeField(verbose_name="Start")),
+ ("end", models.DateTimeField(verbose_name="End")),
+ (
+ "state",
+ models.CharField(
+ choices=[
+ ("paid", "paid"),
+ ("partial", "partially paid"),
+ ("unpaid", "unpaid"),
+ ],
+ default="unpaid",
+ max_length=7,
+ ),
+ ),
+ (
+ "member",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="balances",
+ to="members.Member",
+ ),
+ ),
],
- ),
+ )
]
"Encountered 'None' while following {}".format(self.path)
)
setattr(target, prop, value)
- if callable(getattr(target, 'save', None)):
+ if callable(getattr(target, "save", None)):
target.save()
class MemberTypes:
- MEMBER = 'member'
- EXTERNAL = 'external'
+ MEMBER = "member"
+ EXTERNAL = "external"
class MemberContactTypes(Choices):
- ORGANIZATION = 'organization'
- PERSON = 'person'
- ROLE = 'role'
+ ORGANIZATION = "organization"
+ PERSON = "person"
+ ROLE = "role"
class MemberQuerySet(models.QuerySet):
def with_active_membership(self):
- return self.filter(
- Q(memberships__start__lte=now().date())
- & (
+ return (
+ self.filter(
+ Q(memberships__start__lte=now().date())
+ & (
Q(memberships__end__isnull=True)
| Q(memberships__end__gte=now().date())
+ )
)
- ).order_by('-id').distinct()
+ .order_by("-id")
+ .distinct()
+ )
class MemberManager(models.Manager):
def get_queryset(self):
- return MemberQuerySet(self.model, using=self._db).filter(membership_type=MemberTypes.MEMBER)
+ return MemberQuerySet(self.model, using=self._db).filter(
+ membership_type=MemberTypes.MEMBER
+ )
def with_active_membership(self):
return self.get_queryset().with_active_membership()
def get_next_member_number():
- all_numbers = Member.all_objects.all().values_list('number', flat=True)
+ all_numbers = Member.all_objects.all().values_list("number", flat=True)
numeric_numbers = [n for n in all_numbers if n is not None and n.isdigit()]
try:
return max(int(n) for n in numeric_numbers) + 1
def get_member_data(obj):
- if hasattr(obj, 'get_member_data'):
+ if hasattr(obj, "get_member_data"):
return obj.get_member_data()
return [
(field.verbose_name, str(getattr(obj, field.name)))
for field in obj._meta.fields
if field.name
- not in ('id', 'created_by', 'modified_by', 'created', 'modified', 'member')
+ not in ("id", "created_by", "modified_by", "created", "modified", "member")
]
class Member(Auditable, models.Model, LogTargetMixin):
- LOG_TARGET_BASE = 'byro.members'
+ LOG_TARGET_BASE = "byro.members"
number = models.CharField(
max_length=100,
- verbose_name=_('Membership number/ID'),
+ verbose_name=_("Membership number/ID"),
null=True,
blank=True,
db_index=True,
)
name = models.CharField(
- max_length=100, verbose_name=_('Name'), null=True, blank=True
+ max_length=100, verbose_name=_("Name"), null=True, blank=True
)
address = models.TextField(
- max_length=300, verbose_name=_('Address'), null=True, blank=True
+ max_length=300, verbose_name=_("Address"), null=True, blank=True
)
email = models.EmailField(
- max_length=200, verbose_name=_('E-Mail'), null=True, blank=True
+ max_length=200, verbose_name=_("E-Mail"), null=True, blank=True
)
membership_type = models.CharField(max_length=40, default=MemberTypes.MEMBER)
member_contact_type = models.CharField(
max_length=MemberContactTypes.max_length,
- verbose_name=_('Contact type'),
+ verbose_name=_("Contact type"),
choices=MemberContactTypes.choices,
default=MemberContactTypes.PERSON,
)
- form_title = _('Member')
+ form_title = _("Member")
objects = MemberManager()
all_objects = AllMemberManager()
return [
related.related_model
for related in cls._meta.related_objects
- if isinstance(related, OneToOneRel) and related.name.startswith('profile_')
+ if isinstance(related, OneToOneRel) and related.name.startswith("profile_")
]
@property
return [
getattr(self, related.name)
for related in self._meta.related_objects
- if isinstance(related, OneToOneRel) and related.name.startswith('profile_')
+ if isinstance(related, OneToOneRel) and related.name.startswith("profile_")
]
@classmethod
result.append(
Field(
- '_internal_id',
- _('Internal database ID'),
+ "_internal_id",
+ _("Internal database ID"),
"",
- 'pk',
+ "pk",
computed=True,
read_only=True,
)
)
result.append(
Field(
- '_internal_active',
- _('Member active?'),
+ "_internal_active",
+ _("Member active?"),
"",
- 'is_active',
+ "is_active",
computed=True,
read_only=True,
)
)
result.append(
Field(
- '_internal_balance',
- _('Account balance'),
+ "_internal_balance",
+ _("Account balance"),
"",
- 'balance',
+ "balance",
computed=True,
read_only=True,
)
)
reg_form = Configuration.get_solo().registration_form or []
- form_config = {entry['name']: entry for entry in reg_form}
+ form_config = {entry["name"]: entry for entry in reg_form}
profile_map = {
profile.related_model: profile.name
for profile in cls._meta.related_objects
- if isinstance(profile, OneToOneRel) and profile.name.startswith('profile_')
+ if isinstance(profile, OneToOneRel) and profile.name.startswith("profile_")
}
for model in [cls, Membership] + cls.profile_classes:
for field in model._meta.fields:
- if field.name in ('id', 'member') or (
- model is Member and field.name == 'membership_type'
+ if field.name in ("id", "member") or (
+ model is Member and field.name == "membership_type"
):
continue
)
if asset_start:
credits = credits.filter(transaction__value_datetime__gte=asset_start)
- liability = debits.aggregate(liability=models.Sum('amount'))[
- 'liability'
- ] or Decimal('0.00')
- asset = credits.aggregate(asset=models.Sum('amount'))['asset'] or Decimal(
- '0.00'
+ liability = debits.aggregate(liability=models.Sum("amount"))[
+ "liability"
+ ] or Decimal("0.00")
+ asset = credits.aggregate(asset=models.Sum("amount"))["asset"] or Decimal(
+ "0.00"
)
return asset - liability
) # end overlaps
).exists():
raise Exception(
- 'Cannot create overlapping balance: {} from {} to {}'.format(
+ "Cannot create overlapping balance: {} from {} to {}".format(
self, start, end
)
)
last_unenforceable_date = (
now().replace(month=12, day=31) - limit - relativedelta(years=1)
)
- return max(Decimal('0.00'), -self._calc_balance(last_unenforceable_date))
+ return max(Decimal("0.00"), -self._calc_balance(last_unenforceable_date))
@property
def donation_balance(self) -> Decimal:
- return self.donations.aggregate(donations=models.Sum('amount'))[
- 'donations'
- ] or Decimal('0.00')
+ return self.donations.aggregate(donations=models.Sum("amount"))[
+ "donations"
+ ] or Decimal("0.00")
@property
def donations(self):
key_length = min(max(len(d[0]) for d in key_value_data), 20)
key_value_text = []
for key, value in key_value_data:
- key = (key + ':').ljust(key_length) + ' '
+ key = (key + ":").ljust(key_length) + " "
value = value.strip()
- if value in [None, 'None', '']:
- value = '-'
- if isinstance(value, str) and '\n' in value:
- value = '\n'.join(
- [' ' * (key_length + 1) + line for line in value.split('\n')]
+ if value in [None, "None", ""]:
+ value = "-"
+ if isinstance(value, str) and "\n" in value:
+ value = "\n".join(
+ [" " * (key_length + 1) + line for line in value.split("\n")]
).strip()
key_value_text.append(key + value)
- key_value_text = '\n'.join(key_value_text)
+ key_value_text = "\n".join(key_value_text)
if text_data:
- key_value_text += '\n' + '\n'.join(text_data)
+ key_value_text += "\n" + "\n".join(text_data)
context = {
- 'association_name': config.name,
- 'data': key_value_text,
- 'number': self.number,
- 'balance': '{currency} {balance}'.format(
+ "association_name": config.name,
+ "data": key_value_text,
+ "number": self.number,
+ "balance": "{currency} {balance}".format(
currency=config.currency, balance=self.balance
),
}
for wrong_due in sorted(wrong_dues_in_db):
booking = dues_in_db[wrong_due]
booking.transaction.reverse(
- memo=_('Due amount canceled because of change in membership amount'),
- user_or_context='internal: update_liabilites, membership amount changed',
+ memo=_("Due amount canceled because of change in membership amount"),
+ user_or_context="internal: update_liabilites, membership amount changed",
)
# Step 5:
value_datetime=date,
booking_datetime=_now,
memo=_("Membership due"),
- user_or_context='internal: update_liabilites, add missing liabilities',
+ user_or_context="internal: update_liabilites, add missing liabilities",
)
t.credit(
account=src_account,
amount=amount,
member=self,
- user_or_context='internal: update_liabilites, add missing liabilities',
+ user_or_context="internal: update_liabilites, add missing liabilities",
)
t.debit(
account=dst_account,
amount=amount,
member=self,
- user_or_context='internal: update_liabilites, add missing liabilities',
+ user_or_context="internal: update_liabilites, add missing liabilities",
)
t.save()
)
if membership_ranges:
stray_liabilities_qs = stray_liabilities_qs.exclude(date_range_q)
- stray_liabilities_qs = stray_liabilities_qs.prefetch_related('transaction')
+ stray_liabilities_qs = stray_liabilities_qs.prefetch_related("transaction")
for stray_liability in stray_liabilities_qs.all():
stray_liability.transaction.reverse(
memo=_("Due amount outside of membership canceled"),
- user_or_context='internal: update_liabilites, reverse stray liabilities',
+ user_or_context="internal: update_liabilites, reverse stray liabilities",
)
def __str__(self):
- return 'Member {self.number} ({self.name})'.format(self=self)
+ return "Member {self.number} ({self.name})".format(self=self)
def get_absolute_url(self):
- return reverse('office:members.data', kwargs={'pk': self.pk})
+ return reverse("office:members.data", kwargs={"pk": self.pk})
def get_object_icon(self):
return mark_safe('<i class="fa fa-user"></i> ')
@classproperty
def choices(cls):
return (
- (cls.MONTHLY, _('monthly')),
- (cls.QUARTERLY, _('quarterly')),
- (cls.BIANNUAL, _('biannually')),
- (cls.ANNUALLY, _('annually')),
+ (cls.MONTHLY, _("monthly")),
+ (cls.QUARTERLY, _("quarterly")),
+ (cls.BIANNUAL, _("biannually")),
+ (cls.ANNUALLY, _("annually")),
)
class MembershipType(Auditable, models.Model):
- name = models.CharField(max_length=200, verbose_name=_('name'))
+ name = models.CharField(max_length=200, verbose_name=_("name"))
amount = models.DecimalField(
max_digits=8,
decimal_places=2,
- verbose_name=_('amount'),
- help_text=_('Please enter the yearly fee for this membership type.'),
+ verbose_name=_("amount"),
+ help_text=_("Please enter the yearly fee for this membership type."),
)
class Membership(Auditable, models.Model, LogTargetMixin):
- LOG_TARGET_BASE = 'byro.members.membership'
+ LOG_TARGET_BASE = "byro.members.membership"
member = models.ForeignKey(
- to='members.Member', on_delete=models.CASCADE, related_name='memberships'
+ to="members.Member", on_delete=models.CASCADE, related_name="memberships"
)
- start = models.DateField(verbose_name=_('start'))
- end = models.DateField(verbose_name=_('end'), null=True, blank=True)
+ start = models.DateField(verbose_name=_("start"))
+ end = models.DateField(verbose_name=_("end"), null=True, blank=True)
amount = models.DecimalField(
max_digits=8,
decimal_places=2,
- verbose_name=_('membership fee'),
- help_text=_('The amount to be paid in the chosen interval'),
+ verbose_name=_("membership fee"),
+ help_text=_("The amount to be paid in the chosen interval"),
)
interval = models.IntegerField(
choices=FeeIntervals.choices,
- verbose_name=_('payment interval'),
- help_text=_('How often does the member pay their fees?'),
+ verbose_name=_("payment interval"),
+ help_text=_("How often does the member pay their fees?"),
)
- form_title = _('Membership')
+ form_title = _("Membership")
def get_absolute_url(self):
- return reverse('office:members.data', kwargs={'pk': self.member.pk})
+ return reverse("office:members.data", kwargs={"pk": self.member.pk})
def get_dues(self, _now=None):
_now = _now or now()
return (self.start, end), dues
-SPECIAL_NAMES = {Member: 'member', Membership: 'membership'}
+SPECIAL_NAMES = {Member: "member", Membership: "membership"}
SPECIAL_ORDER = [
- 'member__number',
- 'member__name',
- 'member__address',
- 'member__email',
- 'membership__start',
- 'membership__interval',
- 'membership__amount',
+ "member__number",
+ "member__name",
+ "member__address",
+ "member__email",
+ "membership__start",
+ "membership__interval",
+ "membership__amount",
]
"""
member = models.ForeignKey(
- to='members.Member', related_name='balances', on_delete=models.PROTECT
+ to="members.Member", related_name="balances", on_delete=models.PROTECT
)
reference = models.CharField(
null=True,
blank=True,
- verbose_name=_('Reference'),
- help_text=_('For example an invoice number or a payment reference'),
+ verbose_name=_("Reference"),
+ help_text=_("For example an invoice number or a payment reference"),
unique=True,
max_length=50,
)
amount = models.DecimalField(
- max_digits=8, decimal_places=2, verbose_name=_('Amount')
+ max_digits=8, decimal_places=2, verbose_name=_("Amount")
)
- start = models.DateTimeField(verbose_name=_('Start'))
- end = models.DateTimeField(verbose_name=_('End'))
+ start = models.DateTimeField(verbose_name=_("Start"))
+ end = models.DateTimeField(verbose_name=_("End"))
state = models.CharField(
choices=[
- ('paid', _('paid')),
- ('partial', _('partially paid')),
- ('unpaid', _('unpaid')),
+ ("paid", _("paid")),
+ ("partial", _("partially paid")),
+ ("unpaid", _("unpaid")),
],
- default='unpaid',
+ default="unpaid",
max_length=7,
)
"""
Returns a list of tuples of the form ((year, month), joins, quits).
"""
- first_member = Membership.objects.order_by('start').first()
+ first_member = Membership.objects.order_by("start").first()
if not first_member:
return []
date = first_member.start
- end_member = Membership.objects.filter(end__isnull=False).order_by('-end').first()
+ end_member = Membership.objects.filter(end__isnull=False).order_by("-end").first()
if not end_member:
return []
end = end_member.end
def get_misc_finance_timeline(member):
- for entry in member.log_entries().filter(action_type__startswith="byro.members.finance."):
+ for entry in member.log_entries().filter(
+ action_type__startswith="byro.members.finance."
+ ):
base_data = {
"type": "finance",
"icon": "money",
"instance": entry,
"date": entry.datetime,
}
- if entry.action_type == "byro.members.finance.sepadd.mandate_reference_assigned":
- yield dict(base_data, subtype="sepadd-mandate-reference-assigned", icon="info")
+ if (
+ entry.action_type
+ == "byro.members.finance.sepadd.mandate_reference_assigned"
+ ):
+ yield dict(
+ base_data, subtype="sepadd-mandate-reference-assigned", icon="info"
+ )
else:
yield dict(base_data, subtype="other")
def get_finance_timeline(member):
return sorted_merge(
- get_base_finance_timeline(member),
- get_misc_finance_timeline(member),
+ get_base_finance_timeline(member), get_misc_finance_timeline(member)
)
def get_misc_ops_timeline(member):
- for entry in member.log_entries().exclude(action_type__startswith="byro.members.finance."):
+ for entry in member.log_entries().exclude(
+ action_type__startswith="byro.members.finance."
+ ):
base_data = {
"type": "ops",
"icon": "user",
def get_file_icon(document):
- return {"application/pdf": "file-pdf-o"}.get(document.mime_type_guessed, 'file-o')
+ return {"application/pdf": "file-pdf-o"}.get(document.mime_type_guessed, "file-o")
def get_document_timeline(member):
- for document in member.documents.order_by('-date', '-id').all():
+ for document in member.documents.order_by("-date", "-id").all():
base_data = {
"type": "document",
"icon": get_file_icon(document),
class OfficeConfig(AppConfig):
- name = 'byro.office'
+ name = "byro.office"
def ready(self):
from . import signals # noqa
from byro.documents.models import get_document_category_names
-@register.filter(name='translate_document_category')
+@register.filter(name="translate_document_category")
def translate_document_category(data):
- return get_document_category_names().get(data, _('Unknown document type'))
+ return get_document_category_names().get(data, _("Unknown document type"))
from django.template.defaultfilters import register
-@register.filter(name='paginate_loop')
+@register.filter(name="paginate_loop")
def translate_document_category(page_obj, context=5):
items = [1, page_obj.number, page_obj.paginator.num_pages]
for i in range(context):
users,
)
-app_name = 'office'
+app_name = "office"
urlpatterns = [
- url('^settings/initial$', settings.InitialSettings.as_view(), name='settings.initial'),
- url('^settings/log$', settings.LogView.as_view(), name='settings.log'),
- url('^settings/about$', settings.AboutByroView.as_view(), name='settings.about'),
- url('^settings/registration$', settings.RegistrationConfigView.as_view(), name='settings.registration'),
- url('^settings/users/$', users.UserListView.as_view(), name='settings.users.list'),
- url('^settings/users/add$', users.UserCreateView.as_view(), name='settings.users.add'),
- url(r'^settings/users/(?P<pk>\d+)/$', users.UserDetailView.as_view(), name='settings.users.detail'),
- url('^settings$', settings.ConfigurationView.as_view(), name='settings.base'),
- url('^$', dashboard.DashboardView.as_view(), name='dashboard'),
- url(r'^members/typeahead$', members.MemberListTypeaheadView.as_view(), name='members.typeahead'),
- url(r'^members/list$', members.MemberListView.as_view(), name='members.list'),
- url(r'^members/list/export$', members.MemberListExportView.as_view(), name='members.list.export'),
- url(r'^members/list/import$', members.MemberListImportView.as_view(), name='members.list.import'),
- url(r'^members/list/disclosure$', members.MemberDisclosureView.as_view(), name='members.disclosure'),
- url(r'^members/list/balance$', members.MemberBalanceView.as_view(), name='members.balance'),
- url(r'^members/add$', members.MemberCreateView.as_view(), name='members.add'),
- url(r'^members/view/(?P<pk>\d+)/', include([
- url('data$', members.MemberDataView.as_view(), name='members.data'),
- url('timeline$', members.MemberTimelineView.as_view(), name='members.timeline'),
- url('finance$', members.MemberFinanceView.as_view(), name='members.finance'),
- url('operations$', members.MemberOperationsView.as_view(), name='members.operations'),
- url('record-disclosure$', members.MemberRecordDisclosureView.as_view(), name='members.record-disclosure'),
- url('log$', members.MemberLogView.as_view(), name='members.log'),
- url('mails$', members.MemberMailsView.as_view(), name='members.mails'),
- url('documents$', members.MemberDocumentsView.as_view(), name='members.documents'),
- url('$', members.MemberDashboardView.as_view(), name='members.dashboard'),
- ])),
-
- url(r'^transactions/(?P<pk>\d+)/', transactions.TransactionDetailView.as_view(), name='finance.transactions.detail'),
-
- url('^upload/list', upload.UploadListView.as_view(), name='finance.uploads.list'),
- url(r'^upload/process/(?P<pk>\d+)', upload.UploadProcessView.as_view(), name='finance.uploads.process'),
- url(r'^upload/match/(?P<pk>\d+)', upload.UploadMatchView.as_view(), name='finance.uploads.match'),
- url('^upload/add', upload.CsvUploadView.as_view(), name='finance.uploads.add'),
-
- url('^documents/add', documents.DocumentUploadView.as_view(), name='documents.add'),
- url(r'^documents/(?P<pk>\d+)', documents.DocumentDetailView.as_view(), name='documents.detail'),
-
- url('^accounts/$', accounts.AccountListView.as_view(), name='finance.accounts.list'),
- url('^accounts/add$', accounts.AccountCreateView.as_view(), name='finance.accounts.add'),
- url(r'^accounts/(?P<pk>\d+)/$', accounts.AccountDetailView.as_view(), name='finance.accounts.detail'),
- url(r'^accounts/(?P<pk>\d+)/delete$', accounts.AccountDeleteView.as_view(), name='finance.accounts.delete'),
-
- url('^mails/(?P<pk>[0-9]+)$', mails.MailDetail.as_view(), name='mails.mail.view'),
- url('^mails/(?P<pk>[0-9]+)/copy$', mails.MailCopy.as_view(), name='mails.mail.copy'),
- url('^mails/(?P<pk>[0-9]+)/delete$', mails.OutboxPurge.as_view(), name='mails.mail.delete'),
- url('^mails/(?P<pk>[0-9]+)/send$', mails.OutboxSend.as_view(), name='mails.mail.send'),
- url('^mails/compose$', mails.Compose.as_view(), name='mails.compose'),
- url('^mails/sent$', mails.SentMail.as_view(), name='mails.sent'),
- url('^mails/outbox$', mails.OutboxList.as_view(), name='mails.outbox.list'),
- url('^mails/outbox/send$', mails.OutboxSend.as_view(), name='mails.outbox.send'),
- url('^mails/outbox/purge$', mails.OutboxPurge.as_view(), name='mails.outbox.purge'),
-
- url('^mails/templates$', mails.TemplateList.as_view(), name='mails.templates.list'),
- url('^mails/templates/add$', mails.TemplateCreate.as_view(), name='mails.templates.add'),
- url('^mails/templates/(?P<pk>[0-9]+)$', mails.TemplateDetail.as_view(), name='mails.templates.view'),
- url('^templates/(?P<pk>[0-9]+)/delete$', mails.TemplateDelete.as_view(), name='mails.templates.delete'),
-
+ url(
+ "^settings/initial$",
+ settings.InitialSettings.as_view(),
+ name="settings.initial",
+ ),
+ url("^settings/log$", settings.LogView.as_view(), name="settings.log"),
+ url("^settings/about$", settings.AboutByroView.as_view(), name="settings.about"),
+ url(
+ "^settings/registration$",
+ settings.RegistrationConfigView.as_view(),
+ name="settings.registration",
+ ),
+ url("^settings/users/$", users.UserListView.as_view(), name="settings.users.list"),
+ url(
+ "^settings/users/add$",
+ users.UserCreateView.as_view(),
+ name="settings.users.add",
+ ),
+ url(
+ r"^settings/users/(?P<pk>\d+)/$",
+ users.UserDetailView.as_view(),
+ name="settings.users.detail",
+ ),
+ url("^settings$", settings.ConfigurationView.as_view(), name="settings.base"),
+ url("^$", dashboard.DashboardView.as_view(), name="dashboard"),
+ url(
+ r"^members/typeahead$",
+ members.MemberListTypeaheadView.as_view(),
+ name="members.typeahead",
+ ),
+ url(r"^members/list$", members.MemberListView.as_view(), name="members.list"),
+ url(
+ r"^members/list/export$",
+ members.MemberListExportView.as_view(),
+ name="members.list.export",
+ ),
+ url(
+ r"^members/list/import$",
+ members.MemberListImportView.as_view(),
+ name="members.list.import",
+ ),
+ url(
+ r"^members/list/disclosure$",
+ members.MemberDisclosureView.as_view(),
+ name="members.disclosure",
+ ),
+ url(
+ r"^members/list/balance$",
+ members.MemberBalanceView.as_view(),
+ name="members.balance",
+ ),
+ url(r"^members/add$", members.MemberCreateView.as_view(), name="members.add"),
+ url(
+ r"^members/view/(?P<pk>\d+)/",
+ include(
+ [
+ url("data$", members.MemberDataView.as_view(), name="members.data"),
+ url(
+ "timeline$",
+ members.MemberTimelineView.as_view(),
+ name="members.timeline",
+ ),
+ url(
+ "finance$",
+ members.MemberFinanceView.as_view(),
+ name="members.finance",
+ ),
+ url(
+ "operations$",
+ members.MemberOperationsView.as_view(),
+ name="members.operations",
+ ),
+ url(
+ "record-disclosure$",
+ members.MemberRecordDisclosureView.as_view(),
+ name="members.record-disclosure",
+ ),
+ url("log$", members.MemberLogView.as_view(), name="members.log"),
+ url("mails$", members.MemberMailsView.as_view(), name="members.mails"),
+ url(
+ "documents$",
+ members.MemberDocumentsView.as_view(),
+ name="members.documents",
+ ),
+ url(
+ "$", members.MemberDashboardView.as_view(), name="members.dashboard"
+ ),
+ ]
+ ),
+ ),
+ url(
+ r"^transactions/(?P<pk>\d+)/",
+ transactions.TransactionDetailView.as_view(),
+ name="finance.transactions.detail",
+ ),
+ url("^upload/list", upload.UploadListView.as_view(), name="finance.uploads.list"),
+ url(
+ r"^upload/process/(?P<pk>\d+)",
+ upload.UploadProcessView.as_view(),
+ name="finance.uploads.process",
+ ),
+ url(
+ r"^upload/match/(?P<pk>\d+)",
+ upload.UploadMatchView.as_view(),
+ name="finance.uploads.match",
+ ),
+ url("^upload/add", upload.CsvUploadView.as_view(), name="finance.uploads.add"),
+ url("^documents/add", documents.DocumentUploadView.as_view(), name="documents.add"),
+ url(
+ r"^documents/(?P<pk>\d+)",
+ documents.DocumentDetailView.as_view(),
+ name="documents.detail",
+ ),
+ url(
+ "^accounts/$", accounts.AccountListView.as_view(), name="finance.accounts.list"
+ ),
+ url(
+ "^accounts/add$",
+ accounts.AccountCreateView.as_view(),
+ name="finance.accounts.add",
+ ),
+ url(
+ r"^accounts/(?P<pk>\d+)/$",
+ accounts.AccountDetailView.as_view(),
+ name="finance.accounts.detail",
+ ),
+ url(
+ r"^accounts/(?P<pk>\d+)/delete$",
+ accounts.AccountDeleteView.as_view(),
+ name="finance.accounts.delete",
+ ),
+ url("^mails/(?P<pk>[0-9]+)$", mails.MailDetail.as_view(), name="mails.mail.view"),
+ url(
+ "^mails/(?P<pk>[0-9]+)/copy$", mails.MailCopy.as_view(), name="mails.mail.copy"
+ ),
+ url(
+ "^mails/(?P<pk>[0-9]+)/delete$",
+ mails.OutboxPurge.as_view(),
+ name="mails.mail.delete",
+ ),
+ url(
+ "^mails/(?P<pk>[0-9]+)/send$",
+ mails.OutboxSend.as_view(),
+ name="mails.mail.send",
+ ),
+ url("^mails/compose$", mails.Compose.as_view(), name="mails.compose"),
+ url("^mails/sent$", mails.SentMail.as_view(), name="mails.sent"),
+ url("^mails/outbox$", mails.OutboxList.as_view(), name="mails.outbox.list"),
+ url("^mails/outbox/send$", mails.OutboxSend.as_view(), name="mails.outbox.send"),
+ url("^mails/outbox/purge$", mails.OutboxPurge.as_view(), name="mails.outbox.purge"),
+ url("^mails/templates$", mails.TemplateList.as_view(), name="mails.templates.list"),
+ url(
+ "^mails/templates/add$",
+ mails.TemplateCreate.as_view(),
+ name="mails.templates.add",
+ ),
+ url(
+ "^mails/templates/(?P<pk>[0-9]+)$",
+ mails.TemplateDetail.as_view(),
+ name="mails.templates.view",
+ ),
+ url(
+ "^templates/(?P<pk>[0-9]+)/delete$",
+ mails.TemplateDelete.as_view(),
+ name="mails.templates.delete",
+ ),
]
from .users import UserCreateView, UserDetailView, UserListView
__all__ = [
- 'AccountCreateView',
- 'AccountDeleteView',
- 'AccountDetailView',
- 'AccountListView',
- 'ConfigurationView',
- 'DashboardView',
- 'MemberCreateView',
- 'MemberDashboardView',
- 'MemberDataView',
- 'MemberFinanceView',
- 'MemberOperationsView',
- 'MemberListView',
- 'MemberListExportView',
- 'MemberListImportView',
- 'MemberMailsView',
- 'MemberTimelineView',
- 'UserListView',
- 'UserCreateView',
- 'UserDetailView',
- 'RegistrationConfigView',
- 'TransactionDetailView',
+ "AccountCreateView",
+ "AccountDeleteView",
+ "AccountDetailView",
+ "AccountListView",
+ "ConfigurationView",
+ "DashboardView",
+ "MemberCreateView",
+ "MemberDashboardView",
+ "MemberDataView",
+ "MemberFinanceView",
+ "MemberOperationsView",
+ "MemberListView",
+ "MemberListExportView",
+ "MemberListImportView",
+ "MemberMailsView",
+ "MemberTimelineView",
+ "UserListView",
+ "UserCreateView",
+ "UserDetailView",
+ "RegistrationConfigView",
+ "TransactionDetailView",
]
from byro.bookkeeping.models import Account, AccountCategory, Transaction
-FORM_CLASS = forms.modelform_factory(Account, fields=['name', 'account_category'])
+FORM_CLASS = forms.modelform_factory(Account, fields=["name", "account_category"])
ACCOUNT_COLUMN_HEADERS = {
# FIXME Check this with an accountant who is a native english speaker
- AccountCategory.INCOME: (_('Charge'), _('Revenue')),
- AccountCategory.ASSET: (_('Increase'), _('Decrease')),
- AccountCategory.EQUITY: (_('Decrease'), _('Increase')),
- AccountCategory.LIABILITY: (_('Decrease'), _('Increase')),
- AccountCategory.EXPENSE: (_('Expense'), _('Rebate')),
+ AccountCategory.INCOME: (_("Charge"), _("Revenue")),
+ AccountCategory.ASSET: (_("Increase"), _("Decrease")),
+ AccountCategory.EQUITY: (_("Decrease"), _("Increase")),
+ AccountCategory.LIABILITY: (_("Decrease"), _("Increase")),
+ AccountCategory.EXPENSE: (_("Expense"), _("Rebate")),
}
class AccountListView(ListView):
- template_name = 'office/account/list.html'
- context_object_name = 'accounts'
+ template_name = "office/account/list.html"
+ context_object_name = "accounts"
model = Account
class AccountCreateView(FormView):
- template_name = 'office/account/add.html'
+ template_name = "office/account/add.html"
model = Account
form_class = FORM_CLASS
form.save()
messages.success(
self.request,
- _('The account was added, please edit additional details if applicable.'),
+ _("The account was added, please edit additional details if applicable."),
)
self.form = form
return super().form_valid(form)
def get_success_url(self):
return reverse(
- 'office:finance.accounts.detail', kwargs={'pk': self.form.instance.pk}
+ "office:finance.accounts.detail", kwargs={"pk": self.form.instance.pk}
)
class AccountDetailView(ListView):
- template_name = 'office/account/detail.html'
- context_object_name = 'bookings'
+ template_name = "office/account/detail.html"
+ context_object_name = "bookings"
model = Transaction
paginate_by = 25
def get_object(self):
- if not hasattr(self, 'object'):
- self.object = Account.objects.get(pk=self.kwargs['pk'])
+ if not hasattr(self, "object"):
+ self.object = Account.objects.get(pk=self.kwargs["pk"])
return self.object
def get_queryset(self):
qs = self.get_object().bookings_with_transaction_data
- if self.request.GET.get('filter') == 'unbalanced':
+ if self.request.GET.get("filter") == "unbalanced":
qs = qs.exclude(
- transaction_balances_debit=models.F('transaction_balances_credit')
+ transaction_balances_debit=models.F("transaction_balances_credit")
)
qs = qs.filter(transaction__value_datetime__lte=now()).order_by(
- '-transaction__value_datetime'
+ "-transaction__value_datetime"
)
return qs
def get_form(self, request=None):
form = FORM_CLASS(request.POST if request else None, instance=self.get_object())
- form.fields['account_category'].disabled = True
+ form.fields["account_category"].disabled = True
return form
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
- context['form'] = self.get_form()
- context['account'] = self.get_object()
- context['ACCOUNT_COLUMN_HEADERS'] = ACCOUNT_COLUMN_HEADERS.get(
+ context["form"] = self.get_form()
+ context["account"] = self.get_object()
+ context["ACCOUNT_COLUMN_HEADERS"] = ACCOUNT_COLUMN_HEADERS.get(
self.get_object().account_category, (_("Debit"), _("Credit"))
)
return context
form = self.get_form(request)
if form.is_valid() and form.has_changed():
form.save()
- messages.success(self.request, _('Your changes have been saved.'))
- return redirect(reverse('office:finance.accounts.detail', kwargs=self.kwargs))
+ messages.success(self.request, _("Your changes have been saved."))
+ return redirect(reverse("office:finance.accounts.detail", kwargs=self.kwargs))
class AccountDeleteView(DetailView):
model = Account
- context_object_name = 'account'
+ context_object_name = "account"
class DashboardView(TemplateView):
- template_name = 'office/dashboard.html'
+ template_name = "office/dashboard.html"
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
- context['member_count'] = Member.objects.all().count()
- context['active_count'] = Member.objects.with_active_membership().count()
- context['stats'] = get_member_statistics()
+ context["member_count"] = Member.objects.all().count()
+ context["active_count"] = Member.objects.with_active_membership().count()
+ context["stats"] = get_member_statistics()
return context
class DocumentUploadForm(forms.ModelForm):
class Meta:
model = Document
- exclude = ('content_hash', 'member')
+ exclude = ("content_hash", "member")
def __init__(self, *args, **kwargs):
- initial_category = kwargs.pop('initial_category', 'byro.documents.misc')
+ initial_category = kwargs.pop("initial_category", "byro.documents.misc")
super().__init__(*args, **kwargs)
categories = get_document_category_names()
- self.fields['category'] = forms.ChoiceField(
+ self.fields["category"] = forms.ChoiceField(
choices=sorted(categories.items()), initial=initial_category
)
- if 'class' in self.fields['date'].widget.attrs:
- self.fields['date'].widget.attrs['class'] += ' datepicker'
+ if "class" in self.fields["date"].widget.attrs:
+ self.fields["date"].widget.attrs["class"] += " datepicker"
else:
- self.fields['date'].widget.attrs['class'] = 'datepicker'
+ self.fields["date"].widget.attrs["class"] = "datepicker"
class DocumentUploadView(FormView):
- template_name = 'office/documents/add.html'
+ template_name = "office/documents/add.html"
model = Document
form_class = DocumentUploadForm
form.save()
form.instance.log(
self,
- '.saved',
+ ".saved",
file_name=form.instance.document.name,
content_size=form.instance.document.size,
content_hash=form.instance.content_hash,
)
try:
- messages.success(self.request, _('The upload was processed successfully.'))
+ messages.success(self.request, _("The upload was processed successfully."))
except Exception as e:
messages.error(
self.request,
- _('The upload was added successfully, but could not be processed: ')
+ _("The upload was added successfully, but could not be processed: ")
+ str(e),
)
self.form = form
class DocumentDetailView(DetailView):
- template_name = 'office/documents/detail.html'
+ template_name = "office/documents/detail.html"
model = Document
- context_object_name = 'document'
+ context_object_name = "document"
class RestrictedLanguagesI18nModelForm(I18nModelForm):
def __init__(self, *args, **kwargs):
- if 'locales' not in kwargs:
+ if "locales" not in kwargs:
from byro.common.models import Configuration
config = Configuration.get_solo()
- kwargs['locales'] = [config.language or settings.LANGUAGE_CODE]
+ kwargs["locales"] = [config.language or settings.LANGUAGE_CODE]
return super().__init__(*args, **kwargs)
class Meta:
model = EMail
form = RestrictedLanguagesI18nModelForm
- fields = ['to', 'reply_to', 'cc', 'bcc', 'subject', 'text']
+ fields = ["to", "reply_to", "cc", "bcc", "subject", "text"]
to_type = forms.ChoiceField(
choices=[
- ('addr', _('Specific address')),
- ('member', _('Member')),
- ('all', _('All members')),
+ ("addr", _("Specific address")),
+ ("member", _("Member")),
+ ("all", _("All members")),
],
widget=forms.RadioSelect,
- initial='addr',
+ initial="addr",
)
to_member = forms.ModelChoiceField(
super().__init__(*args, **kwargs)
self._decode_special(self.initial)
self.fields[
- 'to'
+ "to"
].required = (
False
) # FIXME Needs to be re-added in case no special mode is active
- self.order_fields(['to_type', 'to_member'])
+ self.order_fields(["to_type", "to_member"])
@staticmethod
def _decode_special(d):
if not d:
return
- if 'to' in d:
- to = d['to']
- d['to_type'] = 'addr'
+ if "to" in d:
+ to = d["to"]
+ d["to_type"] = "addr"
if to.startswith("special:"):
if to.startswith("special:all"):
- d['to_type'] = 'all'
- del d['to']
- elif to.startswith('special:member:'):
- d['to_type'] = 'member'
- d['to_member'] = Member.objects.get(pk=to.split(":", 2)[2])
- del d['to']
+ d["to_type"] = "all"
+ del d["to"]
+ elif to.startswith("special:member:"):
+ d["to_type"] = "member"
+ d["to_member"] = Member.objects.get(pk=to.split(":", 2)[2])
+ del d["to"]
def _encode_special(self):
- if self.cleaned_data['to_type'] == 'all':
- self.cleaned_data['to'] = 'special:all'
- elif self.cleaned_data['to_type'] == 'member':
- self.cleaned_data['to'] = 'special:member:{}'.format(
- self.cleaned_data['to_member'].pk
+ if self.cleaned_data["to_type"] == "all":
+ self.cleaned_data["to"] = "special:all"
+ elif self.cleaned_data["to_type"] == "member":
+ self.cleaned_data["to"] = "special:member:{}".format(
+ self.cleaned_data["to_member"].pk
)
- self.initial['to_type'] = None
- self.initial['to_member'] = None
- del self.cleaned_data['to_type']
- del self.cleaned_data['to_member']
- self.instance.to = self.cleaned_data['to']
- if not self.cleaned_data['to']:
+ self.initial["to_type"] = None
+ self.initial["to_member"] = None
+ del self.cleaned_data["to_type"]
+ del self.cleaned_data["to_member"]
+ self.instance.to = self.cleaned_data["to"]
+ if not self.cleaned_data["to"]:
self.add_error("to", _("Field cannot be empty"))
- self.initial['to_type'] = "addr"
+ self.initial["to_type"] = "addr"
def clean(self):
self._encode_special()
if form.instance.sent:
raise forms.ValidationError(
_(
- 'This mail has been sent already, and cannot be modified. Copy it to a draft instead!'
+ "This mail has been sent already, and cannot be modified. Copy it to a draft instead!"
)
)
result = super().form_valid(form)
- if form.data.get('action', 'save') == 'send':
+ if form.data.get("action", "save") == "send":
form.instance.send()
messages.success(
- self.request, _('Your changes have been saved and the email was sent.')
+ self.request, _("Your changes have been saved and the email was sent.")
)
else:
- messages.success(self.request, _('Your changes have been saved.'))
+ messages.success(self.request, _("Your changes have been saved."))
return result
class MailDetail(MailSendMixin, UpdateView):
queryset = EMail.objects.all()
- template_name = 'office/mails/detail.html'
- context_object_name = 'mail'
+ template_name = "office/mails/detail.html"
+ context_object_name = "mail"
form_class = MailSpecialToFormClass
- success_url = '/mails/outbox'
+ success_url = "/mails/outbox"
def get_form(self, *args, **kwargs):
form = super().get_form(*args, **kwargs)
class MailCopy(View):
def get_object(self):
- return EMail.objects.get(pk=self.kwargs['pk'])
+ return EMail.objects.get(pk=self.kwargs["pk"])
def dispatch(self, request, *args, **kwargs):
new_mail = self.get_object().copy_to_draft()
- messages.success(request, _('Here is your new mail draft!'))
- return redirect(reverse('office:mails.mail.view', kwargs={'pk': new_mail.pk}))
+ messages.success(request, _("Here is your new mail draft!"))
+ return redirect(reverse("office:mails.mail.view", kwargs={"pk": new_mail.pk}))
class OutboxQueryset:
def get_queryset(self):
qs = EMail.objects.filter(sent__isnull=True)
- if 'pk' in self.kwargs:
- return qs.filter(pk=self.kwargs['pk'])
+ if "pk" in self.kwargs:
+ return qs.filter(pk=self.kwargs["pk"])
return qs
class OutboxList(OutboxQueryset, ListView):
- template_name = 'office/mails/outbox.html'
- context_object_name = 'mails'
+ template_name = "office/mails/outbox.html"
+ context_object_name = "mails"
class OutboxPurge(OutboxQueryset, View):
length = len(qs)
qs.delete()
if length > 1:
- message = _('{count} mails have been deleted.').format(count=length)
+ message = _("{count} mails have been deleted.").format(count=length)
elif length == 1:
- message = 'The mail has been deleted.'
+ message = "The mail has been deleted."
else:
- message = 'No mail has been deleted.'
+ message = "No mail has been deleted."
messages.success(request, message)
- return redirect(reverse('office:mails.outbox.list'))
+ return redirect(reverse("office:mails.outbox.list"))
class OutboxSend(OutboxQueryset, View):
for mail in qs:
mail.send()
if length > 1:
- message = _('{count} mails have been sent.').format(count=length)
+ message = _("{count} mails have been sent.").format(count=length)
elif length == 1:
- message = _('The mail has been sent.')
+ message = _("The mail has been sent.")
else:
- message = _('No mail has been sent.')
+ message = _("No mail has been sent.")
messages.success(request, message)
- return redirect(reverse('office:mails.outbox.list'))
+ return redirect(reverse("office:mails.outbox.list"))
class SentMail(ListView):
- queryset = EMail.objects.filter(sent__isnull=False).order_by('-sent')
- template_name = 'office/mails/sent.html'
- context_object_name = 'mails'
+ queryset = EMail.objects.filter(sent__isnull=False).order_by("-sent")
+ template_name = "office/mails/sent.html"
+ context_object_name = "mails"
class TemplateList(ListView):
queryset = MailTemplate.objects.all()
- template_name = 'office/mails/templates.html'
- context_object_name = 'templates'
+ template_name = "office/mails/templates.html"
+ context_object_name = "templates"
MAIL_TEMPLATE_FORM_CLASS = forms.modelform_factory(
MailTemplate,
form=RestrictedLanguagesI18nModelForm,
- fields=['subject', 'text', 'reply_to', 'bcc'],
+ fields=["subject", "text", "reply_to", "bcc"],
)
class TemplateDetail(UpdateView):
queryset = MailTemplate.objects.all()
- template_name = 'office/mails/template_detail.html'
- context_object_name = 'template'
- success_url = '/mails/templates'
+ template_name = "office/mails/template_detail.html"
+ context_object_name = "template"
+ success_url = "/mails/templates"
form_class = MAIL_TEMPLATE_FORM_CLASS
class TemplateCreate(CreateView):
model = MailTemplate
- template_name = 'office/mails/template_detail.html'
- context_object_name = 'template'
- success_url = '/mails/templates'
+ template_name = "office/mails/template_detail.html"
+ context_object_name = "template"
+ success_url = "/mails/templates"
form_class = MAIL_TEMPLATE_FORM_CLASS
class Compose(SuccessMessageMixin, MailSendMixin, CreateView):
model = MailTemplate
- template_name = 'office/mails/compose.html'
- context_object_name = 'template'
- success_url = '/mails/outbox'
+ template_name = "office/mails/compose.html"
+ context_object_name = "template"
+ success_url = "/mails/outbox"
form_class = MailSpecialToFormClass
def get_initial(self):
class MemberView(DetailView):
- context_object_name = 'member'
+ context_object_name = "member"
model = Member
def get_member(self):
- return Member.all_objects.get(pk=self.kwargs.get('pk'))
+ return Member.all_objects.get(pk=self.kwargs.get("pk"))
def get_queryset(self):
return Member.all_objects.all()
r[1]
for r in member_view.send_robust(self.get_object(), request=self.request)
]
- ctx['member_views'] = responses
- ctx['member'] = self.get_member()
+ ctx["member_views"] = responses
+ ctx["member"] = self.get_member()
return ctx
class MemberListMixin:
- def get_members_queryset(self, search=None, _filter='active'):
+ def get_members_queryset(self, search=None, _filter="active"):
qs = Member.objects.all()
if search:
qs = qs.filter(Q(name__icontains=search) | Q(number=search))
Q(memberships__end__isnull=True) | Q(memberships__end__gte=now().date())
)
inactive_q = ~active_q
- if _filter == 'all':
+ if _filter == "all":
pass
- elif _filter == 'inactive':
+ elif _filter == "inactive":
qs = qs.filter(inactive_q)
else: # Default to 'active'
qs = qs.filter(active_q)
- return qs.order_by('-id').distinct()
+ return qs.order_by("-id").distinct()
class MemberListView(MemberListMixin, ListView):
- template_name = 'office/member/list.html'
- context_object_name = 'members'
+ template_name = "office/member/list.html"
+ context_object_name = "members"
model = Member
paginate_by = 50
def get_queryset(self):
- search = self.request.GET.get('q')
- _filter = self.request.GET.get('filter', 'active')
+ search = self.request.GET.get("q")
+ _filter = self.request.GET.get("filter", "active")
return self.get_members_queryset(search, _filter)
def post(self, request, *args, **kwargs):
class MemberDisclosureView(MemberListMixin, TemplateView):
- template_name = 'office/member/disclosure.html'
- context_object_name = 'members'
+ template_name = "office/member/disclosure.html"
+ context_object_name = "members"
model = Member
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
- context['count'] = self.get_members_queryset().count()
+ context["count"] = self.get_members_queryset().count()
return context
def post(self, request, *args, **kwargs):
messages.success(
request,
_(
- 'Generated {count} data disclosure emails and placed them in the outbox.'
+ "Generated {count} data disclosure emails and placed them in the outbox."
).format(count=len(members)),
)
- return redirect('/members/list')
+ return redirect("/members/list")
class MemberBalanceForm(forms.Form):
- start = forms.DateField(label=_('Start of balance timeframe'))
- end = forms.DateField(label=_('End of balance timeframe'))
+ start = forms.DateField(label=_("Start of balance timeframe"))
+ end = forms.DateField(label=_("End of balance timeframe"))
create_if_zero = forms.BooleanField(
- label=_('Create balances even if there was no payment due in the time chosen?'),
+ label=_("Create balances even if there was no payment due in the time chosen?"),
required=False,
initial=False,
)
balance_cutoff = forms.DecimalField(
required=False,
- label=_('Balance cutoff'),
+ label=_("Balance cutoff"),
help_text=_(
- 'Only members with a deficit greater than this will receive an email.'
+ "Only members with a deficit greater than this will receive an email."
),
min_value=0,
)
- subject = forms.CharField(label=_('Subject'))
+ subject = forms.CharField(label=_("Subject"))
text = forms.CharField(
- label=_('Text'),
+ label=_("Text"),
initial=_(
- '''Hello {name},
+ """Hello {name},
we wanted to let you know that within the time from {start}
to {end} you incurred unpaid liabilities in the amount of
EUR {amount}
Please settle this amount as soon as possible, or let us
-know if you think this email is incorrect.'''
+know if you think this email is incorrect."""
),
widget=forms.Textarea,
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- self.fields['start'].widget.attrs['class'] = ' datepicker'
- self.fields['end'].widget.attrs['class'] = ' datepicker'
+ self.fields["start"].widget.attrs["class"] = " datepicker"
+ self.fields["end"].widget.attrs["class"] = " datepicker"
# TODO: set start/end
def clean(self):
cleaned_data = super().clean()
- end = cleaned_data.get('end')
- start = cleaned_data.get('start')
+ end = cleaned_data.get("end")
+ start = cleaned_data.get("start")
if end >= datetime.now().date():
- raise forms.ValidationError(_('End must be in the past!'))
+ raise forms.ValidationError(_("End must be in the past!"))
if end < start:
- raise forms.ValidationError(_('Start must be before end!'))
+ raise forms.ValidationError(_("Start must be before end!"))
return cleaned_data
class MemberBalanceView(MemberListMixin, FormView):
- template_name = 'office/member/balance.html'
- context_object_name = 'members'
+ template_name = "office/member/balance.html"
+ context_object_name = "members"
model = Member
form_class = MemberBalanceForm
def form_valid(self, form):
members = Member.objects.with_active_membership()
mails = errors = balance_count = 0
- start = datetime.combine(form.cleaned_data.get('start'), time(0, 0))
- end = datetime.combine(form.cleaned_data.get('end'), time(23, 59))
- create_if_zero = form.cleaned_data.get('create_if_zero')
- balance_cutoff = form.cleaned_data.get('balance_cutoff')
- text = form.cleaned_data.get('text')
- subject = form.cleaned_data.get('subject')
+ start = datetime.combine(form.cleaned_data.get("start"), time(0, 0))
+ end = datetime.combine(form.cleaned_data.get("end"), time(23, 59))
+ create_if_zero = form.cleaned_data.get("create_if_zero")
+ balance_cutoff = form.cleaned_data.get("balance_cutoff")
+ text = form.cleaned_data.get("text")
+ subject = form.cleaned_data.get("subject")
for member in members:
balance = None
try:
mails += 1
message = str(
_(
- '{balance_count} balances were created and {mail_count} reminder emails were placed in the outbox.'
+ "{balance_count} balances were created and {mail_count} reminder emails were placed in the outbox."
).format(balance_count=balance_count, mail_count=mails)
)
if errors:
- message += ' ' + str(
+ message += " " + str(
_(
- '{errors} balances could not be created due to errors. Presumably, these members already have balances overlapping this timeframe.'.format(
+ "{errors} balances could not be created due to errors. Presumably, these members already have balances overlapping this timeframe.".format(
errors=errors
)
)
)
messages.success(self.request, message)
- return redirect('/members/list')
+ return redirect("/members/list")
class MemberListExportForm(forms.Form):
)
member_filter = forms.ChoiceField(
choices=[
- ('active', _('Active members')),
- ('inactive', _('Only inactive members')),
- ('all', _('All members')),
+ ("active", _("Active members")),
+ ("inactive", _("Only inactive members")),
+ ("all", _("All members")),
]
)
export_format = forms.ChoiceField(
choices=[
- ('csv', _("CSV (Comma Separated Values)")),
- ('csv_de', _("CSV (Semicolon Separated Values, German Windows versions)")),
+ ("csv", _("CSV (Comma Separated Values)")),
+ ("csv_de", _("CSV (Semicolon Separated Values, German Windows versions)")),
# ('xlsx', _("XLSX (Excel)")),
]
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
fields = Member.get_fields()
- self.fields['field_list'].choices = [
+ self.fields["field_list"].choices = [
(f.field_id, f.name) for f in fields.values()
]
- self.fields['field_list'].initial = [
+ self.fields["field_list"].initial = [
f.field_id
for f in fields.values()
- if f.registration_form.get('position', -1) > -1
+ if f.registration_form.get("position", -1) > -1
]
class csv_excel_de(csv.excel):
- delimiter = ';'
+ delimiter = ";"
def filter_excel_de(data):
class MemberListExportView(
FormView, MemberListMixin, MultipleObjectMixin, MultipleObjectTemplateResponseMixin
):
- template_name = 'office/member/list_export.html'
- context_object_name = 'members'
+ template_name = "office/member/list_export.html"
+ context_object_name = "members"
model = Member
form_class = MemberListExportForm
@transaction.atomic
def form_valid(self, form):
fields = Member.get_fields()
- selected_fields = form.cleaned_data['field_list']
+ selected_fields = form.cleaned_data["field_list"]
header = collections.OrderedDict(
[
(f.field_id, f.name)
user=self.request.user,
action_type="byro.members.export",
data={
- 'filter': form.cleaned_data['member_filter'],
- 'format': form.cleaned_data['export_format'],
- 'fields': collections.OrderedDict(
+ "filter": form.cleaned_data["member_filter"],
+ "format": form.cleaned_data["export_format"],
+ "fields": collections.OrderedDict(
[(f_id, str(f_name)) for (f_id, f_name) in header.items()]
),
},
)
- if form.cleaned_data['export_format'].startswith('csv'):
+ if form.cleaned_data["export_format"].startswith("csv"):
return self.export_csv(
- header, data, csv_format=form.cleaned_data['export_format']
+ header, data, csv_format=form.cleaned_data["export_format"]
)
return redirect(self.request.get_full_path())
- def export_csv(self, header, data, csv_format='default'):
+ def export_csv(self, header, data, csv_format="default"):
class EchoBOM:
"""Dummy, based on the Django docs.
This one adds one feature: It outputs a Unicode BOM (Byte-Order Mark) as the first
character."""
def write(self, value):
- if not hasattr(self, 'have_bom'):
+ if not hasattr(self, "have_bom"):
self.have_bom = True
return "\ufeff" + value
else:
writer = csv.DictWriter(
pseudo_buffer,
header.keys(),
- dialect={'csv_de': csv_excel_de}.get(csv_format, 'excel'),
+ dialect={"csv_de": csv_excel_de}.get(csv_format, "excel"),
)
- converter = {'csv_de': row_converter_de}.get(csv_format, lambda x: x)
+ converter = {"csv_de": row_converter_de}.get(csv_format, lambda x: x)
response = StreamingHttpResponse(
(writer.writerow(converter(row)) for row in chain([header], data)),
- content_type='text/csv; charset=utf-8',
- charset='utf-8',
+ content_type="text/csv; charset=utf-8",
+ charset="utf-8",
)
response[
- 'Content-Disposition'
+ "Content-Disposition"
] = 'attachment; filename="members_{}.csv"'.format(now().date())
return response
def get_data(self, form, field_mapping):
- qs = self.get_members_queryset(_filter=form.cleaned_data['member_filter'])
+ qs = self.get_members_queryset(_filter=form.cleaned_data["member_filter"])
for m in qs.all():
yield {f_id: f_getter(m) for (f_id, f_getter) in field_mapping}
class MemberListImportForm(forms.Form):
importer = forms.ChoiceField(
choices=lambda: tuple(
- (i['id'], i['label']) for i in get_member_list_importers()
+ (i["id"], i["label"]) for i in get_member_list_importers()
)
)
upload_file = forms.FileField()
class MemberListImportView(FormView):
- template_name = 'office/member/list_import.html'
- context_object_name = 'members'
+ template_name = "office/member/list_import.html"
+ context_object_name = "members"
model = Member
form_class = MemberListImportForm
@transaction.atomic
def form_valid(self, form):
- importers = {i['id']: i for i in get_member_list_importers()}
+ importers = {i["id"]: i for i in get_member_list_importers()}
- importer = importers[form.cleaned_data['importer']]
+ importer = importers[form.cleaned_data["importer"]]
sha256sum = hashlib.sha256()
- for chunk in form.cleaned_data['upload_file'].chunks():
+ for chunk in form.cleaned_data["upload_file"].chunks():
sha256sum.update(chunk)
LogEntry.objects.create(
user=self.request.user,
action_type="byro.members.import",
data={
- 'importer': form.cleaned_data['importer'],
- 'sha256': sha256sum.hexdigest(),
+ "importer": form.cleaned_data["importer"],
+ "sha256": sha256sum.hexdigest(),
},
)
- return importer['form_valid'](self, form)
+ return importer["form_valid"](self, form)
def get_encoding(form):
detector = UniversalDetector()
- for chunk in form.cleaned_data['upload_file'].chunks():
+ for chunk in form.cleaned_data["upload_file"].chunks():
detector.feed(chunk)
if detector.done:
break
detector.close()
- return detector.result['encoding']
+ return detector.result["encoding"]
def create_membership(membership_parms, member):
if membership_parms:
- for k in 'start', 'end':
+ for k in "start", "end":
# FIXME Also, we should find a way to handle dates in a generic way
if membership_parms.get(k, None):
membership_parms[k] = dateparser.parse(
- membership_parms[k], languages=[settings.LANGUAGE_CODE, 'en']
+ membership_parms[k], languages=[settings.LANGUAGE_CODE, "en"]
)
Membership.objects.create(member=member, **membership_parms)
@transaction.atomic
-def default_csv_form_valid(view, form, dialect='excel'):
+def default_csv_form_valid(view, form, dialect="excel"):
mapping = None
fields = Member.get_fields()
encoding = get_encoding(form)
- with form.cleaned_data['upload_file'].open() as fp:
+ with form.cleaned_data["upload_file"].open() as fp:
instream = unicodecsv.DictReader(fp, dialect=dialect, encoding=encoding)
for indict in instream:
do_update = False
have_changes = False
for verb_name, field in mapping.items():
- if field.field_id == '_internal_id':
- member = Member.all_objects.filter(pk=indict[verb_name.strip()]).first()
+ if field.field_id == "_internal_id":
+ member = Member.all_objects.filter(
+ pk=indict[verb_name.strip()]
+ ).first()
if member:
do_update = True
del indict[verb_name]
# if no Membership exists.)
if field.field_id.startswith("membership__"):
if v:
- membership_parms[field.field_id.split('__', 1)[1]] = v
+ membership_parms[field.field_id.split("__", 1)[1]] = v
else:
if do_update:
old_v = field.getter(member)
if do_update:
if have_changes:
- member.log(view, '.updated')
+ member.log(view, ".updated")
member.save()
else:
- member.log(view, '.created')
+ member.log(view, ".created")
member.save()
# FIXME Changing membership when do_update is not implemented
if membership_parms:
create_membership(membership_parms, member)
- return redirect(reverse('office:members.list'))
+ return redirect(reverse("office:members.list"))
@receiver(member_list_importers)
def default_csv_importer(sender, **kwargs):
return {
- 'id': 'byro.office.members.import.default_csv',
- 'label': _('Generic CSV import (Comma Separated Values)'),
- 'form_valid': default_csv_form_valid,
+ "id": "byro.office.members.import.default_csv",
+ "label": _("Generic CSV import (Comma Separated Values)"),
+ "form_valid": default_csv_form_valid,
}
@receiver(member_list_importers)
def default_csv_importer_german(sender, **kwargs):
return {
- 'id': 'byro.office.members.import.default_csv_german',
- 'label': _(
- 'Generic CSV import (Semicolon Separated Values, German Windows versions)'
+ "id": "byro.office.members.import.default_csv_german",
+ "label": _(
+ "Generic CSV import (Semicolon Separated Values, German Windows versions)"
),
- 'form_valid': partial(default_csv_form_valid, dialect=csv_excel_de),
+ "form_valid": partial(default_csv_form_valid, dialect=csv_excel_de),
}
class MemberCreateView(FormView):
- template_name = 'office/member/add.html'
+ template_name = "office/member/add.html"
form_class = CreateMemberForm
def get_object(self):
- return Member.objects.get(pk=self.kwargs['pk'])
+ return Member.objects.get(pk=self.kwargs["pk"])
@transaction.atomic
def form_valid(self, form):
form.save()
messages.success(
self.request,
- _('The member was added, please edit additional details if applicable.'),
+ _("The member was added, please edit additional details if applicable."),
)
- form.instance.log(self, '.created')
+ form.instance.log(self, ".created")
responses = new_member.send_robust(sender=form.instance)
for module, response in responses:
if isinstance(response, Exception):
messages.warning(
self.request,
- _('Some post processing steps could not be completed: ')
+ _("Some post processing steps could not be completed: ")
+ str(response),
)
config = Configuration.get_solo()
if config.welcome_member_template and form.instance.email:
context = {
- 'name': config.name,
- 'contact': config.mail_from,
- 'number': form.instance.number,
- 'member_name': form.instance.name,
+ "name": config.name,
+ "contact": config.mail_from,
+ "number": form.instance.number,
+ "member_name": form.instance.name,
}
responses = [
r[1]
for r in new_member_mail_information.send_robust(sender=form.instance)
if r
]
- context['additional_information'] = '\n'.join(responses).strip()
+ context["additional_information"] = "\n".join(responses).strip()
config.welcome_member_template.to_mail(
email=form.instance.email, context=context
)
if config.welcome_office_template:
- context = {'member_name': form.instance.name}
+ context = {"member_name": form.instance.name}
responses = [
r[1]
for r in new_member_office_mail_information.send_robust(
)
if r
]
- context['additional_information'] = '\n'.join(responses).strip()
+ context["additional_information"] = "\n".join(responses).strip()
config.welcome_office_template.to_mail(
email=config.backoffice_mail, context=context
)
return super().form_valid(form)
def get_success_url(self):
- return reverse('office:members.data', kwargs={'pk': self.form.instance.pk})
+ return reverse("office:members.data", kwargs={"pk": self.form.instance.pk})
class MemberDashboardView(MemberView):
- template_name = 'office/member/dashboard.html'
+ template_name = "office/member/dashboard.html"
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
return context
first = obj.memberships.first().start
delta = now().date() - first
- context['member_since'] = {
- 'days': int(delta.total_seconds() / (60 * 60 * 24)),
- 'years': round(delta.days / 365, 1),
- 'first': first,
+ context["member_since"] = {
+ "days": int(delta.total_seconds() / (60 * 60 * 24)),
+ "years": round(delta.days / 365, 1),
+ "first": first,
}
- context['current_membership'] = {
- 'amount': obj.memberships.last().amount,
- 'interval': obj.memberships.last().get_interval_display(),
+ context["current_membership"] = {
+ "amount": obj.memberships.last().amount,
+ "interval": obj.memberships.last().get_interval_display(),
}
- context['statute_barred_debt'] = {'now': obj.statute_barred_debt()}
- context['statute_barred_debt']['in1year'] = (
+ context["statute_barred_debt"] = {"now": obj.statute_barred_debt()}
+ context["statute_barred_debt"]["in1year"] = (
obj.statute_barred_debt(relativedelta(years=1))
- - context['statute_barred_debt']['now']
+ - context["statute_barred_debt"]["now"]
)
- context['tiles'] = []
+ context["tiles"] = []
for __, response in member_dashboard_tile.send(self.request, member=obj):
if not response:
continue
if isinstance(response, collections.Mapping):
- context['tiles'].append(response)
+ context["tiles"].append(response)
return context
class MemberDataView(MemberView):
- template_name = 'office/member/data.html'
+ template_name = "office/member/data.html"
def _instantiate(
self,
empty=False,
):
params = {
- 'instance': (
+ "instance": (
getattr(
- member, profile_class._meta.get_field('member').related_query_name()
+ member, profile_class._meta.get_field("member").related_query_name()
)
if profile_class
else instance
)
if not empty
else None,
- 'prefix': prefix
+ "prefix": prefix
or (
profile_class.__name__
if profile_class
- else instance.__class__.__name__ + '_'
+ else instance.__class__.__name__ + "_"
if instance
- else 'member_'
+ else "member_"
),
- 'data': self.request.POST if self.request.method == 'POST' else None,
+ "data": self.request.POST if self.request.method == "POST" else None,
}
return form_class(**params)
def get_forms(self):
obj = self.get_object()
membership_create_form = forms.modelform_factory(
- Membership, fields=['start', 'end', 'interval', 'amount']
+ Membership, fields=["start", "end", "interval", "amount"]
)
for key in membership_create_form.base_fields:
- setattr(membership_create_form.base_fields[key], 'required', False)
+ setattr(membership_create_form.base_fields[key], "required", False)
return (
[
self._instantiate(
- forms.modelform_factory(Member, exclude=['membership_type']),
+ forms.modelform_factory(Member, exclude=["membership_type"]),
member=obj,
instance=obj,
)
]
+ [
self._instantiate(
- forms.modelform_factory(Membership, exclude=['member']),
+ forms.modelform_factory(Membership, exclude=["member"]),
member=obj,
instance=m,
prefix=m.id,
fields=[
f.name
for f in profile_class._meta.fields
- if f.name not in ['id', 'member']
+ if f.name not in ["id", "member"]
],
),
member=obj,
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
- context['forms'] = self.get_forms()
+ context["forms"] = self.get_forms()
return context
@transaction.atomic
return self.get(self.request)
for form in form_list:
if form.has_changed():
- if not getattr(form.instance, 'member', False):
+ if not getattr(form.instance, "member", False):
form.instance.member = self.get_object()
any_changed = True
form.save()
if any_changed:
- self.get_object().log(self, '.updated')
- messages.success(self.request, _('Your changes have been saved.'))
+ self.get_object().log(self, ".updated")
+ messages.success(self.request, _("Your changes have been saved."))
update_member.send_robust(sender=self.request, form_list=form_list)
- return redirect(reverse('office:members.data', kwargs=self.kwargs))
+ return redirect(reverse("office:members.data", kwargs=self.kwargs))
class MemberFinanceView(MemberView):
- template_name = 'office/member/finance.html'
+ template_name = "office/member/finance.html"
paginate_by = 50
def get_bookings(self):
transaction__value_datetime__lte=now(),
)
.order_by(
- '-transaction__value_datetime',
- '-booking_datetime',
- '-transaction__booking_datetime',
+ "-transaction__value_datetime",
+ "-booking_datetime",
+ "-transaction__booking_datetime",
)
)
if not member.balances.exists():
return bookings
result = []
- balances = list(member.balances.order_by('-start'))
+ balances = list(member.balances.order_by("-start"))
current_balance = balances.pop(0)
current_balance.bookings = []
for booking in bookings:
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
- context['member'] = self.get_member()
- context['bookings'] = self.get_bookings()
+ context["member"] = self.get_member()
+ context["bookings"] = self.get_bookings()
return context
class MemberDocumentsView(MemberView, FormView):
- template_name = 'office/member/documents.html'
+ template_name = "office/member/documents.html"
paginate_by = 50
form_class = DocumentUploadForm
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
- context['member'] = self.get_member()
+ context["member"] = self.get_member()
return context
def get_form(self):
return form
def get_success_url(self):
- return reverse('office:members.documents', kwargs={'pk': self.get_member().pk})
+ return reverse("office:members.documents", kwargs={"pk": self.get_member().pk})
@transaction.atomic
def form_valid(self, form):
form.save()
member.log(
self,
- '.document.created',
+ ".document.created",
document=form.instance,
content_hash=form.instance.content_hash,
)
class MemberAccountAdjustmentForm(forms.Form):
- form_title = _('Adjust member account balance')
+ form_title = _("Adjust member account balance")
date = forms.DateField(initial=lambda: now().date())
adjustment_reason = forms.ChoiceField(
- choices=[('initial', _("Initial balance")), ('waiver', _("Fees waived"))]
+ choices=[("initial", _("Initial balance")), ("waiver", _("Fees waived"))]
)
adjustment_memo = forms.CharField(required=False)
adjustment_type = forms.ChoiceField(
widget=forms.RadioSelect,
choices=[
- ('relative', _('Relative (Add or subtract amount to/from balance)')),
- ('absolute', _('Absolute (Balance should be)')),
+ ("relative", _("Relative (Add or subtract amount to/from balance)")),
+ ("absolute", _("Absolute (Balance should be)")),
],
- initial='relative',
+ initial="relative",
)
- amount = forms.DecimalField(initial=Decimal('0.00'), decimal_places=2)
+ amount = forms.DecimalField(initial=Decimal("0.00"), decimal_places=2)
- date.widget.attrs.update({'class': 'datepicker'})
+ date.widget.attrs.update({"class": "datepicker"})
class MultipleFormsMixin:
raise NotImplementedError
def mangle_button(self, name, prefix):
- return 'submit_{}_{}'.format(prefix, name)
+ return "submit_{}_{}".format(prefix, name)
def get_forms(self):
"""Instantiate forms, return a list of tuples like get_operations(), but with Form objects and expanded prefix values."""
form_class(
prefix=prefix,
data=self.request.POST
- if self.request.method == 'POST'
+ if self.request.method == "POST"
else None,
),
buttons,
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
- context['forms'] = self.get_forms()
+ context["forms"] = self.get_forms()
return context
def post(self, *args, **kwargs):
class MemberOperationsView(MultipleFormsMixin, MemberView):
- template_name = 'office/member/operations.html'
+ template_name = "office/member/operations.html"
membership_form_class = forms.modelform_factory(
- Membership, fields=['start', 'end', 'interval', 'amount']
+ Membership, fields=["start", "end", "interval", "amount"]
)
def get_operations(self):
# Add Leave forms for all current memberships
def _create_ms_leave_form(*args, **kwargs):
f = self.membership_form_class(instance=ms, *args, **kwargs)
- f.fields['start'].disabled = True
- f.fields['interval'].disabled = True
- f.fields['amount'].disabled = True
- f.fields['end'].required = True
- f.fields['end'].widget.attrs['class'] = 'datepicker'
+ f.fields["start"].disabled = True
+ f.fields["interval"].disabled = True
+ f.fields["amount"].disabled = True
+ f.fields["end"].required = True
+ f.fields["end"].widget.attrs["class"] = "datepicker"
return f
- for ms in member.memberships.all().order_by('-start'):
+ for ms in member.memberships.all().order_by("-start"):
if ms.start <= now_.date() and (not ms.end or ms.end > now_.date()):
retval.append(
(
- 'ms_{}_leave'.format(ms.pk),
- _('End membership'),
+ "ms_{}_leave".format(ms.pk),
+ _("End membership"),
_create_ms_leave_form,
- {'end': _('End membership')},
+ {"end": _("End membership")},
lambda *args, **kwargs: self.end_membership(
ms, *args, **kwargs
),
# Add account adjustment form
retval.append(
(
- 'member_account_adjustment',
- _('Adjust member account balance'),
+ "member_account_adjustment",
+ _("Adjust member account balance"),
MemberAccountAdjustmentForm,
- {'adjust': _('Adjust balance')},
+ {"adjust": _("Adjust balance")},
self.adjust_balance,
)
)
@transaction.atomic
def adjust_balance(self, form, active_buttons):
memo = (
- form.cleaned_data['adjustment_memo']
- or dict(form.fields['adjustment_reason'].choices).get(
- form.cleaned_data['adjustment_reason'], None
+ form.cleaned_data["adjustment_memo"]
+ or dict(form.fields["adjustment_reason"].choices).get(
+ form.cleaned_data["adjustment_reason"], None
)
- or _('Account adjustment')
+ or _("Account adjustment")
)
member = self.get_member()
now_ = now()
- if form.cleaned_data['adjustment_type'] == 'relative':
- amount = form.cleaned_data['amount']
+ if form.cleaned_data["adjustment_type"] == "relative":
+ amount = form.cleaned_data["amount"]
else:
old_balance = member._calc_balance(
- form.cleaned_data['date'], form.cleaned_data['date']
+ form.cleaned_data["date"], form.cleaned_data["date"]
)
- amount = old_balance - form.cleaned_data['amount']
+ amount = old_balance - form.cleaned_data["amount"]
amount_, from_, to_ = None, None, None
if amount != 0:
- if form.cleaned_data['adjustment_reason'] == 'initial':
+ if form.cleaned_data["adjustment_reason"] == "initial":
amount_, from_, to_ = (
amount,
SpecialAccounts.opening_balance,
SpecialAccounts.fees_receivable,
)
- elif form.cleaned_data['adjustment_reason'] == 'waiver':
+ elif form.cleaned_data["adjustment_reason"] == "waiver":
if amount < 0:
amount_, from_, to_ = (
-amount,
to_member = member if to_ == SpecialAccounts.fees_receivable else None
t = Transaction.objects.create(
- value_datetime=form.cleaned_data['date'],
+ value_datetime=form.cleaned_data["date"],
booking_datetime=now_,
user_or_context=self,
memo=memo,
balance = member.balance
- if form.cleaned_data['adjustment_reason'] == 'initial':
- member.log(self, '.finance.initial_balance', balance=balance)
- elif form.cleaned_data['adjustment_reason'] == 'waiver':
- member.log(self, '.finance.fees_waived', amount=amount)
+ if form.cleaned_data["adjustment_reason"] == "initial":
+ member.log(self, ".finance.initial_balance", balance=balance)
+ elif form.cleaned_data["adjustment_reason"] == "waiver":
+ member.log(self, ".finance.fees_waived", amount=amount)
else:
member.log(
- self, '.finance.account_adjusted', balance=balance, amount=amount
+ self, ".finance.account_adjusted", balance=balance, amount=amount
)
messages.success(
self.request,
_(
- 'Membership account adjusted by {amount}, current balance is {balance}'
+ "Membership account adjusted by {amount}, current balance is {balance}"
).format(amount=amount, balance=balance),
)
@transaction.atomic
def end_membership(self, ms, form, active_buttons):
if form.instance.end:
- if not getattr(form.instance, 'member', False):
+ if not getattr(form.instance, "member", False):
form.instance.member = self.get_object()
form.save()
- form.instance.log(self, '.ended')
+ form.instance.log(self, ".ended")
messages.success(
self.request,
_(
- 'The membership has been terminated. Please check the outbox for the notifications.'
+ "The membership has been terminated. Please check the outbox for the notifications."
),
)
if isinstance(response, Exception):
messages.warning(
self.request,
- _('Some post processing steps could not be completed: ')
+ _("Some post processing steps could not be completed: ")
+ str(response),
)
config = Configuration.get_solo()
if config.leave_member_template:
context = {
- 'name': config.name,
- 'contact': config.mail_from,
- 'number': form.instance.member.number,
- 'member_name': form.instance.member.name,
- 'end': form.instance.end,
+ "name": config.name,
+ "contact": config.mail_from,
+ "number": form.instance.member.number,
+ "member_name": form.instance.member.name,
+ "end": form.instance.end,
}
responses = [
r[1]
)
if r
]
- context['additional_information'] = '\n'.join(responses).strip()
+ context["additional_information"] = "\n".join(responses).strip()
config.leave_member_template.to_mail(
email=form.instance.member.email, context=context
)
if config.leave_office_template:
context = {
- 'member_name': form.instance.member.name,
- 'end': form.instance.end,
+ "member_name": form.instance.member.name,
+ "end": form.instance.end,
}
responses = [
r[1]
)
if r
]
- context['additional_information'] = '\n'.join(responses).strip()
+ context["additional_information"] = "\n".join(responses).strip()
config.leave_office_template.to_mail(
email=config.backoffice_mail, context=context
)
class MemberListTypeaheadView(View):
def dispatch(self, request, *args, **kwargs):
- search = request.GET.get('search')
+ search = request.GET.get("search")
if not search or len(search) < 2:
- return JsonResponse({'count': 0, 'results': []})
+ return JsonResponse({"count": 0, "results": []})
queryset = Member.objects.filter(
Q(name__icontains=search) | Q(profile_profile__nick__icontains=search)
)
return JsonResponse(
{
- 'count': len(queryset),
- 'results': [
+ "count": len(queryset),
+ "results": [
{
- 'id': member.pk,
- 'nick': member.profile_profile.nick,
- 'name': member.name,
+ "id": member.pk,
+ "nick": member.profile_profile.nick,
+ "name": member.name,
}
for member in queryset
],
class MemberRecordDisclosureView(MemberView):
- template_name = 'office/member/data_disclosure.html'
+ template_name = "office/member/data_disclosure.html"
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
- ctx['mail'] = self.get_member().record_disclosure_email
+ ctx["mail"] = self.get_member().record_disclosure_email
return ctx
def post(self, request, *args, **kwargs):
self.get_member().record_disclosure_email.save()
- self.get_member().log(self, '.disclosure_email_generated')
+ self.get_member().log(self, ".disclosure_email_generated")
messages.success(
- request, _('The email was generated and can be sent in the outbox.')
+ request, _("The email was generated and can be sent in the outbox.")
)
- return redirect(reverse('office:members.dashboard', kwargs=self.kwargs))
+ return redirect(reverse("office:members.dashboard", kwargs=self.kwargs))
class MemberLogView(MemberView):
- template_name = 'office/member/log.html'
+ template_name = "office/member/log.html"
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
- ctx['log_entries'] = self.get_member().log_entries()
+ ctx["log_entries"] = self.get_member().log_entries()
return ctx
class MemberMailsView(MemberView):
- template_name = 'office/member/mails.html'
+ template_name = "office/member/mails.html"
class MemberTimelineView(MemberView):
- template_name = 'office/member/timeline.html'
+ template_name = "office/member/timeline.html"
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
- ctx['timeline'] = self.get_timeline()
+ ctx["timeline"] = self.get_timeline()
return ctx
def get_timeline(self):
class InitialSettings(FormView):
form_class = InitialForm
- template_name = 'office/settings/initial.html'
+ template_name = "office/settings/initial.html"
def get_form_kwargs(self):
form_kwargs = super().get_form_kwargs()
- form_kwargs['instance'] = Configuration.get_solo()
+ form_kwargs["instance"] = Configuration.get_solo()
return form_kwargs
@transaction.atomic
def form_valid(self, form):
form.save()
other_data = {
- 'accounts': [
- {'account': acc, 'name': acc.name, 'balances': acc.balances()}
+ "accounts": [
+ {"account": acc, "name": acc.name, "balances": acc.balances()}
for acc in Account.objects.all()
]
}
messages.success(
self.request,
_(
- 'You\'re nearly ready to go – configure how you want to add new members, and you\'re done.'
+ "You're nearly ready to go – configure how you want to add new members, and you're done."
),
)
return super().form_valid(form)
def get_success_url(self):
- return reverse('office:settings.registration')
+ return reverse("office:settings.registration")
class ConfigurationView(FormView):
form_class = ConfigurationForm
- template_name = 'office/settings/form.html'
+ template_name = "office/settings/form.html"
def get_form_kwargs(self):
form_kwargs = super().get_form_kwargs()
- form_kwargs['instance'] = Configuration.get_solo()
+ form_kwargs["instance"] = Configuration.get_solo()
return form_kwargs
def get_form(self):
config_models = [
model for model in apps.get_models() if issubclass(model, ByroConfiguration)
]
- data = self.request.POST if self.request.method == 'POST' else None
+ data = self.request.POST if self.request.method == "POST" else None
return [
forms.modelform_factory(
- model, fields='__all__', exclude=('registration_form',)
+ model, fields="__all__", exclude=("registration_form",)
)(prefix=model.__name__, instance=model.get_solo(), data=data)
for model in config_models
]
},
)
- messages.success(self.request, _('The config was saved successfully.'))
+ messages.success(self.request, _("The config was saved successfully."))
return super().form_valid(f)
def post(self, request, *args, **kwargs):
return self.form_invalid(form)
def get_success_url(self):
- return reverse('office:settings.base')
+ return reverse("office:settings.base")
class RegistrationConfigView(FormView):
form_class = RegistrationConfigForm
- template_name = 'office/settings/registration_form.html'
+ template_name = "office/settings/registration_form.html"
def form_valid(self, form):
form.save()
- messages.success(self.request, _('The config was saved successfully.'))
+ messages.success(self.request, _("The config was saved successfully."))
LogEntry.objects.create(
content_object=Configuration.get_solo(),
user=self.request.user,
return super().form_valid(form)
def get_success_url(self):
- return reverse('office:settings.registration')
+ return reverse("office:settings.registration")
class AboutByroView(TemplateView):
- template_name = 'office/settings/about.html'
+ template_name = "office/settings/about.html"
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
- context['plugins'] = []
+ context["plugins"] = []
for app in apps.get_app_configs():
- if hasattr(app, 'ByroPluginMeta') and hasattr(app.ByroPluginMeta, 'name'):
- context['plugins'].append({'meta': app.ByroPluginMeta})
+ if hasattr(app, "ByroPluginMeta") and hasattr(app.ByroPluginMeta, "name"):
+ context["plugins"].append({"meta": app.ByroPluginMeta})
return context
class LogView(ListView):
- template_name = 'office/settings/log.html'
- context_object_name = 'log_entries'
+ template_name = "office/settings/log.html"
+ context_object_name = "log_entries"
model = LogEntry
paginate_by = 50
class NewBookingForm(forms.Form):
- memo = forms.CharField(label=_('Memo'), max_length=1000, required=False)
- member = Booking._meta.get_field('member').formfield(widget=Select2Widget)
- account = Booking._meta.get_field('debit_account').formfield(widget=Select2Widget)
+ memo = forms.CharField(label=_("Memo"), max_length=1000, required=False)
+ member = Booking._meta.get_field("member").formfield(widget=Select2Widget)
+ account = Booking._meta.get_field("debit_account").formfield(widget=Select2Widget)
debit_value = forms.DecimalField(
min_value=0, max_digits=8, decimal_places=2, required=False
)
class TransactionDetailView(ListView):
- template_name = 'office/transaction/detail.html'
- context_object_name = 'bookings'
+ template_name = "office/transaction/detail.html"
+ context_object_name = "bookings"
model = Transaction
paginate_by = None
def get_form(self, input_data=None):
form = NewBookingForm(input_data)
- form.fields['account'].required = True
- form.fields['member'].required = False
+ form.fields["account"].required = True
+ form.fields["member"].required = False
t = self.get_object()
if t.balances_credit != t.balances_debit:
if t.balances_credit > t.balances_debit:
- form.fields['debit_value'].initial = (
+ form.fields["debit_value"].initial = (
t.balances_credit - t.balances_debit
)
else:
- form.fields['credit_value'].initial = (
+ form.fields["credit_value"].initial = (
t.balances_debit - t.balances_credit
)
return form
form = DocumentUploadForm(
input_data,
input_files,
- prefix='upload_form',
- initial_category='byro.bookkeeping.receipt',
+ prefix="upload_form",
+ initial_category="byro.bookkeeping.receipt",
)
- form.fields['title'].required = False
+ form.fields["title"].required = False
return form
def get_object(self):
- return Transaction.objects.with_balances().get(pk=self.kwargs['pk'])
+ return Transaction.objects.with_balances().get(pk=self.kwargs["pk"])
def get_queryset(self):
return self.get_object().bookings.all()
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
- context['transaction'] = self.get_object()
- context['form'] = self.get_form()
- context['upload_form'] = self.get_upload_form()
+ context["transaction"] = self.get_object()
+ context["form"] = self.get_form()
+ context["upload_form"] = self.get_upload_form()
return context
@transaction.atomic
t = self.get_object()
if t.is_balanced:
- if 'in_account' in request.GET:
- account = Account.objects.get(pk=request.GET['in_account'])
+ if "in_account" in request.GET:
+ account = Account.objects.get(pk=request.GET["in_account"])
if account.unbalanced_transactions.count():
return redirect(
"{}?filter=unbalanced".format(
reverse(
- 'office:finance.accounts.detail',
- kwargs={'pk': account.pk},
+ "office:finance.accounts.detail",
+ kwargs={"pk": account.pk},
)
)
)
- return redirect('office:finance.accounts.list')
+ return redirect("office:finance.accounts.list")
- if 'in_account' in request.GET:
+ if "in_account" in request.GET:
return redirect(
"{}?in_account={}".format(
- reverse('office:finance.transactions.detail', kwargs={'pk': t.pk}),
- request.GET['in_account'],
+ reverse("office:finance.transactions.detail", kwargs={"pk": t.pk}),
+ request.GET["in_account"],
)
)
else:
def process_transaction_changes(self, form, t):
arguments = dict(
- memo=form.cleaned_data['memo'],
- account=form.cleaned_data['account'],
- member=form.cleaned_data['member'],
+ memo=form.cleaned_data["memo"],
+ account=form.cleaned_data["account"],
+ member=form.cleaned_data["member"],
importer="_manual_entry",
user_or_context=self,
)
- if form.cleaned_data['debit_value']:
- t.debit(amount=form.cleaned_data['debit_value'], **arguments)
- if form.cleaned_data['credit_value']:
- t.credit(amount=form.cleaned_data['credit_value'], **arguments)
+ if form.cleaned_data["debit_value"]:
+ t.debit(amount=form.cleaned_data["debit_value"], **arguments)
+ if form.cleaned_data["credit_value"]:
+ t.credit(amount=form.cleaned_data["credit_value"], **arguments)
t.save()
- messages.success(self.request, _('The transaction was updated.'))
- t.log(self, '.updated')
+ messages.success(self.request, _("The transaction was updated."))
+ t.log(self, ".updated")
def process_upload_form(self, form, t):
form.save()
DocumentTransactionLink.objects.create(transaction=t, document=form.instance)
t.log(
self,
- '.document.created',
+ ".document.created",
document=form.instance,
content_hash=form.instance.content_hash,
)
class UploadForm(forms.ModelForm):
class Meta:
model = RealTransactionSource
- fields = ('source_file',)
+ fields = ("source_file",)
class UploadListView(ListView):
- template_name = 'office/upload/list.html'
- context_object_name = 'uploads'
+ template_name = "office/upload/list.html"
+ context_object_name = "uploads"
model = RealTransactionSource
class CsvUploadView(FormView):
- template_name = 'office/upload/add.html'
+ template_name = "office/upload/add.html"
model = RealTransactionSource
form_class = UploadForm
form.save()
try:
form.instance.process()
- messages.success(self.request, _('The upload was processed successfully.'))
+ messages.success(self.request, _("The upload was processed successfully."))
except Exception as e:
messages.error(
self.request,
- _('The upload was added successfully, but could not be processed: ')
+ _("The upload was added successfully, but could not be processed: ")
+ str(e),
)
self.form = form
obj = self.get_object()
try:
obj.process()
- messages.success(self.request, _('The upload was processed successfully.'))
+ messages.success(self.request, _("The upload was processed successfully."))
except Exception as e:
messages.error(
- self.request, _('The upload could not be processed: ') + str(e)
+ self.request, _("The upload could not be processed: ") + str(e)
)
- return redirect('office:finance.uploads.list')
+ return redirect("office:finance.uploads.list")
class UploadMatchView(DetailView):
errors += 1
messages.info(
request,
- '{success} successful matches, {errors} errors.'.format(
+ "{success} successful matches, {errors} errors.".format(
success=success, errors=errors
),
)
- return redirect('office:finance.uploads.list')
+ return redirect("office:finance.uploads.list")
class UserForm(forms.ModelForm):
- password = forms.CharField(label=_('Password'), widget=forms.PasswordInput)
+ password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- self.fields['last_name'].label = _('Name')
+ self.fields["last_name"].label = _("Name")
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
- password = self.cleaned_data.get('password')
+ password = self.cleaned_data.get("password")
if password:
self.instance.set_password(password)
self.instance.save()
class Meta:
model = User
fields = [
- 'username',
- 'last_name',
- 'email',
- 'is_superuser',
- 'is_staff',
- 'is_staff',
+ "username",
+ "last_name",
+ "email",
+ "is_superuser",
+ "is_staff",
+ "is_staff",
]
class UserListView(ListView):
- template_name = 'office/user/list.html'
- context_object_name = 'users'
+ template_name = "office/user/list.html"
+ context_object_name = "users"
model = User
paginate_by = 25
class UserCreateView(FormView):
- template_name = 'office/user/add.html'
+ template_name = "office/user/add.html"
model = User
form_class = UserForm
def get_success_url(self):
return reverse(
- 'office:settings.users.detail', kwargs={'pk': self.form.instance.pk}
+ "office:settings.users.detail", kwargs={"pk": self.form.instance.pk}
)
class UserDetailView(UpdateView):
- template_name = 'office/user/detail.html'
- context_object_name = 'user'
+ template_name = "office/user/detail.html"
+ context_object_name = "user"
model = User
form_class = UserForm
return super().form_valid(form)
def get_object(self):
- return User.objects.get(pk=self.kwargs['pk'])
+ return User.objects.get(pk=self.kwargs["pk"])
def get_success_url(self):
- return reverse('office:settings.users.detail', kwargs={'pk': self.kwargs['pk']})
+ return reverse("office:settings.users.detail", kwargs={"pk": self.kwargs["pk"]})
# FIXME No implemented yet
class UserDeleteView(DetailView):
model = User
- context_object_name = 'user'
+ context_object_name = "user"
class ProfilePluginConfig(AppConfig):
- name = 'byro.plugins.profile'
+ name = "byro.plugins.profile"
initial = True
- dependencies = [
- ('members', '0001_initial'),
- ]
+ dependencies = [("members", "0001_initial")]
operations = [
migrations.CreateModel(
- name='MemberProfile',
+ name="MemberProfile",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('member_identifier', models.CharField(max_length=50, null=True)),
- ('birth_date', models.DateField(null=True)),
- ('address', models.CharField(max_length=500, null=True)),
- ('nick', models.CharField(max_length=200, null=True)),
- ('name', models.CharField(max_length=200, null=True)),
- ('member', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='profile_profile', to='members.Member')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("member_identifier", models.CharField(max_length=50, null=True)),
+ ("birth_date", models.DateField(null=True)),
+ ("address", models.CharField(max_length=500, null=True)),
+ ("nick", models.CharField(max_length=200, null=True)),
+ ("name", models.CharField(max_length=200, null=True)),
+ (
+ "member",
+ models.OneToOneField(
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="profile_profile",
+ to="members.Member",
+ ),
+ ),
],
bases=(byro.common.models.auditable.Auditable, models.Model),
- ),
+ )
]
class Migration(migrations.Migration):
- dependencies = [
- ('profile', '0001_initial'),
- ]
+ dependencies = [("profile", "0001_initial")]
operations = [
- migrations.RemoveField(
- model_name='memberprofile',
- name='address',
- ),
- migrations.RemoveField(
- model_name='memberprofile',
- name='member_identifier',
- ),
- migrations.RemoveField(
- model_name='memberprofile',
- name='name',
- ),
+ migrations.RemoveField(model_name="memberprofile", name="address"),
+ migrations.RemoveField(model_name="memberprofile", name="member_identifier"),
+ migrations.RemoveField(model_name="memberprofile", name="name"),
migrations.AddField(
- model_name='memberprofile',
- name='phone_number',
+ model_name="memberprofile",
+ name="phone_number",
field=models.CharField(blank=True, max_length=32, null=True),
),
]
class Migration(migrations.Migration):
- dependencies = [
- ('profile', '0002_auto_20171012_1915'),
- ]
+ dependencies = [("profile", "0002_auto_20171012_1915")]
operations = [
migrations.AlterField(
- model_name='memberprofile',
- name='member',
- field=annoying.fields.AutoOneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='profile_profile', to='members.Member'),
- ),
+ model_name="memberprofile",
+ name="member",
+ field=annoying.fields.AutoOneToOneField(
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="profile_profile",
+ to="members.Member",
+ ),
+ )
]
class Migration(migrations.Migration):
- dependencies = [
- ('profile', '0003_auto_20171012_2032'),
- ]
+ dependencies = [("profile", "0003_auto_20171012_2032")]
operations = [
migrations.AlterField(
- model_name='memberprofile',
- name='birth_date',
- field=models.DateField(null=True, verbose_name='Birth date'),
+ model_name="memberprofile",
+ name="birth_date",
+ field=models.DateField(null=True, verbose_name="Birth date"),
),
migrations.AlterField(
- model_name='memberprofile',
- name='nick',
- field=models.CharField(max_length=200, null=True, verbose_name='Nick'),
+ model_name="memberprofile",
+ name="nick",
+ field=models.CharField(max_length=200, null=True, verbose_name="Nick"),
),
migrations.AlterField(
- model_name='memberprofile',
- name='phone_number',
- field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Phone number'),
+ model_name="memberprofile",
+ name="phone_number",
+ field=models.CharField(
+ blank=True, max_length=32, null=True, verbose_name="Phone number"
+ ),
),
]
class Migration(migrations.Migration):
- dependencies = [
- ('profile', '0004_auto_20171206_1919'),
- ]
+ dependencies = [("profile", "0004_auto_20171206_1919")]
operations = [
migrations.AlterField(
- model_name='memberprofile',
- name='birth_date',
- field=models.DateField(blank=True, null=True, verbose_name='Birth date'),
+ model_name="memberprofile",
+ name="birth_date",
+ field=models.DateField(blank=True, null=True, verbose_name="Birth date"),
),
migrations.AlterField(
- model_name='memberprofile',
- name='nick',
- field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Nick'),
+ model_name="memberprofile",
+ name="nick",
+ field=models.CharField(
+ blank=True, max_length=200, null=True, verbose_name="Nick"
+ ),
),
]
class MemberProfile(Auditable, models.Model):
member = AutoOneToOneField(
- to='members.Member', related_name='profile_profile', on_delete=models.PROTECT
+ to="members.Member", related_name="profile_profile", on_delete=models.PROTECT
)
nick = models.CharField(
- verbose_name=_('Nick'), max_length=200, blank=True, null=True
+ verbose_name=_("Nick"), max_length=200, blank=True, null=True
)
- birth_date = models.DateField(verbose_name=_('Birth date'), blank=True, null=True)
+ birth_date = models.DateField(verbose_name=_("Birth date"), blank=True, null=True)
phone_number = models.CharField(
- verbose_name=_('Phone number'), max_length=32, blank=True, null=True
+ verbose_name=_("Phone number"), max_length=32, blank=True, null=True
)
- form_title = _('General information')
+ form_title = _("General information")
@pytest.fixture
def member_bob():
- member = Member.objects.create(email='bob@hacker.space')
+ member = Member.objects.create(email="bob@hacker.space")
yield member
[profile.delete() for profile in member.profiles]
member.delete()
class SepaPluginConfig(AppConfig):
- name = 'byro.plugins.sepa'
+ name = "byro.plugins.sepa"
def ready(self):
from . import signals # noqa
initial = True
- dependencies = [
- ('members', '0002_auto_20171012_1857'),
- ]
+ dependencies = [("members", "0002_auto_20171012_1857")]
operations = [
migrations.CreateModel(
- name='MemberSepa',
+ name="MemberSepa",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('iban', localflavor.generic.models.IBANField(blank=True, include_countries=None, max_length=34, null=True, use_nordea_extensions=False, verbose_name='IBAN')),
- ('bic', localflavor.generic.models.BICField(blank=True, max_length=11, null=True, verbose_name='BIC')),
- ('institute', models.CharField(blank=True, max_length=255, null=True, verbose_name='IBAN Institute')),
- ('issue_date', models.DateField(blank=True, help_text='The issue date of the direct debit mandate. (1970-01-01 means there is no issue date in the database )', null=True, verbose_name='IBAN Issue Date')),
- ('fullname', models.CharField(blank=True, help_text='Full name for IBAN account owner', max_length=255, null=True, verbose_name='IBAN full name')),
- ('address', models.CharField(blank=True, help_text='Address line (e.g. Street / House Number)', max_length=255, null=True, verbose_name='IBAN address')),
- ('zip_code', models.CharField(blank=True, help_text='ZIP Code', max_length=20, null=True, verbose_name='IBAN zip code')),
- ('city', models.CharField(blank=True, max_length=255, null=True, verbose_name='IBAN City')),
- ('country', models.CharField(blank=True, default='Deutschland', max_length=255, null=True, verbose_name='IBAN Country')),
- ('mandate_reference', models.CharField(blank=True, max_length=255, null=True, verbose_name='IBAN Mandate Reference')),
- ('mandate_reason', models.CharField(blank=True, max_length=255, null=True, verbose_name='IBAN Mandate Reason')),
- ('member', annoying.fields.AutoOneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='sepa', to='members.Member')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "iban",
+ localflavor.generic.models.IBANField(
+ blank=True,
+ include_countries=None,
+ max_length=34,
+ null=True,
+ use_nordea_extensions=False,
+ verbose_name="IBAN",
+ ),
+ ),
+ (
+ "bic",
+ localflavor.generic.models.BICField(
+ blank=True, max_length=11, null=True, verbose_name="BIC"
+ ),
+ ),
+ (
+ "institute",
+ models.CharField(
+ blank=True,
+ max_length=255,
+ null=True,
+ verbose_name="IBAN Institute",
+ ),
+ ),
+ (
+ "issue_date",
+ models.DateField(
+ blank=True,
+ help_text="The issue date of the direct debit mandate. (1970-01-01 means there is no issue date in the database )",
+ null=True,
+ verbose_name="IBAN Issue Date",
+ ),
+ ),
+ (
+ "fullname",
+ models.CharField(
+ blank=True,
+ help_text="Full name for IBAN account owner",
+ max_length=255,
+ null=True,
+ verbose_name="IBAN full name",
+ ),
+ ),
+ (
+ "address",
+ models.CharField(
+ blank=True,
+ help_text="Address line (e.g. Street / House Number)",
+ max_length=255,
+ null=True,
+ verbose_name="IBAN address",
+ ),
+ ),
+ (
+ "zip_code",
+ models.CharField(
+ blank=True,
+ help_text="ZIP Code",
+ max_length=20,
+ null=True,
+ verbose_name="IBAN zip code",
+ ),
+ ),
+ (
+ "city",
+ models.CharField(
+ blank=True, max_length=255, null=True, verbose_name="IBAN City"
+ ),
+ ),
+ (
+ "country",
+ models.CharField(
+ blank=True,
+ default="Deutschland",
+ max_length=255,
+ null=True,
+ verbose_name="IBAN Country",
+ ),
+ ),
+ (
+ "mandate_reference",
+ models.CharField(
+ blank=True,
+ max_length=255,
+ null=True,
+ verbose_name="IBAN Mandate Reference",
+ ),
+ ),
+ (
+ "mandate_reason",
+ models.CharField(
+ blank=True,
+ max_length=255,
+ null=True,
+ verbose_name="IBAN Mandate Reason",
+ ),
+ ),
+ (
+ "member",
+ annoying.fields.AutoOneToOneField(
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="sepa",
+ to="members.Member",
+ ),
+ ),
],
bases=(byro.common.models.auditable.Auditable, models.Model),
- ),
+ )
]
class Migration(migrations.Migration):
- dependencies = [
- ('sepa', '0001_initial'),
- ]
+ dependencies = [("sepa", "0001_initial")]
operations = [
migrations.AlterField(
- model_name='membersepa',
- name='member',
- field=annoying.fields.AutoOneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='profile_sepa', to='members.Member'),
- ),
+ model_name="membersepa",
+ name="member",
+ field=annoying.fields.AutoOneToOneField(
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="profile_sepa",
+ to="members.Member",
+ ),
+ )
]
class Migration(migrations.Migration):
- dependencies = [
- ('sepa', '0002_auto_20171013_1436'),
- ]
+ dependencies = [("sepa", "0002_auto_20171013_1436")]
operations = [
migrations.AddField(
- model_name='membersepa',
- name='mandate_state',
- field=models.CharField(choices=[('inactive', 'Inactive'), ('active', 'Active'), ('bounced', 'Bounced'), ('rescinded', 'Rescinded')], default='active', max_length=10, verbose_name='Mandate state'),
- ),
+ model_name="membersepa",
+ name="mandate_state",
+ field=models.CharField(
+ choices=[
+ ("inactive", "Inactive"),
+ ("active", "Active"),
+ ("bounced", "Bounced"),
+ ("rescinded", "Rescinded"),
+ ],
+ default="active",
+ max_length=10,
+ verbose_name="Mandate state",
+ ),
+ )
]
class MemberSepa(Auditable, models.Model):
member = AutoOneToOneField(
- to='members.Member', related_name='profile_sepa', on_delete=models.PROTECT
+ to="members.Member", related_name="profile_sepa", on_delete=models.PROTECT
)
iban = IBANField(null=True, blank=True, verbose_name="IBAN")
mandate_state = models.CharField(
choices=[
- ('inactive', _('Inactive')),
- ('active', _('Active')),
- ('bounced', _('Bounced')),
- ('rescinded', _('Rescinded')),
+ ("inactive", _("Inactive")),
+ ("active", _("Active")),
+ ("bounced", _("Bounced")),
+ ("rescinded", _("Rescinded")),
],
- default='active', max_length=10, blank=False, null=False,
+ default="active",
+ max_length=10,
+ blank=False,
+ null=False,
verbose_name=_("Mandate state"),
)
null=True,
blank=True,
verbose_name=_("IBAN Issue Date"),
- help_text=_("The issue date of the direct debit mandate. (1970-01-01 means there is no issue date in the database )"),
+ help_text=_(
+ "The issue date of the direct debit mandate. (1970-01-01 means there is no issue date in the database )"
+ ),
)
fullname = models.CharField(
null=True, blank=True, max_length=255, verbose_name=_("IBAN Mandate Reason")
)
- form_title = _('SEPA information')
+ form_title = _("SEPA information")
@property
def is_usable(self):
if not self.iban_parsed:
return SepaDirectDebitState.INVALID_IBAN
- if self.mandate_state == 'rescinded':
+ if self.mandate_state == "rescinded":
return SepaDirectDebitState.RESCINDED
- if self.mandate_state == 'bounced':
+ if self.mandate_state == "bounced":
return SepaDirectDebitState.BOUNCED
- if self.mandate_state == 'inactive':
+ if self.mandate_state == "inactive":
return SepaDirectDebitState.INACTIVE
if not self.bic_autocomplete:
@receiver(new_member_mail_information)
def new_member_mail_info_sepa(sender, signal, **kwargs):
if not (
- hasattr(sender, 'profile_sepa')
+ hasattr(sender, "profile_sepa")
and sender.profile_sepa
and sender.profile_sepa.is_usable
):
- return ''
- return _('Your SEPA mandate reference is {}.').format(
+ return ""
+ return _("Your SEPA mandate reference is {}.").format(
sender.profile_sepa.mandate_reference
)
@receiver(new_member_office_mail_information)
def new_member_office_mail_info_sepa(sender, signal, **kwargs):
if not (
- hasattr(sender, 'profile_sepa')
+ hasattr(sender, "profile_sepa")
and sender.profile_sepa
and sender.profile_sepa.is_usable
):
- return ''
+ return ""
if not sender.memberships.count() == 1:
- raise Exception('Cannot determine which membership to put in email!')
+ raise Exception("Cannot determine which membership to put in email!")
membership = sender.memberships.first()
data = {
- 'iban': sender.profile_sepa.iban,
- 'bic': sender.profile_sepa.bic,
- 'institute': sender.profile_sepa.institute,
- 'issue_date': sender.profile_sepa.issue_date.isoformat(),
- 'name': sender.profile_sepa.fullname,
- 'mandate_reference': sender.profile_sepa.mandate_reference,
- 'mandate_reason': sender.profile_sepa.mandate_reason,
- 'amount': '{:2f}'.format(membership.amount),
- 'interval': membership.get_interval_display(),
- 'start': membership.start.isoformat(),
+ "iban": sender.profile_sepa.iban,
+ "bic": sender.profile_sepa.bic,
+ "institute": sender.profile_sepa.institute,
+ "issue_date": sender.profile_sepa.issue_date.isoformat(),
+ "name": sender.profile_sepa.fullname,
+ "mandate_reference": sender.profile_sepa.mandate_reference,
+ "mandate_reason": sender.profile_sepa.mandate_reason,
+ "amount": "{:2f}".format(membership.amount),
+ "interval": membership.get_interval_display(),
+ "start": membership.start.isoformat(),
}
return _(
- '''The new member has given us a SEPA mandate for {amount} ({interval}), starting on {start}:
+ """The new member has given us a SEPA mandate for {amount} ({interval}), starting on {start}:
Name: {name}
IBAN: {iban}
Mandate reference: {mandate_reference}
Mandate reason: {mandate_reason}
-'''
+"""
).format(**data)
member = sender.member
if not (
- hasattr(member, 'profile_sepa')
+ hasattr(member, "profile_sepa")
and member.profile_sepa
and member.profile_sepa.is_usable
):
- return ''
+ return ""
data = {
- 'number': member.number,
- 'iban': member.profile_sepa.iban,
- 'bic': member.profile_sepa.bic,
- 'institute': member.profile_sepa.institute,
- 'issue_date': member.profile_sepa.issue_date.isoformat(),
- 'name': member.profile_sepa.fullname,
- 'mandate_reference': member.profile_sepa.mandate_reference,
- 'mandate_reason': member.profile_sepa.mandate_reason,
- 'end': membership.end.isoformat(),
+ "number": member.number,
+ "iban": member.profile_sepa.iban,
+ "bic": member.profile_sepa.bic,
+ "institute": member.profile_sepa.institute,
+ "issue_date": member.profile_sepa.issue_date.isoformat(),
+ "name": member.profile_sepa.fullname,
+ "mandate_reference": member.profile_sepa.mandate_reference,
+ "mandate_reason": member.profile_sepa.mandate_reason,
+ "end": membership.end.isoformat(),
}
return _(
- '''Please terminate SEPA mandate member number: {number} to {end}:
+ """Please terminate SEPA mandate member number: {number} to {end}:
Name: {name}
IBAN: {iban}
Mandate reference: {mandate_reference}
Mandate reason: {mandate_reason}
-'''
+"""
).format(**data)
@pytest.fixture
def member_bob():
- member = Member.objects.create(email='bob@hacker.space')
+ member = Member.objects.create(email="bob@hacker.space")
yield member
[profile.delete() for profile in member.profiles]
member.delete()
class PublicConfig(AppConfig):
- name = 'byro.public'
+ name = "byro.public"
def ready(self):
from . import signals # noqa
initial = True
- dependencies = [
- ('members', '0009_auto_20180512_1810'),
- ]
+ dependencies = [("members", "0009_auto_20180512_1810")]
operations = [
migrations.CreateModel(
- name='MemberpageProfile',
+ name="MemberpageProfile",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('secret_token', models.CharField(blank=True, default=byro.public.models.generate_default_token, max_length=128, null=True)),
- ('is_visible_to_members', models.BooleanField(default=False, verbose_name='Consent: Visible to other members')),
- ('member', annoying.fields.AutoOneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile_memberpage', to='members.Member')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "secret_token",
+ models.CharField(
+ blank=True,
+ default=byro.public.models.generate_default_token,
+ max_length=128,
+ null=True,
+ ),
+ ),
+ (
+ "is_visible_to_members",
+ models.BooleanField(
+ default=False, verbose_name="Consent: Visible to other members"
+ ),
+ ),
+ (
+ "member",
+ annoying.fields.AutoOneToOneField(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="profile_memberpage",
+ to="members.Member",
+ ),
+ ),
],
- ),
+ )
]
class Migration(migrations.Migration):
- dependencies = [
- ('public', '0001_initial'),
- ]
+ dependencies = [("public", "0001_initial")]
operations = [
migrations.AddField(
- model_name='memberpageprofile',
- name='publication_consent',
- field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=byro.public.models.get_default_consent, null=True),
- ),
+ model_name="memberpageprofile",
+ name="publication_consent",
+ field=django.contrib.postgres.fields.jsonb.JSONField(
+ blank=True, default=byro.public.models.get_default_consent, null=True
+ ),
+ )
]
form_title = _("Memberpage settings")
member = AutoOneToOneField(
- to='members.Member', on_delete=models.CASCADE, related_name='profile_memberpage'
+ to="members.Member", on_delete=models.CASCADE, related_name="profile_memberpage"
)
secret_token = models.CharField(
max_length=128, null=True, blank=True, default=generate_default_token
)
is_visible_to_members = models.BooleanField(
- default=False, verbose_name=_('Consent: Visible to other members')
+ default=False, verbose_name=_("Consent: Visible to other members")
)
# publication_consent format: {"fields": {"profile_memberpage__secret_token": {"visibility": "share"}}}
publication_consent = JSONField(default=get_default_consent, null=True, blank=True)
def get_url(self):
config = Configuration.get_solo()
relative_url = reverse(
- 'public:memberpage:member.dashboard',
- kwargs={'secret_token': self.secret_token},
+ "public:memberpage:member.dashboard",
+ kwargs={"secret_token": self.secret_token},
)
if config.public_base_url:
return urljoin(config.public_base_url, relative_url)
if request.resolver_match and request.resolver_match.view_name.startswith(
"public:memberpage"
):
- secret_token = request.resolver_match.kwargs.get('secret_token')
+ secret_token = request.resolver_match.kwargs.get("secret_token")
if secret_token:
- kwargs = {'secret_token': secret_token}
+ kwargs = {"secret_token": secret_token}
config = Configuration.get_solo()
result = [
{
- 'label': _('Member page'),
- 'url': reverse('public:memberpage:member.dashboard', kwargs=kwargs),
- 'active': request.resolver_match.view_name
- == 'public:memberpage.dashboard',
+ "label": _("Member page"),
+ "url": reverse("public:memberpage:member.dashboard", kwargs=kwargs),
+ "active": request.resolver_match.view_name
+ == "public:memberpage.dashboard",
}
]
if config.can_see_other_members in (
if member.is_active:
result.append(
{
- 'label': _('Member list'),
- 'url': reverse(
- 'public:memberpage:member.list', kwargs=kwargs
+ "label": _("Member list"),
+ "url": reverse(
+ "public:memberpage:member.list", kwargs=kwargs
),
- 'active': request.resolver_match.view_name
- == 'public:memberpage:member.list',
+ "active": request.resolver_match.view_name
+ == "public:memberpage:member.list",
}
)
return result
def new_member_mail_info_memberpage(sender, signal, **kwargs):
url = sender.profile_memberpage.get_url()
if url:
- return _('Your personal member page is at {link}').format(
+ return _("Your personal member page is at {link}").format(
link=sender.profile_memberpage.get_url()
)
def new_member_office_mail_info_memberpage(sender, signal, **kwargs):
url = sender.profile_memberpage.get_url()
if url:
- return _('Their personal member page is at {link}').format(
+ return _("Their personal member page is at {link}").format(
link=sender.profile_memberpage.get_url()
)
return
url = member.profile_memberpage.get_url()
if url:
- return {'url': url, 'title': _('Public profile')}
+ return {"url": url, "title": _("Public profile")}
from . import views
member_pages = [
- url(r'^$', views.MemberView.as_view(), name='member.dashboard'),
- url(r'^update$', views.MemberUpdateView.as_view(), name='member.update'),
- url(r'^member_list$', views.MemberListView.as_view(), name='member.list'),
+ url(r"^$", views.MemberView.as_view(), name="member.dashboard"),
+ url(r"^update$", views.MemberUpdateView.as_view(), name="member.update"),
+ url(r"^member_list$", views.MemberListView.as_view(), name="member.list"),
]
urlpatterns = [
- url(r'^member/(?P<secret_token>[^/]+)/', include((member_pages, 'memberpage'))),
+ url(r"^member/(?P<secret_token>[^/]+)/", include((member_pages, "memberpage")))
]
-app_name = 'public'
+app_name = "public"
class MemberConsentForm(forms.Form):
is_visible_to_members = forms.BooleanField(
- label=_('Consent: Visible to other members'), required=False
+ label=_("Consent: Visible to other members"), required=False
)
class MemberBaseView(DetailView):
- slug_field = 'profile_memberpage__secret_token'
- slug_url_kwarg = 'secret_token'
+ slug_field = "profile_memberpage__secret_token"
+ slug_url_kwarg = "secret_token"
model = Member
class MemberView(MemberBaseView):
- template_name = 'public/members/dashboard.html'
+ template_name = "public/members/dashboard.html"
def get_bookings(self, member):
account_list = [SpecialAccounts.donations, SpecialAccounts.fees_receivable]
member=member,
transaction__value_datetime__lte=now(),
)
- .order_by('-transaction__value_datetime')
+ .order_by("-transaction__value_datetime")
)
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
- obj = context['member']
+ obj = context["member"]
config = Configuration.get_solo()
- context['config'] = config
- context['bookings'] = self.get_bookings(obj)
- context['member_view_level'] = MemberViewLevel
+ context["config"] = config
+ context["bookings"] = self.get_bookings(obj)
+ context["member_view_level"] = MemberViewLevel
_now = now()
- memberships = obj.memberships.order_by('-start').all()
+ memberships = obj.memberships.order_by("-start").all()
if not memberships:
return context
for ms in memberships:
delta += (ms.end or _now.date()) - ms.start
if not ms.end or ms.end <= _now.date():
- context['current_membership'] = ms
- context['memberships'] = memberships
- context['member_since'] = {
- 'days': int(delta.total_seconds() / (60 * 60 * 24)),
- 'years': round(delta.days / 365, 1),
- 'first': first,
+ context["current_membership"] = ms
+ context["memberships"] = memberships
+ context["member_since"] = {
+ "days": int(delta.total_seconds() / (60 * 60 * 24)),
+ "years": round(delta.days / 365, 1),
+ "first": first,
}
- context['tiles'] = []
+ context["tiles"] = []
for __, response in member_dashboard_tile.send(self.request, member=obj):
if not response:
continue
if isinstance(response, collections.Mapping) and response.get(
- 'public', False
+ "public", False
):
- context['tiles'].append(response)
+ context["tiles"].append(response)
return context
def get_success_url(self):
return reverse(
- 'public:memberpage:member.dashboard',
- kwargs={'secret_token': self.kwargs['secret_token']},
+ "public:memberpage:member.dashboard",
+ kwargs={"secret_token": self.kwargs["secret_token"]},
)
def post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
self.object.profile_memberpage.is_visible_to_members = form.cleaned_data[
- 'is_visible_to_members'
+ "is_visible_to_members"
]
self.object.profile_memberpage.save()
return HttpResponseRedirect(self.get_success_url())
class MemberListView(ListView):
- template_name = 'public/members/memberlist.html'
+ template_name = "public/members/memberlist.html"
paginate_by = 50
- context_object_name = 'members'
+ context_object_name = "members"
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
config = Configuration.get_solo()
- context['config'] = config
- context['member_view_level'] = MemberViewLevel
- context['member_undisclosed'] = Member.objects.exclude(
+ context["config"] = config
+ context["member_view_level"] = MemberViewLevel
+ context["member_undisclosed"] = Member.objects.exclude(
profile_memberpage__is_visible_to_members=True
).count()
return context
):
raise Http404("Page does not exist")
- secret_token = self.kwargs.get('secret_token')
+ secret_token = self.kwargs.get("secret_token")
if not secret_token:
raise Http404("Page does not exist")
return Member.objects.filter(
profile_memberpage__is_visible_to_members=True
- ).order_by('name')
+ ).order_by("name")
# Search for "## {AREA} SETTINGS" to navigate this file
##
-DEBUG = config.getboolean('site', 'debug')
+DEBUG = config.getboolean("site", "debug")
## DIRECTORY SETTINGS
-BASE_DIR = config.get('filesystem', 'base')
+BASE_DIR = config.get("filesystem", "base")
DATA_DIR = config.get(
- 'filesystem',
- 'data',
- fallback=os.environ.get('BYRO_DATA_DIR', os.path.join(BASE_DIR, 'data')),
+ "filesystem",
+ "data",
+ fallback=os.environ.get("BYRO_DATA_DIR", os.path.join(BASE_DIR, "data")),
)
-LOG_DIR = config.get('filesystem', 'logs', fallback=os.path.join(DATA_DIR, 'logs'))
-MEDIA_ROOT = config.get('filesystem', 'media', fallback=os.path.join(DATA_DIR, 'media'))
+LOG_DIR = config.get("filesystem", "logs", fallback=os.path.join(DATA_DIR, "logs"))
+MEDIA_ROOT = config.get("filesystem", "media", fallback=os.path.join(DATA_DIR, "media"))
STATIC_ROOT = config.get(
- 'filesystem', 'static', fallback=os.path.join(BASE_DIR, 'static.dist')
+ "filesystem", "static", fallback=os.path.join(BASE_DIR, "static.dist")
)
for directory in (BASE_DIR, LOG_DIR, STATIC_ROOT, MEDIA_ROOT):
## APP SETTINGS
INSTALLED_APPS = [
- 'django.contrib.contenttypes',
- 'django.contrib.auth',
- 'django.contrib.sessions',
- 'django.contrib.messages',
- 'django.contrib.staticfiles',
- 'compressor',
- 'bootstrap4',
- 'djangoformsetjs',
- 'solo.apps.SoloAppConfig',
- 'django_select2',
- 'byro.common.apps.CommonConfig',
- 'byro.bookkeeping.apps.BookkeepingConfig',
- 'byro.documents.apps.DocumentsConfig',
- 'byro.mails.apps.MailsConfig',
- 'byro.members.apps.MemberConfig',
- 'byro.office.apps.OfficeConfig',
- 'byro.public.apps.PublicConfig',
- 'byro.plugins.profile.ProfilePluginConfig',
- 'byro.plugins.sepa.SepaPluginConfig',
- 'annoying',
- 'django_db_constraints',
+ "django.contrib.contenttypes",
+ "django.contrib.auth",
+ "django.contrib.sessions",
+ "django.contrib.messages",
+ "django.contrib.staticfiles",
+ "compressor",
+ "bootstrap4",
+ "djangoformsetjs",
+ "solo.apps.SoloAppConfig",
+ "django_select2",
+ "byro.common.apps.CommonConfig",
+ "byro.bookkeeping.apps.BookkeepingConfig",
+ "byro.documents.apps.DocumentsConfig",
+ "byro.mails.apps.MailsConfig",
+ "byro.members.apps.MemberConfig",
+ "byro.office.apps.OfficeConfig",
+ "byro.public.apps.PublicConfig",
+ "byro.plugins.profile.ProfilePluginConfig",
+ "byro.plugins.sepa.SepaPluginConfig",
+ "annoying",
+ "django_db_constraints",
]
PLUGINS = []
-for entry_point in iter_entry_points(group='byro.plugin', name=None):
+for entry_point in iter_entry_points(group="byro.plugin", name=None):
PLUGINS.append(entry_point.module_name)
INSTALLED_APPS.append(entry_point.module_name)
## URL SETTINGS
-SITE_URL = config.get('site', 'url', fallback='http://localhost')
-INTERNAL_IPS = ['127.0.0.1', '::1', 'localhost']
+SITE_URL = config.get("site", "url", fallback="http://localhost")
+INTERNAL_IPS = ["127.0.0.1", "::1", "localhost"]
ALLOWED_HOSTS = [SITE_URL]
if DEBUG:
ALLOWED_HOSTS += INTERNAL_IPS
-ROOT_URLCONF = 'byro.urls'
-STATIC_URL = '/static/'
-MEDIA_URL = '/media/'
+ROOT_URLCONF = "byro.urls"
+STATIC_URL = "/static/"
+MEDIA_URL = "/media/"
## SECURITY SETTINGS
-X_FRAME_OPTIONS = 'DENY'
+X_FRAME_OPTIONS = "DENY"
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
-CSRF_COOKIE_NAME = 'byro_csrftoken'
+CSRF_COOKIE_NAME = "byro_csrftoken"
CSRF_TRUSTED_ORIGINS = [urlparse(SITE_URL).hostname]
-SESSION_COOKIE_NAME = 'byro_session'
+SESSION_COOKIE_NAME = "byro_session"
SESSION_COOKIE_SECURE = config.getboolean(
- 'site', 'https', fallback=SITE_URL.startswith('https:')
+ "site", "https", fallback=SITE_URL.startswith("https:")
)
-if config.has_option('site', 'secret'):
- SECRET_KEY = config.get('site', 'secret')
+if config.has_option("site", "secret"):
+ SECRET_KEY = config.get("site", "secret")
else:
- SECRET_FILE = os.path.join(DATA_DIR, '.secret')
+ SECRET_FILE = os.path.join(DATA_DIR, ".secret")
if os.path.exists(SECRET_FILE):
- with open(SECRET_FILE, 'r') as f:
+ with open(SECRET_FILE, "r") as f:
SECRET_KEY = f.read().strip()
else:
- chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
+ chars = "abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)"
SECRET_KEY = get_random_string(50, chars)
- with open(SECRET_FILE, 'w') as f:
+ with open(SECRET_FILE, "w") as f:
os.chmod(SECRET_FILE, 0o600)
- if hasattr(os, 'chown'):
+ if hasattr(os, "chown"):
os.chown(SECRET_FILE, os.getuid(), os.getgid())
f.write(SECRET_KEY)
## DATABASE SETTINGS
-db_name = config.get('database', 'name', fallback=os.path.join(DATA_DIR, 'db.sqlite3'))
+db_name = config.get("database", "name", fallback=os.path.join(DATA_DIR, "db.sqlite3"))
DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.postgresql',
- 'NAME': db_name,
- 'USER': config.get('database', 'user'),
- 'PASSWORD': config.get('database', 'password'),
- 'HOST': config.get('database', 'host'),
- 'PORT': config.get('database', 'port'),
- 'CONN_MAX_AGE': 120,
+ "default": {
+ "ENGINE": "django.db.backends.postgresql",
+ "NAME": db_name,
+ "USER": config.get("database", "user"),
+ "PASSWORD": config.get("database", "password"),
+ "HOST": config.get("database", "host"),
+ "PORT": config.get("database", "port"),
+ "CONN_MAX_AGE": 120,
}
}
-if os.getenv('TRAVIS'):
- DATABASES['default']['USER'] = 'postgres'
- DATABASES['default']['HOST'] = 'localhost'
- DATABASES['default']['PASSWORD'] = ''
+if os.getenv("TRAVIS"):
+ DATABASES["default"]["USER"] = "postgres"
+ DATABASES["default"]["HOST"] = "localhost"
+ DATABASES["default"]["PASSWORD"] = ""
# for docker-compose development
-if os.getenv('DEVELOPMENT'):
- DATABASES['default']['USER'] = 'byro'
- DATABASES['default']['HOST'] = 'db'
- DATABASES['default']['PASSWORD'] = 'byro'
+if os.getenv("DEVELOPMENT"):
+ DATABASES["default"]["USER"] = "byro"
+ DATABASES["default"]["HOST"] = "db"
+ DATABASES["default"]["PASSWORD"] = "byro"
## LOGGING SETTINGS
-loglevel = 'DEBUG' if DEBUG else 'INFO'
+loglevel = "DEBUG" if DEBUG else "INFO"
LOGGING = {
- 'version': 1,
- 'disable_existing_loggers': False,
- 'formatters': {
- 'default': {
- 'format': '%(levelname)s %(asctime)s %(name)s %(module)s %(message)s'
+ "version": 1,
+ "disable_existing_loggers": False,
+ "formatters": {
+ "default": {
+ "format": "%(levelname)s %(asctime)s %(name)s %(module)s %(message)s"
}
},
- 'handlers': {
- 'console': {
- 'level': loglevel,
- 'class': 'logging.StreamHandler',
- 'formatter': 'default',
+ "handlers": {
+ "console": {
+ "level": loglevel,
+ "class": "logging.StreamHandler",
+ "formatter": "default",
},
- 'file': {
- 'level': loglevel,
- 'class': 'logging.FileHandler',
- 'filename': os.path.join(LOG_DIR, 'byro.log'),
- 'formatter': 'default',
+ "file": {
+ "level": loglevel,
+ "class": "logging.FileHandler",
+ "filename": os.path.join(LOG_DIR, "byro.log"),
+ "formatter": "default",
},
},
- 'loggers': {
- '': {'handlers': ['file', 'console'], 'level': loglevel, 'propagate': True},
- 'django.request': {
- 'handlers': ['file', 'console'],
- 'level': loglevel,
- 'propagate': True,
+ "loggers": {
+ "": {"handlers": ["file", "console"], "level": loglevel, "propagate": True},
+ "django.request": {
+ "handlers": ["file", "console"],
+ "level": loglevel,
+ "propagate": True,
},
- 'django.security': {
- 'handlers': ['file', 'console'],
- 'level': loglevel,
- 'propagate': True,
+ "django.security": {
+ "handlers": ["file", "console"],
+ "level": loglevel,
+ "propagate": True,
},
- 'django.db.backends': {
- 'handlers': ['file', 'console'],
- 'level': 'INFO', # Do not output all the queries
- 'propagate': True,
+ "django.db.backends": {
+ "handlers": ["file", "console"],
+ "level": "INFO", # Do not output all the queries
+ "propagate": True,
},
},
}
-email_level = config.get('logging', 'email_level', fallback='ERROR') or 'ERROR'
-emails = config.get('logging', 'email', fallback='').split(',')
+email_level = config.get("logging", "email_level", fallback="ERROR") or "ERROR"
+emails = config.get("logging", "email", fallback="").split(",")
MANAGERS = ADMINS = [(email, email) for email in emails if email]
if ADMINS:
- LOGGING['handlers']['mail_admins'] = {
- 'level': email_level,
- 'class': 'django.utils.log.AdminEmailHandler',
+ LOGGING["handlers"]["mail_admins"] = {
+ "level": email_level,
+ "class": "django.utils.log.AdminEmailHandler",
}
## EMAIL SETTINGS
-MAIL_FROM = SERVER_EMAIL = DEFAULT_FROM_EMAIL = config.get('mail', 'from')
+MAIL_FROM = SERVER_EMAIL = DEFAULT_FROM_EMAIL = config.get("mail", "from")
if DEBUG:
- EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
+ EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
else:
- EMAIL_HOST = config.get('mail', 'host')
- EMAIL_PORT = config.get('mail', 'port')
- EMAIL_HOST_USER = config.get('mail', 'user')
- EMAIL_HOST_PASSWORD = config.get('mail', 'password')
- EMAIL_USE_TLS = config.getboolean('mail', 'tls')
- EMAIL_USE_SSL = config.getboolean('mail', 'ssl')
+ EMAIL_HOST = config.get("mail", "host")
+ EMAIL_PORT = config.get("mail", "port")
+ EMAIL_HOST_USER = config.get("mail", "user")
+ EMAIL_HOST_PASSWORD = config.get("mail", "password")
+ EMAIL_USE_TLS = config.getboolean("mail", "tls")
+ EMAIL_USE_SSL = config.getboolean("mail", "ssl")
## I18N SETTINGS
USE_I18N = True
USE_L10N = True
USE_TZ = True
-LANGUAGES = [('en', _('English')), ('de', _('German'))]
-LANGUAGES_NATURAL_NAMES = [('en', 'English'), ('de', 'Deutsch')]
-LOCALE_PATHS = (os.path.join(os.path.dirname(__file__), 'locale'),)
-FORMAT_MODULE_PATH = ['byro.common.formats']
-TIME_ZONE = config.get('locale', 'time_zone')
-LANGUAGE_CODE = config.get('locale', 'language_code')
+LANGUAGES = [("en", _("English")), ("de", _("German"))]
+LANGUAGES_NATURAL_NAMES = [("en", "English"), ("de", "Deutsch")]
+LOCALE_PATHS = (os.path.join(os.path.dirname(__file__), "locale"),)
+FORMAT_MODULE_PATH = ["byro.common.formats"]
+TIME_ZONE = config.get("locale", "time_zone")
+LANGUAGE_CODE = config.get("locale", "language_code")
## AUTHENTICATION SETTINGS
AUTH_PASSWORD_VALIDATORS = [
{
- 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'
+ "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"
},
- {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'},
- {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
- {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
+ {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
+ {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
+ {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
]
## MIDDLEWARE SETTINGS
MIDDLEWARE = [
- 'django.middleware.security.SecurityMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.common.CommonMiddleware',
- 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware',
- 'django.middleware.clickjacking.XFrameOptionsMiddleware',
- 'byro.common.middleware.PermissionMiddleware',
- 'byro.common.middleware.SettingsMiddleware',
+ "django.middleware.security.SecurityMiddleware",
+ "django.contrib.sessions.middleware.SessionMiddleware",
+ "django.middleware.common.CommonMiddleware",
+ "django.middleware.csrf.CsrfViewMiddleware",
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
+ "django.contrib.messages.middleware.MessageMiddleware",
+ "django.middleware.clickjacking.XFrameOptionsMiddleware",
+ "byro.common.middleware.PermissionMiddleware",
+ "byro.common.middleware.SettingsMiddleware",
]
## TEMPLATE AND STATICFILES SETTINGS
template_loaders = (
- 'django.template.loaders.filesystem.Loader',
- 'django.template.loaders.app_directories.Loader',
+ "django.template.loaders.filesystem.Loader",
+ "django.template.loaders.app_directories.Loader",
)
if not DEBUG:
- template_loaders = (('django.template.loaders.cached.Loader', template_loaders),)
+ template_loaders = (("django.template.loaders.cached.Loader", template_loaders),)
TEMPLATES = [
{
- 'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [],
- 'OPTIONS': {
- 'context_processors': [
- 'django.template.context_processors.debug',
- 'django.template.context_processors.request',
- 'django.template.context_processors.static',
- 'django.contrib.auth.context_processors.auth',
- 'django.contrib.messages.context_processors.messages',
- 'byro.common.context_processors.byro_information',
- 'byro.common.context_processors.sidebar_information',
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
+ "DIRS": [],
+ "OPTIONS": {
+ "context_processors": [
+ "django.template.context_processors.debug",
+ "django.template.context_processors.request",
+ "django.template.context_processors.static",
+ "django.contrib.auth.context_processors.auth",
+ "django.contrib.messages.context_processors.messages",
+ "byro.common.context_processors.byro_information",
+ "byro.common.context_processors.sidebar_information",
],
- 'loaders': template_loaders,
+ "loaders": template_loaders,
},
}
]
STATICFILES_FINDERS = (
- 'django.contrib.staticfiles.finders.FileSystemFinder',
- 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
- 'compressor.finders.CompressorFinder',
+ "django.contrib.staticfiles.finders.FileSystemFinder",
+ "django.contrib.staticfiles.finders.AppDirectoriesFinder",
+ "compressor.finders.CompressorFinder",
)
-STATICFILES_DIRS = [os.path.join(BASE_DIR, 'byro', 'static')]
+STATICFILES_DIRS = [os.path.join(BASE_DIR, "byro", "static")]
## EXTERNAL APP SETTINGS
with suppress(ImportError):
import django_extensions # noqa
- INSTALLED_APPS.append('django_extensions')
+ INSTALLED_APPS.append("django_extensions")
with suppress(ImportError):
import django_securebox
- INSTALLED_APPS.append('django_securebox')
- MIDDLEWARE.insert(2, 'django_securebox.middleware.SecureBoxMiddleware')
+ INSTALLED_APPS.append("django_securebox")
+ MIDDLEWARE.insert(2, "django_securebox.middleware.SecureBoxMiddleware")
with suppress(ImportError):
import debug_toolbar
- INSTALLED_APPS.append('debug_toolbar')
- MIDDLEWARE.insert(2, 'debug_toolbar.middleware.DebugToolbarMiddleware')
+ INSTALLED_APPS.append("debug_toolbar")
+ MIDDLEWARE.insert(2, "debug_toolbar.middleware.DebugToolbarMiddleware")
COMPRESS_ENABLED = COMPRESS_OFFLINE = not DEBUG
-COMPRESS_PRECOMPILERS = (('text/x-scss', 'django_libsass.SassCompiler'),)
+COMPRESS_PRECOMPILERS = (("text/x-scss", "django_libsass.SassCompiler"),)
COMPRESS_CSS_FILTERS = (
# CssAbsoluteFilter is incredibly slow, especially when dealing with our _flags.scss
# However, we don't need it if we consequently use the static() function in Sass
# 'compressor.filters.css_default.CssAbsoluteFilter',
- 'compressor.filters.cssmin.CSSCompressorFilter',
+ "compressor.filters.cssmin.CSSCompressorFilter",
)
-SELECT2_JS = ''
-SELECT2_CSS = ''
-SELECT2_I18N_PATH = '/static/vendored/select2/js/i18n'
+SELECT2_JS = ""
+SELECT2_CSS = ""
+SELECT2_I18N_PATH = "/static/vendored/select2/js/i18n"
with suppress(ImportError):
from .local_settings import *
print(
- 'You are using the deprecated local_settings.py – Please move to the byro.cfg format.'
+ "You are using the deprecated local_settings.py – Please move to the byro.cfg format."
)
-WSGI_APPLICATION = 'byro.wsgi.application'
+WSGI_APPLICATION = "byro.wsgi.application"
log_initial(
debug=DEBUG,
config_files=config_files,
raw_plugin_patterns = []
for app in apps.get_app_configs():
- if hasattr(app, 'ByroPluginMeta'):
- if importlib.util.find_spec(app.name + '.urls'):
- urlmod = importlib.import_module(app.name + '.urls')
+ if hasattr(app, "ByroPluginMeta"):
+ if importlib.util.find_spec(app.name + ".urls"):
+ urlmod = importlib.import_module(app.name + ".urls")
single_plugin_patterns = []
- if hasattr(urlmod, 'urlpatterns'):
+ if hasattr(urlmod, "urlpatterns"):
single_plugin_patterns += urlmod.urlpatterns
raw_plugin_patterns.append(
- url(r'', include((single_plugin_patterns, app.label)))
+ url(r"", include((single_plugin_patterns, app.label)))
)
urlpatterns = [
- url(r'', include((raw_plugin_patterns, 'plugins'))),
- url(r'', include('byro.common.urls', namespace='common')),
- url(r'', include('byro.office.urls', namespace='office')),
- url(r'^p/', include('byro.public.urls', namespace='public')),
+ url(r"", include((raw_plugin_patterns, "plugins"))),
+ url(r"", include("byro.common.urls", namespace="common")),
+ url(r"", include("byro.office.urls", namespace="office")),
+ url(r"^p/", include("byro.public.urls", namespace="public")),
]
if settings.DEBUG:
with suppress(ImportError):
import debug_toolbar
- urlpatterns.insert(0, url(r'^__debug__/', include(debug_toolbar.urls)))
+
+ urlpatterns.insert(0, url(r"^__debug__/", include(debug_toolbar.urls)))
# Get the long description from the relevant file
try:
- with open(path.join(here, '../README.rst'), encoding='utf-8') as f:
+ with open(path.join(here, "../README.rst"), encoding="utf-8") as f:
long_description = f.read()
except: # noqa
- long_description = ''
+ long_description = ""
class CustomBuild(build):
settings.COMPRESS_ENABLED = True
settings.COMPRESS_OFFLINE = True
- management.call_command('compilemessages', verbosity=1)
- management.call_command('collectstatic', verbosity=1, interactive=False)
- management.call_command('compress', verbosity=1)
+ management.call_command("compilemessages", verbosity=1)
+ management.call_command("collectstatic", verbosity=1, interactive=False)
+ management.call_command("compress", verbosity=1)
build.run(self)
-cmdclass = {'build': CustomBuild}
+cmdclass = {"build": CustomBuild}
setup(
- name='byro',
+ name="byro",
version=byro_version,
- python_requires='>=3.5',
- description='Membership and fees management for associations, clubs and groups',
+ python_requires=">=3.5",
+ description="Membership and fees management for associations, clubs and groups",
long_description=long_description,
- url='https://byro.cloud',
- author='Tobias Kunze',
- author_email='r@rixx.de',
- license='Apache License 2.0',
+ url="https://byro.cloud",
+ author="Tobias Kunze",
+ author_email="r@rixx.de",
+ license="Apache License 2.0",
classifiers=[
- 'Development Status :: 5 - Production/Stable',
- 'Environment :: Web Environment',
- 'Framework :: Django',
- 'Framework :: Django :: 2.0',
- 'Intended Audience :: Developers',
- 'Intended Audience :: Other Audience',
- 'License :: OSI Approved',
- 'License :: OSI Approved :: Apache Software License',
- 'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.6',
- 'Programming Language :: Python :: 3.7',
- 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
+ "Development Status :: 5 - Production/Stable",
+ "Environment :: Web Environment",
+ "Framework :: Django",
+ "Framework :: Django :: 2.0",
+ "Intended Audience :: Developers",
+ "Intended Audience :: Other Audience",
+ "License :: OSI Approved",
+ "License :: OSI Approved :: Apache Software License",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
+ "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
],
- keywords='members membership fees club group clubs associations association',
+ keywords="members membership fees club group clubs associations association",
install_requires=[
- 'canonicaljson==1.1.4', # https://github.com/matrix-org/python-canonicaljson/blob/master/CHANGES.md
- 'chardet==3.0.*', # https://github.com/chardet/chardet/releases
- 'celery==4.3.*', # search for "what's new" on http://docs.celeryproject.org/en/latest/
- 'csscompressor==0.9.*', # 2017-11, no changelog, https://github.com/sprymix/csscompressor
- 'dateparser==0.7.*', # https://github.com/scrapinghub/dateparser/blob/master/HISTORY.rst
- 'Django>=2.2.0,<2.3.0', # https://docs.djangoproject.com/en/2.0/releases/
- 'django-annoying==0.10.*', # https://github.com/skorokithakis/django-annoying/releases
- 'django-bootstrap4==0.0.*', # http://django-bootstrap4.readthedocs.io/en/latest/history.html
- 'django-compressor==2.2.*', # https://django-compressor.readthedocs.io/en/latest/changelog/
- 'django-db-constraints==0.3.*',
- 'django-extensions==2.1.*', # https://github.com/django-extensions/django-extensions/blob/master/CHANGELOG.md
- 'django-formset-js-improved==0.5.0.2', # no changelog, https://github.com/pretix/django-formset-js
- 'django-i18nfield==1.4.*', # 2017-11, no changelog, https://github.com/raphaelm/django-i18nfield/
- 'django-libsass==0.7', # inactive, https://github.com/torchbox/django-libsass/blob/master/CHANGELOG.txt
- 'django-localflavor==2.1.*',
- 'django-select2==6.3.*', # https://github.com/applegrew/django-select2/releases
- 'django-solo==1.1.*', # https://github.com/lazybird/django-solo/blob/master/CHANGES
- 'jinja2>=2.10.1', # https://github.com/pallets/jinja/blob/master/CHANGES.rst
- 'psycopg2-binary',
- 'python-dateutil',
- 'python-magic==0.4.15',
- 'pynacl==1.3.0', # https://github.com/pyca/pynacl/blob/master/CHANGELOG.rst
- 'qrcode[pil]==6.1', # https://github.com/lincolnloop/python-qrcode/blob/master/CHANGES.rst
- 'unicodecsv==0.14.*',
- 'more-itertools==7.0.*',
- 'schwifty==2018.9.*',
+ "canonicaljson==1.1.4", # https://github.com/matrix-org/python-canonicaljson/blob/master/CHANGES.md
+ "chardet==3.0.*", # https://github.com/chardet/chardet/releases
+ "celery==4.3.*", # search for "what's new" on http://docs.celeryproject.org/en/latest/
+ "csscompressor==0.9.*", # 2017-11, no changelog, https://github.com/sprymix/csscompressor
+ "dateparser==0.7.*", # https://github.com/scrapinghub/dateparser/blob/master/HISTORY.rst
+ "Django>=2.2.0,<2.3.0", # https://docs.djangoproject.com/en/2.0/releases/
+ "django-annoying==0.10.*", # https://github.com/skorokithakis/django-annoying/releases
+ "django-bootstrap4==0.0.*", # http://django-bootstrap4.readthedocs.io/en/latest/history.html
+ "django-compressor==2.2.*", # https://django-compressor.readthedocs.io/en/latest/changelog/
+ "django-db-constraints==0.3.*",
+ "django-extensions==2.1.*", # https://github.com/django-extensions/django-extensions/blob/master/CHANGELOG.md
+ "django-formset-js-improved==0.5.0.2", # no changelog, https://github.com/pretix/django-formset-js
+ "django-i18nfield==1.4.*", # 2017-11, no changelog, https://github.com/raphaelm/django-i18nfield/
+ "django-libsass==0.7", # inactive, https://github.com/torchbox/django-libsass/blob/master/CHANGELOG.txt
+ "django-localflavor==2.1.*",
+ "django-select2==6.3.*", # https://github.com/applegrew/django-select2/releases
+ "django-solo==1.1.*", # https://github.com/lazybird/django-solo/blob/master/CHANGES
+ "jinja2>=2.10.1", # https://github.com/pallets/jinja/blob/master/CHANGES.rst
+ "psycopg2-binary",
+ "python-dateutil",
+ "python-magic==0.4.15",
+ "pynacl==1.3.0", # https://github.com/pyca/pynacl/blob/master/CHANGELOG.rst
+ "qrcode[pil]==6.1", # https://github.com/lincolnloop/python-qrcode/blob/master/CHANGES.rst
+ "unicodecsv==0.14.*",
+ "more-itertools==7.0.*",
+ "schwifty==2018.9.*",
],
extras_require={
- 'dev': [
- 'black',
- 'freezegun',
- 'isort',
- 'ipython',
- 'pytest==3.9.3',
- 'pytest-cov',
- 'pytest-django',
- 'pytest-sugar',
+ "dev": [
+ "black",
+ "freezegun",
+ "isort",
+ "ipython",
+ "pytest==3.9.3",
+ "pytest-cov",
+ "pytest-django",
+ "pytest-sugar",
]
},
- packages=find_packages(exclude=['tests', 'tests.*']),
+ packages=find_packages(exclude=["tests", "tests.*"]),
include_package_data=True,
cmdclass=cmdclass,
)
@pytest.fixture
def configuration():
config = Configuration.get_solo()
- config.name = 'Association Name'
- config.backoffice_mail = 'associationname@example.com'
- config.mail_from = 'associationname@example.com'
+ config.name = "Association Name"
+ config.backoffice_mail = "associationname@example.com"
+ config.mail_from = "associationname@example.com"
config.can_see_other_members = MemberViewLevel.NAME_AND_CONTACT
config.save()
return config
@pytest.fixture
def full_testdata(django_db_setup, django_db_blocker):
with django_db_blocker.unblock():
- call_command('loaddata', 'tests/fixtures/test_full_testdata.json')
+ call_command("loaddata", "tests/fixtures/test_full_testdata.json")
@pytest.fixture
def login_user():
def do_login(client, user):
client.post(
- reverse('common:login'),
- {'username': user.username, 'password': 'test_password'},
+ reverse("common:login"),
+ {"username": user.username, "password": "test_password"},
)
return do_login
@pytest.fixture
def user(login_user):
- user = get_user_model().objects.create(username='regular_user', is_staff=True)
- user.set_password('test_password')
+ user = get_user_model().objects.create(username="regular_user", is_staff=True)
+ user.set_password("test_password")
user.save()
yield user
user.delete()
@pytest.fixture
def member():
- member = Member.objects.create(email='joe@hacker.space', number='1', name='Jo Ey')
+ member = Member.objects.create(email="joe@hacker.space", number="1", name="Jo Ey")
yield member
[profile.delete() for profile in member.profiles]
def member_with_sepa_profile(member):
MemberSepa.objects.create(
member=member,
- iban='DE89370400440532013000',
- bic='COBADEFFXXX',
+ iban="DE89370400440532013000",
+ bic="COBADEFFXXX",
issue_date=now().date(),
fullname=member.name,
- address='Somewhere',
- mandate_reference='\'tis a reference',
+ address="Somewhere",
+ mandate_reference="'tis a reference",
)
return member
@pytest.fixture
def inactive_member():
- member = Member.objects.create(email='joe@ex-hacker.space', name='Inactive Joe')
+ member = Member.objects.create(email="joe@ex-hacker.space", name="Inactive Joe")
today = now()
begin = today.replace(day=1) - relativedelta(months=3)
end = today.replace(day=1) - relativedelta(months=1, days=-1)
@pytest.fixture
def partial_transaction():
- t = Transaction.objects.create(value_datetime=now(), user_or_context='test')
+ t = Transaction.objects.create(value_datetime=now(), user_or_context="test")
t.debit(
- account=SpecialAccounts.bank, amount=10, memo="Fee ID 3", user_or_context='test'
+ account=SpecialAccounts.bank, amount=10, memo="Fee ID 3", user_or_context="test"
)
yield t
t.bookings.all().delete()
@pytest.fixture
def mail_template():
return MailTemplate.objects.create(
- subject='Test Mail', text='Hi!\nThis is just a test mail.\nThe robo clerk'
+ subject="Test Mail", text="Hi!\nThis is just a test mail.\nThe robo clerk"
)
@pytest.fixture
def email():
return EMail.objects.create(
- to='test@localhost',
- subject='Test Mail',
- text='Hi!\nThis is just a nice test mail.\nThe robo clerk',
+ to="test@localhost",
+ subject="Test Mail",
+ text="Hi!\nThis is just a nice test mail.\nThe robo clerk",
)
@pytest.fixture
def sent_email():
return EMail.objects.create(
- to='test@localhost',
- subject='Test Mail',
- text='Hi!\nThis is just a nice test mail.\nThe robo clerk',
+ to="test@localhost",
+ subject="Test Mail",
+ text="Hi!\nThis is just a nice test mail.\nThe robo clerk",
sent=now(),
)
@pytest.fixture
def real_transaction_source():
csv = open(
- os.path.join(os.path.dirname(__file__), 'fixtures/transactions.csv')
+ os.path.join(os.path.dirname(__file__), "fixtures/transactions.csv")
).read()
- f = SimpleUploadedFile('testresource.csv', csv.encode())
+ f = SimpleUploadedFile("testresource.csv", csv.encode())
return RealTransactionSource.objects.create(source_file=f)
return pytest.fixture(f)
-fee_account = account_helper('fee_account', 'member_fees')
-income_account = account_helper('income_account', AccountCategory.INCOME)
-receivable_account = account_helper('receivable_account', AccountCategory.ASSET)
-bank_account = account_helper('asset_account', AccountCategory.ASSET)
+fee_account = account_helper("fee_account", "member_fees")
+income_account = account_helper("income_account", AccountCategory.INCOME)
+receivable_account = account_helper("receivable_account", AccountCategory.ASSET)
+bank_account = account_helper("asset_account", AccountCategory.ASSET)
@pytest.mark.parametrize(
- 'url',
+ "url",
(
- 'settings.base',
- 'settings.registration',
- 'dashboard',
- 'members.typeahead',
- 'members.list',
- 'members.add',
- 'finance.uploads.list',
- 'finance.uploads.add',
- 'finance.accounts.list',
- 'finance.accounts.add',
- 'mails.sent',
- 'mails.outbox.list',
- 'mails.outbox.send',
- 'mails.outbox.purge',
- 'mails.templates.list',
+ "settings.base",
+ "settings.registration",
+ "dashboard",
+ "members.typeahead",
+ "members.list",
+ "members.add",
+ "finance.uploads.list",
+ "finance.uploads.add",
+ "finance.accounts.list",
+ "finance.accounts.add",
+ "mails.sent",
+ "mails.outbox.list",
+ "mails.outbox.send",
+ "mails.outbox.purge",
+ "mails.templates.list",
),
)
-@pytest.mark.parametrize('logged_in', (True, False))
+@pytest.mark.parametrize("logged_in", (True, False))
@pytest.mark.django_db
def test_office_access_urls(client, user, login_user, url, logged_in):
if logged_in:
login_user(client, user)
- response = client.get(reverse('office:{}'.format(url)), follow=True)
+ response = client.get(reverse("office:{}".format(url)), follow=True)
assert response.status_code == 200
- assert (response.resolver_match.url_name == 'login') is not logged_in
+ assert (response.resolver_match.url_name == "login") is not logged_in
@pytest.mark.django_db
def test_office_login_client(client, user):
log_count = LogEntry.objects.count()
- user.set_password('thepassword')
+ user.set_password("thepassword")
user.save()
response = client.post(
- reverse('common:login'),
- {'username': user.username, 'password': 'thepassword'},
+ reverse("common:login"),
+ {"username": user.username, "password": "thepassword"},
follow=True,
)
assert response.status_code == 200
@pytest.mark.django_db
def test_office_login_client_incorrect_password(client, user):
log_count = LogEntry.objects.count()
- user.set_password('thepassword')
+ user.set_password("thepassword")
user.save()
response = client.post(
- reverse('common:login'),
- {'username': user.username, 'password': 'thepasswordddddd'},
+ reverse("common:login"),
+ {"username": user.username, "password": "thepasswordddddd"},
follow=True,
)
assert response.status_code == 200
@pytest.mark.django_db
def test_office_login_client_inactive_user(client, user):
log_count = LogEntry.objects.count()
- user.set_password('thepassword')
+ user.set_password("thepassword")
user.is_active = False
user.save()
response = client.post(
- reverse('common:login'),
- {'username': user.username, 'password': 'thepasswordddddd'},
+ reverse("common:login"),
+ {"username": user.username, "password": "thepasswordddddd"},
follow=True,
)
assert response.status_code == 200
@pytest.mark.django_db
def test_office_logout(logged_in_client, configuration):
log_count = LogEntry.objects.count()
- response = logged_in_client.post(reverse('common:logout'), follow=True)
+ response = logged_in_client.post(reverse("common:logout"), follow=True)
assert response.status_code == 200
assert LogEntry.objects.count() == log_count + 1
@pytest.mark.django_db
def test_member_dashboard(full_testdata, logged_in_client, user):
response = logged_in_client.get(
- reverse('office:members.dashboard', kwargs={'pk': 3}), follow=True
+ reverse("office:members.dashboard", kwargs={"pk": 3}), follow=True
)
assert response.status_code == 200
@pytest.mark.django_db
def test_member_data(full_testdata, logged_in_client, user):
response = logged_in_client.get(
- reverse('office:members.data', kwargs={'pk': 3}), follow=True
+ reverse("office:members.data", kwargs={"pk": 3}), follow=True
)
assert response.status_code == 200
@pytest.mark.django_db
def test_member_finance(full_testdata, logged_in_client, user):
response = logged_in_client.get(
- reverse('office:members.finance', kwargs={'pk': 3}), follow=True
+ reverse("office:members.finance", kwargs={"pk": 3}), follow=True
)
assert response.status_code == 200
@pytest.mark.django_db
def test_member_add(full_testdata, logged_in_client, user):
- response = logged_in_client.get(reverse('office:members.add'), follow=True)
+ response = logged_in_client.get(reverse("office:members.add"), follow=True)
assert response.status_code == 200
response = logged_in_client.post(
- reverse('office:members.add'),
+ reverse("office:members.add"),
{
- 'member__number': '23',
- 'member__name': 'Torsten Est',
- 'membership__start': str(now().date()),
- 'membership__interval': '1',
- 'membership__amount': '10',
+ "member__number": "23",
+ "member__name": "Torsten Est",
+ "membership__start": str(now().date()),
+ "membership__interval": "1",
+ "membership__amount": "10",
},
follow=True,
)
assert response.status_code == 200
assert b'"alert alert-success"' in response.content
- assert response.resolver_match.url_name == 'members.data'
+ assert response.resolver_match.url_name == "members.data"
@pytest.mark.django_db
def test_transaction_detail(full_testdata, logged_in_client, user):
# Balanced transaction
response = logged_in_client.get(
- reverse('office:finance.transactions.detail', kwargs={'pk': 1}), follow=True
+ reverse("office:finance.transactions.detail", kwargs={"pk": 1}), follow=True
)
assert response.status_code == 200
# Unbalanced transaction, credit
response = logged_in_client.get(
- reverse('office:finance.transactions.detail', kwargs={'pk': 167}), follow=True
+ reverse("office:finance.transactions.detail", kwargs={"pk": 167}), follow=True
)
assert response.status_code == 200
# Unbalanced transaction, debit
response = logged_in_client.get(
- reverse('office:finance.transactions.detail', kwargs={'pk': 163}), follow=True
+ reverse("office:finance.transactions.detail", kwargs={"pk": 163}), follow=True
)
assert response.status_code == 200
@pytest.mark.django_db
def test_account_detail(full_testdata, logged_in_client, user):
response = logged_in_client.get(
- reverse('office:finance.accounts.detail', kwargs={'pk': '3'}), follow=True
+ reverse("office:finance.accounts.detail", kwargs={"pk": "3"}), follow=True
)
assert response.status_code == 200
@pytest.mark.django_db
def test_office_dashboard_view_redirects_without_settings(logged_in_client, member):
- response = logged_in_client.get(reverse('office:dashboard'))
+ response = logged_in_client.get(reverse("office:dashboard"))
assert response.status_code == 302
@pytest.mark.django_db
def test_office_dashboard_view(logged_in_client, member, configuration):
- response = logged_in_client.get(reverse('office:dashboard'))
+ response = logged_in_client.get(reverse("office:dashboard"))
assert response.status_code == 200
from byro.members.models import Member
-pytestmark = pytest.mark.usefixtures('configuration')
+pytestmark = pytest.mark.usefixtures("configuration")
@pytest.mark.django_db
def test_members_list(member, membership, inactive_member, logged_in_client):
- response = logged_in_client.get(reverse('office:members.list'))
+ response = logged_in_client.get(reverse("office:members.list"))
content = response.content.decode()
assert response.status_code == 200, content
assert member.name in content
@pytest.mark.django_db
def test_filtered_members_list(member, membership, inactive_member, logged_in_client):
response = logged_in_client.get(
- reverse('office:members.list') + '?filter=all&q=' + member.name[:4]
+ reverse("office:members.list") + "?filter=all&q=" + member.name[:4]
)
content = response.content.decode()
assert response.status_code == 200, content
@pytest.mark.django_db
def test_inactive_members_list(member, membership, inactive_member, logged_in_client):
- response = logged_in_client.get(reverse('office:members.list') + '?filter=inactive')
+ response = logged_in_client.get(reverse("office:members.list") + "?filter=inactive")
content = response.content.decode()
assert response.status_code == 200, content
assert member.name not in content
@pytest.mark.django_db
def test_all_members_list(member, membership, inactive_member, logged_in_client):
- response = logged_in_client.get(reverse('office:members.list') + '?filter=all')
+ response = logged_in_client.get(reverse("office:members.list") + "?filter=all")
content = response.content.decode()
assert response.status_code == 200, content
assert member.name in content
@pytest.mark.django_db
def test_member_view(member, membership, logged_in_client):
response = logged_in_client.get(
- reverse('office:members.dashboard', kwargs={'pk': member.pk})
+ reverse("office:members.dashboard", kwargs={"pk": member.pk})
)
content = response.content.decode()
assert response.status_code == 200, content
def test_member_view_different_public_address(
member, membership, logged_in_client, configuration
):
- configuration.public_base_url = 'https://complicated.long.url.example.org'
+ configuration.public_base_url = "https://complicated.long.url.example.org"
configuration.save()
response = logged_in_client.get(
- reverse('office:members.dashboard', kwargs={'pk': member.pk})
+ reverse("office:members.dashboard", kwargs={"pk": member.pk})
)
content = response.content.decode()
assert response.status_code == 200, content
@pytest.mark.django_db
def test_members_export_list_csv(member, membership, inactive_member, logged_in_client):
response = logged_in_client.post(
- reverse('office:members.list.export'),
+ reverse("office:members.list.export"),
{
- 'member_filter': 'all',
- 'export_format': 'csv',
- 'field_list': ['_internal_id', 'member__name'],
+ "member_filter": "all",
+ "export_format": "csv",
+ "field_list": ["_internal_id", "member__name"],
},
)
content = b"".join(response.streaming_content).decode()
def test_members_adjust_account_initial(member, logged_in_client):
assert member.balance == 0
response = logged_in_client.post(
- reverse('office:members.operations', kwargs={'pk': member.pk}),
+ reverse("office:members.operations", kwargs={"pk": member.pk}),
{
- 'member_account_adjustment-date': str(now().date()),
- 'member_account_adjustment-adjustment_reason': 'initial',
- 'member_account_adjustment-adjustment_type': 'absolute',
- 'member_account_adjustment-amount': '23',
- 'submit_member_account_adjustment_adjust': 'adjust',
+ "member_account_adjustment-date": str(now().date()),
+ "member_account_adjustment-adjustment_reason": "initial",
+ "member_account_adjustment-adjustment_type": "absolute",
+ "member_account_adjustment-amount": "23",
+ "submit_member_account_adjustment_adjust": "adjust",
},
)
content = response.content.decode()
def test_members_adjust_account_waiver(member, logged_in_client):
assert member.balance == 0
response = logged_in_client.post(
- reverse('office:members.operations', kwargs={'pk': member.pk}),
+ reverse("office:members.operations", kwargs={"pk": member.pk}),
{
- 'member_account_adjustment-date': str(now().date()),
- 'member_account_adjustment-adjustment_reason': 'waiver',
- 'member_account_adjustment-adjustment_type': 'relative',
- 'member_account_adjustment-amount': '-2',
- 'submit_member_account_adjustment_adjust': 'adjust',
+ "member_account_adjustment-date": str(now().date()),
+ "member_account_adjustment-adjustment_reason": "waiver",
+ "member_account_adjustment-adjustment_type": "relative",
+ "member_account_adjustment-amount": "-2",
+ "submit_member_account_adjustment_adjust": "adjust",
},
)
content = response.content.decode()
def test_members_end_membership(member, membership, logged_in_client):
assert member.is_active
response = logged_in_client.post(
- reverse('office:members.operations', kwargs={'pk': member.pk}),
+ reverse("office:members.operations", kwargs={"pk": member.pk}),
{
- 'ms_{}_leave-end'.format(membership.pk): (
+ "ms_{}_leave-end".format(membership.pk): (
now() + relativedelta(days=-1)
).date(),
- 'submit_ms_{}_leave_end'.format(membership.pk): 'end',
+ "submit_ms_{}_leave_end".format(membership.pk): "end",
},
)
content = response.content.decode()
@pytest.mark.django_db
def test_member_download_and_edit(member, membership, logged_in_client):
response = logged_in_client.post(
- reverse('office:members.list.export'),
+ reverse("office:members.list.export"),
{
- "field_list": [
- '_internal_id',
- 'member__name',
- 'MemberSepa__iban',
- ],
- 'member_filter': 'all',
- 'export_format': 'csv',
-
- }
+ "field_list": ["_internal_id", "member__name", "MemberSepa__iban"],
+ "member_filter": "all",
+ "export_format": "csv",
+ },
)
- assert response['Content-Type'].startswith("text/csv")
+ assert response["Content-Type"].startswith("text/csv")
response_body = b"".join(response.streaming_content)
- assert member.name.encode('utf-8') in response_body
+ assert member.name.encode("utf-8") in response_body
new_body = response_body.replace(
- ",{},".format(member.name).encode('utf-8'),
- ",{},{}".format("Fnord!", "DE11520513735120710131").encode('utf-8'),
+ ",{},".format(member.name).encode("utf-8"),
+ ",{},{}".format("Fnord!", "DE11520513735120710131").encode("utf-8"),
)
new_response = logged_in_client.post(
- reverse('office:members.list.import'),
+ reverse("office:members.list.import"),
{
- 'importer': 'byro.office.members.import.default_csv',
- 'upload_file': SimpleUploadedFile('members.csv', new_body, content_type=response['Content-Type'])
- }
+ "importer": "byro.office.members.import.default_csv",
+ "upload_file": SimpleUploadedFile(
+ "members.csv", new_body, content_type=response["Content-Type"]
+ ),
+ },
)
assert new_response.status_code == 302
new_member = Member.objects.filter(pk=member.pk).first()
- assert new_member.name == 'Fnord!'
- assert new_member.profile_sepa.iban == 'DE11520513735120710131'
+ assert new_member.name == "Fnord!"
+ assert new_member.profile_sepa.iban == "DE11520513735120710131"
def test_memberpage_access_dashboard(member, membership, client, configuration):
response = client.get(
reverse(
- 'public:memberpage:member.dashboard',
- kwargs={'secret_token': member.profile_memberpage.secret_token},
+ "public:memberpage:member.dashboard",
+ kwargs={"secret_token": member.profile_memberpage.secret_token},
)
)
assert response.status_code == 200
assert member.name in response.content.decode()
- assert 'Member page' in response.content.decode()
- assert 'Settings' not in response.content.decode()
+ assert "Member page" in response.content.decode()
+ assert "Settings" not in response.content.decode()
@pytest.mark.django_db
def test_memberpage_access_list(member, membership, client, configuration):
response = client.get(
reverse(
- 'public:memberpage:member.list',
- kwargs={'secret_token': member.profile_memberpage.secret_token},
+ "public:memberpage:member.list",
+ kwargs={"secret_token": member.profile_memberpage.secret_token},
)
)
assert response.status_code == 200
- assert 'Member page' in response.content.decode()
- assert 'Settings' not in response.content.decode()
+ assert "Member page" in response.content.decode()
+ assert "Settings" not in response.content.decode()
-@pytest.mark.parametrize('page', ('list', 'dashboard'))
+@pytest.mark.parametrize("page", ("list", "dashboard"))
@pytest.mark.django_db
def test_memberpage_access_dashboard_wrong_token(
member, membership, client, configuration, page
):
response = client.get(
reverse(
- 'public:memberpage:member.{}'.format(page),
- kwargs={'secret_token': member.profile_memberpage.secret_token + 'lol'},
+ "public:memberpage:member.{}".format(page),
+ kwargs={"secret_token": member.profile_memberpage.secret_token + "lol"},
)
)
assert response.status_code == 404
class connected_signal:
""" connect a signal and make sure it is disconnected after use, so it doesn't leak into other tests. """
- def __init__(self, signal, receiver, uid='test-plugin'):
+ def __init__(self, signal, receiver, uid="test-plugin"):
self.signal = signal
self.receiver = receiver
self.dispatch_uid = uid
account=SpecialAccounts.fees_receivable,
amount=10,
member=member,
- user_or_context='test',
+ user_or_context="test",
)
return True
return False
assert partial_transaction.is_balanced
assert (
- SpecialAccounts.fees_receivable.balances(end=None)['credit']
+ SpecialAccounts.fees_receivable.balances(end=None)["credit"]
== partial_transaction.bookings.first().amount
)
with connected_signal(process_transaction, derive_test_transaction):
partial_transaction.process_transaction()
- assert 'No plugin tried to augment' in str(excinfo)
+ assert "No plugin tried to augment" in str(excinfo)
assert not partial_transaction.is_balanced
- assert SpecialAccounts.fees_receivable.balances(end=None)['credit'] == 0
+ assert SpecialAccounts.fees_receivable.balances(end=None)["credit"] == 0
@pytest.mark.django_db
call_log = Counter()
def derive_test_transaction_donation(sender, signal):
- call_log['d'] += 1
+ call_log["d"] += 1
if sender.is_read_only:
return False
if not sender.is_balanced:
account=SpecialAccounts.donations,
amount=5,
member=member,
- user_or_context='test',
+ user_or_context="test",
)
return True
return False
def derive_test_transaction_fee(sender, signal):
- call_log['f'] += 1
+ call_log["f"] += 1
if sender.is_read_only:
return False
if not sender.is_balanced:
account=SpecialAccounts.fees_receivable,
amount=5,
member=member,
- user_or_context='test',
+ user_or_context="test",
)
return True
return False
with connected_signal(
- process_transaction, derive_test_transaction_donation, 'test-donation'
+ process_transaction, derive_test_transaction_donation, "test-donation"
):
with connected_signal(
- process_transaction, derive_test_transaction_fee, 'test-fee'
+ process_transaction, derive_test_transaction_fee, "test-fee"
):
partial_transaction.process_transaction()
- assert dict(call_log) == {'d': 2, 'f': 2}
+ assert dict(call_log) == {"d": 2, "f": 2}
- assert SpecialAccounts.fees_receivable.balances(end=None)['credit'] == 5
- assert SpecialAccounts.donations.balances(end=None)['credit'] == 5
+ assert SpecialAccounts.fees_receivable.balances(end=None)["credit"] == 5
+ assert SpecialAccounts.donations.balances(end=None)["credit"] == 5
def direct_match(sender, **kwargs):
- return ['office:dashboard']
+ return ["office:dashboard"]
def lambda_match(sender, **kwargs):
return [
- lambda request, resolver_match: resolver_match.view_name == 'office:dashboard'
+ lambda request, resolver_match: resolver_match.view_name == "office:dashboard"
]
-@pytest.mark.parametrize('variant', (direct_match, lambda_match))
+@pytest.mark.parametrize("variant", (direct_match, lambda_match))
@pytest.mark.django_db
def test_unauthenticated_urls(client, variant):
with connected_signal(unauthenticated_urls, variant):
- response = client.get(reverse('office:dashboard'))
+ response = client.get(reverse("office:dashboard"))
assert response.status_code == 200
- assert response.resolver_match.url_name != 'login'
+ assert response.resolver_match.url_name != "login"
- response = client.get(reverse('office:settings.base'), follow=True)
+ response = client.get(reverse("office:settings.base"), follow=True)
assert response.status_code == 200
- assert response.resolver_match.url_name == 'login'
+ assert response.resolver_match.url_name == "login"
for db_name in self._databases_names(include_mirrors=False):
try:
call_command(
- 'loaddata',
+ "loaddata",
*fixtures,
- **{'verbosity': 0, 'commit': False, 'database': db_name}
+ **{"verbosity": 0, "commit": False, "database": db_name}
)
except Exception:
self._rollback_atomics(self.cls_atomics)
class TestWithShackdataBase(TestMigrations):
- app = 'bookkeeping'
- migrate_fixtures = ['tests/fixtures/test_shackspace_transactions.json']
- migrate_from = '0012_auto_20180617_1926'
+ app = "bookkeeping"
+ migrate_fixtures = ["tests/fixtures/test_shackspace_transactions.json"]
+ migrate_from = "0012_auto_20180617_1926"
@pytest.mark.xfail
@pytest.mark.django_db
class TestBookkeepingMigrationsFirst(TestWithShackdataBase):
- migrate_to = '0013_new_data_model'
+ migrate_to = "0013_new_data_model"
def setUpBeforeMigration(self, apps):
- RealTransaction = apps.get_model('bookkeeping', 'RealTransaction')
- VirtualTransaction = apps.get_model('bookkeeping', 'VirtualTransaction')
+ RealTransaction = apps.get_model("bookkeeping", "RealTransaction")
+ VirtualTransaction = apps.get_model("bookkeeping", "VirtualTransaction")
# For test comparison
self.real_transaction_count = RealTransaction.objects.count()
self.virtual_transaction_member_fees_count = VirtualTransaction.objects.filter(
Q(
source_account__isnull=True,
- destination_account__account_category='member_fees',
+ destination_account__account_category="member_fees",
real_transaction__isnull=True,
)
| Q(
destination_account__isnull=True,
- source_account__account_category='member_fees',
+ source_account__account_category="member_fees",
real_transaction__isnull=True,
)
).count()
def test_accounts_migrated(self):
from byro.bookkeeping.models import Account
- assert Account.objects.filter(tags__name='bank').count() == 1
- assert Account.objects.filter(tags__name='fees').count() == 1
- assert Account.objects.filter(tags__name='fees_receivable').count() == 1
+ assert Account.objects.filter(tags__name="bank").count() == 1
+ assert Account.objects.filter(tags__name="fees").count() == 1
+ assert Account.objects.filter(tags__name="fees_receivable").count() == 1
def test_transactions_migrated(self):
from byro.bookkeeping.models import Transaction, Booking
@pytest.mark.xfail
@pytest.mark.django_db
class TestBookkeepingMigrationsFinal(TestWithShackdataBase):
- migrate_to = '0014_auto_20180707_1410'
+ migrate_to = "0014_auto_20180707_1410"
def test_accounts_migrated_fully(self):
from byro.bookkeeping.models import Account, AccountCategory
@pytest.mark.parametrize(
- "app", [app for app in settings.INSTALLED_APPS if app.startswith('byro.')]
+ "app", [app for app in settings.INSTALLED_APPS if app.startswith("byro.")]
)
def test_documentation_includes_signals(app):
- app = 'byro.' + app.split(".")[1]
+ app = "byro." + app.split(".")[1]
with suppress(ImportError):
module = importlib.import_module(app + ".signals")
for key in dir(module):
@pytest.mark.django_db
def test_account_model_str(bank_account):
- assert str(bank_account) == 'asset account #{}'.format(bank_account.id)
- bank_account.name = 'foo'
- assert str(bank_account) == 'foo'
+ assert str(bank_account) == "asset account #{}".format(bank_account.id)
+ bank_account.name = "foo"
+ assert str(bank_account) == "foo"
@pytest.mark.django_db
def test_account_methods(bank_account):
assert not bank_account.transactions
- assert bank_account.balances(start=now())['credit'] == 0
- assert bank_account.balances(start=now())['debit'] == 0
+ assert bank_account.balances(start=now())["credit"] == 0
+ assert bank_account.balances(start=now())["debit"] == 0
@pytest.mark.django_db
def test_account_tags(bank_account):
assert bank_account.tags.all().count() == 0
- tag, _ignore = AccountTag.objects.get_or_create(name='TEST')
+ tag, _ignore = AccountTag.objects.get_or_create(name="TEST")
bank_account.tags.add(tag)
assert not bank_account.bookings
assert not bank_account.unbalanced_transactions
assert str(tag) == tag.name
assert bank_account.tags.all().count() == 1
assert (
- bank_account in AccountTag.objects.get_or_create(name='TEST')[0].accounts.all()
+ bank_account in AccountTag.objects.get_or_create(name="TEST")[0].accounts.all()
)
assert fees_receivable != fees
bank = SpecialAccounts.bank
- assert Account.objects.filter(tags__name='bank').count() == 1
- assert bank in Account.objects.filter(tags__name='bank').all()
+ assert Account.objects.filter(tags__name="bank").count() == 1
+ assert bank in Account.objects.filter(tags__name="bank").all()
@pytest.mark.django_db
def test_transaction_balances(receivable_account, income_account):
t = Transaction.objects.create(
- memo='Member fee is due', value_datetime=now(), user_or_context='test'
+ memo="Member fee is due", value_datetime=now(), user_or_context="test"
)
for booking in [
dict(amount=10, debit_account=receivable_account),
@pytest.mark.django_db
def test_transaction_balances_decimal(bank_account, receivable_account):
- t = Transaction.objects.create(value_datetime=now(), user_or_context='test')
+ t = Transaction.objects.create(value_datetime=now(), user_or_context="test")
for booking in [
dict(amount=9.5, debit_account=bank_account),
dict(amount=9.95, credit_account=receivable_account),
@pytest.mark.django_db
def test_transaction_methods(bank_account):
- t = Transaction.objects.create(value_datetime=now(), user_or_context='test')
- t.debit(amount=10, account=bank_account, user_or_context='test')
+ t = Transaction.objects.create(value_datetime=now(), user_or_context="test")
+ t.debit(amount=10, account=bank_account, user_or_context="test")
t.save()
- assert t.balances['credit'] == 0
- assert t.balances['debit'] == 10
+ assert t.balances["credit"] == 0
+ assert t.balances["debit"] == 10
assert not t.is_balanced
@pytest.mark.django_db
def test_transaction_balance_accesses(bank_account):
- t = Transaction.objects.create(value_datetime=now(), user_or_context='test')
- t.debit(amount=10, account=bank_account, user_or_context='test')
+ t = Transaction.objects.create(value_datetime=now(), user_or_context="test")
+ t.debit(amount=10, account=bank_account, user_or_context="test")
t.save()
t1 = Transaction.objects.with_balances().get(pk=t.pk)
assert t1.balances_credit == 0
assert t1.balances_debit == 10
assert not t1.is_balanced
- assert t1.balances['credit'] == 0
- assert t1.balances['debit'] == 10
+ assert t1.balances["credit"] == 0
+ assert t1.balances["debit"] == 10
@pytest.mark.django_db
def test_account_balances(bank_account, receivable_account, income_account):
t1 = Transaction.objects.create(
- memo='Member fee is due', value_datetime=now(), user_or_context='test'
+ memo="Member fee is due", value_datetime=now(), user_or_context="test"
)
- t1.debit(amount=10, account=receivable_account, user_or_context='test')
- t1.credit(amount=10, account=income_account, user_or_context='test')
+ t1.debit(amount=10, account=receivable_account, user_or_context="test")
+ t1.credit(amount=10, account=income_account, user_or_context="test")
t1.save()
- assert income_account.balances(end=None)['net'] == 10
- assert receivable_account.balances(end=None)['net'] == 10
+ assert income_account.balances(end=None)["net"] == 10
+ assert receivable_account.balances(end=None)["net"] == 10
t2 = Transaction.objects.create(
- memo='Member fee payment', value_datetime=now(), user_or_context='test'
+ memo="Member fee payment", value_datetime=now(), user_or_context="test"
)
- t2.debit(amount=10, account=bank_account, user_or_context='test')
- t2.credit(amount=10, account=receivable_account, user_or_context='test')
+ t2.debit(amount=10, account=bank_account, user_or_context="test")
+ t2.credit(amount=10, account=receivable_account, user_or_context="test")
t2.save()
- assert income_account.balances(end=None)['net'] == 10
- assert receivable_account.balances(end=None)['net'] == 0
- assert bank_account.balances(end=None)['net'] == 10
+ assert income_account.balances(end=None)["net"] == 10
+ assert receivable_account.balances(end=None)["net"] == 0
+ assert bank_account.balances(end=None)["net"] == 10
@pytest.mark.django_db
@pytest.mark.parametrize(
- 'GET,key,value,expected',
+ "GET,key,value,expected",
(
- ('foo=bar', 'foo', 'baz', ['foo=baz']),
- ('foo=bar', 'fork', 'baz', ['foo=bar', 'fork=baz']),
+ ("foo=bar", "foo", "baz", ["foo=baz"]),
+ ("foo=bar", "fork", "baz", ["foo=bar", "fork=baz"]),
),
)
def test_templatetag_url_replace(GET, key, value, expected):
LogEntry(
content_object=mail_template,
user=user,
- data={'source': 'value'},
- action_type='action.type',
+ data={"source": "value"},
+ action_type="action.type",
)
) == 'action.type (<a href="/mails/templates/{}">Test Mail</a>)'.format(
mail_template.pk
LogEntry(
content_object=user,
user=user,
- data={'source': 'value'},
- action_type='action.type',
+ data={"source": "value"},
+ action_type="action.type",
)
) == 'action.type (<a href="/settings/users/{u.id}/">{u.username}</a>)'.format(
u=user
format_log_source(
LogEntry(
content_object=mail_template,
- data={'source': 'value'},
- action_type='action.type',
+ data={"source": "value"},
+ action_type="action.type",
)
)
- == 'value'
+ == "value"
)
assert (
format_log_source(
LogEntry(
content_object=mail_template,
user=user,
- data={'source': 'value'},
- action_type='action.type',
+ data={"source": "value"},
+ action_type="action.type",
)
)
== 'value (via <span class="fa fa-user"></span> regular_user)'
@pytest.fixture
def document():
- f = SimpleUploadedFile('testresource.txt', b'a resource')
- d = Document.objects.create(document=f, title='Test document')
+ f = SimpleUploadedFile("testresource.txt", b"a resource")
+ d = Document.objects.create(document=f, title="Test document")
yield d
d.delete()
@pytest.mark.django_db
-@pytest.mark.parametrize('immediately', (True, False))
+@pytest.mark.parametrize("immediately", (True, False))
def test_document_send(document, member, immediately):
count = EMail.objects.count()
document.member = member
document.save()
- assert 'Document' in document.get_display()
+ assert "Document" in document.get_display()
document.send(immediately=immediately)
assert EMail.objects.count() == count + 1
mail = EMail.objects.last()
a = LogEntry.objects.create(
content_object=member,
action_type="test.test_log_create",
- data={'source': 'test'},
+ data={"source": "test"},
)
assert a
LogEntry.objects.create(
content_object=member,
action_type="test.test_log_filter_content_object",
- data={'source': 'test'},
+ data={"source": "test"},
)
assert (
LogEntry.objects.create(
content_object=member,
action_type="test.test_log_immutable",
- data={'source': 'test'},
+ data={"source": "test"},
)
a = LogEntry.objects.get(action_type="test.test_log_immutable")
LogEntry.objects.create(
content_object=member,
action_type="test.test_log_immutable_2",
- data={'source': 'test'},
+ data={"source": "test"},
)
a = LogEntry.objects.get(action_type="test.test_log_immutable_2")
with pytest.raises(Exception):
LogEntry.objects.create(content_object=member, action_type="test.test_log_user")
- assert LogEntry.objects.filter(user=user).first().data['source'] == str(user)
+ assert LogEntry.objects.filter(user=user).first().data["source"] == str(user)
@pytest.mark.django_db
-@pytest.mark.parametrize('skip_queue', (True, False))
+@pytest.mark.parametrize("skip_queue", (True, False))
def test_template_to_mail(mail_template, skip_queue):
count = EMail.objects.count()
- mail_template.to_mail('test@localhost', skip_queue=skip_queue)
+ mail_template.to_mail("test@localhost", skip_queue=skip_queue)
assert EMail.objects.count() == count + 1
obj = EMail.objects.last()
assert obj.subject == mail_template.subject
assert obj.text == mail_template.text
- assert obj.to == 'test@localhost'
+ assert obj.to == "test@localhost"
assert (obj.sent is None) is not skip_queue
assert str(mail_template) == mail_template.subject
@pytest.mark.django_db
def test_template_to_mail_fail(mail_template):
- mail_template.subject = '{incorrect_key}'
+ mail_template.subject = "{incorrect_key}"
count = EMail.objects.count()
with pytest.raises(SendMailException):
- mail_template.to_mail('test@localhost')
+ mail_template.to_mail("test@localhost")
assert EMail.objects.count() == count
@pytest.fixture
@pytest.mark.django_db
def new_member():
- m = Member.objects.create(number='007')
+ m = Member.objects.create(number="007")
yield m
m.delete()
assert member.balance == -900.0
assert member.statute_barred_debt() == 160.0
- t = Transaction.objects.create(value_datetime=test_date, user_or_context='test')
- t.debit(account=SpecialAccounts.bank, amount=12, user_or_context='test')
+ t = Transaction.objects.create(value_datetime=test_date, user_or_context="test")
+ t.debit(account=SpecialAccounts.bank, amount=12, user_or_context="test")
t.credit(
account=SpecialAccounts.fees_receivable,
amount=12,
member=member,
- user_or_context='test',
+ user_or_context="test",
)
t.save()
t = Transaction.objects.create(
value_datetime=test_date.replace(year=2007, month=7, day=1),
- user_or_context='test',
+ user_or_context="test",
)
- t.debit(account=SpecialAccounts.bank, amount=13, user_or_context='test')
+ t.debit(account=SpecialAccounts.bank, amount=13, user_or_context="test")
t.credit(
account=SpecialAccounts.fees_receivable,
amount=13,
member=member,
- user_or_context='test',
+ user_or_context="test",
)
t.save()
t = Transaction.objects.create(
value_datetime=test_date.replace(year=2007, month=12, day=31),
- user_or_context='test',
+ user_or_context="test",
)
- t.debit(account=SpecialAccounts.bank, amount=136, user_or_context='test')
+ t.debit(account=SpecialAccounts.bank, amount=136, user_or_context="test")
t.credit(
account=SpecialAccounts.fees_receivable,
amount=136,
member=member,
- user_or_context='test',
+ user_or_context="test",
)
t.save()
a = A()
- assert Field._follow_path(a, 'd') == (a, 'd')
- assert Field._follow_path(a, 'B.c') == (a.B, 'c')
+ assert Field._follow_path(a, "d") == (a, "d")
+ assert Field._follow_path(a, "B.c") == (a.B, "c")
- o, p = Field._follow_path(a, 'e().g')
+ o, p = Field._follow_path(a, "e().g")
assert getattr(o, p) == 4
# Negative tests
- assert Field._follow_path(a, 'A.a') == (None, 'a')
- assert Field._follow_path(a, 'A().a') == (None, 'a')
- assert Field._follow_path(a, 'e().A.a') == (None, 'a')
+ assert Field._follow_path(a, "A.a") == (None, "a")
+ assert Field._follow_path(a, "A().a") == (None, "a")
+ assert Field._follow_path(a, "e().A.a") == (None, "a")
@pytest.mark.django_db
def test_member_field_reading(member, membership, inactive_member):
f = Member.get_fields()
- assert '_internal_id' in f
- assert f['_internal_id'].computed
- assert f['_internal_id'].read_only
- assert f['_internal_id'].getter(member) == member.pk
+ assert "_internal_id" in f
+ assert f["_internal_id"].computed
+ assert f["_internal_id"].read_only
+ assert f["_internal_id"].getter(member) == member.pk
- assert f['member__name'].getter(member) == member.name
+ assert f["member__name"].getter(member) == member.name
- assert f['_internal_active'].getter(member) is True
- assert f['_internal_active'].getter(inactive_member) is False
+ assert f["_internal_active"].getter(member) is True
+ assert f["_internal_active"].getter(inactive_member) is False
@pytest.mark.django_db
def test_member_field_writing(member):
f = Member.get_fields()
- assert member.name != 'Fnord'
- f['member__name'].setter(member, 'Fnord')
- assert member.name == 'Fnord'
+ assert member.name != "Fnord"
+ f["member__name"].setter(member, "Fnord")
+ assert member.name == "Fnord"
@pytest.mark.django_db
def test_member_field_writing_sepa_iban(member):
f = Member.get_fields()
- f['MemberSepa__iban'].setter(member, 'DE491234567890')
- assert member.profile_sepa.iban == 'DE491234567890'
+ f["MemberSepa__iban"].setter(member, "DE491234567890")
+ assert member.profile_sepa.iban == "DE491234567890"
@pytest.mark.django_db
def test_new_member_info_sepa(member_with_sepa_profile):
assert str(new_member_mail_info_sepa(member_with_sepa_profile, None)).startswith(
- 'Your SEPA'
+ "Your SEPA"
)
@pytest.mark.django_db
def test_new_member_info_sepa_without_mandate(member):
- assert new_member_mail_info_sepa(member, None) == ''
+ assert new_member_mail_info_sepa(member, None) == ""
@pytest.mark.django_db
def test_new_member_office_mail_info_sepa(member_with_sepa_profile, membership):
assert str(
new_member_office_mail_info_sepa(member_with_sepa_profile, None)
- ).startswith('The new member')
+ ).startswith("The new member")
@pytest.mark.django_db
def test_leave_member_office_info_sepa(member_with_sepa_profile, membership):
assert str(leave_member_office_mail_info_sepa(membership, None)).startswith(
- 'Please terminate'
+ "Please terminate"
)
@pytest.mark.django_db
def test_leave_member_office_info_sepa_without_mandate(membership):
- assert leave_member_office_mail_info_sepa(membership, None) == ''
+ assert leave_member_office_mail_info_sepa(membership, None) == ""