All: Apply Black coding rules

This commit is contained in:
Antoine Bartuccio 2018-10-04 21:29:19 +02:00
parent 0581c667de
commit cb58b00b6e
Signed by: klmp200
GPG Key ID: E7245548C53F904B
204 changed files with 13173 additions and 6376 deletions

View File

@ -8,11 +8,12 @@ setup:
- apt-get update - apt-get update
- apt-get install -y gettext - apt-get install -y gettext
- pip install -r requirements.txt - pip install -r requirements.txt
- pip install coverage
- pip install black
test: test:
stage: test stage: test
script: script:
- pip install coverage
- ./manage.py compilemessages - ./manage.py compilemessages
- coverage run ./manage.py test - coverage run ./manage.py test
- coverage html - coverage html
@ -24,5 +25,4 @@ test:
black: black:
stage: test stage: test
script: script:
- pip install black
- black --check . - black --check .

View File

@ -1,5 +1,6 @@
[![pipeline status](https://ae-dev.utbm.fr/ae/Sith/badges/master/pipeline.svg)](https://ae-dev.utbm.fr/ae/Sith/commits/master) [![pipeline status](https://ae-dev.utbm.fr/ae/Sith/badges/master/pipeline.svg)](https://ae-dev.utbm.fr/ae/Sith/commits/master)
[![coverage report](https://ae-dev.utbm.fr/ae/Sith/badges/master/coverage.svg)](https://ae-dev.utbm.fr/ae/Sith/commits/master) [![coverage report](https://ae-dev.utbm.fr/ae/Sith/badges/master/coverage.svg)](https://ae-dev.utbm.fr/ae/Sith/commits/master)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)
## Sith AE ## Sith AE

View File

@ -21,4 +21,3 @@
# Place - Suite 330, Boston, MA 02111-1307, USA. # Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #

View File

@ -8,103 +8,271 @@ import accounting.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = []
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='AccountingType', name="AccountingType",
fields=[ fields=[
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), (
('code', models.CharField(max_length=16, verbose_name='code', validators=[django.core.validators.RegexValidator('^[0-9]*$', 'An accounting type code contains only numbers')])), "id",
('label', models.CharField(max_length=128, verbose_name='label')), models.AutoField(
('movement_type', models.CharField(choices=[('CREDIT', 'Credit'), ('DEBIT', 'Debit'), ('NEUTRAL', 'Neutral')], max_length=12, verbose_name='movement type')), primary_key=True,
serialize=False,
verbose_name="ID",
auto_created=True,
),
),
(
"code",
models.CharField(
max_length=16,
verbose_name="code",
validators=[
django.core.validators.RegexValidator(
"^[0-9]*$",
"An accounting type code contains only numbers",
)
],
),
),
("label", models.CharField(max_length=128, verbose_name="label")),
(
"movement_type",
models.CharField(
choices=[
("CREDIT", "Credit"),
("DEBIT", "Debit"),
("NEUTRAL", "Neutral"),
],
max_length=12,
verbose_name="movement type",
),
),
], ],
options={ options={
'verbose_name': 'accounting type', "verbose_name": "accounting type",
'ordering': ['movement_type', 'code'], "ordering": ["movement_type", "code"],
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='BankAccount', name="BankAccount",
fields=[ fields=[
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), (
('name', models.CharField(max_length=30, verbose_name='name')), "id",
('iban', models.CharField(max_length=255, blank=True, verbose_name='iban')), models.AutoField(
('number', models.CharField(max_length=255, blank=True, verbose_name='account number')), primary_key=True,
serialize=False,
verbose_name="ID",
auto_created=True,
),
),
("name", models.CharField(max_length=30, verbose_name="name")),
(
"iban",
models.CharField(max_length=255, blank=True, verbose_name="iban"),
),
(
"number",
models.CharField(
max_length=255, blank=True, verbose_name="account number"
),
),
],
options={"verbose_name": "Bank account", "ordering": ["club", "name"]},
),
migrations.CreateModel(
name="ClubAccount",
fields=[
(
"id",
models.AutoField(
primary_key=True,
serialize=False,
verbose_name="ID",
auto_created=True,
),
),
("name", models.CharField(max_length=30, verbose_name="name")),
], ],
options={ options={
'verbose_name': 'Bank account', "verbose_name": "Club account",
'ordering': ['club', 'name'], "ordering": ["bank_account", "name"],
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='ClubAccount', name="Company",
fields=[ fields=[
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), (
('name', models.CharField(max_length=30, verbose_name='name')), "id",
models.AutoField(
primary_key=True,
serialize=False,
verbose_name="ID",
auto_created=True,
),
),
("name", models.CharField(max_length=60, verbose_name="name")),
], ],
options={ options={"verbose_name": "company"},
'verbose_name': 'Club account',
'ordering': ['bank_account', 'name'],
},
), ),
migrations.CreateModel( migrations.CreateModel(
name='Company', name="GeneralJournal",
fields=[ fields=[
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), (
('name', models.CharField(max_length=60, verbose_name='name')), "id",
models.AutoField(
primary_key=True,
serialize=False,
verbose_name="ID",
auto_created=True,
),
),
("start_date", models.DateField(verbose_name="start date")),
(
"end_date",
models.DateField(
null=True, verbose_name="end date", default=None, blank=True
),
),
("name", models.CharField(max_length=40, verbose_name="name")),
(
"closed",
models.BooleanField(verbose_name="is closed", default=False),
),
(
"amount",
accounting.models.CurrencyField(
decimal_places=2,
default=0,
verbose_name="amount",
max_digits=12,
),
),
(
"effective_amount",
accounting.models.CurrencyField(
decimal_places=2,
default=0,
verbose_name="effective_amount",
max_digits=12,
),
),
], ],
options={ options={"verbose_name": "General journal", "ordering": ["-start_date"]},
'verbose_name': 'company',
},
), ),
migrations.CreateModel( migrations.CreateModel(
name='GeneralJournal', name="Operation",
fields=[ fields=[
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), (
('start_date', models.DateField(verbose_name='start date')), "id",
('end_date', models.DateField(null=True, verbose_name='end date', default=None, blank=True)), models.AutoField(
('name', models.CharField(max_length=40, verbose_name='name')), primary_key=True,
('closed', models.BooleanField(verbose_name='is closed', default=False)), serialize=False,
('amount', accounting.models.CurrencyField(decimal_places=2, default=0, verbose_name='amount', max_digits=12)), verbose_name="ID",
('effective_amount', accounting.models.CurrencyField(decimal_places=2, default=0, verbose_name='effective_amount', max_digits=12)), auto_created=True,
),
),
("number", models.IntegerField(verbose_name="number")),
(
"amount",
accounting.models.CurrencyField(
decimal_places=2, max_digits=12, verbose_name="amount"
),
),
("date", models.DateField(verbose_name="date")),
("remark", models.CharField(max_length=128, verbose_name="comment")),
(
"mode",
models.CharField(
choices=[
("CHECK", "Check"),
("CASH", "Cash"),
("TRANSFERT", "Transfert"),
("CARD", "Credit card"),
],
max_length=255,
verbose_name="payment method",
),
),
(
"cheque_number",
models.CharField(
max_length=32,
null=True,
verbose_name="cheque number",
default="",
blank=True,
),
),
("done", models.BooleanField(verbose_name="is done", default=False)),
(
"target_type",
models.CharField(
choices=[
("USER", "User"),
("CLUB", "Club"),
("ACCOUNT", "Account"),
("COMPANY", "Company"),
("OTHER", "Other"),
],
max_length=10,
verbose_name="target type",
),
),
(
"target_id",
models.IntegerField(
null=True, verbose_name="target id", blank=True
),
),
(
"target_label",
models.CharField(
max_length=32,
blank=True,
verbose_name="target label",
default="",
),
),
(
"accounting_type",
models.ForeignKey(
null=True,
related_name="operations",
verbose_name="accounting type",
to="accounting.AccountingType",
blank=True,
),
),
], ],
options={ options={"ordering": ["-number"]},
'verbose_name': 'General journal',
'ordering': ['-start_date'],
},
), ),
migrations.CreateModel( migrations.CreateModel(
name='Operation', name="SimplifiedAccountingType",
fields=[ fields=[
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), (
('number', models.IntegerField(verbose_name='number')), "id",
('amount', accounting.models.CurrencyField(decimal_places=2, max_digits=12, verbose_name='amount')), models.AutoField(
('date', models.DateField(verbose_name='date')), primary_key=True,
('remark', models.CharField(max_length=128, verbose_name='comment')), serialize=False,
('mode', models.CharField(choices=[('CHECK', 'Check'), ('CASH', 'Cash'), ('TRANSFERT', 'Transfert'), ('CARD', 'Credit card')], max_length=255, verbose_name='payment method')), verbose_name="ID",
('cheque_number', models.CharField(max_length=32, null=True, verbose_name='cheque number', default='', blank=True)), auto_created=True,
('done', models.BooleanField(verbose_name='is done', default=False)), ),
('target_type', models.CharField(choices=[('USER', 'User'), ('CLUB', 'Club'), ('ACCOUNT', 'Account'), ('COMPANY', 'Company'), ('OTHER', 'Other')], max_length=10, verbose_name='target type')), ),
('target_id', models.IntegerField(null=True, verbose_name='target id', blank=True)), ("label", models.CharField(max_length=128, verbose_name="label")),
('target_label', models.CharField(max_length=32, blank=True, verbose_name='target label', default='')), (
('accounting_type', models.ForeignKey(null=True, related_name='operations', verbose_name='accounting type', to='accounting.AccountingType', blank=True)), "accounting_type",
models.ForeignKey(
verbose_name="simplified accounting types",
to="accounting.AccountingType",
related_name="simplified_types",
),
),
], ],
options={ options={
'ordering': ['-number'], "verbose_name": "simplified type",
}, "ordering": ["accounting_type__movement_type", "accounting_type__code"],
),
migrations.CreateModel(
name='SimplifiedAccountingType',
fields=[
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)),
('label', models.CharField(max_length=128, verbose_name='label')),
('accounting_type', models.ForeignKey(verbose_name='simplified accounting types', to='accounting.AccountingType', related_name='simplified_types')),
],
options={
'verbose_name': 'simplified type',
'ordering': ['accounting_type__movement_type', 'accounting_type__code'],
}, },
), ),
] ]

View File

@ -7,54 +7,88 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('club', '0001_initial'), ("club", "0001_initial"),
('accounting', '0001_initial'), ("accounting", "0001_initial"),
('core', '0001_initial'), ("core", "0001_initial"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='operation', model_name="operation",
name='invoice', name="invoice",
field=models.ForeignKey(null=True, related_name='operations', verbose_name='invoice', to='core.SithFile', blank=True), field=models.ForeignKey(
null=True,
related_name="operations",
verbose_name="invoice",
to="core.SithFile",
blank=True,
),
), ),
migrations.AddField( migrations.AddField(
model_name='operation', model_name="operation",
name='journal', name="journal",
field=models.ForeignKey(verbose_name='journal', to='accounting.GeneralJournal', related_name='operations'), field=models.ForeignKey(
verbose_name="journal",
to="accounting.GeneralJournal",
related_name="operations",
),
), ),
migrations.AddField( migrations.AddField(
model_name='operation', model_name="operation",
name='linked_operation', name="linked_operation",
field=models.OneToOneField(blank=True, to='accounting.Operation', null=True, related_name='operation_linked_to', verbose_name='linked operation', default=None), field=models.OneToOneField(
blank=True,
to="accounting.Operation",
null=True,
related_name="operation_linked_to",
verbose_name="linked operation",
default=None,
),
), ),
migrations.AddField( migrations.AddField(
model_name='operation', model_name="operation",
name='simpleaccounting_type', name="simpleaccounting_type",
field=models.ForeignKey(null=True, related_name='operations', verbose_name='simple type', to='accounting.SimplifiedAccountingType', blank=True), field=models.ForeignKey(
null=True,
related_name="operations",
verbose_name="simple type",
to="accounting.SimplifiedAccountingType",
blank=True,
),
), ),
migrations.AddField( migrations.AddField(
model_name='generaljournal', model_name="generaljournal",
name='club_account', name="club_account",
field=models.ForeignKey(verbose_name='club account', to='accounting.ClubAccount', related_name='journals'), field=models.ForeignKey(
verbose_name="club account",
to="accounting.ClubAccount",
related_name="journals",
),
), ),
migrations.AddField( migrations.AddField(
model_name='clubaccount', model_name="clubaccount",
name='bank_account', name="bank_account",
field=models.ForeignKey(verbose_name='bank account', to='accounting.BankAccount', related_name='club_accounts'), field=models.ForeignKey(
verbose_name="bank account",
to="accounting.BankAccount",
related_name="club_accounts",
),
), ),
migrations.AddField( migrations.AddField(
model_name='clubaccount', model_name="clubaccount",
name='club', name="club",
field=models.ForeignKey(verbose_name='club', to='club.Club', related_name='club_account'), field=models.ForeignKey(
verbose_name="club", to="club.Club", related_name="club_account"
),
), ),
migrations.AddField( migrations.AddField(
model_name='bankaccount', model_name="bankaccount",
name='club', name="club",
field=models.ForeignKey(verbose_name='club', to='club.Club', related_name='bank_accounts'), field=models.ForeignKey(
verbose_name="club", to="club.Club", related_name="bank_accounts"
),
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name='operation', name="operation", unique_together=set([("number", "journal")])
unique_together=set([('number', 'journal')]),
), ),
] ]

View File

@ -7,44 +7,44 @@ import phonenumber_field.modelfields
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("accounting", "0002_auto_20160824_2152")]
('accounting', '0002_auto_20160824_2152'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='company', model_name="company",
name='city', name="city",
field=models.CharField(blank=True, verbose_name='city', max_length=60), field=models.CharField(blank=True, verbose_name="city", max_length=60),
), ),
migrations.AddField( migrations.AddField(
model_name='company', model_name="company",
name='country', name="country",
field=models.CharField(blank=True, verbose_name='country', max_length=32), field=models.CharField(blank=True, verbose_name="country", max_length=32),
), ),
migrations.AddField( migrations.AddField(
model_name='company', model_name="company",
name='email', name="email",
field=models.EmailField(blank=True, verbose_name='email', max_length=254), field=models.EmailField(blank=True, verbose_name="email", max_length=254),
), ),
migrations.AddField( migrations.AddField(
model_name='company', model_name="company",
name='phone', name="phone",
field=phonenumber_field.modelfields.PhoneNumberField(blank=True, verbose_name='phone', max_length=128), field=phonenumber_field.modelfields.PhoneNumberField(
blank=True, verbose_name="phone", max_length=128
),
), ),
migrations.AddField( migrations.AddField(
model_name='company', model_name="company",
name='postcode', name="postcode",
field=models.CharField(blank=True, verbose_name='postcode', max_length=10), field=models.CharField(blank=True, verbose_name="postcode", max_length=10),
), ),
migrations.AddField( migrations.AddField(
model_name='company', model_name="company",
name='street', name="street",
field=models.CharField(blank=True, verbose_name='street', max_length=60), field=models.CharField(blank=True, verbose_name="street", max_length=60),
), ),
migrations.AddField( migrations.AddField(
model_name='company', model_name="company",
name='website', name="website",
field=models.CharField(blank=True, verbose_name='website', max_length=64), field=models.CharField(blank=True, verbose_name="website", max_length=64),
), ),
] ]

View File

@ -7,26 +7,45 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("accounting", "0003_auto_20160824_2203")]
('accounting', '0003_auto_20160824_2203'),
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Label', name="Label",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), (
('name', models.CharField(max_length=64, verbose_name='label')), "id",
('club_account', models.ForeignKey(related_name='labels', verbose_name='club account', to='accounting.ClubAccount')), models.AutoField(
verbose_name="ID",
primary_key=True,
auto_created=True,
serialize=False,
),
),
("name", models.CharField(max_length=64, verbose_name="label")),
(
"club_account",
models.ForeignKey(
related_name="labels",
verbose_name="club account",
to="accounting.ClubAccount",
),
),
], ],
), ),
migrations.AddField( migrations.AddField(
model_name='operation', model_name="operation",
name='label', name="label",
field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, related_name='operations', null=True, blank=True, verbose_name='label', to='accounting.Label'), field=models.ForeignKey(
on_delete=django.db.models.deletion.SET_NULL,
related_name="operations",
null=True,
blank=True,
verbose_name="label",
to="accounting.Label",
),
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name='label', name="label", unique_together=set([("name", "club_account")])
unique_together=set([('name', 'club_account')]),
), ),
] ]

View File

@ -6,14 +6,14 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("accounting", "0004_auto_20161005_1505")]
('accounting', '0004_auto_20161005_1505'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='operation', model_name="operation",
name='remark', name="remark",
field=models.CharField(null=True, max_length=128, blank=True, verbose_name='comment'), field=models.CharField(
), null=True, max_length=128, blank=True, verbose_name="comment"
),
)
] ]

View File

@ -41,9 +41,10 @@ class CurrencyField(models.DecimalField):
""" """
This is a custom database field used for currency This is a custom database field used for currency
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['max_digits'] = 12 kwargs["max_digits"] = 12
kwargs['decimal_places'] = 2 kwargs["decimal_places"] = 2
super(CurrencyField, self).__init__(*args, **kwargs) super(CurrencyField, self).__init__(*args, **kwargs)
def to_python(self, value): def to_python(self, value):
@ -52,18 +53,19 @@ class CurrencyField(models.DecimalField):
except AttributeError: except AttributeError:
return None return None
# Accounting classes # Accounting classes
class Company(models.Model): class Company(models.Model):
name = models.CharField(_('name'), max_length=60) name = models.CharField(_("name"), max_length=60)
street = models.CharField(_('street'), max_length=60, blank=True) street = models.CharField(_("street"), max_length=60, blank=True)
city = models.CharField(_('city'), max_length=60, blank=True) city = models.CharField(_("city"), max_length=60, blank=True)
postcode = models.CharField(_('postcode'), max_length=10, blank=True) postcode = models.CharField(_("postcode"), max_length=10, blank=True)
country = models.CharField(_('country'), max_length=32, blank=True) country = models.CharField(_("country"), max_length=32, blank=True)
phone = PhoneNumberField(_('phone'), blank=True) phone = PhoneNumberField(_("phone"), blank=True)
email = models.EmailField(_('email'), blank=True) email = models.EmailField(_("email"), blank=True)
website = models.CharField(_('website'), max_length=64, blank=True) website = models.CharField(_("website"), max_length=64, blank=True)
class Meta: class Meta:
verbose_name = _("company") verbose_name = _("company")
@ -81,7 +83,7 @@ class Company(models.Model):
Method to see if that object can be edited by the given user Method to see if that object can be edited by the given user
""" """
for club in user.memberships.filter(end_date=None).all(): for club in user.memberships.filter(end_date=None).all():
if club and club.role == settings.SITH_CLUB_ROLES_ID['Treasurer']: if club and club.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]:
return True return True
return False return False
@ -90,12 +92,12 @@ class Company(models.Model):
Method to see if that object can be viewed by the given user Method to see if that object can be viewed by the given user
""" """
for club in user.memberships.filter(end_date=None).all(): for club in user.memberships.filter(end_date=None).all():
if club and club.role >= settings.SITH_CLUB_ROLES_ID['Treasurer']: if club and club.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]:
return True return True
return False return False
def get_absolute_url(self): def get_absolute_url(self):
return reverse('accounting:co_edit', kwargs={'co_id': self.id}) return reverse("accounting:co_edit", kwargs={"co_id": self.id})
def get_display_name(self): def get_display_name(self):
return self.name return self.name
@ -105,14 +107,14 @@ class Company(models.Model):
class BankAccount(models.Model): class BankAccount(models.Model):
name = models.CharField(_('name'), max_length=30) name = models.CharField(_("name"), max_length=30)
iban = models.CharField(_('iban'), max_length=255, blank=True) iban = models.CharField(_("iban"), max_length=255, blank=True)
number = models.CharField(_('account number'), max_length=255, blank=True) number = models.CharField(_("account number"), max_length=255, blank=True)
club = models.ForeignKey(Club, related_name="bank_accounts", verbose_name=_("club")) club = models.ForeignKey(Club, related_name="bank_accounts", verbose_name=_("club"))
class Meta: class Meta:
verbose_name = _("Bank account") verbose_name = _("Bank account")
ordering = ['club', 'name'] ordering = ["club", "name"]
def is_owned_by(self, user): def is_owned_by(self, user):
""" """
@ -121,25 +123,27 @@ class BankAccount(models.Model):
if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID): if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
return True return True
m = self.club.get_membership_for(user) m = self.club.get_membership_for(user)
if m is not None and m.role >= settings.SITH_CLUB_ROLES_ID['Treasurer']: if m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]:
return True return True
return False return False
def get_absolute_url(self): def get_absolute_url(self):
return reverse('accounting:bank_details', kwargs={'b_account_id': self.id}) return reverse("accounting:bank_details", kwargs={"b_account_id": self.id})
def __str__(self): def __str__(self):
return self.name return self.name
class ClubAccount(models.Model): class ClubAccount(models.Model):
name = models.CharField(_('name'), max_length=30) name = models.CharField(_("name"), max_length=30)
club = models.ForeignKey(Club, related_name="club_account", verbose_name=_("club")) club = models.ForeignKey(Club, related_name="club_account", verbose_name=_("club"))
bank_account = models.ForeignKey(BankAccount, related_name="club_accounts", verbose_name=_("bank account")) bank_account = models.ForeignKey(
BankAccount, related_name="club_accounts", verbose_name=_("bank account")
)
class Meta: class Meta:
verbose_name = _("Club account") verbose_name = _("Club account")
ordering = ['bank_account', 'name'] ordering = ["bank_account", "name"]
def is_owned_by(self, user): def is_owned_by(self, user):
""" """
@ -154,7 +158,7 @@ class ClubAccount(models.Model):
Method to see if that object can be edited by the given user Method to see if that object can be edited by the given user
""" """
m = self.club.get_membership_for(user) m = self.club.get_membership_for(user)
if m and m.role == settings.SITH_CLUB_ROLES_ID['Treasurer']: if m and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]:
return True return True
return False return False
@ -163,7 +167,7 @@ class ClubAccount(models.Model):
Method to see if that object can be viewed by the given user Method to see if that object can be viewed by the given user
""" """
m = self.club.get_membership_for(user) m = self.club.get_membership_for(user)
if m and m.role >= settings.SITH_CLUB_ROLES_ID['Treasurer']: if m and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]:
return True return True
return False return False
@ -177,30 +181,36 @@ class ClubAccount(models.Model):
return self.journals.filter(closed=False).first() return self.journals.filter(closed=False).first()
def get_absolute_url(self): def get_absolute_url(self):
return reverse('accounting:club_details', kwargs={'c_account_id': self.id}) return reverse("accounting:club_details", kwargs={"c_account_id": self.id})
def __str__(self): def __str__(self):
return self.name return self.name
def get_display_name(self): def get_display_name(self):
return _("%(club_account)s on %(bank_account)s") % {"club_account": self.name, "bank_account": self.bank_account} return _("%(club_account)s on %(bank_account)s") % {
"club_account": self.name,
"bank_account": self.bank_account,
}
class GeneralJournal(models.Model): class GeneralJournal(models.Model):
""" """
Class storing all the operations for a period of time Class storing all the operations for a period of time
""" """
start_date = models.DateField(_('start date'))
end_date = models.DateField(_('end date'), null=True, blank=True, default=None) start_date = models.DateField(_("start date"))
name = models.CharField(_('name'), max_length=40) end_date = models.DateField(_("end date"), null=True, blank=True, default=None)
closed = models.BooleanField(_('is closed'), default=False) name = models.CharField(_("name"), max_length=40)
club_account = models.ForeignKey(ClubAccount, related_name="journals", null=False, verbose_name=_("club account")) closed = models.BooleanField(_("is closed"), default=False)
amount = CurrencyField(_('amount'), default=0) club_account = models.ForeignKey(
effective_amount = CurrencyField(_('effective_amount'), default=0) ClubAccount, related_name="journals", null=False, verbose_name=_("club account")
)
amount = CurrencyField(_("amount"), default=0)
effective_amount = CurrencyField(_("effective_amount"), default=0)
class Meta: class Meta:
verbose_name = _("General journal") verbose_name = _("General journal")
ordering = ['-start_date'] ordering = ["-start_date"]
def is_owned_by(self, user): def is_owned_by(self, user):
""" """
@ -226,7 +236,7 @@ class GeneralJournal(models.Model):
return self.club_account.can_be_viewed_by(user) return self.club_account.can_be_viewed_by(user)
def get_absolute_url(self): def get_absolute_url(self):
return reverse('accounting:journal_details', kwargs={'j_id': self.id}) return reverse("accounting:journal_details", kwargs={"j_id": self.id})
def __str__(self): def __str__(self):
return self.name return self.name
@ -250,31 +260,79 @@ class Operation(models.Model):
""" """
An operation is a line in the journal, a debit or a credit An operation is a line in the journal, a debit or a credit
""" """
number = models.IntegerField(_('number'))
journal = models.ForeignKey(GeneralJournal, related_name="operations", null=False, verbose_name=_("journal")) number = models.IntegerField(_("number"))
amount = CurrencyField(_('amount')) journal = models.ForeignKey(
date = models.DateField(_('date')) GeneralJournal, related_name="operations", null=False, verbose_name=_("journal")
remark = models.CharField(_('comment'), max_length=128, null=True, blank=True) )
mode = models.CharField(_('payment method'), max_length=255, choices=settings.SITH_ACCOUNTING_PAYMENT_METHOD) amount = CurrencyField(_("amount"))
cheque_number = models.CharField(_('cheque number'), max_length=32, default="", null=True, blank=True) date = models.DateField(_("date"))
invoice = models.ForeignKey(SithFile, related_name='operations', verbose_name=_("invoice"), null=True, blank=True) remark = models.CharField(_("comment"), max_length=128, null=True, blank=True)
done = models.BooleanField(_('is done'), default=False) mode = models.CharField(
simpleaccounting_type = models.ForeignKey('SimplifiedAccountingType', related_name="operations", _("payment method"),
verbose_name=_("simple type"), null=True, blank=True) max_length=255,
accounting_type = models.ForeignKey('AccountingType', related_name="operations", choices=settings.SITH_ACCOUNTING_PAYMENT_METHOD,
verbose_name=_("accounting type"), null=True, blank=True) )
label = models.ForeignKey('Label', related_name="operations", cheque_number = models.CharField(
verbose_name=_("label"), null=True, blank=True, on_delete=models.SET_NULL) _("cheque number"), max_length=32, default="", null=True, blank=True
target_type = models.CharField(_('target type'), max_length=10, )
choices=[('USER', _('User')), ('CLUB', _('Club')), ('ACCOUNT', _('Account')), ('COMPANY', _('Company')), ('OTHER', _('Other'))]) invoice = models.ForeignKey(
target_id = models.IntegerField(_('target id'), null=True, blank=True) SithFile,
target_label = models.CharField(_('target label'), max_length=32, default="", blank=True) related_name="operations",
linked_operation = models.OneToOneField('self', related_name='operation_linked_to', verbose_name=_("linked operation"), verbose_name=_("invoice"),
null=True, blank=True, default=None) null=True,
blank=True,
)
done = models.BooleanField(_("is done"), default=False)
simpleaccounting_type = models.ForeignKey(
"SimplifiedAccountingType",
related_name="operations",
verbose_name=_("simple type"),
null=True,
blank=True,
)
accounting_type = models.ForeignKey(
"AccountingType",
related_name="operations",
verbose_name=_("accounting type"),
null=True,
blank=True,
)
label = models.ForeignKey(
"Label",
related_name="operations",
verbose_name=_("label"),
null=True,
blank=True,
on_delete=models.SET_NULL,
)
target_type = models.CharField(
_("target type"),
max_length=10,
choices=[
("USER", _("User")),
("CLUB", _("Club")),
("ACCOUNT", _("Account")),
("COMPANY", _("Company")),
("OTHER", _("Other")),
],
)
target_id = models.IntegerField(_("target id"), null=True, blank=True)
target_label = models.CharField(
_("target label"), max_length=32, default="", blank=True
)
linked_operation = models.OneToOneField(
"self",
related_name="operation_linked_to",
verbose_name=_("linked operation"),
null=True,
blank=True,
default=None,
)
class Meta: class Meta:
unique_together = ('number', 'journal') unique_together = ("number", "journal")
ordering = ['-number'] ordering = ["-number"]
def __getattribute__(self, attr): def __getattribute__(self, attr):
if attr == "target": if attr == "target":
@ -287,14 +345,29 @@ class Operation(models.Model):
if self.date is None: if self.date is None:
raise ValidationError(_("The date must be set.")) raise ValidationError(_("The date must be set."))
elif self.date < self.journal.start_date: elif self.date < self.journal.start_date:
raise ValidationError(_("""The date can not be before the start date of the journal, which is raise ValidationError(
%(start_date)s.""") % {'start_date': defaultfilters.date(self.journal.start_date, settings.DATE_FORMAT)}) _(
"""The date can not be before the start date of the journal, which is
%(start_date)s."""
)
% {
"start_date": defaultfilters.date(
self.journal.start_date, settings.DATE_FORMAT
)
}
)
if self.target_type != "OTHER" and self.get_target() is None: if self.target_type != "OTHER" and self.get_target() is None:
raise ValidationError(_("Target does not exists")) raise ValidationError(_("Target does not exists"))
if self.target_type == "OTHER" and self.target_label == "": if self.target_type == "OTHER" and self.target_label == "":
raise ValidationError(_("Please add a target label if you set no existing target")) raise ValidationError(
_("Please add a target label if you set no existing target")
)
if not self.accounting_type and not self.simpleaccounting_type: if not self.accounting_type and not self.simpleaccounting_type:
raise ValidationError(_("You need to provide ether a simplified accounting type or a standard accounting type")) raise ValidationError(
_(
"You need to provide ether a simplified accounting type or a standard accounting type"
)
)
if self.simpleaccounting_type: if self.simpleaccounting_type:
self.accounting_type = self.simpleaccounting_type.accounting_type self.accounting_type = self.simpleaccounting_type.accounting_type
@ -329,7 +402,7 @@ class Operation(models.Model):
if self.journal.closed: if self.journal.closed:
return False return False
m = self.journal.club_account.club.get_membership_for(user) m = self.journal.club_account.club.get_membership_for(user)
if m is not None and m.role >= settings.SITH_CLUB_ROLES_ID['Treasurer']: if m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]:
return True return True
return False return False
@ -342,16 +415,19 @@ class Operation(models.Model):
if self.journal.closed: if self.journal.closed:
return False return False
m = self.journal.club_account.club.get_membership_for(user) m = self.journal.club_account.club.get_membership_for(user)
if m is not None and m.role == settings.SITH_CLUB_ROLES_ID['Treasurer']: if m is not None and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]:
return True return True
return False return False
def get_absolute_url(self): def get_absolute_url(self):
return reverse('accounting:journal_details', kwargs={'j_id': self.journal.id}) return reverse("accounting:journal_details", kwargs={"j_id": self.journal.id})
def __str__(self): def __str__(self):
return "%d € | %s | %s | %s" % ( return "%d € | %s | %s | %s" % (
self.amount, self.date, self.accounting_type, self.done, self.amount,
self.date,
self.accounting_type,
self.done,
) )
@ -361,18 +437,30 @@ class AccountingType(models.Model):
Thoses are numbers used in accounting to classify operations Thoses are numbers used in accounting to classify operations
""" """
code = models.CharField(_('code'), max_length=16,
validators=[ code = models.CharField(
validators.RegexValidator(r'^[0-9]*$', _('An accounting type code contains only numbers')), _("code"),
], max_length=16,
validators=[
validators.RegexValidator(
r"^[0-9]*$", _("An accounting type code contains only numbers")
)
],
)
label = models.CharField(_("label"), max_length=128)
movement_type = models.CharField(
_("movement type"),
choices=[
("CREDIT", _("Credit")),
("DEBIT", _("Debit")),
("NEUTRAL", _("Neutral")),
],
max_length=12,
) )
label = models.CharField(_('label'), max_length=128)
movement_type = models.CharField(_('movement type'), choices=[('CREDIT', _('Credit')), ('DEBIT', _('Debit')),
('NEUTRAL', _('Neutral'))], max_length=12)
class Meta: class Meta:
verbose_name = _("accounting type") verbose_name = _("accounting type")
ordering = ['movement_type', 'code'] ordering = ["movement_type", "code"]
def is_owned_by(self, user): def is_owned_by(self, user):
""" """
@ -383,7 +471,7 @@ class AccountingType(models.Model):
return False return False
def get_absolute_url(self): def get_absolute_url(self):
return reverse('accounting:type_list') return reverse("accounting:type_list")
def __str__(self): def __str__(self):
return self.code + " - " + self.get_movement_type_display() + " - " + self.label return self.code + " - " + self.get_movement_type_display() + " - " + self.label
@ -393,13 +481,17 @@ class SimplifiedAccountingType(models.Model):
""" """
Class describing the simplified accounting types. Class describing the simplified accounting types.
""" """
label = models.CharField(_('label'), max_length=128)
accounting_type = models.ForeignKey(AccountingType, related_name="simplified_types", label = models.CharField(_("label"), max_length=128)
verbose_name=_("simplified accounting types")) accounting_type = models.ForeignKey(
AccountingType,
related_name="simplified_types",
verbose_name=_("simplified accounting types"),
)
class Meta: class Meta:
verbose_name = _("simplified type") verbose_name = _("simplified type")
ordering = ['accounting_type__movement_type', 'accounting_type__code'] ordering = ["accounting_type__movement_type", "accounting_type__code"]
@property @property
def movement_type(self): def movement_type(self):
@ -409,25 +501,36 @@ class SimplifiedAccountingType(models.Model):
return self.accounting_type.get_movement_type_display() return self.accounting_type.get_movement_type_display()
def get_absolute_url(self): def get_absolute_url(self):
return reverse('accounting:simple_type_list') return reverse("accounting:simple_type_list")
def __str__(self): def __str__(self):
return self.get_movement_type_display() + " - " + self.accounting_type.code + " - " + self.label return (
self.get_movement_type_display()
+ " - "
+ self.accounting_type.code
+ " - "
+ self.label
)
class Label(models.Model): class Label(models.Model):
"""Label allow a club to sort its operations""" """Label allow a club to sort its operations"""
name = models.CharField(_('label'), max_length=64)
club_account = models.ForeignKey(ClubAccount, related_name="labels", verbose_name=_("club account")) name = models.CharField(_("label"), max_length=64)
club_account = models.ForeignKey(
ClubAccount, related_name="labels", verbose_name=_("club account")
)
class Meta: class Meta:
unique_together = ('name', 'club_account') unique_together = ("name", "club_account")
def __str__(self): def __str__(self):
return "%s (%s)" % (self.name, self.club_account.name) return "%s (%s)" % (self.name, self.club_account.name)
def get_absolute_url(self): def get_absolute_url(self):
return reverse('accounting:label_list', kwargs={'clubaccount_id': self.club_account.id}) return reverse(
"accounting:label_list", kwargs={"clubaccount_id": self.club_account.id}
)
def is_owned_by(self, user): def is_owned_by(self, user):
return self.club_account.is_owned_by(user) return self.club_account.is_owned_by(user)

View File

@ -28,7 +28,13 @@ from django.core.management import call_command
from datetime import date from datetime import date
from core.models import User from core.models import User
from accounting.models import GeneralJournal, Operation, Label, AccountingType, SimplifiedAccountingType from accounting.models import (
GeneralJournal,
Operation,
Label,
AccountingType,
SimplifiedAccountingType,
)
class RefoundAccountTest(TestCase): class RefoundAccountTest(TestCase):
@ -40,18 +46,20 @@ class RefoundAccountTest(TestCase):
self.skia.customer.save() self.skia.customer.save()
def test_permission_denied(self): def test_permission_denied(self):
self.client.login(username='guy', password='plop') self.client.login(username="guy", password="plop")
response_post = self.client.post(reverse("accounting:refound_account"), response_post = self.client.post(
{"user": self.skia.id}) reverse("accounting:refound_account"), {"user": self.skia.id}
)
response_get = self.client.get(reverse("accounting:refound_account")) response_get = self.client.get(reverse("accounting:refound_account"))
self.assertTrue(response_get.status_code == 403) self.assertTrue(response_get.status_code == 403)
self.assertTrue(response_post.status_code == 403) self.assertTrue(response_post.status_code == 403)
def test_root_granteed(self): def test_root_granteed(self):
self.client.login(username='root', password='plop') self.client.login(username="root", password="plop")
response_post = self.client.post(reverse("accounting:refound_account"), response_post = self.client.post(
{"user": self.skia.id}) reverse("accounting:refound_account"), {"user": self.skia.id}
self.skia = User.objects.filter(username='skia').first() )
self.skia = User.objects.filter(username="skia").first()
response_get = self.client.get(reverse("accounting:refound_account")) response_get = self.client.get(reverse("accounting:refound_account"))
self.assertFalse(response_get.status_code == 403) self.assertFalse(response_get.status_code == 403)
self.assertTrue('<form action="" method="post">' in str(response_get.content)) self.assertTrue('<form action="" method="post">' in str(response_get.content))
@ -59,10 +67,11 @@ class RefoundAccountTest(TestCase):
self.assertTrue(self.skia.customer.amount == 0) self.assertTrue(self.skia.customer.amount == 0)
def test_comptable_granteed(self): def test_comptable_granteed(self):
self.client.login(username='comptable', password='plop') self.client.login(username="comptable", password="plop")
response_post = self.client.post(reverse("accounting:refound_account"), response_post = self.client.post(
{"user": self.skia.id}) reverse("accounting:refound_account"), {"user": self.skia.id}
self.skia = User.objects.filter(username='skia').first() )
self.skia = User.objects.filter(username="skia").first()
response_get = self.client.get(reverse("accounting:refound_account")) response_get = self.client.get(reverse("accounting:refound_account"))
self.assertFalse(response_get.status_code == 403) self.assertFalse(response_get.status_code == 403)
self.assertTrue('<form action="" method="post">' in str(response_get.content)) self.assertTrue('<form action="" method="post">' in str(response_get.content))
@ -76,146 +85,217 @@ class JournalTest(TestCase):
self.journal = GeneralJournal.objects.filter(id=1).first() self.journal = GeneralJournal.objects.filter(id=1).first()
def test_permission_granted(self): def test_permission_granted(self):
self.client.login(username='comptable', password='plop') self.client.login(username="comptable", password="plop")
response_get = self.client.get(reverse("accounting:journal_details", args=[self.journal.id])) response_get = self.client.get(
reverse("accounting:journal_details", args=[self.journal.id])
)
self.assertTrue(response_get.status_code == 200) self.assertTrue(response_get.status_code == 200)
self.assertTrue('<td>M\\xc3\\xa9thode de paiement</td>' in str(response_get.content)) self.assertTrue(
"<td>M\\xc3\\xa9thode de paiement</td>" in str(response_get.content)
)
def test_permission_not_granted(self): def test_permission_not_granted(self):
self.client.login(username='skia', password='plop') self.client.login(username="skia", password="plop")
response_get = self.client.get(reverse("accounting:journal_details", args=[self.journal.id])) response_get = self.client.get(
reverse("accounting:journal_details", args=[self.journal.id])
)
self.assertTrue(response_get.status_code == 403) self.assertTrue(response_get.status_code == 403)
self.assertFalse('<td>M\xc3\xa9thode de paiement</td>' in str(response_get.content)) self.assertFalse(
"<td>M\xc3\xa9thode de paiement</td>" in str(response_get.content)
)
class OperationTest(TestCase): class OperationTest(TestCase):
def setUp(self): def setUp(self):
call_command("populate") call_command("populate")
self.journal = GeneralJournal.objects.filter(id=1).first() self.journal = GeneralJournal.objects.filter(id=1).first()
self.skia = User.objects.filter(username='skia').first() self.skia = User.objects.filter(username="skia").first()
at = AccountingType(code='443', label="Ce code n'existe pas", movement_type='CREDIT') at = AccountingType(
code="443", label="Ce code n'existe pas", movement_type="CREDIT"
)
at.save() at.save()
l = Label(club_account=self.journal.club_account, name='bob') l = Label(club_account=self.journal.club_account, name="bob")
l.save() l.save()
self.client.login(username='comptable', password='plop') self.client.login(username="comptable", password="plop")
self.op1 = Operation(journal=self.journal, date=date.today(), amount=1, self.op1 = Operation(
remark="Test bilan", mode='CASH', done=True, label=l, journal=self.journal,
accounting_type=at, target_type='USER', target_id=self.skia.id) date=date.today(),
amount=1,
remark="Test bilan",
mode="CASH",
done=True,
label=l,
accounting_type=at,
target_type="USER",
target_id=self.skia.id,
)
self.op1.save() self.op1.save()
self.op2 = Operation(journal=self.journal, date=date.today(), amount=2, self.op2 = Operation(
remark="Test bilan", mode='CASH', done=True, label=l, journal=self.journal,
accounting_type=at, target_type='USER', target_id=self.skia.id) date=date.today(),
amount=2,
remark="Test bilan",
mode="CASH",
done=True,
label=l,
accounting_type=at,
target_type="USER",
target_id=self.skia.id,
)
self.op2.save() self.op2.save()
def test_new_operation(self): def test_new_operation(self):
self.client.login(username='comptable', password='plop') self.client.login(username="comptable", password="plop")
at = AccountingType.objects.filter(code='604').first() at = AccountingType.objects.filter(code="604").first()
response = self.client.post(reverse('accounting:op_new', response = self.client.post(
args=[self.journal.id]), reverse("accounting:op_new", args=[self.journal.id]),
{'amount': 30, {
'remark': "Un gros test", "amount": 30,
'journal': self.journal.id, "remark": "Un gros test",
'target_type': 'OTHER', "journal": self.journal.id,
'target_id': '', "target_type": "OTHER",
'target_label': "Le fantome de la nuit", "target_id": "",
'date': '04/12/2020', "target_label": "Le fantome de la nuit",
'mode': 'CASH', "date": "04/12/2020",
'cheque_number': '', "mode": "CASH",
'invoice': '', "cheque_number": "",
'simpleaccounting_type': '', "invoice": "",
'accounting_type': at.id, "simpleaccounting_type": "",
'label': '', "accounting_type": at.id,
'done': False, "label": "",
}) "done": False,
},
)
self.assertFalse(response.status_code == 403) self.assertFalse(response.status_code == 403)
self.assertTrue(self.journal.operations.filter(target_label="Le fantome de la nuit").exists()) self.assertTrue(
response_get = self.client.get(reverse("accounting:journal_details", args=[self.journal.id])) self.journal.operations.filter(
self.assertTrue('<td>Le fantome de la nuit</td>' in str(response_get.content)) target_label="Le fantome de la nuit"
).exists()
)
response_get = self.client.get(
reverse("accounting:journal_details", args=[self.journal.id])
)
self.assertTrue("<td>Le fantome de la nuit</td>" in str(response_get.content))
def test_bad_new_operation(self): def test_bad_new_operation(self):
self.client.login(username='comptable', password='plop') self.client.login(username="comptable", password="plop")
AccountingType.objects.filter(code='604').first() AccountingType.objects.filter(code="604").first()
response = self.client.post(reverse('accounting:op_new', response = self.client.post(
args=[self.journal.id]), reverse("accounting:op_new", args=[self.journal.id]),
{'amount': 30, {
'remark': "Un gros test", "amount": 30,
'journal': self.journal.id, "remark": "Un gros test",
'target_type': 'OTHER', "journal": self.journal.id,
'target_id': '', "target_type": "OTHER",
'target_label': "Le fantome de la nuit", "target_id": "",
'date': '04/12/2020', "target_label": "Le fantome de la nuit",
'mode': 'CASH', "date": "04/12/2020",
'cheque_number': '', "mode": "CASH",
'invoice': '', "cheque_number": "",
'simpleaccounting_type': '', "invoice": "",
'accounting_type': '', "simpleaccounting_type": "",
'label': '', "accounting_type": "",
'done': False, "label": "",
}) "done": False,
self.assertTrue('Vous devez fournir soit un type comptable simplifi\\xc3\\xa9 ou un type comptable standard' in str(response.content)) },
)
self.assertTrue(
"Vous devez fournir soit un type comptable simplifi\\xc3\\xa9 ou un type comptable standard"
in str(response.content)
)
def test_new_operation_not_authorized(self): def test_new_operation_not_authorized(self):
self.client.login(username='skia', password='plop') self.client.login(username="skia", password="plop")
at = AccountingType.objects.filter(code='604').first() at = AccountingType.objects.filter(code="604").first()
response = self.client.post(reverse('accounting:op_new', response = self.client.post(
args=[self.journal.id]), reverse("accounting:op_new", args=[self.journal.id]),
{'amount': 30, {
'remark': "Un gros test", "amount": 30,
'journal': self.journal.id, "remark": "Un gros test",
'target_type': 'OTHER', "journal": self.journal.id,
'target_id': '', "target_type": "OTHER",
'target_label': "Le fantome du jour", "target_id": "",
'date': '04/12/2020', "target_label": "Le fantome du jour",
'mode': 'CASH', "date": "04/12/2020",
'cheque_number': '', "mode": "CASH",
'invoice': '', "cheque_number": "",
'simpleaccounting_type': '', "invoice": "",
'accounting_type': at.id, "simpleaccounting_type": "",
'label': '', "accounting_type": at.id,
'done': False, "label": "",
}) "done": False,
},
)
self.assertTrue(response.status_code == 403) self.assertTrue(response.status_code == 403)
self.assertFalse(self.journal.operations.filter(target_label="Le fantome du jour").exists()) self.assertFalse(
self.journal.operations.filter(target_label="Le fantome du jour").exists()
)
def test__operation_simple_accounting(self): def test__operation_simple_accounting(self):
self.client.login(username='comptable', password='plop') self.client.login(username="comptable", password="plop")
sat = SimplifiedAccountingType.objects.all().first() sat = SimplifiedAccountingType.objects.all().first()
response = self.client.post(reverse('accounting:op_new', response = self.client.post(
args=[self.journal.id]), reverse("accounting:op_new", args=[self.journal.id]),
{'amount': 23, {
'remark': "Un gros test", "amount": 23,
'journal': self.journal.id, "remark": "Un gros test",
'target_type': 'OTHER', "journal": self.journal.id,
'target_id': '', "target_type": "OTHER",
'target_label': "Le fantome de l'aurore", "target_id": "",
'date': '04/12/2020', "target_label": "Le fantome de l'aurore",
'mode': 'CASH', "date": "04/12/2020",
'cheque_number': '', "mode": "CASH",
'invoice': '', "cheque_number": "",
'simpleaccounting_type': sat.id, "invoice": "",
'accounting_type': '', "simpleaccounting_type": sat.id,
'label': '', "accounting_type": "",
'done': False, "label": "",
}) "done": False,
},
)
self.assertFalse(response.status_code == 403) self.assertFalse(response.status_code == 403)
self.assertTrue(self.journal.operations.filter(amount=23).exists()) self.assertTrue(self.journal.operations.filter(amount=23).exists())
response_get = self.client.get(reverse("accounting:journal_details", args=[self.journal.id])) response_get = self.client.get(
self.assertTrue("<td>Le fantome de l&#39;aurore</td>" in str(response_get.content)) reverse("accounting:journal_details", args=[self.journal.id])
self.assertTrue(self.journal.operations.filter(amount=23).values('accounting_type').first()['accounting_type'] == AccountingType.objects.filter(code=6).values('id').first()['id']) )
self.assertTrue(
"<td>Le fantome de l&#39;aurore</td>" in str(response_get.content)
)
self.assertTrue(
self.journal.operations.filter(amount=23)
.values("accounting_type")
.first()["accounting_type"]
== AccountingType.objects.filter(code=6).values("id").first()["id"]
)
def test_nature_statement(self): def test_nature_statement(self):
self.client.login(username='comptable', password='plop') self.client.login(username="comptable", password="plop")
response_get = self.client.get(reverse("accounting:journal_nature_statement", args=[self.journal.id])) response_get = self.client.get(
self.assertTrue("bob (Troll Pench\\xc3\\xa9) : 3.00" in str(response_get.content)) reverse("accounting:journal_nature_statement", args=[self.journal.id])
)
self.assertTrue(
"bob (Troll Pench\\xc3\\xa9) : 3.00" in str(response_get.content)
)
def test_person_statement(self): def test_person_statement(self):
self.client.login(username='comptable', password='plop') self.client.login(username="comptable", password="plop")
response_get = self.client.get(reverse("accounting:journal_person_statement", args=[self.journal.id])) response_get = self.client.get(
self.assertTrue("<td>3.00</td>" in str(response_get.content) and '<td><a href="/user/1/">S&#39; Kia</a></td>' in str(response_get.content)) reverse("accounting:journal_person_statement", args=[self.journal.id])
)
self.assertTrue(
"<td>3.00</td>" in str(response_get.content)
and '<td><a href="/user/1/">S&#39; Kia</a></td>'
in str(response_get.content)
)
def test_accounting_statement(self): def test_accounting_statement(self):
self.client.login(username='comptable', password='plop') self.client.login(username="comptable", password="plop")
response_get = self.client.get(reverse("accounting:journal_accounting_statement", args=[self.journal.id])) response_get = self.client.get(
self.assertTrue("<td>443 - Cr\\xc3\\xa9dit - Ce code n&#39;existe pas</td>" in str(response_get.content)) reverse("accounting:journal_accounting_statement", args=[self.journal.id])
)
self.assertTrue(
"<td>443 - Cr\\xc3\\xa9dit - Ce code n&#39;existe pas</td>"
in str(response_get.content)
)

View File

@ -28,46 +28,125 @@ from accounting.views import *
urlpatterns = [ urlpatterns = [
# Accounting types # Accounting types
url(r'^simple_type$', SimplifiedAccountingTypeListView.as_view(), name='simple_type_list'), url(
url(r'^simple_type/create$', SimplifiedAccountingTypeCreateView.as_view(), name='simple_type_new'), r"^simple_type$",
url(r'^simple_type/(?P<type_id>[0-9]+)/edit$', SimplifiedAccountingTypeEditView.as_view(), name='simple_type_edit'), SimplifiedAccountingTypeListView.as_view(),
name="simple_type_list",
),
url(
r"^simple_type/create$",
SimplifiedAccountingTypeCreateView.as_view(),
name="simple_type_new",
),
url(
r"^simple_type/(?P<type_id>[0-9]+)/edit$",
SimplifiedAccountingTypeEditView.as_view(),
name="simple_type_edit",
),
# Accounting types # Accounting types
url(r'^type$', AccountingTypeListView.as_view(), name='type_list'), url(r"^type$", AccountingTypeListView.as_view(), name="type_list"),
url(r'^type/create$', AccountingTypeCreateView.as_view(), name='type_new'), url(r"^type/create$", AccountingTypeCreateView.as_view(), name="type_new"),
url(r'^type/(?P<type_id>[0-9]+)/edit$', AccountingTypeEditView.as_view(), name='type_edit'), url(
r"^type/(?P<type_id>[0-9]+)/edit$",
AccountingTypeEditView.as_view(),
name="type_edit",
),
# Bank accounts # Bank accounts
url(r'^$', BankAccountListView.as_view(), name='bank_list'), url(r"^$", BankAccountListView.as_view(), name="bank_list"),
url(r'^bank/create$', BankAccountCreateView.as_view(), name='bank_new'), url(r"^bank/create$", BankAccountCreateView.as_view(), name="bank_new"),
url(r'^bank/(?P<b_account_id>[0-9]+)$', BankAccountDetailView.as_view(), name='bank_details'), url(
url(r'^bank/(?P<b_account_id>[0-9]+)/edit$', BankAccountEditView.as_view(), name='bank_edit'), r"^bank/(?P<b_account_id>[0-9]+)$",
url(r'^bank/(?P<b_account_id>[0-9]+)/delete$', BankAccountDeleteView.as_view(), name='bank_delete'), BankAccountDetailView.as_view(),
name="bank_details",
),
url(
r"^bank/(?P<b_account_id>[0-9]+)/edit$",
BankAccountEditView.as_view(),
name="bank_edit",
),
url(
r"^bank/(?P<b_account_id>[0-9]+)/delete$",
BankAccountDeleteView.as_view(),
name="bank_delete",
),
# Club accounts # Club accounts
url(r'^club/create$', ClubAccountCreateView.as_view(), name='club_new'), url(r"^club/create$", ClubAccountCreateView.as_view(), name="club_new"),
url(r'^club/(?P<c_account_id>[0-9]+)$', ClubAccountDetailView.as_view(), name='club_details'), url(
url(r'^club/(?P<c_account_id>[0-9]+)/edit$', ClubAccountEditView.as_view(), name='club_edit'), r"^club/(?P<c_account_id>[0-9]+)$",
url(r'^club/(?P<c_account_id>[0-9]+)/delete$', ClubAccountDeleteView.as_view(), name='club_delete'), ClubAccountDetailView.as_view(),
name="club_details",
),
url(
r"^club/(?P<c_account_id>[0-9]+)/edit$",
ClubAccountEditView.as_view(),
name="club_edit",
),
url(
r"^club/(?P<c_account_id>[0-9]+)/delete$",
ClubAccountDeleteView.as_view(),
name="club_delete",
),
# Journals # Journals
url(r'^journal/create$', JournalCreateView.as_view(), name='journal_new'), url(r"^journal/create$", JournalCreateView.as_view(), name="journal_new"),
url(r'^journal/(?P<j_id>[0-9]+)$', JournalDetailView.as_view(), name='journal_details'), url(
url(r'^journal/(?P<j_id>[0-9]+)/edit$', JournalEditView.as_view(), name='journal_edit'), r"^journal/(?P<j_id>[0-9]+)$",
url(r'^journal/(?P<j_id>[0-9]+)/delete$', JournalDeleteView.as_view(), name='journal_delete'), JournalDetailView.as_view(),
url(r'^journal/(?P<j_id>[0-9]+)/statement/nature$', JournalNatureStatementView.as_view(), name='journal_nature_statement'), name="journal_details",
url(r'^journal/(?P<j_id>[0-9]+)/statement/person$', JournalPersonStatementView.as_view(), name='journal_person_statement'), ),
url(r'^journal/(?P<j_id>[0-9]+)/statement/accounting$', JournalAccountingStatementView.as_view(), name='journal_accounting_statement'), url(
r"^journal/(?P<j_id>[0-9]+)/edit$",
JournalEditView.as_view(),
name="journal_edit",
),
url(
r"^journal/(?P<j_id>[0-9]+)/delete$",
JournalDeleteView.as_view(),
name="journal_delete",
),
url(
r"^journal/(?P<j_id>[0-9]+)/statement/nature$",
JournalNatureStatementView.as_view(),
name="journal_nature_statement",
),
url(
r"^journal/(?P<j_id>[0-9]+)/statement/person$",
JournalPersonStatementView.as_view(),
name="journal_person_statement",
),
url(
r"^journal/(?P<j_id>[0-9]+)/statement/accounting$",
JournalAccountingStatementView.as_view(),
name="journal_accounting_statement",
),
# Operations # Operations
url(r'^operation/create/(?P<j_id>[0-9]+)$', OperationCreateView.as_view(), name='op_new'), url(
url(r'^operation/(?P<op_id>[0-9]+)$', OperationEditView.as_view(), name='op_edit'), r"^operation/create/(?P<j_id>[0-9]+)$",
url(r'^operation/(?P<op_id>[0-9]+)/pdf$', OperationPDFView.as_view(), name='op_pdf'), OperationCreateView.as_view(),
name="op_new",
),
url(r"^operation/(?P<op_id>[0-9]+)$", OperationEditView.as_view(), name="op_edit"),
url(
r"^operation/(?P<op_id>[0-9]+)/pdf$", OperationPDFView.as_view(), name="op_pdf"
),
# Companies # Companies
url(r'^company/list$', CompanyListView.as_view(), name='co_list'), url(r"^company/list$", CompanyListView.as_view(), name="co_list"),
url(r'^company/create$', CompanyCreateView.as_view(), name='co_new'), url(r"^company/create$", CompanyCreateView.as_view(), name="co_new"),
url(r'^company/(?P<co_id>[0-9]+)$', CompanyEditView.as_view(), name='co_edit'), url(r"^company/(?P<co_id>[0-9]+)$", CompanyEditView.as_view(), name="co_edit"),
# Labels # Labels
url(r'^label/new$', LabelCreateView.as_view(), name='label_new'), url(r"^label/new$", LabelCreateView.as_view(), name="label_new"),
url(r'^label/(?P<clubaccount_id>[0-9]+)$', LabelListView.as_view(), name='label_list'), url(
url(r'^label/(?P<label_id>[0-9]+)/edit$', LabelEditView.as_view(), name='label_edit'), r"^label/(?P<clubaccount_id>[0-9]+)$",
url(r'^label/(?P<label_id>[0-9]+)/delete$', LabelDeleteView.as_view(), name='label_delete'), LabelListView.as_view(),
name="label_list",
),
url(
r"^label/(?P<label_id>[0-9]+)/edit$", LabelEditView.as_view(), name="label_edit"
),
url(
r"^label/(?P<label_id>[0-9]+)/delete$",
LabelDeleteView.as_view(),
name="label_delete",
),
# User account # User account
url(r'^refound/account$', RefoundAccountView.as_view(), name='refound_account'), url(r"^refound/account$", RefoundAccountView.as_view(), name="refound_account"),
] ]

View File

@ -38,9 +38,24 @@ import collections
from ajax_select.fields import AutoCompleteSelectField from ajax_select.fields import AutoCompleteSelectField
from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin, TabedViewMixin from core.views import (
CanViewMixin,
CanEditMixin,
CanEditPropMixin,
CanCreateMixin,
TabedViewMixin,
)
from core.views.forms import SelectFile, SelectDate from core.views.forms import SelectFile, SelectDate
from accounting.models import BankAccount, ClubAccount, GeneralJournal, Operation, AccountingType, Company, SimplifiedAccountingType, Label from accounting.models import (
BankAccount,
ClubAccount,
GeneralJournal,
Operation,
AccountingType,
Company,
SimplifiedAccountingType,
Label,
)
from counter.models import Counter, Selling, Product from counter.models import Counter, Selling, Product
# Main accounting view # Main accounting view
@ -50,185 +65,228 @@ class BankAccountListView(CanViewMixin, ListView):
""" """
A list view for the admins A list view for the admins
""" """
model = BankAccount model = BankAccount
template_name = 'accounting/bank_account_list.jinja' template_name = "accounting/bank_account_list.jinja"
ordering = ['name'] ordering = ["name"]
# Simplified accounting types # Simplified accounting types
class SimplifiedAccountingTypeListView(CanViewMixin, ListView): class SimplifiedAccountingTypeListView(CanViewMixin, ListView):
""" """
A list view for the admins A list view for the admins
""" """
model = SimplifiedAccountingType model = SimplifiedAccountingType
template_name = 'accounting/simplifiedaccountingtype_list.jinja' template_name = "accounting/simplifiedaccountingtype_list.jinja"
class SimplifiedAccountingTypeEditView(CanViewMixin, UpdateView): class SimplifiedAccountingTypeEditView(CanViewMixin, UpdateView):
""" """
An edit view for the admins An edit view for the admins
""" """
model = SimplifiedAccountingType model = SimplifiedAccountingType
pk_url_kwarg = "type_id" pk_url_kwarg = "type_id"
fields = ['label', 'accounting_type'] fields = ["label", "accounting_type"]
template_name = 'core/edit.jinja' template_name = "core/edit.jinja"
class SimplifiedAccountingTypeCreateView(CanCreateMixin, CreateView): class SimplifiedAccountingTypeCreateView(CanCreateMixin, CreateView):
""" """
Create an accounting type (for the admins) Create an accounting type (for the admins)
""" """
model = SimplifiedAccountingType model = SimplifiedAccountingType
fields = ['label', 'accounting_type'] fields = ["label", "accounting_type"]
template_name = 'core/create.jinja' template_name = "core/create.jinja"
# Accounting types # Accounting types
class AccountingTypeListView(CanViewMixin, ListView): class AccountingTypeListView(CanViewMixin, ListView):
""" """
A list view for the admins A list view for the admins
""" """
model = AccountingType model = AccountingType
template_name = 'accounting/accountingtype_list.jinja' template_name = "accounting/accountingtype_list.jinja"
class AccountingTypeEditView(CanViewMixin, UpdateView): class AccountingTypeEditView(CanViewMixin, UpdateView):
""" """
An edit view for the admins An edit view for the admins
""" """
model = AccountingType model = AccountingType
pk_url_kwarg = "type_id" pk_url_kwarg = "type_id"
fields = ['code', 'label', 'movement_type'] fields = ["code", "label", "movement_type"]
template_name = 'core/edit.jinja' template_name = "core/edit.jinja"
class AccountingTypeCreateView(CanCreateMixin, CreateView): class AccountingTypeCreateView(CanCreateMixin, CreateView):
""" """
Create an accounting type (for the admins) Create an accounting type (for the admins)
""" """
model = AccountingType model = AccountingType
fields = ['code', 'label', 'movement_type'] fields = ["code", "label", "movement_type"]
template_name = 'core/create.jinja' template_name = "core/create.jinja"
# BankAccount views # BankAccount views
class BankAccountEditView(CanViewMixin, UpdateView): class BankAccountEditView(CanViewMixin, UpdateView):
""" """
An edit view for the admins An edit view for the admins
""" """
model = BankAccount model = BankAccount
pk_url_kwarg = "b_account_id" pk_url_kwarg = "b_account_id"
fields = ['name', 'iban', 'number', 'club'] fields = ["name", "iban", "number", "club"]
template_name = 'core/edit.jinja' template_name = "core/edit.jinja"
class BankAccountDetailView(CanViewMixin, DetailView): class BankAccountDetailView(CanViewMixin, DetailView):
""" """
A detail view, listing every club account A detail view, listing every club account
""" """
model = BankAccount model = BankAccount
pk_url_kwarg = "b_account_id" pk_url_kwarg = "b_account_id"
template_name = 'accounting/bank_account_details.jinja' template_name = "accounting/bank_account_details.jinja"
class BankAccountCreateView(CanCreateMixin, CreateView): class BankAccountCreateView(CanCreateMixin, CreateView):
""" """
Create a bank account (for the admins) Create a bank account (for the admins)
""" """
model = BankAccount model = BankAccount
fields = ['name', 'club', 'iban', 'number'] fields = ["name", "club", "iban", "number"]
template_name = 'core/create.jinja' template_name = "core/create.jinja"
class BankAccountDeleteView(CanEditPropMixin, DeleteView): # TODO change Delete to Close class BankAccountDeleteView(
CanEditPropMixin, DeleteView
): # TODO change Delete to Close
""" """
Delete a bank account (for the admins) Delete a bank account (for the admins)
""" """
model = BankAccount model = BankAccount
pk_url_kwarg = "b_account_id" pk_url_kwarg = "b_account_id"
template_name = 'core/delete_confirm.jinja' template_name = "core/delete_confirm.jinja"
success_url = reverse_lazy('accounting:bank_list') success_url = reverse_lazy("accounting:bank_list")
# ClubAccount views # ClubAccount views
class ClubAccountEditView(CanViewMixin, UpdateView): class ClubAccountEditView(CanViewMixin, UpdateView):
""" """
An edit view for the admins An edit view for the admins
""" """
model = ClubAccount model = ClubAccount
pk_url_kwarg = "c_account_id" pk_url_kwarg = "c_account_id"
fields = ['name', 'club', 'bank_account'] fields = ["name", "club", "bank_account"]
template_name = 'core/edit.jinja' template_name = "core/edit.jinja"
class ClubAccountDetailView(CanViewMixin, DetailView): class ClubAccountDetailView(CanViewMixin, DetailView):
""" """
A detail view, listing every journal A detail view, listing every journal
""" """
model = ClubAccount model = ClubAccount
pk_url_kwarg = "c_account_id" pk_url_kwarg = "c_account_id"
template_name = 'accounting/club_account_details.jinja' template_name = "accounting/club_account_details.jinja"
class ClubAccountCreateView(CanCreateMixin, CreateView): class ClubAccountCreateView(CanCreateMixin, CreateView):
""" """
Create a club account (for the admins) Create a club account (for the admins)
""" """
model = ClubAccount model = ClubAccount
fields = ['name', 'club', 'bank_account'] fields = ["name", "club", "bank_account"]
template_name = 'core/create.jinja' template_name = "core/create.jinja"
def get_initial(self): def get_initial(self):
ret = super(ClubAccountCreateView, self).get_initial() ret = super(ClubAccountCreateView, self).get_initial()
if 'parent' in self.request.GET.keys(): if "parent" in self.request.GET.keys():
obj = BankAccount.objects.filter(id=int(self.request.GET['parent'])).first() obj = BankAccount.objects.filter(id=int(self.request.GET["parent"])).first()
if obj is not None: if obj is not None:
ret['bank_account'] = obj.id ret["bank_account"] = obj.id
return ret return ret
class ClubAccountDeleteView(CanEditPropMixin, DeleteView): # TODO change Delete to Close class ClubAccountDeleteView(
CanEditPropMixin, DeleteView
): # TODO change Delete to Close
""" """
Delete a club account (for the admins) Delete a club account (for the admins)
""" """
model = ClubAccount model = ClubAccount
pk_url_kwarg = "c_account_id" pk_url_kwarg = "c_account_id"
template_name = 'core/delete_confirm.jinja' template_name = "core/delete_confirm.jinja"
success_url = reverse_lazy('accounting:bank_list') success_url = reverse_lazy("accounting:bank_list")
# Journal views # Journal views
class JournalTabsMixin(TabedViewMixin): class JournalTabsMixin(TabedViewMixin):
def get_tabs_title(self): def get_tabs_title(self):
return _("Journal") return _("Journal")
def get_list_of_tabs(self): def get_list_of_tabs(self):
tab_list = [] tab_list = []
tab_list.append({ tab_list.append(
'url': reverse('accounting:journal_details', kwargs={'j_id': self.object.id}), {
'slug': 'journal', "url": reverse(
'name': _("Journal"), "accounting:journal_details", kwargs={"j_id": self.object.id}
}) ),
tab_list.append({ "slug": "journal",
'url': reverse('accounting:journal_nature_statement', kwargs={'j_id': self.object.id}), "name": _("Journal"),
'slug': 'nature_statement', }
'name': _("Statement by nature"), )
}) tab_list.append(
tab_list.append({ {
'url': reverse('accounting:journal_person_statement', kwargs={'j_id': self.object.id}), "url": reverse(
'slug': 'person_statement', "accounting:journal_nature_statement",
'name': _("Statement by person"), kwargs={"j_id": self.object.id},
}) ),
tab_list.append({ "slug": "nature_statement",
'url': reverse('accounting:journal_accounting_statement', kwargs={'j_id': self.object.id}), "name": _("Statement by nature"),
'slug': 'accounting_statement', }
'name': _("Accounting statement"), )
}) tab_list.append(
{
"url": reverse(
"accounting:journal_person_statement",
kwargs={"j_id": self.object.id},
),
"slug": "person_statement",
"name": _("Statement by person"),
}
)
tab_list.append(
{
"url": reverse(
"accounting:journal_accounting_statement",
kwargs={"j_id": self.object.id},
),
"slug": "accounting_statement",
"name": _("Accounting statement"),
}
)
return tab_list return tab_list
@ -236,17 +294,21 @@ class JournalCreateView(CanCreateMixin, CreateView):
""" """
Create a general journal Create a general journal
""" """
model = GeneralJournal model = GeneralJournal
form_class = modelform_factory(GeneralJournal, fields=['name', 'start_date', 'club_account'], form_class = modelform_factory(
widgets={'start_date': SelectDate, }) GeneralJournal,
template_name = 'core/create.jinja' fields=["name", "start_date", "club_account"],
widgets={"start_date": SelectDate},
)
template_name = "core/create.jinja"
def get_initial(self): def get_initial(self):
ret = super(JournalCreateView, self).get_initial() ret = super(JournalCreateView, self).get_initial()
if 'parent' in self.request.GET.keys(): if "parent" in self.request.GET.keys():
obj = ClubAccount.objects.filter(id=int(self.request.GET['parent'])).first() obj = ClubAccount.objects.filter(id=int(self.request.GET["parent"])).first()
if obj is not None: if obj is not None:
ret['club_account'] = obj.id ret["club_account"] = obj.id
return ret return ret
@ -254,30 +316,33 @@ class JournalDetailView(JournalTabsMixin, CanViewMixin, DetailView):
""" """
A detail view, listing every operation A detail view, listing every operation
""" """
model = GeneralJournal model = GeneralJournal
pk_url_kwarg = "j_id" pk_url_kwarg = "j_id"
template_name = 'accounting/journal_details.jinja' template_name = "accounting/journal_details.jinja"
current_tab = 'journal' current_tab = "journal"
class JournalEditView(CanEditMixin, UpdateView): class JournalEditView(CanEditMixin, UpdateView):
""" """
Update a general journal Update a general journal
""" """
model = GeneralJournal model = GeneralJournal
pk_url_kwarg = "j_id" pk_url_kwarg = "j_id"
fields = ['name', 'start_date', 'end_date', 'club_account', 'closed'] fields = ["name", "start_date", "end_date", "club_account", "closed"]
template_name = 'core/edit.jinja' template_name = "core/edit.jinja"
class JournalDeleteView(CanEditPropMixin, DeleteView): class JournalDeleteView(CanEditPropMixin, DeleteView):
""" """
Delete a club account (for the admins) Delete a club account (for the admins)
""" """
model = GeneralJournal model = GeneralJournal
pk_url_kwarg = "j_id" pk_url_kwarg = "j_id"
template_name = 'core/delete_confirm.jinja' template_name = "core/delete_confirm.jinja"
success_url = reverse_lazy('accounting:club_details') success_url = reverse_lazy("accounting:club_details")
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
@ -289,64 +354,105 @@ class JournalDeleteView(CanEditPropMixin, DeleteView):
# Operation views # Operation views
class OperationForm(forms.ModelForm): class OperationForm(forms.ModelForm):
class Meta: class Meta:
model = Operation model = Operation
fields = ['amount', 'remark', 'journal', 'target_type', 'target_id', 'target_label', 'date', 'mode', fields = [
'cheque_number', 'invoice', 'simpleaccounting_type', 'accounting_type', 'label', 'done'] "amount",
"remark",
"journal",
"target_type",
"target_id",
"target_label",
"date",
"mode",
"cheque_number",
"invoice",
"simpleaccounting_type",
"accounting_type",
"label",
"done",
]
widgets = { widgets = {
'journal': HiddenInput, "journal": HiddenInput,
'target_id': HiddenInput, "target_id": HiddenInput,
'date': SelectDate, "date": SelectDate,
'invoice': SelectFile, "invoice": SelectFile,
} }
user = AutoCompleteSelectField('users', help_text=None, required=False)
club_account = AutoCompleteSelectField('club_accounts', help_text=None, required=False) user = AutoCompleteSelectField("users", help_text=None, required=False)
club = AutoCompleteSelectField('clubs', help_text=None, required=False) club_account = AutoCompleteSelectField(
company = AutoCompleteSelectField('companies', help_text=None, required=False) "club_accounts", help_text=None, required=False
need_link = forms.BooleanField(label=_("Link this operation to the target account"), required=False, initial=False) )
club = AutoCompleteSelectField("clubs", help_text=None, required=False)
company = AutoCompleteSelectField("companies", help_text=None, required=False)
need_link = forms.BooleanField(
label=_("Link this operation to the target account"),
required=False,
initial=False,
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
club_account = kwargs.pop('club_account', None) club_account = kwargs.pop("club_account", None)
super(OperationForm, self).__init__(*args, **kwargs) super(OperationForm, self).__init__(*args, **kwargs)
if club_account: if club_account:
self.fields['label'].queryset = club_account.labels.order_by('name').all() self.fields["label"].queryset = club_account.labels.order_by("name").all()
if self.instance.target_type == "USER": if self.instance.target_type == "USER":
self.fields['user'].initial = self.instance.target_id self.fields["user"].initial = self.instance.target_id
elif self.instance.target_type == "ACCOUNT": elif self.instance.target_type == "ACCOUNT":
self.fields['club_account'].initial = self.instance.target_id self.fields["club_account"].initial = self.instance.target_id
elif self.instance.target_type == "CLUB": elif self.instance.target_type == "CLUB":
self.fields['club'].initial = self.instance.target_id self.fields["club"].initial = self.instance.target_id
elif self.instance.target_type == "COMPANY": elif self.instance.target_type == "COMPANY":
self.fields['company'].initial = self.instance.target_id self.fields["company"].initial = self.instance.target_id
def clean(self): def clean(self):
self.cleaned_data = super(OperationForm, self).clean() self.cleaned_data = super(OperationForm, self).clean()
if 'target_type' in self.cleaned_data.keys(): if "target_type" in self.cleaned_data.keys():
if self.cleaned_data.get("user") is None and self.cleaned_data.get("club") is None and self.cleaned_data.get("club_account") is None and self.cleaned_data.get("company") is None and self.cleaned_data.get("target_label") is None: if (
self.add_error('target_type', ValidationError(_("The target must be set."))) self.cleaned_data.get("user") is None
and self.cleaned_data.get("club") is None
and self.cleaned_data.get("club_account") is None
and self.cleaned_data.get("company") is None
and self.cleaned_data.get("target_label") is None
):
self.add_error(
"target_type", ValidationError(_("The target must be set."))
)
else: else:
if self.cleaned_data['target_type'] == "USER": if self.cleaned_data["target_type"] == "USER":
self.cleaned_data['target_id'] = self.cleaned_data['user'].id self.cleaned_data["target_id"] = self.cleaned_data["user"].id
elif self.cleaned_data['target_type'] == "ACCOUNT": elif self.cleaned_data["target_type"] == "ACCOUNT":
self.cleaned_data['target_id'] = self.cleaned_data['club_account'].id self.cleaned_data["target_id"] = self.cleaned_data[
elif self.cleaned_data['target_type'] == "CLUB": "club_account"
self.cleaned_data['target_id'] = self.cleaned_data['club'].id ].id
elif self.cleaned_data['target_type'] == "COMPANY": elif self.cleaned_data["target_type"] == "CLUB":
self.cleaned_data['target_id'] = self.cleaned_data['company'].id self.cleaned_data["target_id"] = self.cleaned_data["club"].id
elif self.cleaned_data["target_type"] == "COMPANY":
self.cleaned_data["target_id"] = self.cleaned_data["company"].id
if self.cleaned_data.get("amount") is None: if self.cleaned_data.get("amount") is None:
self.add_error('amount', ValidationError(_("The amount must be set."))) self.add_error("amount", ValidationError(_("The amount must be set.")))
return self.cleaned_data return self.cleaned_data
def save(self): def save(self):
ret = super(OperationForm, self).save() ret = super(OperationForm, self).save()
if self.instance.target_type == "ACCOUNT" and not self.instance.linked_operation and self.instance.target.has_open_journal() and self.cleaned_data['need_link']: if (
self.instance.target_type == "ACCOUNT"
and not self.instance.linked_operation
and self.instance.target.has_open_journal()
and self.cleaned_data["need_link"]
):
inst = self.instance inst = self.instance
club_account = inst.target club_account = inst.target
acc_type = AccountingType.objects.exclude(movement_type="NEUTRAL").exclude( acc_type = (
movement_type=inst.accounting_type.movement_type).order_by('code').first() # Select a random opposite accounting type AccountingType.objects.exclude(movement_type="NEUTRAL")
.exclude(movement_type=inst.accounting_type.movement_type)
.order_by("code")
.first()
) # Select a random opposite accounting type
op = Operation( op = Operation(
journal=club_account.get_open_journal(), journal=club_account.get_open_journal(),
amount=inst.amount, amount=inst.amount,
@ -373,26 +479,27 @@ class OperationCreateView(CanCreateMixin, CreateView):
""" """
Create an operation Create an operation
""" """
model = Operation model = Operation
form_class = OperationForm form_class = OperationForm
template_name = 'accounting/operation_edit.jinja' template_name = "accounting/operation_edit.jinja"
def get_form(self, form_class=None): def get_form(self, form_class=None):
self.journal = GeneralJournal.objects.filter(id=self.kwargs['j_id']).first() self.journal = GeneralJournal.objects.filter(id=self.kwargs["j_id"]).first()
ca = self.journal.club_account if self.journal else None ca = self.journal.club_account if self.journal else None
return self.form_class(club_account=ca, **self.get_form_kwargs()) return self.form_class(club_account=ca, **self.get_form_kwargs())
def get_initial(self): def get_initial(self):
ret = super(OperationCreateView, self).get_initial() ret = super(OperationCreateView, self).get_initial()
if self.journal is not None: if self.journal is not None:
ret['journal'] = self.journal.id ret["journal"] = self.journal.id
return ret return ret
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
""" Add journal to the context """ """ Add journal to the context """
kwargs = super(OperationCreateView, self).get_context_data(**kwargs) kwargs = super(OperationCreateView, self).get_context_data(**kwargs)
if self.journal: if self.journal:
kwargs['object'] = self.journal kwargs["object"] = self.journal
return kwargs return kwargs
@ -400,15 +507,16 @@ class OperationEditView(CanEditMixin, UpdateView):
""" """
An edit view, working as detail for the moment An edit view, working as detail for the moment
""" """
model = Operation model = Operation
pk_url_kwarg = "op_id" pk_url_kwarg = "op_id"
form_class = OperationForm form_class = OperationForm
template_name = 'accounting/operation_edit.jinja' template_name = "accounting/operation_edit.jinja"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
""" Add journal to the context """ """ Add journal to the context """
kwargs = super(OperationEditView, self).get_context_data(**kwargs) kwargs = super(OperationEditView, self).get_context_data(**kwargs)
kwargs['object'] = self.object.journal kwargs["object"] = self.object.journal
return kwargs return kwargs
@ -430,7 +538,7 @@ class OperationPDFView(CanViewMixin, DetailView):
from reportlab.pdfbase.ttfonts import TTFont from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase import pdfmetrics
pdfmetrics.registerFont(TTFont('DejaVu', 'DejaVuSerif.ttf')) pdfmetrics.registerFont(TTFont("DejaVu", "DejaVuSerif.ttf"))
self.object = self.get_object() self.object = self.get_object()
amount = self.object.amount amount = self.object.amount
@ -450,11 +558,15 @@ class OperationPDFView(CanViewMixin, DetailView):
else: else:
target = self.object.target.get_display_name() target = self.object.target.get_display_name()
response = HttpResponse(content_type='application/pdf') response = HttpResponse(content_type="application/pdf")
response['Content-Disposition'] = 'filename="op-%d(%s_on_%s).pdf"' % (num, ti, club_name) response["Content-Disposition"] = 'filename="op-%d(%s_on_%s).pdf"' % (
num,
ti,
club_name,
)
p = canvas.Canvas(response) p = canvas.Canvas(response)
p.setFont('DejaVu', 12) p.setFont("DejaVu", 12)
p.setTitle("%s %d" % (_("Operation"), num)) p.setTitle("%s %d" % (_("Operation"), num))
width, height = letter width, height = letter
@ -466,20 +578,29 @@ class OperationPDFView(CanViewMixin, DetailView):
label = Table(labelStr, colWidths=[150], rowHeights=[20]) label = Table(labelStr, colWidths=[150], rowHeights=[20])
label.setStyle(TableStyle([ label.setStyle(TableStyle([("ALIGN", (0, 0), (-1, -1), "RIGHT")]))
('ALIGN', (0, 0), (-1, -1), 'RIGHT'),
]))
w, h = label.wrapOn(label, 0, 0) w, h = label.wrapOn(label, 0, 0)
label.drawOn(p, width - 180, height) label.drawOn(p, width - 180, height)
p.drawString(90, height - 100, _("Financial proof: ") + "OP%010d" % (id_op)) # Justificatif du libellé p.drawString(
p.drawString(90, height - 130, _("Club: %(club_name)s") % ({"club_name": club_name})) 90, height - 100, _("Financial proof: ") + "OP%010d" % (id_op)
p.drawString(90, height - 160, _("Label: %(op_label)s") % {"op_label": op_label if op_label is not None else ""}) ) # Justificatif du libellé
p.drawString(
90, height - 130, _("Club: %(club_name)s") % ({"club_name": club_name})
)
p.drawString(
90,
height - 160,
_("Label: %(op_label)s")
% {"op_label": op_label if op_label is not None else ""},
)
p.drawString(90, height - 190, _("Date: %(date)s") % {"date": date}) p.drawString(90, height - 190, _("Date: %(date)s") % {"date": date})
data = [] data = []
data += [["%s" % (_("Credit").upper() if nature == 'CREDIT' else _("Debit").upper())]] data += [
["%s" % (_("Credit").upper() if nature == "CREDIT" else _("Debit").upper())]
]
data += [[_("Amount: %(amount).2f") % {"amount": amount}]] data += [[_("Amount: %(amount).2f") % {"amount": amount}]]
@ -493,33 +614,50 @@ class OperationPDFView(CanViewMixin, DetailView):
data += [[payment_mode]] data += [[payment_mode]]
data += [["%s : %s" % (_("Debtor") if nature == 'CREDIT' else _("Creditor"), target), ""]] data += [
[
"%s : %s"
% (_("Debtor") if nature == "CREDIT" else _("Creditor"), target),
"",
]
]
data += [["%s \n%s" % (_("Comment:"), remark)]] data += [["%s \n%s" % (_("Comment:"), remark)]]
t = Table(data, colWidths=[(width - 90 * 2) / 2] * 2, rowHeights=[20, 20, 70, 20, 80]) t = Table(
t.setStyle(TableStyle([ data, colWidths=[(width - 90 * 2) / 2] * 2, rowHeights=[20, 20, 70, 20, 80]
('ALIGN', (0, 0), (-1, -1), 'CENTER'), )
('VALIGN', (-2, -1), (-1, -1), 'TOP'), t.setStyle(
('VALIGN', (0, 0), (-1, -2), 'MIDDLE'), TableStyle(
('INNERGRID', (0, 0), (-1, -1), 0.25, colors.black), [
('SPAN', (0, 0), (1, 0)), # line DEBIT/CREDIT ("ALIGN", (0, 0), (-1, -1), "CENTER"),
('SPAN', (0, 1), (1, 1)), # line amount ("VALIGN", (-2, -1), (-1, -1), "TOP"),
('SPAN', (-2, -1), (-1, -1)), # line comment ("VALIGN", (0, 0), (-1, -2), "MIDDLE"),
('SPAN', (0, -2), (-1, -2)), # line creditor/debtor ("INNERGRID", (0, 0), (-1, -1), 0.25, colors.black),
('SPAN', (0, 2), (1, 2)), # line payment_mode ("SPAN", (0, 0), (1, 0)), # line DEBIT/CREDIT
('ALIGN', (0, 2), (1, 2), 'LEFT'), # line payment_mode ("SPAN", (0, 1), (1, 1)), # line amount
('ALIGN', (-2, -1), (-1, -1), 'LEFT'), ("SPAN", (-2, -1), (-1, -1)), # line comment
('BOX', (0, 0), (-1, -1), 0.25, colors.black), ("SPAN", (0, -2), (-1, -2)), # line creditor/debtor
])) ("SPAN", (0, 2), (1, 2)), # line payment_mode
("ALIGN", (0, 2), (1, 2), "LEFT"), # line payment_mode
("ALIGN", (-2, -1), (-1, -1), "LEFT"),
("BOX", (0, 0), (-1, -1), 0.25, colors.black),
]
)
)
signature = [] signature = []
signature += [[_("Signature:")]] signature += [[_("Signature:")]]
tSig = Table(signature, colWidths=[(width - 90 * 2)], rowHeights=[80]) tSig = Table(signature, colWidths=[(width - 90 * 2)], rowHeights=[80])
tSig.setStyle(TableStyle([ tSig.setStyle(
('VALIGN', (0, 0), (-1, -1), 'TOP'), TableStyle(
('BOX', (0, 0), (-1, -1), 0.25, colors.black)])) [
("VALIGN", (0, 0), (-1, -1), "TOP"),
("BOX", (0, 0), (-1, -1), 0.25, colors.black),
]
)
)
w, h = tSig.wrapOn(p, 0, 0) w, h = tSig.wrapOn(p, 0, 0)
tSig.drawOn(p, 90, 200) tSig.drawOn(p, 90, 200)
@ -540,18 +678,22 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView):
""" """
Display a statement sorted by labels Display a statement sorted by labels
""" """
model = GeneralJournal model = GeneralJournal
pk_url_kwarg = "j_id" pk_url_kwarg = "j_id"
template_name = 'accounting/journal_statement_nature.jinja' template_name = "accounting/journal_statement_nature.jinja"
current_tab = 'nature_statement' current_tab = "nature_statement"
def statement(self, queryset, movement_type): def statement(self, queryset, movement_type):
ret = collections.OrderedDict() ret = collections.OrderedDict()
statement = collections.OrderedDict() statement = collections.OrderedDict()
total_sum = 0 total_sum = 0
for sat in [None] + list(SimplifiedAccountingType.objects.order_by('label').all()): for sat in [None] + list(
sum = queryset.filter(accounting_type__movement_type=movement_type, SimplifiedAccountingType.objects.order_by("label").all()
simpleaccounting_type=sat).aggregate(amount_sum=Sum('amount'))['amount_sum'] ):
sum = queryset.filter(
accounting_type__movement_type=movement_type, simpleaccounting_type=sat
).aggregate(amount_sum=Sum("amount"))["amount_sum"]
if sat: if sat:
sat = sat.label sat = sat.label
else: else:
@ -564,7 +706,9 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView):
return ret return ret
def big_statement(self): def big_statement(self):
label_list = self.object.operations.order_by('label').values_list('label').distinct() label_list = (
self.object.operations.order_by("label").values_list("label").distinct()
)
labels = Label.objects.filter(id__in=label_list).all() labels = Label.objects.filter(id__in=label_list).all()
statement = collections.OrderedDict() statement = collections.OrderedDict()
gen_statement = collections.OrderedDict() gen_statement = collections.OrderedDict()
@ -572,20 +716,28 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView):
gen_statement.update(self.statement(self.object.operations.all(), "CREDIT")) gen_statement.update(self.statement(self.object.operations.all(), "CREDIT"))
gen_statement.update(self.statement(self.object.operations.all(), "DEBIT")) gen_statement.update(self.statement(self.object.operations.all(), "DEBIT"))
statement[_("General statement")] = gen_statement statement[_("General statement")] = gen_statement
no_label_statement.update(self.statement(self.object.operations.filter(label=None).all(), "CREDIT")) no_label_statement.update(
no_label_statement.update(self.statement(self.object.operations.filter(label=None).all(), "DEBIT")) self.statement(self.object.operations.filter(label=None).all(), "CREDIT")
)
no_label_statement.update(
self.statement(self.object.operations.filter(label=None).all(), "DEBIT")
)
statement[_("No label operations")] = no_label_statement statement[_("No label operations")] = no_label_statement
for l in labels: for l in labels:
l_stmt = collections.OrderedDict() l_stmt = collections.OrderedDict()
l_stmt.update(self.statement(self.object.operations.filter(label=l).all(), "CREDIT")) l_stmt.update(
l_stmt.update(self.statement(self.object.operations.filter(label=l).all(), "DEBIT")) self.statement(self.object.operations.filter(label=l).all(), "CREDIT")
)
l_stmt.update(
self.statement(self.object.operations.filter(label=l).all(), "DEBIT")
)
statement[l] = l_stmt statement[l] = l_stmt
return statement return statement
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
""" Add infos to the context """ """ Add infos to the context """
kwargs = super(JournalNatureStatementView, self).get_context_data(**kwargs) kwargs = super(JournalNatureStatementView, self).get_context_data(**kwargs)
kwargs['statement'] = self.big_statement() kwargs["statement"] = self.big_statement()
return kwargs return kwargs
@ -593,20 +745,29 @@ class JournalPersonStatementView(JournalTabsMixin, CanViewMixin, DetailView):
""" """
Calculate a dictionary with operation target and sum of operations Calculate a dictionary with operation target and sum of operations
""" """
model = GeneralJournal model = GeneralJournal
pk_url_kwarg = "j_id" pk_url_kwarg = "j_id"
template_name = 'accounting/journal_statement_person.jinja' template_name = "accounting/journal_statement_person.jinja"
current_tab = 'person_statement' current_tab = "person_statement"
def sum_by_target(self, target_id, target_type, movement_type): def sum_by_target(self, target_id, target_type, movement_type):
return self.object.operations.filter(accounting_type__movement_type=movement_type, return self.object.operations.filter(
target_id=target_id, target_type=target_type).aggregate(amount_sum=Sum('amount'))['amount_sum'] accounting_type__movement_type=movement_type,
target_id=target_id,
target_type=target_type,
).aggregate(amount_sum=Sum("amount"))["amount_sum"]
def statement(self, movement_type): def statement(self, movement_type):
statement = collections.OrderedDict() statement = collections.OrderedDict()
for op in self.object.operations.filter(accounting_type__movement_type=movement_type).order_by('target_type', for op in (
'target_id').distinct(): self.object.operations.filter(accounting_type__movement_type=movement_type)
statement[op.target] = self.sum_by_target(op.target_id, op.target_type, movement_type) .order_by("target_type", "target_id")
.distinct()
):
statement[op.target] = self.sum_by_target(
op.target_id, op.target_type, movement_type
)
return statement return statement
def total(self, movement_type): def total(self, movement_type):
@ -615,10 +776,10 @@ class JournalPersonStatementView(JournalTabsMixin, CanViewMixin, DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
""" Add journal to the context """ """ Add journal to the context """
kwargs = super(JournalPersonStatementView, self).get_context_data(**kwargs) kwargs = super(JournalPersonStatementView, self).get_context_data(**kwargs)
kwargs['credit_statement'] = self.statement("CREDIT") kwargs["credit_statement"] = self.statement("CREDIT")
kwargs['debit_statement'] = self.statement("DEBIT") kwargs["debit_statement"] = self.statement("DEBIT")
kwargs['total_credit'] = self.total("CREDIT") kwargs["total_credit"] = self.total("CREDIT")
kwargs['total_debit'] = self.total("DEBIT") kwargs["total_debit"] = self.total("DEBIT")
return kwargs return kwargs
@ -626,16 +787,18 @@ class JournalAccountingStatementView(JournalTabsMixin, CanViewMixin, DetailView)
""" """
Calculate a dictionary with operation type and sum of operations Calculate a dictionary with operation type and sum of operations
""" """
model = GeneralJournal model = GeneralJournal
pk_url_kwarg = "j_id" pk_url_kwarg = "j_id"
template_name = 'accounting/journal_statement_accounting.jinja' template_name = "accounting/journal_statement_accounting.jinja"
current_tab = "accounting_statement" current_tab = "accounting_statement"
def statement(self): def statement(self):
statement = collections.OrderedDict() statement = collections.OrderedDict()
for at in AccountingType.objects.order_by('code').all(): for at in AccountingType.objects.order_by("code").all():
sum_by_type = self.object.operations.filter( sum_by_type = self.object.operations.filter(
accounting_type__code__startswith=at.code).aggregate(amount_sum=Sum('amount'))['amount_sum'] accounting_type__code__startswith=at.code
).aggregate(amount_sum=Sum("amount"))["amount_sum"]
if sum_by_type: if sum_by_type:
statement[at] = sum_by_type statement[at] = sum_by_type
return statement return statement
@ -643,86 +806,95 @@ class JournalAccountingStatementView(JournalTabsMixin, CanViewMixin, DetailView)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
""" Add journal to the context """ """ Add journal to the context """
kwargs = super(JournalAccountingStatementView, self).get_context_data(**kwargs) kwargs = super(JournalAccountingStatementView, self).get_context_data(**kwargs)
kwargs['statement'] = self.statement() kwargs["statement"] = self.statement()
return kwargs return kwargs
# Company views # Company views
class CompanyListView(CanViewMixin, ListView): class CompanyListView(CanViewMixin, ListView):
model = Company model = Company
template_name = 'accounting/co_list.jinja' template_name = "accounting/co_list.jinja"
class CompanyCreateView(CanCreateMixin, CreateView): class CompanyCreateView(CanCreateMixin, CreateView):
""" """
Create a company Create a company
""" """
model = Company model = Company
fields = ['name'] fields = ["name"]
template_name = 'core/create.jinja' template_name = "core/create.jinja"
success_url = reverse_lazy('accounting:co_list') success_url = reverse_lazy("accounting:co_list")
class CompanyEditView(CanCreateMixin, UpdateView): class CompanyEditView(CanCreateMixin, UpdateView):
""" """
Edit a company Edit a company
""" """
model = Company model = Company
pk_url_kwarg = "co_id" pk_url_kwarg = "co_id"
fields = ['name'] fields = ["name"]
template_name = 'core/edit.jinja' template_name = "core/edit.jinja"
success_url = reverse_lazy('accounting:co_list') success_url = reverse_lazy("accounting:co_list")
# Label views # Label views
class LabelListView(CanViewMixin, DetailView): class LabelListView(CanViewMixin, DetailView):
model = ClubAccount model = ClubAccount
pk_url_kwarg = "clubaccount_id" pk_url_kwarg = "clubaccount_id"
template_name = 'accounting/label_list.jinja' template_name = "accounting/label_list.jinja"
class LabelCreateView(CanCreateMixin, CreateView): # FIXME we need to check the rights before creating the object class LabelCreateView(
CanCreateMixin, CreateView
): # FIXME we need to check the rights before creating the object
model = Label model = Label
form_class = modelform_factory(Label, fields=['name', 'club_account'], widgets={ form_class = modelform_factory(
'club_account': HiddenInput, Label, fields=["name", "club_account"], widgets={"club_account": HiddenInput}
}) )
template_name = 'core/create.jinja' template_name = "core/create.jinja"
def get_initial(self): def get_initial(self):
ret = super(LabelCreateView, self).get_initial() ret = super(LabelCreateView, self).get_initial()
if 'parent' in self.request.GET.keys(): if "parent" in self.request.GET.keys():
obj = ClubAccount.objects.filter(id=int(self.request.GET['parent'])).first() obj = ClubAccount.objects.filter(id=int(self.request.GET["parent"])).first()
if obj is not None: if obj is not None:
ret['club_account'] = obj.id ret["club_account"] = obj.id
return ret return ret
class LabelEditView(CanEditMixin, UpdateView): class LabelEditView(CanEditMixin, UpdateView):
model = Label model = Label
pk_url_kwarg = "label_id" pk_url_kwarg = "label_id"
fields = ['name'] fields = ["name"]
template_name = 'core/edit.jinja' template_name = "core/edit.jinja"
class LabelDeleteView(CanEditMixin, DeleteView): class LabelDeleteView(CanEditMixin, DeleteView):
model = Label model = Label
pk_url_kwarg = "label_id" pk_url_kwarg = "label_id"
template_name = 'core/delete_confirm.jinja' template_name = "core/delete_confirm.jinja"
def get_success_url(self): def get_success_url(self):
return self.object.get_absolute_url() return self.object.get_absolute_url()
class CloseCustomerAccountForm(forms.Form): class CloseCustomerAccountForm(forms.Form):
user = AutoCompleteSelectField('users', label=_('Refound this account'), help_text=None, required=True) user = AutoCompleteSelectField(
"users", label=_("Refound this account"), help_text=None, required=True
)
class RefoundAccountView(FormView): class RefoundAccountView(FormView):
""" """
Create a selling with the same amount than the current user money Create a selling with the same amount than the current user money
""" """
template_name = "accounting/refound_account.jinja" template_name = "accounting/refound_account.jinja"
form_class = CloseCustomerAccountForm form_class = CloseCustomerAccountForm
@ -743,21 +915,28 @@ class RefoundAccountView(FormView):
return super(RefoundAccountView, self).post(self, request, *arg, **kwargs) return super(RefoundAccountView, self).post(self, request, *arg, **kwargs)
def form_valid(self, form): def form_valid(self, form):
self.customer = form.cleaned_data['user'] self.customer = form.cleaned_data["user"]
self.create_selling() self.create_selling()
return super(RefoundAccountView, self).form_valid(form) return super(RefoundAccountView, self).form_valid(form)
def get_success_url(self): def get_success_url(self):
return reverse('accounting:refound_account') return reverse("accounting:refound_account")
def create_selling(self): def create_selling(self):
with transaction.atomic(): with transaction.atomic():
uprice = self.customer.customer.amount uprice = self.customer.customer.amount
refound_club_counter = Counter.objects.get(id=settings.SITH_COUNTER_REFOUND_ID) refound_club_counter = Counter.objects.get(
id=settings.SITH_COUNTER_REFOUND_ID
)
refound_club = refound_club_counter.club refound_club = refound_club_counter.club
s = Selling(label=_('Refound account'), unit_price=uprice, s = Selling(
quantity=1, seller=self.operator, label=_("Refound account"),
customer=self.customer.customer, unit_price=uprice,
club=refound_club, counter=refound_club_counter, quantity=1,
product=Product.objects.get(id=settings.SITH_PRODUCT_REFOUND_ID)) seller=self.operator,
customer=self.customer.customer,
club=refound_club,
counter=refound_club_counter,
product=Product.objects.get(id=settings.SITH_PRODUCT_REFOUND_ID),
)
s.save() s.save()

View File

@ -21,4 +21,3 @@
# Place - Suite 330, Boston, MA 02111-1307, USA. # Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #

View File

@ -29,25 +29,28 @@ from rest_framework import routers
# Router config # Router config
router = routers.DefaultRouter() router = routers.DefaultRouter()
router.register(r'counter', CounterViewSet, base_name='api_counter') router.register(r"counter", CounterViewSet, base_name="api_counter")
router.register(r'user', UserViewSet, base_name='api_user') router.register(r"user", UserViewSet, base_name="api_user")
router.register(r'club', ClubViewSet, base_name='api_club') router.register(r"club", ClubViewSet, base_name="api_club")
router.register(r'group', GroupViewSet, base_name='api_group') router.register(r"group", GroupViewSet, base_name="api_group")
# Launderette # Launderette
router.register(r'launderette/place', LaunderettePlaceViewSet, router.register(
base_name='api_launderette_place') r"launderette/place", LaunderettePlaceViewSet, base_name="api_launderette_place"
router.register(r'launderette/machine', LaunderetteMachineViewSet, )
base_name='api_launderette_machine') router.register(
router.register(r'launderette/token', LaunderetteTokenViewSet, r"launderette/machine",
base_name='api_launderette_token') LaunderetteMachineViewSet,
base_name="api_launderette_machine",
)
router.register(
r"launderette/token", LaunderetteTokenViewSet, base_name="api_launderette_token"
)
urlpatterns = [ urlpatterns = [
# API # API
url(r'^', include(router.urls)), url(r"^", include(router.urls)),
url(r'^login/', include('rest_framework.urls', namespace='rest_framework')), url(r"^login/", include("rest_framework.urls", namespace="rest_framework")),
url(r'^markdown$', RenderMarkdown, name='api_markdown'), url(r"^markdown$", RenderMarkdown, name="api_markdown"),
url(r'^mailings$', FetchMailingLists, name='mailings_fetch') url(r"^mailings$", FetchMailingLists, name="mailings_fetch"),
] ]

View File

@ -30,19 +30,21 @@ from django.db.models.query import QuerySet
from core.views import can_view, can_edit from core.views import can_view, can_edit
def check_if(obj, user, test): def check_if(obj, user, test):
""" """
Detect if it's a single object or a queryset Detect if it's a single object or a queryset
aply a given test on individual object and return global permission aply a given test on individual object and return global permission
""" """
if (isinstance(obj, QuerySet)): if isinstance(obj, QuerySet):
for o in obj: for o in obj:
if (test(o, user) is False): if test(o, user) is False:
return False return False
return True return True
else: else:
return test(obj, user) return test(obj, user)
class ManageModelMixin: class ManageModelMixin:
@detail_route() @detail_route()
def id(self, request, pk=None): def id(self, request, pk=None):
@ -53,19 +55,19 @@ class ManageModelMixin:
serializer = self.get_serializer(self.queryset) serializer = self.get_serializer(self.queryset)
return Response(serializer.data) return Response(serializer.data)
class RightModelViewSet(ManageModelMixin, viewsets.ModelViewSet):
class RightModelViewSet(ManageModelMixin, viewsets.ModelViewSet):
def dispatch(self, request, *arg, **kwargs): def dispatch(self, request, *arg, **kwargs):
res = super(RightModelViewSet, res = super(RightModelViewSet, self).dispatch(request, *arg, **kwargs)
self).dispatch(request, *arg, **kwargs)
obj = self.queryset obj = self.queryset
user = self.request.user user = self.request.user
try: try:
if (request.method == 'GET' and check_if(obj, user, can_view)): if request.method == "GET" and check_if(obj, user, can_view):
return res return res
if (request.method != 'GET' and check_if(obj, user, can_edit)): if request.method != "GET" and check_if(obj, user, can_edit):
return res return res
except: pass # To prevent bug with Anonymous user except:
pass # To prevent bug with Anonymous user
raise PermissionDenied raise PermissionDenied
@ -74,4 +76,4 @@ from .counter import *
from .user import * from .user import *
from .club import * from .club import *
from .group import * from .group import *
from .launderette import * from .launderette import *

View File

@ -30,15 +30,14 @@ from rest_framework.views import APIView
from core.templatetags.renderer import markdown from core.templatetags.renderer import markdown
@api_view(['POST']) @api_view(["POST"])
@renderer_classes((StaticHTMLRenderer,)) @renderer_classes((StaticHTMLRenderer,))
def RenderMarkdown(request): def RenderMarkdown(request):
""" """
Render Markdown Render Markdown
""" """
try: try:
data = markdown(request.POST['text']) data = markdown(request.POST["text"])
except: except:
data = 'Error' data = "Error"
return Response(data) return Response(data)

View File

@ -36,10 +36,9 @@ from api.views import RightModelViewSet
class ClubSerializer(serializers.ModelSerializer): class ClubSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Club model = Club
fields = ('id', 'name', 'unix_name', 'address', 'members') fields = ("id", "name", "unix_name", "address", "members")
class ClubViewSet(RightModelViewSet): class ClubViewSet(RightModelViewSet):
@ -51,13 +50,15 @@ class ClubViewSet(RightModelViewSet):
queryset = Club.objects.all() queryset = Club.objects.all()
@api_view(['GET']) @api_view(["GET"])
@renderer_classes((StaticHTMLRenderer,)) @renderer_classes((StaticHTMLRenderer,))
def FetchMailingLists(request): def FetchMailingLists(request):
key = request.GET.get('key', '') key = request.GET.get("key", "")
if key != settings.SITH_MAILING_FETCH_KEY: if key != settings.SITH_MAILING_FETCH_KEY:
raise PermissionDenied raise PermissionDenied
data = '' data = ""
for mailing in Mailing.objects.filter(is_moderated=True, club__is_active=True).all(): for mailing in Mailing.objects.filter(
is_moderated=True, club__is_active=True
).all():
data += mailing.fetch_format() + "\n" data += mailing.fetch_format() + "\n"
return Response(data) return Response(data)

View File

@ -35,14 +35,12 @@ class CounterSerializer(serializers.ModelSerializer):
is_open = serializers.BooleanField(read_only=True) is_open = serializers.BooleanField(read_only=True)
barman_list = serializers.ListField( barman_list = serializers.ListField(
child=serializers.IntegerField(), child=serializers.IntegerField(), read_only=True
read_only=True
) )
class Meta: class Meta:
model = Counter model = Counter
fields = ('id', 'name', 'type', 'club', fields = ("id", "name", "type", "club", "products", "is_open", "barman_list")
'products', 'is_open', 'barman_list')
class CounterViewSet(RightModelViewSet): class CounterViewSet(RightModelViewSet):

View File

@ -30,7 +30,6 @@ from api.views import RightModelViewSet
class GroupSerializer(serializers.ModelSerializer): class GroupSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = RealGroup model = RealGroup

View File

@ -30,34 +30,45 @@ from launderette.models import Launderette, Machine, Token
from api.views import RightModelViewSet from api.views import RightModelViewSet
class LaunderettePlaceSerializer(serializers.ModelSerializer): class LaunderettePlaceSerializer(serializers.ModelSerializer):
machine_list = serializers.ListField( machine_list = serializers.ListField(
child=serializers.IntegerField(), child=serializers.IntegerField(), read_only=True
read_only=True
)
token_list = serializers.ListField(
child=serializers.IntegerField(),
read_only=True
) )
token_list = serializers.ListField(child=serializers.IntegerField(), read_only=True)
class Meta: class Meta:
model = Launderette model = Launderette
fields = ('id', 'name', 'counter', 'machine_list', fields = (
'token_list', 'get_absolute_url') "id",
"name",
"counter",
"machine_list",
"token_list",
"get_absolute_url",
)
class LaunderetteMachineSerializer(serializers.ModelSerializer): class LaunderetteMachineSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Machine model = Machine
fields = ('id', 'name', 'type', 'is_working', 'launderette') fields = ("id", "name", "type", "is_working", "launderette")
class LaunderetteTokenSerializer(serializers.ModelSerializer): class LaunderetteTokenSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Token model = Token
fields = ('id', 'name', 'type', 'launderette', 'borrow_date', fields = (
'user', 'is_avaliable') "id",
"name",
"type",
"launderette",
"borrow_date",
"user",
"is_avaliable",
)
class LaunderettePlaceViewSet(RightModelViewSet): class LaunderettePlaceViewSet(RightModelViewSet):
""" """
@ -67,6 +78,7 @@ class LaunderettePlaceViewSet(RightModelViewSet):
serializer_class = LaunderettePlaceSerializer serializer_class = LaunderettePlaceSerializer
queryset = Launderette.objects.all() queryset = Launderette.objects.all()
class LaunderetteMachineViewSet(RightModelViewSet): class LaunderetteMachineViewSet(RightModelViewSet):
""" """
Manage Washing Machines (api/v1/launderette/machine/) Manage Washing Machines (api/v1/launderette/machine/)
@ -89,7 +101,7 @@ class LaunderetteTokenViewSet(RightModelViewSet):
""" """
Return all washing tokens (api/v1/launderette/token/washing) Return all washing tokens (api/v1/launderette/token/washing)
""" """
self.queryset = self.queryset.filter(type='WASHING') self.queryset = self.queryset.filter(type="WASHING")
serializer = self.get_serializer(self.queryset, many=True) serializer = self.get_serializer(self.queryset, many=True)
return Response(serializer.data) return Response(serializer.data)
@ -98,7 +110,7 @@ class LaunderetteTokenViewSet(RightModelViewSet):
""" """
Return all drying tokens (api/v1/launderette/token/drying) Return all drying tokens (api/v1/launderette/token/drying)
""" """
self.queryset = self.queryset.filter(type='DRYING') self.queryset = self.queryset.filter(type="DRYING")
serializer = self.get_serializer(self.queryset, many=True) serializer = self.get_serializer(self.queryset, many=True)
return Response(serializer.data) return Response(serializer.data)
@ -107,7 +119,9 @@ class LaunderetteTokenViewSet(RightModelViewSet):
""" """
Return all avaliable tokens (api/v1/launderette/token/avaliable) Return all avaliable tokens (api/v1/launderette/token/avaliable)
""" """
self.queryset = self.queryset.filter(borrow_date__isnull=True, user__isnull=True) self.queryset = self.queryset.filter(
borrow_date__isnull=True, user__isnull=True
)
serializer = self.get_serializer(self.queryset, many=True) serializer = self.get_serializer(self.queryset, many=True)
return Response(serializer.data) return Response(serializer.data)
@ -116,6 +130,8 @@ class LaunderetteTokenViewSet(RightModelViewSet):
""" """
Return all unavaliable tokens (api/v1/launderette/token/unavaliable) Return all unavaliable tokens (api/v1/launderette/token/unavaliable)
""" """
self.queryset = self.queryset.filter(borrow_date__isnull=False, user__isnull=False) self.queryset = self.queryset.filter(
borrow_date__isnull=False, user__isnull=False
)
serializer = self.get_serializer(self.queryset, many=True) serializer = self.get_serializer(self.queryset, many=True)
return Response(serializer.data) return Response(serializer.data)

View File

@ -34,11 +34,18 @@ from api.views import RightModelViewSet
class UserSerializer(serializers.ModelSerializer): class UserSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = User model = User
fields = ('id', 'first_name', 'last_name', 'email', fields = (
'date_of_birth', 'nick_name', 'is_active', 'date_joined') "id",
"first_name",
"last_name",
"email",
"date_of_birth",
"nick_name",
"is_active",
"date_joined",
)
class UserViewSet(RightModelViewSet): class UserViewSet(RightModelViewSet):

View File

@ -21,4 +21,3 @@
# Place - Suite 330, Boston, MA 02111-1307, USA. # Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #

View File

@ -7,28 +7,92 @@ import django.core.validators
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = []
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Club', name="Club",
fields=[ fields=[
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), (
('name', models.CharField(max_length=64, verbose_name='name')), "id",
('unix_name', models.CharField(unique=True, max_length=30, error_messages={'unique': 'A club with that unix name already exists.'}, verbose_name='unix name', validators=[django.core.validators.RegexValidator('^[a-z0-9][a-z0-9._-]*[a-z0-9]$', 'Enter a valid unix name. This value may contain only letters, numbers ./-/_ characters.')])), models.AutoField(
('address', models.CharField(max_length=254, verbose_name='address')), primary_key=True,
serialize=False,
verbose_name="ID",
auto_created=True,
),
),
("name", models.CharField(max_length=64, verbose_name="name")),
(
"unix_name",
models.CharField(
unique=True,
max_length=30,
error_messages={
"unique": "A club with that unix name already exists."
},
verbose_name="unix name",
validators=[
django.core.validators.RegexValidator(
"^[a-z0-9][a-z0-9._-]*[a-z0-9]$",
"Enter a valid unix name. This value may contain only letters, numbers ./-/_ characters.",
)
],
),
),
("address", models.CharField(max_length=254, verbose_name="address")),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name='Membership', name="Membership",
fields=[ fields=[
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), (
('start_date', models.DateField(verbose_name='start date', auto_now=True)), "id",
('end_date', models.DateField(null=True, verbose_name='end date', blank=True)), models.AutoField(
('role', models.IntegerField(choices=[(0, 'Curious'), (1, 'Active member'), (2, 'Board member'), (3, 'IT supervisor'), (4, 'Secretary'), (5, 'Communication supervisor'), (7, 'Treasurer'), (9, 'Vice-President'), (10, 'President')], default=0, verbose_name='role')), primary_key=True,
('description', models.CharField(max_length=128, blank=True, verbose_name='description')), serialize=False,
('club', models.ForeignKey(verbose_name='club', to='club.Club', related_name='members')), verbose_name="ID",
auto_created=True,
),
),
(
"start_date",
models.DateField(verbose_name="start date", auto_now=True),
),
(
"end_date",
models.DateField(null=True, verbose_name="end date", blank=True),
),
(
"role",
models.IntegerField(
choices=[
(0, "Curious"),
(1, "Active member"),
(2, "Board member"),
(3, "IT supervisor"),
(4, "Secretary"),
(5, "Communication supervisor"),
(7, "Treasurer"),
(9, "Vice-President"),
(10, "President"),
],
default=0,
verbose_name="role",
),
),
(
"description",
models.CharField(
max_length=128, blank=True, verbose_name="description"
),
),
(
"club",
models.ForeignKey(
verbose_name="club", to="club.Club", related_name="members"
),
),
], ],
), ),
] ]

View File

@ -9,39 +9,57 @@ class Migration(migrations.Migration):
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('club', '0001_initial'), ("club", "0001_initial"),
('core', '0001_initial'), ("core", "0001_initial"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='membership', model_name="membership",
name='user', name="user",
field=models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL, related_name='membership'), field=models.ForeignKey(
verbose_name="user",
to=settings.AUTH_USER_MODEL,
related_name="membership",
),
), ),
migrations.AddField( migrations.AddField(
model_name='club', model_name="club",
name='edit_groups', name="edit_groups",
field=models.ManyToManyField(to='core.Group', blank=True, related_name='editable_club'), field=models.ManyToManyField(
to="core.Group", blank=True, related_name="editable_club"
),
), ),
migrations.AddField( migrations.AddField(
model_name='club', model_name="club",
name='home', name="home",
field=models.OneToOneField(blank=True, null=True, related_name='home_of_club', verbose_name='home', to='core.SithFile'), field=models.OneToOneField(
blank=True,
null=True,
related_name="home_of_club",
verbose_name="home",
to="core.SithFile",
),
), ),
migrations.AddField( migrations.AddField(
model_name='club', model_name="club",
name='owner_group', name="owner_group",
field=models.ForeignKey(default=1, to='core.Group', related_name='owned_club'), field=models.ForeignKey(
default=1, to="core.Group", related_name="owned_club"
),
), ),
migrations.AddField( migrations.AddField(
model_name='club', model_name="club",
name='parent', name="parent",
field=models.ForeignKey(null=True, to='club.Club', related_name='children', blank=True), field=models.ForeignKey(
null=True, to="club.Club", related_name="children", blank=True
),
), ),
migrations.AddField( migrations.AddField(
model_name='club', model_name="club",
name='view_groups', name="view_groups",
field=models.ManyToManyField(to='core.Group', blank=True, related_name='viewable_club'), field=models.ManyToManyField(
to="core.Group", blank=True, related_name="viewable_club"
),
), ),
] ]

View File

@ -6,14 +6,12 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("club", "0002_auto_20160824_2152")]
('club', '0002_auto_20160824_2152'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='membership', model_name="membership",
name='start_date', name="start_date",
field=models.DateField(verbose_name='start date'), field=models.DateField(verbose_name="start date"),
), )
] ]

View File

@ -7,14 +7,16 @@ from django.conf import settings
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("club", "0003_auto_20160902_2042")]
('club', '0003_auto_20160902_2042'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='membership', model_name="membership",
name='user', name="user",
field=models.ForeignKey(verbose_name='user', related_name='memberships', to=settings.AUTH_USER_MODEL), field=models.ForeignKey(
), verbose_name="user",
related_name="memberships",
to=settings.AUTH_USER_MODEL,
),
)
] ]

View File

@ -7,14 +7,19 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("club", "0004_auto_20160915_1057")]
('club', '0004_auto_20160915_1057'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='club', model_name="club",
name='home', name="home",
field=models.OneToOneField(related_name='home_of_club', blank=True, on_delete=django.db.models.deletion.SET_NULL, verbose_name='home', null=True, to='core.SithFile'), field=models.OneToOneField(
), related_name="home_of_club",
blank=True,
on_delete=django.db.models.deletion.SET_NULL,
verbose_name="home",
null=True,
to="core.SithFile",
),
)
] ]

View File

@ -7,14 +7,14 @@ import django.utils.timezone
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("club", "0005_auto_20161120_1149")]
('club', '0005_auto_20161120_1149'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='membership', model_name="membership",
name='start_date', name="start_date",
field=models.DateField(verbose_name='start date', default=django.utils.timezone.now), field=models.DateField(
), verbose_name="start date", default=django.utils.timezone.now
),
)
] ]

View File

@ -6,13 +6,10 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("club", "0006_auto_20161229_0040")]
('club', '0006_auto_20161229_0040'),
]
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='club', name="club", options={"ordering": ["name", "unix_name"]}
options={'ordering': ['name', 'unix_name']}, )
),
] ]

View File

@ -6,14 +6,12 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("club", "0007_auto_20170324_0917")]
('club', '0007_auto_20170324_0917'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='club', model_name="club",
name='id', name="id",
field=models.AutoField(primary_key=True, serialize=False, db_index=True), field=models.AutoField(primary_key=True, serialize=False, db_index=True),
), )
] ]

View File

@ -11,31 +11,98 @@ class Migration(migrations.Migration):
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('club', '0008_auto_20170515_2214'), ("club", "0008_auto_20170515_2214"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Mailing', name="Mailing",
fields=[ fields=[
('id', models.AutoField(auto_created=True, verbose_name='ID', serialize=False, primary_key=True)), (
('email', models.CharField(max_length=256, unique=True, validators=[django.core.validators.RegexValidator(re.compile('(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+)*\\Z|^"([\\001-\\010\\013\\014\\016-\\037!#-\\[\\]-\\177]|\\\\[\\001-\\011\\013\\014\\016-\\177])*"\\Z)', 34), 'Enter a valid address. Only the root of the address is needed.')], verbose_name='Email address')), "id",
('is_moderated', models.BooleanField(default=False, verbose_name='is moderated')), models.AutoField(
('club', models.ForeignKey(verbose_name='Club', related_name='mailings', to='club.Club')), auto_created=True,
('moderator', models.ForeignKey(null=True, verbose_name='moderator', related_name='moderated_mailings', to=settings.AUTH_USER_MODEL)), verbose_name="ID",
serialize=False,
primary_key=True,
),
),
(
"email",
models.CharField(
max_length=256,
unique=True,
validators=[
django.core.validators.RegexValidator(
re.compile(
"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*\\Z|^\"([\\001-\\010\\013\\014\\016-\\037!#-\\[\\]-\\177]|\\\\[\\001-\\011\\013\\014\\016-\\177])*\"\\Z)",
34,
),
"Enter a valid address. Only the root of the address is needed.",
)
],
verbose_name="Email address",
),
),
(
"is_moderated",
models.BooleanField(default=False, verbose_name="is moderated"),
),
(
"club",
models.ForeignKey(
verbose_name="Club", related_name="mailings", to="club.Club"
),
),
(
"moderator",
models.ForeignKey(
null=True,
verbose_name="moderator",
related_name="moderated_mailings",
to=settings.AUTH_USER_MODEL,
),
),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name='MailingSubscription', name="MailingSubscription",
fields=[ fields=[
('id', models.AutoField(auto_created=True, verbose_name='ID', serialize=False, primary_key=True)), (
('email', models.EmailField(max_length=254, verbose_name='Email address')), "id",
('mailing', models.ForeignKey(verbose_name='Mailing', related_name='subscriptions', to='club.Mailing')), models.AutoField(
('user', models.ForeignKey(null=True, verbose_name='User', related_name='mailing_subscriptions', blank=True, to=settings.AUTH_USER_MODEL)), auto_created=True,
verbose_name="ID",
serialize=False,
primary_key=True,
),
),
(
"email",
models.EmailField(max_length=254, verbose_name="Email address"),
),
(
"mailing",
models.ForeignKey(
verbose_name="Mailing",
related_name="subscriptions",
to="club.Mailing",
),
),
(
"user",
models.ForeignKey(
null=True,
verbose_name="User",
related_name="mailing_subscriptions",
blank=True,
to=settings.AUTH_USER_MODEL,
),
),
], ],
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name='mailingsubscription', name="mailingsubscription",
unique_together=set([('user', 'email', 'mailing')]), unique_together=set([("user", "email", "mailing")]),
), ),
] ]

View File

@ -12,34 +12,44 @@ def generate_club_pages(apps, schema_editor):
club.make_page() club.make_page()
for child in Club.objects.filter(parent=club).all(): for child in Club.objects.filter(parent=club).all():
recursive_generate_club_page(child) recursive_generate_club_page(child)
for club in Club.objects.filter(parent=None).all(): for club in Club.objects.filter(parent=None).all():
recursive_generate_club_page(club) recursive_generate_club_page(club)
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0024_auto_20170906_1317"), ("club", "0010_club_logo")]
('core', '0024_auto_20170906_1317'),
('club', '0010_club_logo'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='club', model_name="club",
name='is_active', name="is_active",
field=models.BooleanField(default=True, verbose_name='is active'), field=models.BooleanField(default=True, verbose_name="is active"),
), ),
migrations.AddField( migrations.AddField(
model_name='club', model_name="club",
name='page', name="page",
field=models.OneToOneField(related_name='club', blank=True, null=True, to='core.Page'), field=models.OneToOneField(
related_name="club", blank=True, null=True, to="core.Page"
),
), ),
migrations.AddField( migrations.AddField(
model_name='club', model_name="club",
name='short_description', name="short_description",
field=models.CharField(verbose_name='short description', max_length=1000, default='', blank=True, null=True), field=models.CharField(
verbose_name="short description",
max_length=1000,
default="",
blank=True,
null=True,
),
),
PsqlRunOnly(
"SET CONSTRAINTS ALL IMMEDIATE", reverse_sql=migrations.RunSQL.noop
), ),
PsqlRunOnly('SET CONSTRAINTS ALL IMMEDIATE', reverse_sql=migrations.RunSQL.noop),
migrations.RunPython(generate_club_pages), migrations.RunPython(generate_club_pages),
PsqlRunOnly(migrations.RunSQL.noop, reverse_sql='SET CONSTRAINTS ALL IMMEDIATE'), PsqlRunOnly(
migrations.RunSQL.noop, reverse_sql="SET CONSTRAINTS ALL IMMEDIATE"
),
] ]

View File

@ -6,14 +6,14 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("club", "0009_auto_20170822_2232")]
('club', '0009_auto_20170822_2232'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='club', model_name="club",
name='logo', name="logo",
field=models.ImageField(null=True, upload_to='club_logos', blank=True, verbose_name='logo'), field=models.ImageField(
), null=True, upload_to="club_logos", blank=True, verbose_name="logo"
),
)
] ]

View File

@ -7,14 +7,16 @@ import club.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("club", "0010_auto_20170912_2028")]
('club', '0010_auto_20170912_2028'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='club', model_name="club",
name='owner_group', name="owner_group",
field=models.ForeignKey(default=club.models.Club.get_default_owner_group, related_name='owned_club', to='core.Group'), field=models.ForeignKey(
), default=club.models.Club.get_default_owner_group,
related_name="owned_club",
to="core.Group",
),
)
] ]

View File

@ -43,40 +43,64 @@ class Club(models.Model):
""" """
The Club class, made as a tree to allow nice tidy organization The Club class, made as a tree to allow nice tidy organization
""" """
id = models.AutoField(primary_key=True, db_index=True) id = models.AutoField(primary_key=True, db_index=True)
name = models.CharField(_('name'), max_length=64) name = models.CharField(_("name"), max_length=64)
parent = models.ForeignKey('Club', related_name='children', null=True, blank=True) parent = models.ForeignKey("Club", related_name="children", null=True, blank=True)
unix_name = models.CharField(_('unix name'), max_length=30, unique=True, unix_name = models.CharField(
validators=[ _("unix name"),
validators.RegexValidator( max_length=30,
r'^[a-z0-9][a-z0-9._-]*[a-z0-9]$', unique=True,
_('Enter a valid unix name. This value may contain only ' validators=[
'letters, numbers ./-/_ characters.') validators.RegexValidator(
), r"^[a-z0-9][a-z0-9._-]*[a-z0-9]$",
], _(
error_messages={ "Enter a valid unix name. This value may contain only "
'unique': _("A club with that unix name already exists."), "letters, numbers ./-/_ characters."
}, ),
)
],
error_messages={"unique": _("A club with that unix name already exists.")},
) )
logo = models.ImageField(upload_to='club_logos', verbose_name=_('logo'), null=True, blank=True) logo = models.ImageField(
is_active = models.BooleanField(_('is active'), default=True) upload_to="club_logos", verbose_name=_("logo"), null=True, blank=True
short_description = models.CharField(_('short description'), max_length=1000, default='', blank=True, null=True) )
address = models.CharField(_('address'), max_length=254) is_active = models.BooleanField(_("is active"), default=True)
short_description = models.CharField(
_("short description"), max_length=1000, default="", blank=True, null=True
)
address = models.CharField(_("address"), max_length=254)
# This function prevents generating migration upon settings change # This function prevents generating migration upon settings change
def get_default_owner_group(): return settings.SITH_GROUP_ROOT_ID def get_default_owner_group():
owner_group = models.ForeignKey(Group, related_name="owned_club", default=get_default_owner_group) return settings.SITH_GROUP_ROOT_ID
edit_groups = models.ManyToManyField(Group, related_name="editable_club", blank=True)
view_groups = models.ManyToManyField(Group, related_name="viewable_club", blank=True) owner_group = models.ForeignKey(
home = models.OneToOneField(SithFile, related_name='home_of_club', verbose_name=_("home"), null=True, blank=True, Group, related_name="owned_club", default=get_default_owner_group
on_delete=models.SET_NULL) )
edit_groups = models.ManyToManyField(
Group, related_name="editable_club", blank=True
)
view_groups = models.ManyToManyField(
Group, related_name="viewable_club", blank=True
)
home = models.OneToOneField(
SithFile,
related_name="home_of_club",
verbose_name=_("home"),
null=True,
blank=True,
on_delete=models.SET_NULL,
)
page = models.OneToOneField(Page, related_name="club", blank=True, null=True) page = models.OneToOneField(Page, related_name="club", blank=True, null=True)
class Meta: class Meta:
ordering = ['name', 'unix_name'] ordering = ["name", "unix_name"]
@cached_property @cached_property
def president(self): def president(self):
return self.members.filter(role=settings.SITH_CLUB_ROLES_ID['President'], end_date=None).first() return self.members.filter(
role=settings.SITH_CLUB_ROLES_ID["President"], end_date=None
).first()
def check_loop(self): def check_loop(self):
"""Raise a validation error when a loop is found within the parent list""" """Raise a validation error when a loop is found within the parent list"""
@ -84,7 +108,7 @@ class Club(models.Model):
cur = self cur = self
while cur.parent is not None: while cur.parent is not None:
if cur in objs: if cur in objs:
raise ValidationError(_('You can not make loops in clubs')) raise ValidationError(_("You can not make loops in clubs"))
objs.append(cur) objs.append(cur)
cur = cur.parent cur = cur.parent
@ -130,7 +154,12 @@ class Club(models.Model):
self.page.unset_lock() self.page.unset_lock()
self.page.name = self.unix_name self.page.name = self.unix_name
self.page.save(force_lock=True) self.page.save(force_lock=True)
elif self.page and self.parent and self.parent.page and self.page.parent != self.parent.page: elif (
self.page
and self.parent
and self.parent.page
and self.page.parent != self.parent.page
):
self.page.unset_lock() self.page.unset_lock()
self.page.parent = self.parent.page self.page.parent = self.parent.page
self.page.save(force_lock=True) self.page.save(force_lock=True)
@ -150,7 +179,9 @@ class Club(models.Model):
board.save() board.save()
member = MetaGroup(name=self.unix_name + settings.SITH_MEMBER_SUFFIX) member = MetaGroup(name=self.unix_name + settings.SITH_MEMBER_SUFFIX)
member.save() member.save()
subscribers = Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first() subscribers = Group.objects.filter(
name=settings.SITH_MAIN_MEMBERS_GROUP
).first()
self.make_home() self.make_home()
self.home.edit_groups = [board] self.home.edit_groups = [board]
self.home.view_groups = [member, subscribers] self.home.view_groups = [member, subscribers]
@ -161,7 +192,7 @@ class Club(models.Model):
return self.name return self.name
def get_absolute_url(self): def get_absolute_url(self):
return reverse('club:club_view', kwargs={'club_id': self.id}) return reverse("club:club_view", kwargs={"club_id": self.id})
def get_display_name(self): def get_display_name(self):
return self.name return self.name
@ -223,24 +254,48 @@ class Membership(models.Model):
A User is currently member of all the Clubs where its Membership has an end_date set to null/None. A User is currently member of all the Clubs where its Membership has an end_date set to null/None.
Otherwise, it's a past membership kept because it can be very useful to see who was in which Club in the past. Otherwise, it's a past membership kept because it can be very useful to see who was in which Club in the past.
""" """
user = models.ForeignKey(User, verbose_name=_('user'), related_name="memberships", null=False, blank=False)
club = models.ForeignKey(Club, verbose_name=_('club'), related_name="members", null=False, blank=False) user = models.ForeignKey(
start_date = models.DateField(_('start date'), default=timezone.now) User,
end_date = models.DateField(_('end date'), null=True, blank=True) verbose_name=_("user"),
role = models.IntegerField(_('role'), choices=sorted(settings.SITH_CLUB_ROLES.items()), related_name="memberships",
default=sorted(settings.SITH_CLUB_ROLES.items())[0][0]) null=False,
description = models.CharField(_('description'), max_length=128, null=False, blank=True) blank=False,
)
club = models.ForeignKey(
Club, verbose_name=_("club"), related_name="members", null=False, blank=False
)
start_date = models.DateField(_("start date"), default=timezone.now)
end_date = models.DateField(_("end date"), null=True, blank=True)
role = models.IntegerField(
_("role"),
choices=sorted(settings.SITH_CLUB_ROLES.items()),
default=sorted(settings.SITH_CLUB_ROLES.items())[0][0],
)
description = models.CharField(
_("description"), max_length=128, null=False, blank=True
)
def clean(self): def clean(self):
sub = User.objects.filter(pk=self.user.pk).first() sub = User.objects.filter(pk=self.user.pk).first()
if sub is None or not sub.is_subscribed: if sub is None or not sub.is_subscribed:
raise ValidationError(_('User must be subscriber to take part to a club')) raise ValidationError(_("User must be subscriber to take part to a club"))
if Membership.objects.filter(user=self.user).filter(club=self.club).filter(end_date=None).exists(): if (
raise ValidationError(_('User is already member of that club')) Membership.objects.filter(user=self.user)
.filter(club=self.club)
.filter(end_date=None)
.exists()
):
raise ValidationError(_("User is already member of that club"))
def __str__(self): def __str__(self):
return self.club.name + ' - ' + self.user.username + ' - ' + str(settings.SITH_CLUB_ROLES[self.role]) + str( return (
" - " + str(_('past member')) if self.end_date is not None else "" self.club.name
+ " - "
+ self.user.username
+ " - "
+ str(settings.SITH_CLUB_ROLES[self.role])
+ str(" - " + str(_("past member")) if self.end_date is not None else "")
) )
def is_owned_by(self, user): def is_owned_by(self, user):
@ -255,11 +310,13 @@ class Membership(models.Model):
""" """
if user.memberships: if user.memberships:
ms = user.memberships.filter(club=self.club, end_date=None).first() ms = user.memberships.filter(club=self.club, end_date=None).first()
return (ms and ms.role >= self.role) or user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) return (ms and ms.role >= self.role) or user.is_in_group(
settings.SITH_MAIN_BOARD_GROUP
)
return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP)
def get_absolute_url(self): def get_absolute_url(self):
return reverse('club:club_members', kwargs={'club_id': self.club.id}) return reverse("club:club_members", kwargs={"club_id": self.club.id})
class Mailing(models.Model): class Mailing(models.Model):
@ -267,14 +324,27 @@ class Mailing(models.Model):
This class correspond to a mailing list This class correspond to a mailing list
Remember that mailing lists should be validated by UTBM Remember that mailing lists should be validated by UTBM
""" """
club = models.ForeignKey(Club, verbose_name=_('Club'), related_name="mailings", null=False, blank=False)
email = models.CharField(_('Email address'), unique=True, null=False, blank=False, max_length=256, club = models.ForeignKey(
validators=[ Club, verbose_name=_("Club"), related_name="mailings", null=False, blank=False
RegexValidator(validate_email.user_regex, )
_('Enter a valid address. Only the root of the address is needed.')) email = models.CharField(
]) _("Email address"),
is_moderated = models.BooleanField(_('is moderated'), default=False) unique=True,
moderator = models.ForeignKey(User, related_name="moderated_mailings", verbose_name=_("moderator"), null=True) null=False,
blank=False,
max_length=256,
validators=[
RegexValidator(
validate_email.user_regex,
_("Enter a valid address. Only the root of the address is needed."),
)
],
)
is_moderated = models.BooleanField(_("is moderated"), default=False)
moderator = models.ForeignKey(
User, related_name="moderated_mailings", verbose_name=_("moderator"), null=True
)
def clean(self): def clean(self):
if self.can_moderate(self.moderator): if self.can_moderate(self.moderator):
@ -285,13 +355,17 @@ class Mailing(models.Model):
@property @property
def email_full(self): def email_full(self):
return self.email + '@' + settings.SITH_MAILING_DOMAIN return self.email + "@" + settings.SITH_MAILING_DOMAIN
def can_moderate(self, user): def can_moderate(self, user):
return user.is_root or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) return user.is_root or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
def is_owned_by(self, user): def is_owned_by(self, user):
return user.is_in_group(self) or user.is_root or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) return (
user.is_in_group(self)
or user.is_root
or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
)
def can_view(self, user): def can_view(self, user):
return self.club.has_rights_in_club(user) return self.club.has_rights_in_club(user)
@ -305,16 +379,26 @@ class Mailing(models.Model):
super(Mailing, self).delete() super(Mailing, self).delete()
def fetch_format(self): def fetch_format(self):
resp = self.email + ': ' resp = self.email + ": "
for sub in self.subscriptions.all(): for sub in self.subscriptions.all():
resp += sub.fetch_format() resp += sub.fetch_format()
return resp return resp
def save(self): def save(self):
if not self.is_moderated: if not self.is_moderated:
for user in RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID).first().users.all(): for user in (
if not user.notifications.filter(type="MAILING_MODERATION", viewed=False).exists(): RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID)
Notification(user=user, url=reverse('com:mailing_admin'), type="MAILING_MODERATION").save() .first()
.users.all()
):
if not user.notifications.filter(
type="MAILING_MODERATION", viewed=False
).exists():
Notification(
user=user,
url=reverse("com:mailing_admin"),
type="MAILING_MODERATION",
).save()
super(Mailing, self).save() super(Mailing, self).save()
def __str__(self): def __str__(self):
@ -325,12 +409,25 @@ class MailingSubscription(models.Model):
""" """
This class makes the link between user and mailing list This class makes the link between user and mailing list
""" """
mailing = models.ForeignKey(Mailing, verbose_name=_('Mailing'), related_name="subscriptions", null=False, blank=False)
user = models.ForeignKey(User, verbose_name=_('User'), related_name="mailing_subscriptions", null=True, blank=True) mailing = models.ForeignKey(
email = models.EmailField(_('Email address'), blank=False, null=False) Mailing,
verbose_name=_("Mailing"),
related_name="subscriptions",
null=False,
blank=False,
)
user = models.ForeignKey(
User,
verbose_name=_("User"),
related_name="mailing_subscriptions",
null=True,
blank=True,
)
email = models.EmailField(_("Email address"), blank=False, null=False)
class Meta: class Meta:
unique_together = (('user', 'email', 'mailing'),) unique_together = (("user", "email", "mailing"),)
def clean(self): def clean(self):
if not self.user and not self.email: if not self.user and not self.email:
@ -338,17 +435,25 @@ class MailingSubscription(models.Model):
try: try:
if self.user and not self.email: if self.user and not self.email:
self.email = self.user.email self.email = self.user.email
if MailingSubscription.objects.filter(mailing=self.mailing, email=self.email).exists(): if MailingSubscription.objects.filter(
raise ValidationError(_("This email is already suscribed in this mailing")) mailing=self.mailing, email=self.email
).exists():
raise ValidationError(
_("This email is already suscribed in this mailing")
)
except ObjectDoesNotExist: except ObjectDoesNotExist:
pass pass
super(MailingSubscription, self).clean() super(MailingSubscription, self).clean()
def is_owned_by(self, user): def is_owned_by(self, user):
return self.mailing.club.has_rights_in_club(user) or user.is_root or self.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) return (
self.mailing.club.has_rights_in_club(user)
or user.is_root
or self.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
)
def can_be_edited_by(self, user): def can_be_edited_by(self, user):
return (self.user is not None and user.id == self.user.id) return self.user is not None and user.id == self.user.id
@property @property
def get_email(self): def get_email(self):
@ -357,7 +462,7 @@ class MailingSubscription(models.Model):
return self.email return self.email
def fetch_format(self): def fetch_format(self):
return self.get_email + ' ' return self.get_email + " "
def __str__(self): def __str__(self):
if self.user: if self.user:

View File

@ -41,66 +41,92 @@ class ClubTest(TestCase):
self.bdf = Club.objects.filter(unix_name="bdf").first() self.bdf = Club.objects.filter(unix_name="bdf").first()
def test_create_add_user_to_club_from_root_ok(self): def test_create_add_user_to_club_from_root_ok(self):
self.client.login(username='root', password='plop') self.client.login(username="root", password="plop")
self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { self.client.post(
"user": self.skia.id, reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
"start_date": "12/06/2016", {"user": self.skia.id, "start_date": "12/06/2016", "role": 3},
"role": 3}) )
response = self.client.get(reverse("club:club_members", kwargs={"club_id": self.bdf.id})) response = self.client.get(
reverse("club:club_members", kwargs={"club_id": self.bdf.id})
)
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue("S&#39; Kia</a></td>\\n <td>Responsable info</td>" in str(response.content)) self.assertTrue(
"S&#39; Kia</a></td>\\n <td>Responsable info</td>"
in str(response.content)
)
def test_create_add_user_to_club_from_root_fail_not_subscriber(self): def test_create_add_user_to_club_from_root_fail_not_subscriber(self):
self.client.login(username='root', password='plop') self.client.login(username="root", password="plop")
response = self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { response = self.client.post(
"user": self.guy.id, reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
"start_date": "12/06/2016", {"user": self.guy.id, "start_date": "12/06/2016", "role": 3},
"role": 3}) )
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('<ul class="errorlist nonfield"><li>' in str(response.content)) self.assertTrue('<ul class="errorlist nonfield"><li>' in str(response.content))
response = self.client.get(reverse("club:club_members", kwargs={"club_id": self.bdf.id})) response = self.client.get(
self.assertFalse("Guy Carlier</a></td>\\n <td>Responsable info</td>" in str(response.content)) reverse("club:club_members", kwargs={"club_id": self.bdf.id})
)
self.assertFalse(
"Guy Carlier</a></td>\\n <td>Responsable info</td>"
in str(response.content)
)
def test_create_add_user_to_club_from_root_fail_already_in_club(self): def test_create_add_user_to_club_from_root_fail_already_in_club(self):
self.client.login(username='root', password='plop') self.client.login(username="root", password="plop")
self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { self.client.post(
"user": self.skia.id, reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
"start_date": "12/06/2016", {"user": self.skia.id, "start_date": "12/06/2016", "role": 3},
"role": 3}) )
response = self.client.get(reverse("club:club_members", kwargs={"club_id": self.bdf.id})) response = self.client.get(
self.assertTrue("S&#39; Kia</a></td>\\n <td>Responsable info</td>" in str(response.content)) reverse("club:club_members", kwargs={"club_id": self.bdf.id})
response = self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { )
"user": self.skia.id, self.assertTrue(
"start_date": "12/06/2016", "S&#39; Kia</a></td>\\n <td>Responsable info</td>"
"role": 4}) in str(response.content)
)
response = self.client.post(
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
{"user": self.skia.id, "start_date": "12/06/2016", "role": 4},
)
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertFalse("S&#39; Kia</a></td>\\n <td>Secrétaire</td>" in str(response.content)) self.assertFalse(
"S&#39; Kia</a></td>\\n <td>Secrétaire</td>"
in str(response.content)
)
def test_create_add_user_to_club_from_skia_ok(self): def test_create_add_user_to_club_from_skia_ok(self):
self.client.login(username='root', password='plop') self.client.login(username="root", password="plop")
self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { self.client.post(
"user": self.skia.id, reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
"start_date": "12/06/2016", {"user": self.skia.id, "start_date": "12/06/2016", "role": 10},
"role": 10}) )
self.client.login(username='skia', password='plop') self.client.login(username="skia", password="plop")
self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { self.client.post(
"user": self.rbatsbak.id, reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
"start_date": "12/06/2016", {"user": self.rbatsbak.id, "start_date": "12/06/2016", "role": 9},
"role": 9}) )
response = self.client.get(reverse("club:club_members", kwargs={"club_id": self.bdf.id})) response = self.client.get(
reverse("club:club_members", kwargs={"club_id": self.bdf.id})
)
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue("""Richard Batsbak</a></td>\\n <td>Vice-Pr\\xc3\\xa9sident</td>""" in str(response.content)) self.assertTrue(
"""Richard Batsbak</a></td>\\n <td>Vice-Pr\\xc3\\xa9sident</td>"""
in str(response.content)
)
def test_create_add_user_to_club_from_richard_fail(self): def test_create_add_user_to_club_from_richard_fail(self):
self.client.login(username='root', password='plop') self.client.login(username="root", password="plop")
self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { self.client.post(
"user": self.rbatsbak.id, reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
"start_date": "12/06/2016", {"user": self.rbatsbak.id, "start_date": "12/06/2016", "role": 3},
"role": 3}) )
self.client.login(username='rbatsbak', password='plop') self.client.login(username="rbatsbak", password="plop")
response = self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { response = self.client.post(
"user": self.skia.id, reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
"start_date": "12/06/2016", {"user": self.skia.id, "start_date": "12/06/2016", "role": 10},
"role": 10}) )
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue("<li>Vous n&#39;avez pas la permission de faire cela</li>" in str(response.content)) self.assertTrue(
"<li>Vous n&#39;avez pas la permission de faire cela</li>"
in str(response.content)
)

View File

@ -28,30 +28,96 @@ from django.conf.urls import url
from club.views import * from club.views import *
urlpatterns = [ urlpatterns = [
url(r'^$', ClubListView.as_view(), name='club_list'), url(r"^$", ClubListView.as_view(), name="club_list"),
url(r'^new$', ClubCreateView.as_view(), name='club_new'), url(r"^new$", ClubCreateView.as_view(), name="club_new"),
url(r'^stats$', ClubStatView.as_view(), name='club_stats'), url(r"^stats$", ClubStatView.as_view(), name="club_stats"),
url(r'^(?P<club_id>[0-9]+)/$', ClubView.as_view(), name='club_view'), url(r"^(?P<club_id>[0-9]+)/$", ClubView.as_view(), name="club_view"),
url(r'^(?P<club_id>[0-9]+)/rev/(?P<rev_id>[0-9]+)/$', ClubRevView.as_view(), name='club_view_rev'), url(
url(r'^(?P<club_id>[0-9]+)/hist$', ClubPageHistView.as_view(), name='club_hist'), r"^(?P<club_id>[0-9]+)/rev/(?P<rev_id>[0-9]+)/$",
url(r'^(?P<club_id>[0-9]+)/edit$', ClubEditView.as_view(), name='club_edit'), ClubRevView.as_view(),
url(r'^(?P<club_id>[0-9]+)/edit/page$', ClubPageEditView.as_view(), name='club_edit_page'), name="club_view_rev",
url(r'^(?P<club_id>[0-9]+)/members$', ClubMembersView.as_view(), name='club_members'), ),
url(r'^(?P<club_id>[0-9]+)/elderlies$', ClubOldMembersView.as_view(), name='club_old_members'), url(r"^(?P<club_id>[0-9]+)/hist$", ClubPageHistView.as_view(), name="club_hist"),
url(r'^(?P<club_id>[0-9]+)/sellings$', ClubSellingView.as_view(), name='club_sellings'), url(r"^(?P<club_id>[0-9]+)/edit$", ClubEditView.as_view(), name="club_edit"),
url(r'^(?P<club_id>[0-9]+)/sellings/csv$', ClubSellingCSVView.as_view(), name='sellings_csv'), url(
url(r'^(?P<club_id>[0-9]+)/prop$', ClubEditPropView.as_view(), name='club_prop'), r"^(?P<club_id>[0-9]+)/edit/page$",
url(r'^(?P<club_id>[0-9]+)/tools$', ClubToolsView.as_view(), name='tools'), ClubPageEditView.as_view(),
url(r'^(?P<club_id>[0-9]+)/mailing$', ClubMailingView.as_view(action="display"), name='mailing'), name="club_edit_page",
url(r'^(?P<club_id>[0-9]+)/mailing/new/mailing$', ClubMailingView.as_view(action="add_mailing"), name='mailing_create'), ),
url(r'^(?P<club_id>[0-9]+)/mailing/new/subscription$', ClubMailingView.as_view(action="add_member"), name='mailing_subscription_create'), url(
url(r'^(?P<mailing_id>[0-9]+)/mailing/generate$', MailingAutoGenerationView.as_view(), name='mailing_generate'), r"^(?P<club_id>[0-9]+)/members$", ClubMembersView.as_view(), name="club_members"
url(r'^(?P<mailing_id>[0-9]+)/mailing/clean$', MailingAutoCleanView.as_view(), name='mailing_clean'), ),
url(r'^(?P<mailing_id>[0-9]+)/mailing/delete$', MailingDeleteView.as_view(), name='mailing_delete'), url(
url(r'^(?P<mailing_subscription_id>[0-9]+)/mailing/delete/subscription$', MailingSubscriptionDeleteView.as_view(), name='mailing_subscription_delete'), r"^(?P<club_id>[0-9]+)/elderlies$",
url(r'^membership/(?P<membership_id>[0-9]+)/set_old$', MembershipSetOldView.as_view(), name='membership_set_old'), ClubOldMembersView.as_view(),
url(r'^(?P<club_id>[0-9]+)/poster$', PosterListView.as_view(), name='poster_list'), name="club_old_members",
url(r'^(?P<club_id>[0-9]+)/poster/create$', PosterCreateView.as_view(), name='poster_create'), ),
url(r'^(?P<club_id>[0-9]+)/poster/(?P<poster_id>[0-9]+)/edit$', PosterEditView.as_view(), name='poster_edit'), url(
url(r'^(?P<club_id>[0-9]+)/poster/(?P<poster_id>[0-9]+)/delete$', PosterDeleteView.as_view(), name='poster_delete'), r"^(?P<club_id>[0-9]+)/sellings$",
ClubSellingView.as_view(),
name="club_sellings",
),
url(
r"^(?P<club_id>[0-9]+)/sellings/csv$",
ClubSellingCSVView.as_view(),
name="sellings_csv",
),
url(r"^(?P<club_id>[0-9]+)/prop$", ClubEditPropView.as_view(), name="club_prop"),
url(r"^(?P<club_id>[0-9]+)/tools$", ClubToolsView.as_view(), name="tools"),
url(
r"^(?P<club_id>[0-9]+)/mailing$",
ClubMailingView.as_view(action="display"),
name="mailing",
),
url(
r"^(?P<club_id>[0-9]+)/mailing/new/mailing$",
ClubMailingView.as_view(action="add_mailing"),
name="mailing_create",
),
url(
r"^(?P<club_id>[0-9]+)/mailing/new/subscription$",
ClubMailingView.as_view(action="add_member"),
name="mailing_subscription_create",
),
url(
r"^(?P<mailing_id>[0-9]+)/mailing/generate$",
MailingAutoGenerationView.as_view(),
name="mailing_generate",
),
url(
r"^(?P<mailing_id>[0-9]+)/mailing/clean$",
MailingAutoCleanView.as_view(),
name="mailing_clean",
),
url(
r"^(?P<mailing_id>[0-9]+)/mailing/delete$",
MailingDeleteView.as_view(),
name="mailing_delete",
),
url(
r"^(?P<mailing_subscription_id>[0-9]+)/mailing/delete/subscription$",
MailingSubscriptionDeleteView.as_view(),
name="mailing_subscription_delete",
),
url(
r"^membership/(?P<membership_id>[0-9]+)/set_old$",
MembershipSetOldView.as_view(),
name="membership_set_old",
),
url(r"^(?P<club_id>[0-9]+)/poster$", PosterListView.as_view(), name="poster_list"),
url(
r"^(?P<club_id>[0-9]+)/poster/create$",
PosterCreateView.as_view(),
name="poster_create",
),
url(
r"^(?P<club_id>[0-9]+)/poster/(?P<poster_id>[0-9]+)/edit$",
PosterEditView.as_view(),
name="poster_edit",
),
url(
r"^(?P<club_id>[0-9]+)/poster/(?P<poster_id>[0-9]+)/delete$",
PosterDeleteView.as_view(),
name="poster_delete",
),
] ]

View File

@ -21,4 +21,3 @@
# Place - Suite 330, Boston, MA 02111-1307, USA. # Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #

View File

@ -41,4 +41,3 @@ admin.site.register(News, NewsAdmin)
admin.site.register(Weekmail, WeekmailAdmin) admin.site.register(Weekmail, WeekmailAdmin)
admin.site.register(Screen) admin.site.register(Screen)
admin.site.register(Poster) admin.site.register(Poster)

View File

@ -6,17 +6,37 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = []
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Sith', name="Sith",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', auto_created=True, serialize=False, primary_key=True)), (
('alert_msg', models.TextField(default='', verbose_name='alert message', blank=True)), "id",
('info_msg', models.TextField(default='', verbose_name='info message', blank=True)), models.AutoField(
('index_page', models.TextField(default='', verbose_name='index page', blank=True)), verbose_name="ID",
auto_created=True,
serialize=False,
primary_key=True,
),
),
(
"alert_msg",
models.TextField(
default="", verbose_name="alert message", blank=True
),
),
(
"info_msg",
models.TextField(
default="", verbose_name="info message", blank=True
),
),
(
"index_page",
models.TextField(default="", verbose_name="index page", blank=True),
),
], ],
), )
] ]

View File

@ -8,33 +8,100 @@ from django.conf import settings
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('club', '0005_auto_20161120_1149'), ("club", "0005_auto_20161120_1149"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('com', '0001_initial'), ("com", "0001_initial"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='News', name="News",
fields=[ fields=[
('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')), (
('title', models.CharField(max_length=64, verbose_name='title')), "id",
('summary', models.TextField(verbose_name='summary')), models.AutoField(
('content', models.TextField(verbose_name='content')), primary_key=True,
('type', models.CharField(choices=[('NOTICE', 'Notice'), ('EVENT', 'Event'), ('WEEKLY', 'Weekly'), ('CALL', 'Call')], default='EVENT', max_length=16, verbose_name='type')), serialize=False,
('is_moderated', models.BooleanField(default=False, verbose_name='is moderated')), auto_created=True,
('author', models.ForeignKey(related_name='owned_news', to=settings.AUTH_USER_MODEL, verbose_name='author')), verbose_name="ID",
('club', models.ForeignKey(related_name='news', to='club.Club', verbose_name='club')), ),
('moderator', models.ForeignKey(related_name='moderated_news', null=True, to=settings.AUTH_USER_MODEL, verbose_name='moderator')), ),
("title", models.CharField(max_length=64, verbose_name="title")),
("summary", models.TextField(verbose_name="summary")),
("content", models.TextField(verbose_name="content")),
(
"type",
models.CharField(
choices=[
("NOTICE", "Notice"),
("EVENT", "Event"),
("WEEKLY", "Weekly"),
("CALL", "Call"),
],
default="EVENT",
max_length=16,
verbose_name="type",
),
),
(
"is_moderated",
models.BooleanField(default=False, verbose_name="is moderated"),
),
(
"author",
models.ForeignKey(
related_name="owned_news",
to=settings.AUTH_USER_MODEL,
verbose_name="author",
),
),
(
"club",
models.ForeignKey(
related_name="news", to="club.Club", verbose_name="club"
),
),
(
"moderator",
models.ForeignKey(
related_name="moderated_news",
null=True,
to=settings.AUTH_USER_MODEL,
verbose_name="moderator",
),
),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name='NewsDate', name="NewsDate",
fields=[ fields=[
('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')), (
('start_date', models.DateTimeField(null=True, blank=True, verbose_name='start_date')), "id",
('end_date', models.DateTimeField(null=True, blank=True, verbose_name='end_date')), models.AutoField(
('news', models.ForeignKey(related_name='dates', to='com.News', verbose_name='news_date')), primary_key=True,
serialize=False,
auto_created=True,
verbose_name="ID",
),
),
(
"start_date",
models.DateTimeField(
null=True, blank=True, verbose_name="start_date"
),
),
(
"end_date",
models.DateTimeField(
null=True, blank=True, verbose_name="end_date"
),
),
(
"news",
models.ForeignKey(
related_name="dates", to="com.News", verbose_name="news_date"
),
),
], ],
), ),
] ]

View File

@ -8,42 +8,81 @@ from django.conf import settings
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('club', '0006_auto_20161229_0040'), ("club", "0006_auto_20161229_0040"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('com', '0002_news_newsdate'), ("com", "0002_news_newsdate"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Weekmail', name="Weekmail",
fields=[ fields=[
('id', models.AutoField(serialize=False, primary_key=True, verbose_name='ID', auto_created=True)), (
('title', models.CharField(max_length=64, verbose_name='title', blank=True)), "id",
('intro', models.TextField(verbose_name='intro', blank=True)), models.AutoField(
('joke', models.TextField(verbose_name='joke', blank=True)), serialize=False,
('protip', models.TextField(verbose_name='protip', blank=True)), primary_key=True,
('conclusion', models.TextField(verbose_name='conclusion', blank=True)), verbose_name="ID",
('sent', models.BooleanField(verbose_name='sent', default=False)), auto_created=True,
),
),
(
"title",
models.CharField(max_length=64, verbose_name="title", blank=True),
),
("intro", models.TextField(verbose_name="intro", blank=True)),
("joke", models.TextField(verbose_name="joke", blank=True)),
("protip", models.TextField(verbose_name="protip", blank=True)),
("conclusion", models.TextField(verbose_name="conclusion", blank=True)),
("sent", models.BooleanField(verbose_name="sent", default=False)),
], ],
options={ options={"ordering": ["-id"]},
'ordering': ['-id'],
},
), ),
migrations.CreateModel( migrations.CreateModel(
name='WeekmailArticle', name="WeekmailArticle",
fields=[ fields=[
('id', models.AutoField(serialize=False, primary_key=True, verbose_name='ID', auto_created=True)), (
('title', models.CharField(max_length=64, verbose_name='title')), "id",
('content', models.TextField(verbose_name='content')), models.AutoField(
('rank', models.IntegerField(verbose_name='rank', default=-1)), serialize=False,
('author', models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name='author', related_name='owned_weekmail_articles')), primary_key=True,
('club', models.ForeignKey(to='club.Club', verbose_name='club', related_name='weekmail_articles')), verbose_name="ID",
('weekmail', models.ForeignKey(to='com.Weekmail', verbose_name='weekmail', related_name='articles', null=True)), auto_created=True,
),
),
("title", models.CharField(max_length=64, verbose_name="title")),
("content", models.TextField(verbose_name="content")),
("rank", models.IntegerField(verbose_name="rank", default=-1)),
(
"author",
models.ForeignKey(
to=settings.AUTH_USER_MODEL,
verbose_name="author",
related_name="owned_weekmail_articles",
),
),
(
"club",
models.ForeignKey(
to="club.Club",
verbose_name="club",
related_name="weekmail_articles",
),
),
(
"weekmail",
models.ForeignKey(
to="com.Weekmail",
verbose_name="weekmail",
related_name="articles",
null=True,
),
),
], ],
), ),
migrations.AddField( migrations.AddField(
model_name='sith', model_name="sith",
name='weekmail_destinations', name="weekmail_destinations",
field=models.TextField(verbose_name='weekmail destinations', default=''), field=models.TextField(verbose_name="weekmail destinations", default=""),
), ),
] ]

View File

@ -9,36 +9,78 @@ from django.conf import settings
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('club', '0010_auto_20170912_2028'), ("club", "0010_auto_20170912_2028"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('com', '0003_auto_20170115_2300'), ("com", "0003_auto_20170115_2300"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Poster', name="Poster",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), (
('name', models.CharField(verbose_name='name', max_length=128, default='')), "id",
('file', models.ImageField(verbose_name='file', upload_to='com/posters')), models.AutoField(
('date_begin', models.DateTimeField(default=django.utils.timezone.now)), verbose_name="ID",
('date_end', models.DateTimeField(blank=True, null=True)), primary_key=True,
('display_time', models.IntegerField(verbose_name='display time', default=30)), serialize=False,
('is_moderated', models.BooleanField(verbose_name='is moderated', default=False)), auto_created=True,
('club', models.ForeignKey(verbose_name='club', related_name='posters', to='club.Club')), ),
('moderator', models.ForeignKey(verbose_name='moderator', blank=True, null=True, related_name='moderated_posters', to=settings.AUTH_USER_MODEL)), ),
(
"name",
models.CharField(verbose_name="name", max_length=128, default=""),
),
(
"file",
models.ImageField(verbose_name="file", upload_to="com/posters"),
),
("date_begin", models.DateTimeField(default=django.utils.timezone.now)),
("date_end", models.DateTimeField(blank=True, null=True)),
(
"display_time",
models.IntegerField(verbose_name="display time", default=30),
),
(
"is_moderated",
models.BooleanField(verbose_name="is moderated", default=False),
),
(
"club",
models.ForeignKey(
verbose_name="club", related_name="posters", to="club.Club"
),
),
(
"moderator",
models.ForeignKey(
verbose_name="moderator",
blank=True,
null=True,
related_name="moderated_posters",
to=settings.AUTH_USER_MODEL,
),
),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name='Screen', name="Screen",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), (
('name', models.CharField(verbose_name='name', max_length=128)), "id",
models.AutoField(
verbose_name="ID",
primary_key=True,
serialize=False,
auto_created=True,
),
),
("name", models.CharField(verbose_name="name", max_length=128)),
], ],
), ),
migrations.AddField( migrations.AddField(
model_name='poster', model_name="poster",
name='screens', name="screens",
field=models.ManyToManyField(related_name='posters', to='com.Screen'), field=models.ManyToManyField(related_name="posters", to="com.Screen"),
), ),
] ]

View File

@ -6,14 +6,12 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("com", "0004_auto_20171221_1614")]
('com', '0004_auto_20171221_1614'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='poster', model_name="poster",
name='display_time', name="display_time",
field=models.IntegerField(verbose_name='display time', default=15), field=models.IntegerField(verbose_name="display time", default=15),
), )
] ]

View File

@ -40,10 +40,9 @@ from core.models import User, Preferences, RealGroup, Notification, SithFile
from club.models import Club from club.models import Club
class Sith(models.Model): class Sith(models.Model):
"""A one instance class storing all the modifiable infos""" """A one instance class storing all the modifiable infos"""
alert_msg = models.TextField(_("alert message"), default="", blank=True) alert_msg = models.TextField(_("alert message"), default="", blank=True)
info_msg = models.TextField(_("info message"), default="", blank=True) info_msg = models.TextField(_("info message"), default="", blank=True)
index_page = models.TextField(_("index page"), default="", blank=True) index_page = models.TextField(_("index page"), default="", blank=True)
@ -57,23 +56,30 @@ class Sith(models.Model):
NEWS_TYPES = [ NEWS_TYPES = [
('NOTICE', _('Notice')), ("NOTICE", _("Notice")),
('EVENT', _('Event')), ("EVENT", _("Event")),
('WEEKLY', _('Weekly')), ("WEEKLY", _("Weekly")),
('CALL', _('Call')), ("CALL", _("Call")),
] ]
class News(models.Model): class News(models.Model):
"""The news class""" """The news class"""
title = models.CharField(_("title"), max_length=64) title = models.CharField(_("title"), max_length=64)
summary = models.TextField(_("summary")) summary = models.TextField(_("summary"))
content = models.TextField(_("content")) content = models.TextField(_("content"))
type = models.CharField(_("type"), max_length=16, choices=NEWS_TYPES, default="EVENT") type = models.CharField(
_("type"), max_length=16, choices=NEWS_TYPES, default="EVENT"
)
club = models.ForeignKey(Club, related_name="news", verbose_name=_("club")) club = models.ForeignKey(Club, related_name="news", verbose_name=_("club"))
author = models.ForeignKey(User, related_name="owned_news", verbose_name=_("author")) author = models.ForeignKey(
User, related_name="owned_news", verbose_name=_("author")
)
is_moderated = models.BooleanField(_("is moderated"), default=False) is_moderated = models.BooleanField(_("is moderated"), default=False)
moderator = models.ForeignKey(User, related_name="moderated_news", verbose_name=_("moderator"), null=True) moderator = models.ForeignKey(
User, related_name="moderated_news", verbose_name=_("moderator"), null=True
)
def is_owned_by(self, user): def is_owned_by(self, user):
return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) or user == self.author return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) or user == self.author
@ -85,7 +91,7 @@ class News(models.Model):
return self.is_moderated or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) return self.is_moderated or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
def get_absolute_url(self): def get_absolute_url(self):
return reverse('com:news_detail', kwargs={'news_id': self.id}) return reverse("com:news_detail", kwargs={"news_id": self.id})
def get_full_url(self): def get_full_url(self):
return "https://%s%s" % (settings.SITH_URL, self.get_absolute_url()) return "https://%s%s" % (settings.SITH_URL, self.get_absolute_url())
@ -95,15 +101,28 @@ class News(models.Model):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
super(News, self).save(*args, **kwargs) super(News, self).save(*args, **kwargs)
for u in RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID).first().users.all(): for u in (
Notification(user=u, url=reverse("com:news_admin_list"), RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID)
type="NEWS_MODERATION", param="1").save() .first()
.users.all()
):
Notification(
user=u,
url=reverse("com:news_admin_list"),
type="NEWS_MODERATION",
param="1",
).save()
def news_notification_callback(notif): def news_notification_callback(notif):
count = News.objects.filter( count = (
Q(dates__start_date__gt=timezone.now(), is_moderated=False) | News.objects.filter(
Q(type="NOTICE", is_moderated=False) Q(dates__start_date__gt=timezone.now(), is_moderated=False)
).distinct().count() | Q(type="NOTICE", is_moderated=False)
)
.distinct()
.count()
)
if count: if count:
notif.viewed = False notif.viewed = False
notif.param = "%s" % count notif.param = "%s" % count
@ -111,6 +130,7 @@ def news_notification_callback(notif):
else: else:
notif.viewed = True notif.viewed = True
class NewsDate(models.Model): class NewsDate(models.Model):
""" """
A date class, useful for weekly events, or for events that just have no date A date class, useful for weekly events, or for events that just have no date
@ -118,9 +138,10 @@ class NewsDate(models.Model):
This class allows more flexibilty managing the dates related to a news, particularly when this news is weekly, since This class allows more flexibilty managing the dates related to a news, particularly when this news is weekly, since
we don't have to make copies we don't have to make copies
""" """
news = models.ForeignKey(News, related_name="dates", verbose_name=_("news_date")) news = models.ForeignKey(News, related_name="dates", verbose_name=_("news_date"))
start_date = models.DateTimeField(_('start_date'), null=True, blank=True) start_date = models.DateTimeField(_("start_date"), null=True, blank=True)
end_date = models.DateTimeField(_('end_date'), null=True, blank=True) end_date = models.DateTimeField(_("end_date"), null=True, blank=True)
def __str__(self): def __str__(self):
return "%s: %s - %s" % (self.news.title, self.start_date, self.end_date) return "%s: %s - %s" % (self.news.title, self.start_date, self.end_date)
@ -130,6 +151,7 @@ class Weekmail(models.Model):
""" """
The weekmail class The weekmail class
""" """
title = models.CharField(_("title"), max_length=64, blank=True) title = models.CharField(_("title"), max_length=64, blank=True)
intro = models.TextField(_("intro"), blank=True) intro = models.TextField(_("intro"), blank=True)
joke = models.TextField(_("joke"), blank=True) joke = models.TextField(_("joke"), blank=True)
@ -138,16 +160,21 @@ class Weekmail(models.Model):
sent = models.BooleanField(_("sent"), default=False) sent = models.BooleanField(_("sent"), default=False)
class Meta: class Meta:
ordering = ['-id'] ordering = ["-id"]
def send(self): def send(self):
dest = [i[0] for i in Preferences.objects.filter(receive_weekmail=True).values_list('user__email')] dest = [
i[0]
for i in Preferences.objects.filter(receive_weekmail=True).values_list(
"user__email"
)
]
with transaction.atomic(): with transaction.atomic():
email = EmailMultiAlternatives( email = EmailMultiAlternatives(
subject=self.title, subject=self.title,
body=self.render_text(), body=self.render_text(),
from_email=settings.SITH_COM_EMAIL, from_email=settings.SITH_COM_EMAIL,
to=Sith.objects.first().weekmail_destinations.split(' '), to=Sith.objects.first().weekmail_destinations.split(" "),
bcc=dest, bcc=dest,
) )
email.attach_alternative(self.render_html(), "text/html") email.attach_alternative(self.render_html(), "text/html")
@ -157,14 +184,14 @@ class Weekmail(models.Model):
Weekmail().save() Weekmail().save()
def render_text(self): def render_text(self):
return render(None, "com/weekmail_renderer_text.jinja", context={ return render(
'weekmail': self, None, "com/weekmail_renderer_text.jinja", context={"weekmail": self}
}).content.decode('utf-8') ).content.decode("utf-8")
def render_html(self): def render_html(self):
return render(None, "com/weekmail_renderer_html.jinja", context={ return render(
'weekmail': self, None, "com/weekmail_renderer_html.jinja", context={"weekmail": self}
}).content.decode('utf-8') ).content.decode("utf-8")
def get_banner(self): def get_banner(self):
return "http://" + settings.SITH_URL + static("com/img/weekmail_bannerA18.jpg") return "http://" + settings.SITH_URL + static("com/img/weekmail_bannerA18.jpg")
@ -180,12 +207,18 @@ class Weekmail(models.Model):
class WeekmailArticle(models.Model): class WeekmailArticle(models.Model):
weekmail = models.ForeignKey(Weekmail, related_name="articles", verbose_name=_("weekmail"), null=True) weekmail = models.ForeignKey(
Weekmail, related_name="articles", verbose_name=_("weekmail"), null=True
)
title = models.CharField(_("title"), max_length=64) title = models.CharField(_("title"), max_length=64)
content = models.TextField(_("content")) content = models.TextField(_("content"))
author = models.ForeignKey(User, related_name="owned_weekmail_articles", verbose_name=_("author")) author = models.ForeignKey(
club = models.ForeignKey(Club, related_name="weekmail_articles", verbose_name=_("club")) User, related_name="owned_weekmail_articles", verbose_name=_("author")
rank = models.IntegerField(_('rank'), default=-1) )
club = models.ForeignKey(
Club, related_name="weekmail_articles", verbose_name=_("club")
)
rank = models.IntegerField(_("rank"), default=-1)
def is_owned_by(self, user): def is_owned_by(self, user):
return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
@ -199,7 +232,9 @@ class Screen(models.Model):
def active_posters(self): def active_posters(self):
now = timezone.now() now = timezone.now()
return self.posters.filter(is_moderated=True, date_begin__lte=now).filter(Q(date_end__isnull=True) | Q(date_end__gte=now)) return self.posters.filter(is_moderated=True, date_begin__lte=now).filter(
Q(date_end__isnull=True) | Q(date_end__gte=now)
)
def is_owned_by(self, user): def is_owned_by(self, user):
return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
@ -209,21 +244,40 @@ class Screen(models.Model):
class Poster(models.Model): class Poster(models.Model):
name = models.CharField(_("name"), blank=False, null=False, max_length=128, default="") name = models.CharField(
_("name"), blank=False, null=False, max_length=128, default=""
)
file = models.ImageField(_("file"), null=False, upload_to="com/posters") file = models.ImageField(_("file"), null=False, upload_to="com/posters")
club = models.ForeignKey(Club, related_name="posters", verbose_name=_("club"), null=False) club = models.ForeignKey(
Club, related_name="posters", verbose_name=_("club"), null=False
)
screens = models.ManyToManyField(Screen, related_name="posters") screens = models.ManyToManyField(Screen, related_name="posters")
date_begin = models.DateTimeField(blank=False, null=False, default=timezone.now) date_begin = models.DateTimeField(blank=False, null=False, default=timezone.now)
date_end = models.DateTimeField(blank=True, null=True) date_end = models.DateTimeField(blank=True, null=True)
display_time = models.IntegerField(_("display time"), blank=False, null=False, default=15) display_time = models.IntegerField(
_("display time"), blank=False, null=False, default=15
)
is_moderated = models.BooleanField(_("is moderated"), default=False) is_moderated = models.BooleanField(_("is moderated"), default=False)
moderator = models.ForeignKey(User, related_name="moderated_posters", verbose_name=_("moderator"), null=True, blank=True) moderator = models.ForeignKey(
User,
related_name="moderated_posters",
verbose_name=_("moderator"),
null=True,
blank=True,
)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.is_moderated: if not self.is_moderated:
for u in RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID).first().users.all(): for u in (
Notification(user=u, url=reverse("com:poster_moderate_list"), RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID)
type="POSTER_MODERATION").save() .first()
.users.all()
):
Notification(
user=u,
url=reverse("com:poster_moderate_list"),
type="POSTER_MODERATION",
).save()
return super(Poster, self).save(*args, **kwargs) return super(Poster, self).save(*args, **kwargs)
def clean(self, *args, **kwargs): def clean(self, *args, **kwargs):
@ -231,7 +285,9 @@ class Poster(models.Model):
raise ValidationError(_("Begin date should be before end date")) raise ValidationError(_("Begin date should be before end date"))
def is_owned_by(self, user): def is_owned_by(self, user):
return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) or Club.objects.filter(id__in=user.clubs_with_rights) return user.is_in_group(
settings.SITH_GROUP_COM_ADMIN_ID
) or Club.objects.filter(id__in=user.clubs_with_rights)
def can_be_moderated_by(self, user): def can_be_moderated_by(self, user):
return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)

View File

@ -34,25 +34,43 @@ class ComTest(TestCase):
def setUp(self): def setUp(self):
call_command("populate") call_command("populate")
self.skia = User.objects.filter(username="skia").first() self.skia = User.objects.filter(username="skia").first()
self.com_group = RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID).first() self.com_group = RealGroup.objects.filter(
id=settings.SITH_GROUP_COM_ADMIN_ID
).first()
self.skia.groups = [self.com_group] self.skia.groups = [self.com_group]
self.skia.save() self.skia.save()
self.client.login(username=self.skia.username, password='plop') self.client.login(username=self.skia.username, password="plop")
def test_alert_msg(self): def test_alert_msg(self):
response = self.client.post(reverse("com:alert_edit"), {"alert_msg": """ response = self.client.post(
reverse("com:alert_edit"),
{
"alert_msg": """
### ALERTE! ### ALERTE!
**Caaaataaaapuuuulte!!!!** **Caaaataaaapuuuulte!!!!**
"""}) """
},
)
r = self.client.get(reverse("core:index")) r = self.client.get(reverse("core:index"))
self.assertTrue(r.status_code == 200) self.assertTrue(r.status_code == 200)
self.assertTrue("""<div id="alert_box">\\n <div class="markdown"><h3>ALERTE!</h3>\\n<p><strong>Caaaataaaapuuuulte!!!!</strong></p>""" in str(r.content)) self.assertTrue(
"""<div id="alert_box">\\n <div class="markdown"><h3>ALERTE!</h3>\\n<p><strong>Caaaataaaapuuuulte!!!!</strong></p>"""
in str(r.content)
)
def test_info_msg(self): def test_info_msg(self):
response = self.client.post(reverse("com:info_edit"), {"info_msg": """ response = self.client.post(
reverse("com:info_edit"),
{
"info_msg": """
### INFO: **Caaaataaaapuuuulte!!!!** ### INFO: **Caaaataaaapuuuulte!!!!**
"""}) """
},
)
r = self.client.get(reverse("core:index")) r = self.client.get(reverse("core:index"))
self.assertTrue(r.status_code == 200) self.assertTrue(r.status_code == 200)
self.assertTrue("""<div id="info_box">\\n <div class="markdown"><h3>INFO: <strong>Caaaataaaapuuuulte!!!!</strong></h3>""" in str(r.content)) self.assertTrue(
"""<div id="info_box">\\n <div class="markdown"><h3>INFO: <strong>Caaaataaaapuuuulte!!!!</strong></h3>"""
in str(r.content)
)

View File

@ -28,35 +28,94 @@ from com.views import *
from club.views import MailingDeleteView from club.views import MailingDeleteView
urlpatterns = [ urlpatterns = [
url(r'^sith/edit/alert$', AlertMsgEditView.as_view(), name='alert_edit'), url(r"^sith/edit/alert$", AlertMsgEditView.as_view(), name="alert_edit"),
url(r'^sith/edit/info$', InfoMsgEditView.as_view(), name='info_edit'), url(r"^sith/edit/info$", InfoMsgEditView.as_view(), name="info_edit"),
url(r'^sith/edit/index$', IndexEditView.as_view(), name='index_edit'), url(r"^sith/edit/index$", IndexEditView.as_view(), name="index_edit"),
url(r'^sith/edit/weekmail_destinations$', WeekmailDestinationEditView.as_view(), name='weekmail_destinations'), url(
url(r'^weekmail$', WeekmailEditView.as_view(), name='weekmail'), r"^sith/edit/weekmail_destinations$",
url(r'^weekmail/preview$', WeekmailPreviewView.as_view(), name='weekmail_preview'), WeekmailDestinationEditView.as_view(),
url(r'^weekmail/new_article$', WeekmailArticleCreateView.as_view(), name='weekmail_article'), name="weekmail_destinations",
url(r'^weekmail/article/(?P<article_id>[0-9]+)/delete$', WeekmailArticleDeleteView.as_view(), name='weekmail_article_delete'), ),
url(r'^weekmail/article/(?P<article_id>[0-9]+)/edit$', WeekmailArticleEditView.as_view(), name='weekmail_article_edit'), url(r"^weekmail$", WeekmailEditView.as_view(), name="weekmail"),
url(r'^news$', NewsListView.as_view(), name='news_list'), url(r"^weekmail/preview$", WeekmailPreviewView.as_view(), name="weekmail_preview"),
url(r'^news/admin$', NewsAdminListView.as_view(), name='news_admin_list'), url(
url(r'^news/create$', NewsCreateView.as_view(), name='news_new'), r"^weekmail/new_article$",
url(r'^news/(?P<news_id>[0-9]+)/delete$', NewsDeleteView.as_view(), name='news_delete'), WeekmailArticleCreateView.as_view(),
url(r'^news/(?P<news_id>[0-9]+)/moderate$', NewsModerateView.as_view(), name='news_moderate'), name="weekmail_article",
url(r'^news/(?P<news_id>[0-9]+)/edit$', NewsEditView.as_view(), name='news_edit'), ),
url(r'^news/(?P<news_id>[0-9]+)$', NewsDetailView.as_view(), name='news_detail'), url(
url(r'^mailings$', MailingListAdminView.as_view(), name='mailing_admin'), r"^weekmail/article/(?P<article_id>[0-9]+)/delete$",
url(r'^mailings/(?P<mailing_id>[0-9]+)/moderate$', MailingModerateView.as_view(), name='mailing_moderate'), WeekmailArticleDeleteView.as_view(),
url(r'^mailings/(?P<mailing_id>[0-9]+)/delete$', MailingDeleteView.as_view(redirect_page='com:mailing_admin'), name='mailing_delete'), name="weekmail_article_delete",
url(r'^poster$', PosterListView.as_view(), name='poster_list'), ),
url(r'^poster/create$', PosterCreateView.as_view(), name='poster_create'), url(
url(r'^poster/(?P<poster_id>[0-9]+)/edit$', PosterEditView.as_view(), name='poster_edit'), r"^weekmail/article/(?P<article_id>[0-9]+)/edit$",
url(r'^poster/(?P<poster_id>[0-9]+)/delete$', PosterDeleteView.as_view(), name='poster_delete'), WeekmailArticleEditView.as_view(),
url(r'^poster/moderate$', PosterModerateListView.as_view(), name='poster_moderate_list'), name="weekmail_article_edit",
url(r'^poster/(?P<object_id>[0-9]+)/moderate$', PosterModerateView.as_view(), name='poster_moderate'), ),
url(r'^screen$', ScreenListView.as_view(), name='screen_list'), url(r"^news$", NewsListView.as_view(), name="news_list"),
url(r'^screen/create$', ScreenCreateView.as_view(), name='screen_create'), url(r"^news/admin$", NewsAdminListView.as_view(), name="news_admin_list"),
url(r'^screen/(?P<screen_id>[0-9]+)/slideshow$', ScreenSlideshowView.as_view(), name='screen_slideshow'), url(r"^news/create$", NewsCreateView.as_view(), name="news_new"),
url(r'^screen/(?P<screen_id>[0-9]+)/edit$', ScreenEditView.as_view(), name='screen_edit'), url(
url(r'^screen/(?P<screen_id>[0-9]+)/delete$', ScreenDeleteView.as_view(), name='screen_delete'), r"^news/(?P<news_id>[0-9]+)/delete$",
NewsDeleteView.as_view(),
name="news_delete",
),
url(
r"^news/(?P<news_id>[0-9]+)/moderate$",
NewsModerateView.as_view(),
name="news_moderate",
),
url(r"^news/(?P<news_id>[0-9]+)/edit$", NewsEditView.as_view(), name="news_edit"),
url(r"^news/(?P<news_id>[0-9]+)$", NewsDetailView.as_view(), name="news_detail"),
url(r"^mailings$", MailingListAdminView.as_view(), name="mailing_admin"),
url(
r"^mailings/(?P<mailing_id>[0-9]+)/moderate$",
MailingModerateView.as_view(),
name="mailing_moderate",
),
url(
r"^mailings/(?P<mailing_id>[0-9]+)/delete$",
MailingDeleteView.as_view(redirect_page="com:mailing_admin"),
name="mailing_delete",
),
url(r"^poster$", PosterListView.as_view(), name="poster_list"),
url(r"^poster/create$", PosterCreateView.as_view(), name="poster_create"),
url(
r"^poster/(?P<poster_id>[0-9]+)/edit$",
PosterEditView.as_view(),
name="poster_edit",
),
url(
r"^poster/(?P<poster_id>[0-9]+)/delete$",
PosterDeleteView.as_view(),
name="poster_delete",
),
url(
r"^poster/moderate$",
PosterModerateListView.as_view(),
name="poster_moderate_list",
),
url(
r"^poster/(?P<object_id>[0-9]+)/moderate$",
PosterModerateView.as_view(),
name="poster_moderate",
),
url(r"^screen$", ScreenListView.as_view(), name="screen_list"),
url(r"^screen/create$", ScreenCreateView.as_view(), name="screen_create"),
url(
r"^screen/(?P<screen_id>[0-9]+)/slideshow$",
ScreenSlideshowView.as_view(),
name="screen_slideshow",
),
url(
r"^screen/(?P<screen_id>[0-9]+)/edit$",
ScreenEditView.as_view(),
name="screen_edit",
),
url(
r"^screen/(?P<screen_id>[0-9]+)/delete$",
ScreenDeleteView.as_view(),
name="screen_delete",
),
] ]

View File

@ -41,7 +41,14 @@ from django import forms
from datetime import timedelta from datetime import timedelta
from com.models import Sith, News, NewsDate, Weekmail, WeekmailArticle, Screen, Poster from com.models import Sith, News, NewsDate, Weekmail, WeekmailArticle, Screen, Poster
from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, TabedViewMixin, CanCreateMixin, QuickNotifMixin from core.views import (
CanViewMixin,
CanEditMixin,
CanEditPropMixin,
TabedViewMixin,
CanCreateMixin,
QuickNotifMixin,
)
from core.views.forms import SelectDateTime from core.views.forms import SelectDateTime
from core.models import Notification, RealGroup, User from core.models import Notification, RealGroup, User
from club.models import Club, Mailing from club.models import Club, Mailing
@ -55,23 +62,40 @@ sith = Sith.objects.first
class PosterForm(forms.ModelForm): class PosterForm(forms.ModelForm):
class Meta: class Meta:
model = Poster model = Poster
fields = ['name', 'file', 'club', 'screens', 'date_begin', 'date_end', 'display_time'] fields = [
widgets = { "name",
'screens': forms.CheckboxSelectMultiple, "file",
} "club",
"screens",
"date_begin",
"date_end",
"display_time",
]
widgets = {"screens": forms.CheckboxSelectMultiple}
date_begin = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Start date"), date_begin = forms.DateTimeField(
widget=SelectDateTime, required=True, initial=timezone.now().strftime("%Y-%m-%d %H:%M:%S")) ["%Y-%m-%d %H:%M:%S"],
date_end = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End date"), label=_("Start date"),
widget=SelectDateTime, required=False) widget=SelectDateTime,
required=True,
initial=timezone.now().strftime("%Y-%m-%d %H:%M:%S"),
)
date_end = forms.DateTimeField(
["%Y-%m-%d %H:%M:%S"],
label=_("End date"),
widget=SelectDateTime,
required=False,
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None) self.user = kwargs.pop("user", None)
super(PosterForm, self).__init__(*args, **kwargs) super(PosterForm, self).__init__(*args, **kwargs)
if self.user: if self.user:
if not self.user.is_com_admin: if not self.user.is_com_admin:
self.fields['club'].queryset = Club.objects.filter(id__in=self.user.clubs_with_rights) self.fields["club"].queryset = Club.objects.filter(
self.fields.pop('display_time') id__in=self.user.clubs_with_rights
)
self.fields.pop("display_time")
class ComTabsMixin(TabedViewMixin): class ComTabsMixin(TabedViewMixin):
@ -80,51 +104,54 @@ class ComTabsMixin(TabedViewMixin):
def get_list_of_tabs(self): def get_list_of_tabs(self):
tab_list = [] tab_list = []
tab_list.append({ tab_list.append(
'url': reverse('com:weekmail'), {"url": reverse("com:weekmail"), "slug": "weekmail", "name": _("Weekmail")}
'slug': 'weekmail', )
'name': _("Weekmail"), tab_list.append(
}) {
tab_list.append({ "url": reverse("com:weekmail_destinations"),
'url': reverse('com:weekmail_destinations'), "slug": "weekmail_destinations",
'slug': 'weekmail_destinations', "name": _("Weekmail destinations"),
'name': _("Weekmail destinations"), }
}) )
tab_list.append({ tab_list.append(
'url': reverse('com:index_edit'), {"url": reverse("com:index_edit"), "slug": "index", "name": _("Index page")}
'slug': 'index', )
'name': _("Index page"), tab_list.append(
}) {"url": reverse("com:info_edit"), "slug": "info", "name": _("Info message")}
tab_list.append({ )
'url': reverse('com:info_edit'), tab_list.append(
'slug': 'info', {
'name': _("Info message"), "url": reverse("com:alert_edit"),
}) "slug": "alert",
tab_list.append({ "name": _("Alert message"),
'url': reverse('com:alert_edit'), }
'slug': 'alert', )
'name': _("Alert message"), tab_list.append(
}) {
tab_list.append({ "url": reverse("com:mailing_admin"),
'url': reverse('com:mailing_admin'), "slug": "mailings",
'slug': 'mailings', "name": _("Mailing lists administration"),
'name': _("Mailing lists administration"), }
}) )
tab_list.append({ tab_list.append(
'url': reverse('com:poster_list'), {
'slug': 'posters', "url": reverse("com:poster_list"),
'name': _("Posters list"), "slug": "posters",
}) "name": _("Posters list"),
tab_list.append({ }
'url': reverse('com:screen_list'), )
'slug': 'screens', tab_list.append(
'name': _("Screens list"), {
}) "url": reverse("com:screen_list"),
"slug": "screens",
"name": _("Screens list"),
}
)
return tab_list return tab_list
class IsComAdminMixin(View): class IsComAdminMixin(View):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not (request.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)): if not (request.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)):
raise PermissionDenied raise PermissionDenied
@ -133,34 +160,35 @@ class IsComAdminMixin(View):
class ComEditView(ComTabsMixin, CanEditPropMixin, UpdateView): class ComEditView(ComTabsMixin, CanEditPropMixin, UpdateView):
model = Sith model = Sith
template_name = 'core/edit.jinja' template_name = "core/edit.jinja"
def get_object(self, queryset=None): def get_object(self, queryset=None):
return Sith.objects.first() return Sith.objects.first()
class AlertMsgEditView(ComEditView): class AlertMsgEditView(ComEditView):
fields = ['alert_msg'] fields = ["alert_msg"]
current_tab = "alert" current_tab = "alert"
success_url = reverse_lazy('com:alert_edit') success_url = reverse_lazy("com:alert_edit")
class InfoMsgEditView(ComEditView): class InfoMsgEditView(ComEditView):
fields = ['info_msg'] fields = ["info_msg"]
current_tab = "info" current_tab = "info"
success_url = reverse_lazy('com:info_edit') success_url = reverse_lazy("com:info_edit")
class IndexEditView(ComEditView): class IndexEditView(ComEditView):
fields = ['index_page'] fields = ["index_page"]
current_tab = "index" current_tab = "index"
success_url = reverse_lazy('com:index_edit') success_url = reverse_lazy("com:index_edit")
class WeekmailDestinationEditView(ComEditView): class WeekmailDestinationEditView(ComEditView):
fields = ['weekmail_destinations'] fields = ["weekmail_destinations"]
current_tab = "weekmail_destinations" current_tab = "weekmail_destinations"
success_url = reverse_lazy('com:weekmail_destinations') success_url = reverse_lazy("com:weekmail_destinations")
# News # News
@ -168,43 +196,64 @@ class WeekmailDestinationEditView(ComEditView):
class NewsForm(forms.ModelForm): class NewsForm(forms.ModelForm):
class Meta: class Meta:
model = News model = News
fields = ['type', 'title', 'club', 'summary', 'content', 'author'] fields = ["type", "title", "club", "summary", "content", "author"]
widgets = { widgets = {"author": forms.HiddenInput, "type": forms.RadioSelect}
'author': forms.HiddenInput,
'type': forms.RadioSelect, start_date = forms.DateTimeField(
} ["%Y-%m-%d %H:%M:%S"],
start_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Start date"), widget=SelectDateTime, required=False) label=_("Start date"),
end_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End date"), widget=SelectDateTime, required=False) widget=SelectDateTime,
until = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Until"), widget=SelectDateTime, required=False) required=False,
)
end_date = forms.DateTimeField(
["%Y-%m-%d %H:%M:%S"],
label=_("End date"),
widget=SelectDateTime,
required=False,
)
until = forms.DateTimeField(
["%Y-%m-%d %H:%M:%S"], label=_("Until"), widget=SelectDateTime, required=False
)
automoderation = forms.BooleanField(label=_("Automoderation"), required=False) automoderation = forms.BooleanField(label=_("Automoderation"), required=False)
def clean(self): def clean(self):
self.cleaned_data = super(NewsForm, self).clean() self.cleaned_data = super(NewsForm, self).clean()
if self.cleaned_data['type'] != "NOTICE": if self.cleaned_data["type"] != "NOTICE":
if not self.cleaned_data['start_date']: if not self.cleaned_data["start_date"]:
self.add_error('start_date', ValidationError(_("This field is required."))) self.add_error(
if not self.cleaned_data['end_date']: "start_date", ValidationError(_("This field is required."))
self.add_error('end_date', ValidationError(_("This field is required."))) )
if self.cleaned_data['start_date'] > self.cleaned_data['end_date']: if not self.cleaned_data["end_date"]:
self.add_error('end_date', ValidationError(_("You crazy? You can not finish an event before starting it."))) self.add_error(
if self.cleaned_data['type'] == "WEEKLY" and not self.cleaned_data['until']: "end_date", ValidationError(_("This field is required."))
self.add_error('until', ValidationError(_("This field is required."))) )
if self.cleaned_data["start_date"] > self.cleaned_data["end_date"]:
self.add_error(
"end_date",
ValidationError(
_("You crazy? You can not finish an event before starting it.")
),
)
if self.cleaned_data["type"] == "WEEKLY" and not self.cleaned_data["until"]:
self.add_error("until", ValidationError(_("This field is required.")))
return self.cleaned_data return self.cleaned_data
def save(self): def save(self):
ret = super(NewsForm, self).save() ret = super(NewsForm, self).save()
self.instance.dates.all().delete() self.instance.dates.all().delete()
if self.instance.type == "EVENT" or self.instance.type == "CALL": if self.instance.type == "EVENT" or self.instance.type == "CALL":
NewsDate(start_date=self.cleaned_data['start_date'], NewsDate(
end_date=self.cleaned_data['end_date'], start_date=self.cleaned_data["start_date"],
news=self.instance).save() end_date=self.cleaned_data["end_date"],
news=self.instance,
).save()
elif self.instance.type == "WEEKLY": elif self.instance.type == "WEEKLY":
start_date = self.cleaned_data['start_date'] start_date = self.cleaned_data["start_date"]
end_date = self.cleaned_data['end_date'] end_date = self.cleaned_data["end_date"]
while start_date <= self.cleaned_data['until']: while start_date <= self.cleaned_data["until"]:
NewsDate(start_date=start_date, NewsDate(
end_date=end_date, start_date=start_date, end_date=end_date, news=self.instance
news=self.instance).save() ).save()
start_date += timedelta(days=7) start_date += timedelta(days=7)
end_date += timedelta(days=7) end_date += timedelta(days=7)
return ret return ret
@ -213,59 +262,81 @@ class NewsForm(forms.ModelForm):
class NewsEditView(CanEditMixin, UpdateView): class NewsEditView(CanEditMixin, UpdateView):
model = News model = News
form_class = NewsForm form_class = NewsForm
template_name = 'com/news_edit.jinja' template_name = "com/news_edit.jinja"
pk_url_kwarg = 'news_id' pk_url_kwarg = "news_id"
def get_initial(self): def get_initial(self):
init = {} init = {}
try: try:
init['start_date'] = self.object.dates.order_by('id').first().start_date.strftime('%Y-%m-%d %H:%M:%S') init["start_date"] = (
self.object.dates.order_by("id")
.first()
.start_date.strftime("%Y-%m-%d %H:%M:%S")
)
except: except:
pass pass
try: try:
init['end_date'] = self.object.dates.order_by('id').first().end_date.strftime('%Y-%m-%d %H:%M:%S') init["end_date"] = (
self.object.dates.order_by("id")
.first()
.end_date.strftime("%Y-%m-%d %H:%M:%S")
)
except: except:
pass pass
return init return init
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
form = self.get_form() form = self.get_form()
if form.is_valid() and 'preview' not in request.POST.keys(): if form.is_valid() and "preview" not in request.POST.keys():
return self.form_valid(form) return self.form_valid(form)
else: else:
return self.form_invalid(form) return self.form_invalid(form)
def form_valid(self, form): def form_valid(self, form):
self.object = form.save() self.object = form.save()
if form.cleaned_data['automoderation'] and self.request.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID): if form.cleaned_data["automoderation"] and self.request.user.is_in_group(
settings.SITH_GROUP_COM_ADMIN_ID
):
self.object.moderator = self.request.user self.object.moderator = self.request.user
self.object.is_moderated = True self.object.is_moderated = True
self.object.save() self.object.save()
else: else:
self.object.is_moderated = False self.object.is_moderated = False
self.object.save() self.object.save()
for u in RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID).first().users.all(): for u in (
if not u.notifications.filter(type="NEWS_MODERATION", viewed=False).exists(): RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID)
Notification(user=u, url=reverse("com:news_detail", kwargs={'news_id': self.object.id}), type="NEWS_MODERATION").save() .first()
.users.all()
):
if not u.notifications.filter(
type="NEWS_MODERATION", viewed=False
).exists():
Notification(
user=u,
url=reverse(
"com:news_detail", kwargs={"news_id": self.object.id}
),
type="NEWS_MODERATION",
).save()
return super(NewsEditView, self).form_valid(form) return super(NewsEditView, self).form_valid(form)
class NewsCreateView(CanCreateMixin, CreateView): class NewsCreateView(CanCreateMixin, CreateView):
model = News model = News
form_class = NewsForm form_class = NewsForm
template_name = 'com/news_edit.jinja' template_name = "com/news_edit.jinja"
def get_initial(self): def get_initial(self):
init = {'author': self.request.user} init = {"author": self.request.user}
try: try:
init['club'] = Club.objects.filter(id=self.request.GET['club']).first() init["club"] = Club.objects.filter(id=self.request.GET["club"]).first()
except: except:
pass pass
return init return init
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
form = self.get_form() form = self.get_form()
if form.is_valid() and 'preview' not in request.POST.keys(): if form.is_valid() and "preview" not in request.POST.keys():
return self.form_valid(form) return self.form_valid(form)
else: else:
self.object = form.instance self.object = form.instance
@ -273,176 +344,216 @@ class NewsCreateView(CanCreateMixin, CreateView):
def form_valid(self, form): def form_valid(self, form):
self.object = form.save() self.object = form.save()
if form.cleaned_data['automoderation'] and self.request.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID): if form.cleaned_data["automoderation"] and self.request.user.is_in_group(
settings.SITH_GROUP_COM_ADMIN_ID
):
self.object.moderator = self.request.user self.object.moderator = self.request.user
self.object.is_moderated = True self.object.is_moderated = True
self.object.save() self.object.save()
else: else:
for u in RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID).first().users.all(): for u in (
if not u.notifications.filter(type="NEWS_MODERATION", viewed=False).exists(): RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID)
Notification(user=u, url=reverse("com:news_admin_list"), type="NEWS_MODERATION").save() .first()
.users.all()
):
if not u.notifications.filter(
type="NEWS_MODERATION", viewed=False
).exists():
Notification(
user=u,
url=reverse("com:news_admin_list"),
type="NEWS_MODERATION",
).save()
return super(NewsCreateView, self).form_valid(form) return super(NewsCreateView, self).form_valid(form)
class NewsDeleteView(CanEditMixin, DeleteView): class NewsDeleteView(CanEditMixin, DeleteView):
model = News model = News
pk_url_kwarg = 'news_id' pk_url_kwarg = "news_id"
template_name = 'core/delete_confirm.jinja' template_name = "core/delete_confirm.jinja"
success_url = reverse_lazy('com:news_admin_list') success_url = reverse_lazy("com:news_admin_list")
class NewsModerateView(CanEditMixin, SingleObjectMixin): class NewsModerateView(CanEditMixin, SingleObjectMixin):
model = News model = News
pk_url_kwarg = 'news_id' pk_url_kwarg = "news_id"
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
if 'remove' in request.GET.keys(): if "remove" in request.GET.keys():
self.object.is_moderated = False self.object.is_moderated = False
else: else:
self.object.is_moderated = True self.object.is_moderated = True
self.object.moderator = request.user self.object.moderator = request.user
self.object.save() self.object.save()
if 'next' in self.request.GET.keys(): if "next" in self.request.GET.keys():
return redirect(self.request.GET['next']) return redirect(self.request.GET["next"])
return redirect('com:news_admin_list') return redirect("com:news_admin_list")
class NewsAdminListView(CanEditMixin, ListView): class NewsAdminListView(CanEditMixin, ListView):
model = News model = News
template_name = 'com/news_admin_list.jinja' template_name = "com/news_admin_list.jinja"
queryset = News.objects.all() queryset = News.objects.all()
class NewsListView(CanViewMixin, ListView): class NewsListView(CanViewMixin, ListView):
model = News model = News
template_name = 'com/news_list.jinja' template_name = "com/news_list.jinja"
queryset = News.objects.filter(is_moderated=True) queryset = News.objects.filter(is_moderated=True)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(NewsListView, self).get_context_data(**kwargs) kwargs = super(NewsListView, self).get_context_data(**kwargs)
kwargs['NewsDate'] = NewsDate kwargs["NewsDate"] = NewsDate
kwargs['timedelta'] = timedelta kwargs["timedelta"] = timedelta
kwargs['birthdays'] = User.objects\ kwargs["birthdays"] = (
.filter(date_of_birth__month=timezone.now().month, date_of_birth__day=timezone.now().day)\ User.objects.filter(
.filter(role__in=['STUDENT', 'FORMER STUDENT'])\ date_of_birth__month=timezone.now().month,
.order_by('-date_of_birth') date_of_birth__day=timezone.now().day,
)
.filter(role__in=["STUDENT", "FORMER STUDENT"])
.order_by("-date_of_birth")
)
return kwargs return kwargs
class NewsDetailView(CanViewMixin, DetailView): class NewsDetailView(CanViewMixin, DetailView):
model = News model = News
template_name = 'com/news_detail.jinja' template_name = "com/news_detail.jinja"
pk_url_kwarg = 'news_id' pk_url_kwarg = "news_id"
# Weekmail # Weekmail
class WeekmailPreviewView(ComTabsMixin, CanEditPropMixin, DetailView): class WeekmailPreviewView(ComTabsMixin, CanEditPropMixin, DetailView):
model = Weekmail model = Weekmail
template_name = 'com/weekmail_preview.jinja' template_name = "com/weekmail_preview.jinja"
success_url = reverse_lazy('com:weekmail') success_url = reverse_lazy("com:weekmail")
current_tab = "weekmail" current_tab = "weekmail"
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
try: try:
if request.POST['send'] == "validate": if request.POST["send"] == "validate":
self.object.send() self.object.send()
return HttpResponseRedirect(reverse('com:weekmail') + "?qn_weekmail_send_success") return HttpResponseRedirect(
reverse("com:weekmail") + "?qn_weekmail_send_success"
)
except: except:
pass pass
return super(WeekmailEditView, self).get(request, *args, **kwargs) return super(WeekmailEditView, self).get(request, *args, **kwargs)
def get_object(self, queryset=None): def get_object(self, queryset=None):
return self.model.objects.filter(sent=False).order_by('-id').first() return self.model.objects.filter(sent=False).order_by("-id").first()
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
"""Add rendered weekmail""" """Add rendered weekmail"""
kwargs = super(WeekmailPreviewView, self).get_context_data(**kwargs) kwargs = super(WeekmailPreviewView, self).get_context_data(**kwargs)
kwargs['weekmail_rendered'] = self.object.render_html() kwargs["weekmail_rendered"] = self.object.render_html()
return kwargs return kwargs
class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateView): class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateView):
model = Weekmail model = Weekmail
template_name = 'com/weekmail.jinja' template_name = "com/weekmail.jinja"
form_class = modelform_factory(Weekmail, fields=['title', 'intro', 'joke', 'protip', 'conclusion'], form_class = modelform_factory(
help_texts={'title': _("Delete and save to regenerate")}) Weekmail,
success_url = reverse_lazy('com:weekmail') fields=["title", "intro", "joke", "protip", "conclusion"],
help_texts={"title": _("Delete and save to regenerate")},
)
success_url = reverse_lazy("com:weekmail")
current_tab = "weekmail" current_tab = "weekmail"
def get_object(self, queryset=None): def get_object(self, queryset=None):
weekmail = self.model.objects.filter(sent=False).order_by('-id').first() weekmail = self.model.objects.filter(sent=False).order_by("-id").first()
if not weekmail.title: if not weekmail.title:
now = timezone.now() now = timezone.now()
weekmail.title = _("Weekmail of the ") + (now + timedelta(days=6 - now.weekday())).strftime('%d/%m/%Y') weekmail.title = _("Weekmail of the ") + (
now + timedelta(days=6 - now.weekday())
).strftime("%d/%m/%Y")
weekmail.save() weekmail.save()
return weekmail return weekmail
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
if 'up_article' in request.GET.keys(): if "up_article" in request.GET.keys():
art = get_object_or_404(WeekmailArticle, id=request.GET['up_article'], weekmail=self.object) art = get_object_or_404(
prev_art = self.object.articles.order_by('rank').filter(rank__lt=art.rank).last() WeekmailArticle, id=request.GET["up_article"], weekmail=self.object
)
prev_art = (
self.object.articles.order_by("rank").filter(rank__lt=art.rank).last()
)
if prev_art: if prev_art:
art.rank, prev_art.rank = prev_art.rank, art.rank art.rank, prev_art.rank = prev_art.rank, art.rank
art.save() art.save()
prev_art.save() prev_art.save()
self.quick_notif_list += ['qn_success'] self.quick_notif_list += ["qn_success"]
if 'down_article' in request.GET.keys(): if "down_article" in request.GET.keys():
art = get_object_or_404(WeekmailArticle, id=request.GET['down_article'], weekmail=self.object) art = get_object_or_404(
next_art = self.object.articles.order_by('rank').filter(rank__gt=art.rank).first() WeekmailArticle, id=request.GET["down_article"], weekmail=self.object
)
next_art = (
self.object.articles.order_by("rank").filter(rank__gt=art.rank).first()
)
if next_art: if next_art:
art.rank, next_art.rank = next_art.rank, art.rank art.rank, next_art.rank = next_art.rank, art.rank
art.save() art.save()
next_art.save() next_art.save()
self.quick_notif_list += ['qn_success'] self.quick_notif_list += ["qn_success"]
if 'add_article' in request.GET.keys(): if "add_article" in request.GET.keys():
art = get_object_or_404(WeekmailArticle, id=request.GET['add_article'], weekmail=None) art = get_object_or_404(
WeekmailArticle, id=request.GET["add_article"], weekmail=None
)
art.weekmail = self.object art.weekmail = self.object
art.rank = self.object.articles.aggregate(Max('rank'))['rank__max'] or 0 art.rank = self.object.articles.aggregate(Max("rank"))["rank__max"] or 0
art.rank += 1 art.rank += 1
art.save() art.save()
self.quick_notif_list += ['qn_success'] self.quick_notif_list += ["qn_success"]
if 'del_article' in request.GET.keys(): if "del_article" in request.GET.keys():
art = get_object_or_404(WeekmailArticle, id=request.GET['del_article'], weekmail=self.object) art = get_object_or_404(
WeekmailArticle, id=request.GET["del_article"], weekmail=self.object
)
art.weekmail = None art.weekmail = None
art.rank = -1 art.rank = -1
art.save() art.save()
self.quick_notif_list += ['qn_success'] self.quick_notif_list += ["qn_success"]
return super(WeekmailEditView, self).get(request, *args, **kwargs) return super(WeekmailEditView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
"""Add orphan articles """ """Add orphan articles """
kwargs = super(WeekmailEditView, self).get_context_data(**kwargs) kwargs = super(WeekmailEditView, self).get_context_data(**kwargs)
kwargs['orphans'] = WeekmailArticle.objects.filter(weekmail=None) kwargs["orphans"] = WeekmailArticle.objects.filter(weekmail=None)
return kwargs return kwargs
class WeekmailArticleEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateView): class WeekmailArticleEditView(
ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateView
):
"""Edit an article""" """Edit an article"""
model = WeekmailArticle model = WeekmailArticle
fields = ['title', 'club', 'content'] fields = ["title", "club", "content"]
pk_url_kwarg = "article_id" pk_url_kwarg = "article_id"
template_name = 'core/edit.jinja' template_name = "core/edit.jinja"
success_url = reverse_lazy('com:weekmail') success_url = reverse_lazy("com:weekmail")
quick_notif_url_arg = "qn_weekmail_article_edit" quick_notif_url_arg = "qn_weekmail_article_edit"
current_tab = "weekmail" current_tab = "weekmail"
class WeekmailArticleCreateView(QuickNotifMixin, CreateView): class WeekmailArticleCreateView(QuickNotifMixin, CreateView):
"""Post an article""" """Post an article"""
model = WeekmailArticle model = WeekmailArticle
fields = ['title', 'club', 'content'] fields = ["title", "club", "content"]
template_name = 'core/create.jinja' template_name = "core/create.jinja"
success_url = reverse_lazy('core:user_tools') success_url = reverse_lazy("core:user_tools")
quick_notif_url_arg = "qn_weekmail_new_article" quick_notif_url_arg = "qn_weekmail_new_article"
def get_initial(self): def get_initial(self):
init = {} init = {}
try: try:
init['club'] = Club.objects.filter(id=self.request.GET['club']).first() init["club"] = Club.objects.filter(id=self.request.GET["club"]).first()
except: except:
pass pass
return init return init
@ -456,8 +567,15 @@ class WeekmailArticleCreateView(QuickNotifMixin, CreateView):
if m.role <= settings.SITH_MAXIMUM_FREE_ROLE: if m.role <= settings.SITH_MAXIMUM_FREE_ROLE:
raise raise
except: except:
form.add_error('club', ValidationError(_("You must be a board member of the selected club to post in the Weekmail."))) form.add_error(
if form.is_valid() and not 'preview' in request.POST.keys(): "club",
ValidationError(
_(
"You must be a board member of the selected club to post in the Weekmail."
)
),
)
if form.is_valid() and not "preview" in request.POST.keys():
return self.form_valid(form) return self.form_valid(form)
else: else:
return self.form_invalid(form) return self.form_invalid(form)
@ -469,9 +587,10 @@ class WeekmailArticleCreateView(QuickNotifMixin, CreateView):
class WeekmailArticleDeleteView(CanEditPropMixin, DeleteView): class WeekmailArticleDeleteView(CanEditPropMixin, DeleteView):
"""Delete an article""" """Delete an article"""
model = WeekmailArticle model = WeekmailArticle
template_name = 'core/delete_confirm.jinja' template_name = "core/delete_confirm.jinja"
success_url = reverse_lazy('com:weekmail') success_url = reverse_lazy("com:weekmail")
pk_url_kwarg = "article_id" pk_url_kwarg = "article_id"
@ -481,40 +600,43 @@ class MailingListAdminView(ComTabsMixin, ListView):
current_tab = "mailings" current_tab = "mailings"
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not (request.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) or request.user.is_root): if not (
request.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
or request.user.is_root
):
raise PermissionDenied raise PermissionDenied
return super(MailingListAdminView, self).dispatch(request, *args, **kwargs) return super(MailingListAdminView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(MailingListAdminView, self).get_context_data(**kwargs) kwargs = super(MailingListAdminView, self).get_context_data(**kwargs)
kwargs['moderated'] = self.get_queryset().filter(is_moderated=True).all() kwargs["moderated"] = self.get_queryset().filter(is_moderated=True).all()
kwargs['unmoderated'] = self.get_queryset().filter(is_moderated=False).all() kwargs["unmoderated"] = self.get_queryset().filter(is_moderated=False).all()
kwargs['has_moderated'] = len(kwargs['moderated']) > 0 kwargs["has_moderated"] = len(kwargs["moderated"]) > 0
kwargs['has_unmoderated'] = len(kwargs['unmoderated']) > 0 kwargs["has_unmoderated"] = len(kwargs["unmoderated"]) > 0
return kwargs return kwargs
class MailingModerateView(View): class MailingModerateView(View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
mailing = get_object_or_404(Mailing, pk=kwargs['mailing_id']) mailing = get_object_or_404(Mailing, pk=kwargs["mailing_id"])
if mailing.can_moderate(request.user): if mailing.can_moderate(request.user):
mailing.is_moderated = True mailing.is_moderated = True
mailing.moderator = request.user mailing.moderator = request.user
mailing.save() mailing.save()
return redirect('com:mailing_admin') return redirect("com:mailing_admin")
raise PermissionDenied raise PermissionDenied
class PosterListBaseView(ListView): class PosterListBaseView(ListView):
"""List communication posters""" """List communication posters"""
current_tab = "posters" current_tab = "posters"
model = Poster model = Poster
template_name = 'com/poster_list.jinja' template_name = "com/poster_list.jinja"
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
club_id = kwargs.pop('club_id', None) club_id = kwargs.pop("club_id", None)
self.club = None self.club = None
if club_id: if club_id:
self.club = get_object_or_404(Club, pk=club_id) self.club = get_object_or_404(Club, pk=club_id)
@ -522,40 +644,41 @@ class PosterListBaseView(ListView):
def get_queryset(self): def get_queryset(self):
if self.request.user.is_com_admin: if self.request.user.is_com_admin:
return Poster.objects.all().order_by('-date_begin') return Poster.objects.all().order_by("-date_begin")
else: else:
return Poster.objects.filter(club=self.club.id) return Poster.objects.filter(club=self.club.id)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(PosterListBaseView, self).get_context_data(**kwargs) kwargs = super(PosterListBaseView, self).get_context_data(**kwargs)
if not self.request.user.is_com_admin: if not self.request.user.is_com_admin:
kwargs['club'] = self.club kwargs["club"] = self.club
return kwargs return kwargs
class PosterCreateBaseView(CreateView): class PosterCreateBaseView(CreateView):
"""Create communication poster""" """Create communication poster"""
current_tab = "posters" current_tab = "posters"
form_class = PosterForm form_class = PosterForm
template_name = 'core/create.jinja' template_name = "core/create.jinja"
def get_queryset(self): def get_queryset(self):
return Poster.objects.all() return Poster.objects.all()
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if 'club_id' in kwargs: if "club_id" in kwargs:
self.club = get_object_or_404(Club, pk=kwargs['club_id']) self.club = get_object_or_404(Club, pk=kwargs["club_id"])
return super(PosterCreateBaseView, self).dispatch(request, *args, **kwargs) return super(PosterCreateBaseView, self).dispatch(request, *args, **kwargs)
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super(PosterCreateBaseView, self).get_form_kwargs() kwargs = super(PosterCreateBaseView, self).get_form_kwargs()
kwargs.update({'user': self.request.user}) kwargs.update({"user": self.request.user})
return kwargs return kwargs
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(PosterCreateBaseView, self).get_context_data(**kwargs) kwargs = super(PosterCreateBaseView, self).get_context_data(**kwargs)
if not self.request.user.is_com_admin: if not self.request.user.is_com_admin:
kwargs['club'] = self.club kwargs["club"] = self.club
return kwargs return kwargs
def form_valid(self, form): def form_valid(self, form):
@ -566,27 +689,28 @@ class PosterCreateBaseView(CreateView):
class PosterEditBaseView(UpdateView): class PosterEditBaseView(UpdateView):
"""Edit communication poster""" """Edit communication poster"""
pk_url_kwarg = "poster_id" pk_url_kwarg = "poster_id"
current_tab = "posters" current_tab = "posters"
form_class = PosterForm form_class = PosterForm
template_name = 'com/poster_edit.jinja' template_name = "com/poster_edit.jinja"
def get_initial(self): def get_initial(self):
init = {} init = {}
try: try:
init['date_begin'] = self.object.date_begin.strftime('%Y-%m-%d %H:%M:%S') init["date_begin"] = self.object.date_begin.strftime("%Y-%m-%d %H:%M:%S")
except Exception: except Exception:
pass pass
try: try:
init['date_end'] = self.object.date_end.strftime('%Y-%m-%d %H:%M:%S') init["date_end"] = self.object.date_end.strftime("%Y-%m-%d %H:%M:%S")
except Exception: except Exception:
pass pass
return init return init
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if 'club_id' in kwargs and kwargs['club_id']: if "club_id" in kwargs and kwargs["club_id"]:
try: try:
self.club = Club.objects.get(pk=kwargs['club_id']) self.club = Club.objects.get(pk=kwargs["club_id"])
except Club.DoesNotExist: except Club.DoesNotExist:
raise PermissionDenied raise PermissionDenied
return super(PosterEditBaseView, self).dispatch(request, *args, **kwargs) return super(PosterEditBaseView, self).dispatch(request, *args, **kwargs)
@ -596,13 +720,13 @@ class PosterEditBaseView(UpdateView):
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super(PosterEditBaseView, self).get_form_kwargs() kwargs = super(PosterEditBaseView, self).get_form_kwargs()
kwargs.update({'user': self.request.user}) kwargs.update({"user": self.request.user})
return kwargs return kwargs
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(PosterEditBaseView, self).get_context_data(**kwargs) kwargs = super(PosterEditBaseView, self).get_context_data(**kwargs)
if not self.request.user.is_com_admin: if not self.request.user.is_com_admin:
kwargs['club'] = self.club kwargs["club"] = self.club
return kwargs return kwargs
def form_valid(self, form): def form_valid(self, form):
@ -613,15 +737,16 @@ class PosterEditBaseView(UpdateView):
class PosterDeleteBaseView(DeleteView): class PosterDeleteBaseView(DeleteView):
"""Edit communication poster""" """Edit communication poster"""
pk_url_kwarg = "poster_id" pk_url_kwarg = "poster_id"
current_tab = "posters" current_tab = "posters"
model = Poster model = Poster
template_name = 'core/delete_confirm.jinja' template_name = "core/delete_confirm.jinja"
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if 'club_id' in kwargs and kwargs['club_id']: if "club_id" in kwargs and kwargs["club_id"]:
try: try:
self.club = Club.objects.get(pk=kwargs['club_id']) self.club = Club.objects.get(pk=kwargs["club_id"])
except Club.DoesNotExist: except Club.DoesNotExist:
raise PermissionDenied raise PermissionDenied
return super(PosterDeleteBaseView, self).dispatch(request, *args, **kwargs) return super(PosterDeleteBaseView, self).dispatch(request, *args, **kwargs)
@ -632,107 +757,117 @@ class PosterListView(IsComAdminMixin, ComTabsMixin, PosterListBaseView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(PosterListView, self).get_context_data(**kwargs) kwargs = super(PosterListView, self).get_context_data(**kwargs)
kwargs['app'] = "com" kwargs["app"] = "com"
return kwargs return kwargs
class PosterCreateView(IsComAdminMixin, ComTabsMixin, PosterCreateBaseView): class PosterCreateView(IsComAdminMixin, ComTabsMixin, PosterCreateBaseView):
"""Create communication poster""" """Create communication poster"""
success_url = reverse_lazy('com:poster_list')
success_url = reverse_lazy("com:poster_list")
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(PosterCreateView, self).get_context_data(**kwargs) kwargs = super(PosterCreateView, self).get_context_data(**kwargs)
kwargs['app'] = "com" kwargs["app"] = "com"
return kwargs return kwargs
class PosterEditView(IsComAdminMixin, ComTabsMixin, PosterEditBaseView): class PosterEditView(IsComAdminMixin, ComTabsMixin, PosterEditBaseView):
"""Edit communication poster""" """Edit communication poster"""
success_url = reverse_lazy('com:poster_list')
success_url = reverse_lazy("com:poster_list")
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(PosterEditView, self).get_context_data(**kwargs) kwargs = super(PosterEditView, self).get_context_data(**kwargs)
kwargs['app'] = "com" kwargs["app"] = "com"
return kwargs return kwargs
class PosterDeleteView(IsComAdminMixin, ComTabsMixin, PosterDeleteBaseView): class PosterDeleteView(IsComAdminMixin, ComTabsMixin, PosterDeleteBaseView):
"""Delete communication poster""" """Delete communication poster"""
success_url = reverse_lazy('com:poster_list')
success_url = reverse_lazy("com:poster_list")
class PosterModerateListView(IsComAdminMixin, ComTabsMixin, ListView): class PosterModerateListView(IsComAdminMixin, ComTabsMixin, ListView):
"""Moderate list communication poster""" """Moderate list communication poster"""
current_tab = "posters" current_tab = "posters"
model = Poster model = Poster
template_name = 'com/poster_moderate.jinja' template_name = "com/poster_moderate.jinja"
queryset = Poster.objects.filter(is_moderated=False).all() queryset = Poster.objects.filter(is_moderated=False).all()
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(PosterModerateListView, self).get_context_data(**kwargs) kwargs = super(PosterModerateListView, self).get_context_data(**kwargs)
kwargs['app'] = "com" kwargs["app"] = "com"
return kwargs return kwargs
class PosterModerateView(IsComAdminMixin, ComTabsMixin, View): class PosterModerateView(IsComAdminMixin, ComTabsMixin, View):
"""Moderate communication poster""" """Moderate communication poster"""
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
obj = get_object_or_404(Poster, pk=kwargs['object_id']) obj = get_object_or_404(Poster, pk=kwargs["object_id"])
if obj.can_be_moderated_by(request.user): if obj.can_be_moderated_by(request.user):
obj.is_moderated = True obj.is_moderated = True
obj.moderator = request.user obj.moderator = request.user
obj.save() obj.save()
return redirect('com:poster_moderate_list') return redirect("com:poster_moderate_list")
raise PermissionDenied raise PermissionDenied
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(PosterModerateListView, self).get_context_data(**kwargs) kwargs = super(PosterModerateListView, self).get_context_data(**kwargs)
kwargs['app'] = "com" kwargs["app"] = "com"
return kwargs return kwargs
class ScreenListView(IsComAdminMixin, ComTabsMixin, ListView): class ScreenListView(IsComAdminMixin, ComTabsMixin, ListView):
"""List communication screens""" """List communication screens"""
current_tab = "screens" current_tab = "screens"
model = Screen model = Screen
template_name = 'com/screen_list.jinja' template_name = "com/screen_list.jinja"
class ScreenSlideshowView(DetailView): class ScreenSlideshowView(DetailView):
"""Slideshow of actives posters""" """Slideshow of actives posters"""
pk_url_kwarg = "screen_id" pk_url_kwarg = "screen_id"
model = Screen model = Screen
template_name = 'com/screen_slideshow.jinja' template_name = "com/screen_slideshow.jinja"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(ScreenSlideshowView, self).get_context_data(**kwargs) kwargs = super(ScreenSlideshowView, self).get_context_data(**kwargs)
kwargs['posters'] = self.object.active_posters() kwargs["posters"] = self.object.active_posters()
return kwargs return kwargs
class ScreenCreateView(IsComAdminMixin, ComTabsMixin, CreateView): class ScreenCreateView(IsComAdminMixin, ComTabsMixin, CreateView):
"""Create communication screen""" """Create communication screen"""
current_tab = "screens" current_tab = "screens"
model = Screen model = Screen
fields = ['name', ] fields = ["name"]
template_name = 'core/create.jinja' template_name = "core/create.jinja"
success_url = reverse_lazy('com:screen_list') success_url = reverse_lazy("com:screen_list")
class ScreenEditView(IsComAdminMixin, ComTabsMixin, UpdateView): class ScreenEditView(IsComAdminMixin, ComTabsMixin, UpdateView):
"""Edit communication screen""" """Edit communication screen"""
pk_url_kwarg = "screen_id" pk_url_kwarg = "screen_id"
current_tab = "screens" current_tab = "screens"
model = Screen model = Screen
fields = ['name', ] fields = ["name"]
template_name = 'com/screen_edit.jinja' template_name = "com/screen_edit.jinja"
success_url = reverse_lazy('com:screen_list') success_url = reverse_lazy("com:screen_list")
class ScreenDeleteView(IsComAdminMixin, ComTabsMixin, DeleteView): class ScreenDeleteView(IsComAdminMixin, ComTabsMixin, DeleteView):
"""Delete communication screen""" """Delete communication screen"""
pk_url_kwarg = "screen_id" pk_url_kwarg = "screen_id"
current_tab = "screens" current_tab = "screens"
model = Screen model = Screen
template_name = 'core/delete_confirm.jinja' template_name = "core/delete_confirm.jinja"
success_url = reverse_lazy('com:screen_list') success_url = reverse_lazy("com:screen_list")

View File

@ -22,4 +22,4 @@
# #
# #
default_app_config = 'core.apps.SithConfig' default_app_config = "core.apps.SithConfig"

View File

@ -32,30 +32,38 @@ from haystack.admin import SearchModelAdmin
admin.site.unregister(AuthGroup) admin.site.unregister(AuthGroup)
admin.site.register(RealGroup) admin.site.register(RealGroup)
class UserAdmin(SearchModelAdmin): class UserAdmin(SearchModelAdmin):
list_display = ["first_name", "last_name", "username", "email", "nick_name"] list_display = ["first_name", "last_name", "username", "email", "nick_name"]
form = make_ajax_form(User, { form = make_ajax_form(
'godfathers': 'users', User,
'home': 'files', # ManyToManyField {
'profile_pict': 'files', # ManyToManyField "godfathers": "users",
'avatar_pict': 'files', # ManyToManyField "home": "files", # ManyToManyField
'scrub_pict': 'files', # ManyToManyField "profile_pict": "files", # ManyToManyField
}) "avatar_pict": "files", # ManyToManyField
"scrub_pict": "files", # ManyToManyField
},
)
search_fields = ["first_name", "last_name", "username"] search_fields = ["first_name", "last_name", "username"]
admin.site.register(User, UserAdmin) admin.site.register(User, UserAdmin)
@admin.register(Page) @admin.register(Page)
class PageAdmin(admin.ModelAdmin): class PageAdmin(admin.ModelAdmin):
form = make_ajax_form(Page, { form = make_ajax_form(
'lock_user': 'users', Page,
'owner_group': 'groups', {
'edit_groups': 'groups', "lock_user": "users",
'view_groups': 'groups', "owner_group": "groups",
}) "edit_groups": "groups",
"view_groups": "groups",
},
)
@admin.register(SithFile) @admin.register(SithFile)
class SithFileAdmin(admin.ModelAdmin): class SithFileAdmin(admin.ModelAdmin):
form = make_ajax_form(SithFile, { form = make_ajax_form(SithFile, {"parent": "files"}) # ManyToManyField
'parent': 'files', # ManyToManyField
})

View File

@ -29,7 +29,7 @@ from django.core.signals import request_started
class SithConfig(AppConfig): class SithConfig(AppConfig):
name = 'core' name = "core"
verbose_name = "Core app of the Sith" verbose_name = "Core app of the Sith"
def ready(self): def ready(self):
@ -47,6 +47,12 @@ class SithConfig(AppConfig):
Forum._club_memberships = {} Forum._club_memberships = {}
print("Connecting signals!", file=sys.stderr) print("Connecting signals!", file=sys.stderr)
request_started.connect(clear_cached_groups, weak=False, dispatch_uid="clear_cached_groups") request_started.connect(
request_started.connect(clear_cached_memberships, weak=False, dispatch_uid="clear_cached_memberships") clear_cached_groups, weak=False, dispatch_uid="clear_cached_groups"
)
request_started.connect(
clear_cached_memberships,
weak=False,
dispatch_uid="clear_cached_memberships",
)
# TODO: there may be a need to add more cache clearing # TODO: there may be a need to add more cache clearing

View File

@ -33,9 +33,11 @@ from accounting.models import ClubAccount, Company
def check_token(request): def check_token(request):
return ('counter_token' in request.session.keys() and return (
request.session['counter_token'] and "counter_token" in request.session.keys()
Counter.objects.filter(token=request.session['counter_token']).exists()) and request.session["counter_token"]
and Counter.objects.filter(token=request.session["counter_token"]).exists()
)
class RightManagedLookupChannel(LookupChannel): class RightManagedLookupChannel(LookupChannel):
@ -44,7 +46,7 @@ class RightManagedLookupChannel(LookupChannel):
raise PermissionDenied raise PermissionDenied
@register('users') @register("users")
class UsersLookup(RightManagedLookupChannel): class UsersLookup(RightManagedLookupChannel):
model = User model = User
@ -58,7 +60,7 @@ class UsersLookup(RightManagedLookupChannel):
return item.get_display_name() return item.get_display_name()
@register('groups') @register("groups")
class GroupsLookup(RightManagedLookupChannel): class GroupsLookup(RightManagedLookupChannel):
model = Group model = Group
@ -72,7 +74,7 @@ class GroupsLookup(RightManagedLookupChannel):
return item.name return item.name
@register('clubs') @register("clubs")
class ClubLookup(RightManagedLookupChannel): class ClubLookup(RightManagedLookupChannel):
model = Club model = Club
@ -86,7 +88,7 @@ class ClubLookup(RightManagedLookupChannel):
return item.name return item.name
@register('counters') @register("counters")
class CountersLookup(RightManagedLookupChannel): class CountersLookup(RightManagedLookupChannel):
model = Counter model = Counter
@ -97,19 +99,21 @@ class CountersLookup(RightManagedLookupChannel):
return item.name return item.name
@register('products') @register("products")
class ProductsLookup(RightManagedLookupChannel): class ProductsLookup(RightManagedLookupChannel):
model = Product model = Product
def get_query(self, q, request): def get_query(self, q, request):
return (self.model.objects.filter(name__icontains=q) | return (
self.model.objects.filter(code__icontains=q)).filter(archived=False)[:50] self.model.objects.filter(name__icontains=q)
| self.model.objects.filter(code__icontains=q)
).filter(archived=False)[:50]
def format_item_display(self, item): def format_item_display(self, item):
return "%s (%s)" % (item.name, item.code) return "%s (%s)" % (item.name, item.code)
@register('files') @register("files")
class SithFileLookup(RightManagedLookupChannel): class SithFileLookup(RightManagedLookupChannel):
model = SithFile model = SithFile
@ -117,7 +121,7 @@ class SithFileLookup(RightManagedLookupChannel):
return self.model.objects.filter(name__icontains=q)[:50] return self.model.objects.filter(name__icontains=q)[:50]
@register('club_accounts') @register("club_accounts")
class ClubAccountLookup(RightManagedLookupChannel): class ClubAccountLookup(RightManagedLookupChannel):
model = ClubAccount model = ClubAccount
@ -128,7 +132,7 @@ class ClubAccountLookup(RightManagedLookupChannel):
return item.name return item.name
@register('companies') @register("companies")
class CompaniesLookup(RightManagedLookupChannel): class CompaniesLookup(RightManagedLookupChannel):
model = Company model = Company

View File

@ -21,4 +21,3 @@
# Place - Suite 330, Boston, MA 02111-1307, USA. # Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #

View File

@ -33,10 +33,14 @@ class Command(BaseCommand):
help = "Recursively check the file system with respect to the DB" help = "Recursively check the file system with respect to the DB"
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument('ids', metavar='ID', type=int, nargs='+', help="The file IDs to process") parser.add_argument(
"ids", metavar="ID", type=int, nargs="+", help="The file IDs to process"
)
def handle(self, *args, **options): def handle(self, *args, **options):
root_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) root_path = os.path.dirname(
files = SithFile.objects.filter(id__in=options['ids']).all() os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
)
files = SithFile.objects.filter(id__in=options["ids"]).all()
for f in files: for f in files:
f._check_fs() f._check_fs()

View File

@ -33,15 +33,13 @@ class Command(BaseCommand):
""" """
Compiles scss in static folder for production Compiles scss in static folder for production
""" """
help = "Compile scss files from static folder" help = "Compile scss files from static folder"
def compile(self, filename): def compile(self, filename):
args = { args = {"filename": filename, "include_paths": settings.STATIC_ROOT}
"filename": filename,
"include_paths": settings.STATIC_ROOT,
}
if settings.SASS_PRECISION: if settings.SASS_PRECISION:
args['precision'] = settings.SASS_PRECISION args["precision"] = settings.SASS_PRECISION
return sass.compile(**args) return sass.compile(**args)
def is_compilable(self, file, ext_list): def is_compilable(self, file, ext_list):
@ -54,7 +52,7 @@ class Command(BaseCommand):
file = os.path.join(folder, file) file = os.path.join(folder, file)
if os.path.isdir(file): if os.path.isdir(file):
self.exec_on_folder(file, func) self.exec_on_folder(file, func)
elif self.is_compilable(file, ['.scss']): elif self.is_compilable(file, [".scss"]):
to_exec.append(file) to_exec.append(file)
for file in to_exec: for file in to_exec:
@ -62,7 +60,7 @@ class Command(BaseCommand):
def compilescss(self, file): def compilescss(self, file):
print("compiling %s" % file) print("compiling %s" % file)
with(open(file.replace('.scss', '.css'), "w")) as newfile: with (open(file.replace(".scss", ".css"), "w")) as newfile:
newfile.write(self.compile(file)) newfile.write(self.compile(file))
def removescss(self, file): def removescss(self, file):
@ -77,4 +75,6 @@ class Command(BaseCommand):
print("---- Removing scss files ----") print("---- Removing scss files ----")
self.exec_on_folder(settings.STATIC_ROOT, self.removescss) self.exec_on_folder(settings.STATIC_ROOT, self.removescss)
else: else:
print("No static folder avalaible, please use collectstatic before compiling scss") print(
"No static folder avalaible, please use collectstatic before compiling scss"
)

View File

@ -27,11 +27,14 @@ from django.core.management.base import BaseCommand
from core.markdown import markdown from core.markdown import markdown
class Command(BaseCommand): class Command(BaseCommand):
help = "Output the fully rendered doc/SYNTAX.md file" help = "Output the fully rendered doc/SYNTAX.md file"
def handle(self, *args, **options): def handle(self, *args, **options):
root_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) root_path = os.path.dirname(
with open(os.path.join(root_path) + '/doc/SYNTAX.md', 'r') as md: os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
)
with open(os.path.join(root_path) + "/doc/SYNTAX.md", "r") as md:
result = markdown(md.read()) result = markdown(md.read())
print(result, end='') print(result, end="")

File diff suppressed because it is too large Load Diff

View File

@ -33,10 +33,14 @@ class Command(BaseCommand):
help = "Recursively repair the file system with respect to the DB" help = "Recursively repair the file system with respect to the DB"
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument('ids', metavar='ID', type=int, nargs='+', help="The file IDs to process") parser.add_argument(
"ids", metavar="ID", type=int, nargs="+", help="The file IDs to process"
)
def handle(self, *args, **options): def handle(self, *args, **options):
root_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) root_path = os.path.dirname(
files = SithFile.objects.filter(id__in=options['ids']).all() os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
)
files = SithFile.objects.filter(id__in=options["ids"]).all()
for f in files: for f in files:
f._repair_fs() f._repair_fs()

View File

@ -31,22 +31,24 @@ class Command(BaseCommand):
help = "Set up a new instance of the Sith AE" help = "Set up a new instance of the Sith AE"
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument('--prod', action="store_true") parser.add_argument("--prod", action="store_true")
def handle(self, *args, **options): def handle(self, *args, **options):
root_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) root_path = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
)
try: try:
os.mkdir(os.path.join(root_path) + '/data') os.mkdir(os.path.join(root_path) + "/data")
print("Data dir created") print("Data dir created")
except Exception as e: except Exception as e:
repr(e) repr(e)
try: try:
os.remove(os.path.join(root_path, 'db.sqlite3')) os.remove(os.path.join(root_path, "db.sqlite3"))
print("db.sqlite3 deleted") print("db.sqlite3 deleted")
except Exception as e: except Exception as e:
repr(e) repr(e)
call_command('migrate') call_command("migrate")
if options['prod']: if options["prod"]:
call_command('populate', '--prod') call_command("populate", "--prod")
else: else:
call_command('populate') call_command("populate")

View File

@ -30,7 +30,7 @@ from django.core.urlresolvers import reverse
class SithRenderer(Renderer): class SithRenderer(Renderer):
def file_link(self, id, suffix): def file_link(self, id, suffix):
return reverse('core:file_detail', kwargs={'file_id': id}) + suffix return reverse("core:file_detail", kwargs={"file_id": id}) + suffix
def exposant(self, text): def exposant(self, text):
return """<sup>%s</sup>""" % text return """<sup>%s</sup>""" % text
@ -48,19 +48,19 @@ class SithRenderer(Renderer):
:param text: alt text of the image. :param text: alt text of the image.
""" """
style = None style = None
if '?' in original_src: if "?" in original_src:
src, params = original_src.rsplit('?', maxsplit=1) src, params = original_src.rsplit("?", maxsplit=1)
m = re.search(r'(\d+%?)(x(\d+%?))?', params) m = re.search(r"(\d+%?)(x(\d+%?))?", params)
if not m: if not m:
src = original_src src = original_src
else: else:
width = m.group(1) width = m.group(1)
if not width.endswith('%'): if not width.endswith("%"):
width += "px" width += "px"
style = "width: %s; " % width style = "width: %s; " % width
try: try:
height = m.group(3) height = m.group(3)
if not height.endswith('%'): if not height.endswith("%"):
height += "px" height += "px"
style += "height: %s; " % height style += "height: %s; " % height
except: except:
@ -77,67 +77,57 @@ class SithRenderer(Renderer):
html = '<img src="%s" alt="%s"' % (src, text) html = '<img src="%s" alt="%s"' % (src, text)
if style: if style:
html = '%s style="%s"' % (html, style) html = '%s style="%s"' % (html, style)
if self.options.get('use_xhtml'): if self.options.get("use_xhtml"):
return '%s />' % html return "%s />" % html
return '%s>' % html return "%s>" % html
class SithInlineGrammar(InlineGrammar): class SithInlineGrammar(InlineGrammar):
double_emphasis = re.compile( double_emphasis = re.compile(r"^\*{2}([\s\S]+?)\*{2}(?!\*)") # **word**
r'^\*{2}([\s\S]+?)\*{2}(?!\*)' # **word** emphasis = re.compile(r"^\*((?:\*\*|[^\*])+?)\*(?!\*)") # *word*
) underline = re.compile(r"^_{2}([\s\S]+?)_{2}(?!_)") # __word__
emphasis = re.compile( exposant = re.compile(r"^<sup>([\s\S]+?)</sup>") # <sup>text</sup>
r'^\*((?:\*\*|[^\*])+?)\*(?!\*)' # *word* indice = re.compile(r"^<sub>([\s\S]+?)</sub>") # <sub>text</sub>
)
underline = re.compile(
r'^_{2}([\s\S]+?)_{2}(?!_)' # __word__
)
exposant = re.compile(
r'^<sup>([\s\S]+?)</sup>' # <sup>text</sup>
)
indice = re.compile(
r'^<sub>([\s\S]+?)</sub>' # <sub>text</sub>
)
class SithInlineLexer(InlineLexer): class SithInlineLexer(InlineLexer):
grammar_class = SithInlineGrammar grammar_class = SithInlineGrammar
default_rules = [ default_rules = [
'escape', "escape",
# 'inline_html', # 'inline_html',
'autolink', "autolink",
'url', "url",
'footnote', "footnote",
'link', "link",
'reflink', "reflink",
'nolink', "nolink",
'exposant', "exposant",
'double_emphasis', "double_emphasis",
'emphasis', "emphasis",
'underline', "underline",
'indice', "indice",
'code', "code",
'linebreak', "linebreak",
'strikethrough', "strikethrough",
'text', "text",
] ]
inline_html_rules = [ inline_html_rules = [
'escape', "escape",
'autolink', "autolink",
'url', "url",
'link', "link",
'reflink', "reflink",
'nolink', "nolink",
'exposant', "exposant",
'double_emphasis', "double_emphasis",
'emphasis', "emphasis",
'underline', "underline",
'indice', "indice",
'code', "code",
'linebreak', "linebreak",
'strikethrough', "strikethrough",
'text', "text",
] ]
def output_underline(self, m): def output_underline(self, m):
@ -166,22 +156,18 @@ class SithInlineLexer(InlineLexer):
def _process_link(self, m, link, title=None): def _process_link(self, m, link, title=None):
try: # Add page:// support for links try: # Add page:// support for links
page = re.compile( page = re.compile(r"^page://(\S*)") # page://nom_de_ma_page
r'^page://(\S*)' # page://nom_de_ma_page
)
match = page.search(link) match = page.search(link)
page = match.group(1) or "" page = match.group(1) or ""
link = reverse('core:page', kwargs={'page_name': page}) link = reverse("core:page", kwargs={"page_name": page})
except: except:
pass pass
try: # Add file:// support for links try: # Add file:// support for links
file_link = re.compile( file_link = re.compile(r"^file://(\d*)/?(\S*)?") # file://4000/download
r'^file://(\d*)/?(\S*)?' # file://4000/download
)
match = file_link.search(link) match = file_link.search(link)
id = match.group(1) id = match.group(1)
suffix = match.group(2) or "" suffix = match.group(2) or ""
link = reverse('core:file_detail', kwargs={'file_id': id}) + suffix link = reverse("core:file_detail", kwargs={"file_id": id}) + suffix
except: except:
pass pass
return super(SithInlineLexer, self)._process_link(m, link, title) return super(SithInlineLexer, self)._process_link(m, link, title)
@ -194,6 +180,6 @@ markdown = Markdown(renderer, inline=inline)
if __name__ == "__main__": if __name__ == "__main__":
root_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) root_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
with open(os.path.join(root_path) + '/doc/SYNTAX.md', 'r') as md: with open(os.path.join(root_path) + "/doc/SYNTAX.md", "r") as md:
result = markdown(md.read()) result = markdown(md.read())
print(result, end='') print(result, end="")

View File

@ -26,14 +26,16 @@ import importlib
from django.conf import settings from django.conf import settings
from django.utils.functional import SimpleLazyObject from django.utils.functional import SimpleLazyObject
from django.contrib.auth import get_user from django.contrib.auth import get_user
from django.contrib.auth.middleware import AuthenticationMiddleware as DjangoAuthenticationMiddleware from django.contrib.auth.middleware import (
AuthenticationMiddleware as DjangoAuthenticationMiddleware,
)
module, klass = settings.AUTH_ANONYMOUS_MODEL.rsplit('.', 1) module, klass = settings.AUTH_ANONYMOUS_MODEL.rsplit(".", 1)
AnonymousUser = getattr(importlib.import_module(module), klass) AnonymousUser = getattr(importlib.import_module(module), klass)
def get_cached_user(request): def get_cached_user(request):
if not hasattr(request, '_cached_user'): if not hasattr(request, "_cached_user"):
user = get_user(request) user = get_user(request)
if user.is_anonymous(): if user.is_anonymous():
user = AnonymousUser(request) user = AnonymousUser(request)
@ -45,7 +47,7 @@ def get_cached_user(request):
class AuthenticationMiddleware(DjangoAuthenticationMiddleware): class AuthenticationMiddleware(DjangoAuthenticationMiddleware):
def process_request(self, request): def process_request(self, request):
assert hasattr(request, 'session'), ( assert hasattr(request, "session"), (
"The Django authentication middleware requires session middleware " "The Django authentication middleware requires session middleware "
"to be installed. Edit your MIDDLEWARE_CLASSES setting to insert " "to be installed. Edit your MIDDLEWARE_CLASSES setting to insert "
"'django.contrib.sessions.middleware.SessionMiddleware' before " "'django.contrib.sessions.middleware.SessionMiddleware' before "

View File

@ -12,169 +12,559 @@ from django.conf import settings
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("auth", "0006_require_contenttypes_0002")]
('auth', '0006_require_contenttypes_0002'),
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='User', name="User",
fields=[ fields=[
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), (
('password', models.CharField(max_length=128, verbose_name='password')), "id",
('last_login', models.DateTimeField(null=True, verbose_name='last login', blank=True)), models.AutoField(
('username', models.CharField(help_text='Required. 254 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, max_length=254, error_messages={'unique': 'A user with that username already exists.'}, verbose_name='username', validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.')])), primary_key=True,
('first_name', models.CharField(max_length=64, verbose_name='first name')), serialize=False,
('last_name', models.CharField(max_length=64, verbose_name='last name')), verbose_name="ID",
('email', models.EmailField(unique=True, max_length=254, verbose_name='email address')), auto_created=True,
('date_of_birth', models.DateField(null=True, verbose_name='date of birth', blank=True)), ),
('nick_name', models.CharField(max_length=64, null=True, verbose_name='nick name', blank=True)), ),
('is_staff', models.BooleanField(help_text='Designates whether the user can log into this admin site.', verbose_name='staff status', default=False)), ("password", models.CharField(max_length=128, verbose_name="password")),
('is_active', models.BooleanField(help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active', default=True)), (
('date_joined', models.DateField(auto_now_add=True, verbose_name='date joined')), "last_login",
('last_update', models.DateField(verbose_name='last update', auto_now=True)), models.DateTimeField(
('is_superuser', models.BooleanField(help_text='Designates whether this user is a superuser. ', verbose_name='superuser', default=False)), null=True, verbose_name="last login", blank=True
('sex', models.CharField(choices=[('MAN', 'Man'), ('WOMAN', 'Woman')], max_length=10, default='MAN', verbose_name='sex')), ),
('tshirt_size', models.CharField(choices=[('-', '-'), ('XS', 'XS'), ('S', 'S'), ('M', 'M'), ('L', 'L'), ('XL', 'XL'), ('XXL', 'XXL'), ('XXXL', 'XXXL')], max_length=5, default='-', verbose_name='tshirt size')), ),
('role', models.CharField(choices=[('STUDENT', 'Student'), ('ADMINISTRATIVE', 'Administrative agent'), ('TEACHER', 'Teacher'), ('AGENT', 'Agent'), ('DOCTOR', 'Doctor'), ('FORMER STUDENT', 'Former student'), ('SERVICE', 'Service')], max_length=15, blank=True, verbose_name='role', default='')), (
('department', models.CharField(choices=[('TC', 'TC'), ('IMSI', 'IMSI'), ('IMAP', 'IMAP'), ('INFO', 'INFO'), ('GI', 'GI'), ('E', 'E'), ('EE', 'EE'), ('GESC', 'GESC'), ('GMC', 'GMC'), ('MC', 'MC'), ('EDIM', 'EDIM'), ('HUMA', 'Humanities'), ('NA', 'N/A')], max_length=15, blank=True, verbose_name='department', default='NA')), "username",
('dpt_option', models.CharField(max_length=32, blank=True, verbose_name='dpt option', default='')), models.CharField(
('semester', models.CharField(max_length=5, blank=True, verbose_name='semester', default='')), help_text="Required. 254 characters or fewer. Letters, digits and @/./+/-/_ only.",
('quote', models.CharField(max_length=256, blank=True, verbose_name='quote', default='')), unique=True,
('school', models.CharField(max_length=80, blank=True, verbose_name='school', default='')), max_length=254,
('promo', models.IntegerField(null=True, verbose_name='promo', validators=[core.models.validate_promo], blank=True)), error_messages={
('forum_signature', models.TextField(max_length=256, blank=True, verbose_name='forum signature', default='')), "unique": "A user with that username already exists."
('second_email', models.EmailField(max_length=254, null=True, verbose_name='second email address', blank=True)), },
('phone', phonenumber_field.modelfields.PhoneNumberField(max_length=128, null=True, verbose_name='phone', blank=True)), verbose_name="username",
('parent_phone', phonenumber_field.modelfields.PhoneNumberField(max_length=128, null=True, verbose_name='parent phone', blank=True)), validators=[
('address', models.CharField(max_length=128, blank=True, verbose_name='address', default='')), django.core.validators.RegexValidator(
('parent_address', models.CharField(max_length=128, blank=True, verbose_name='parent address', default='')), "^[\\w.@+-]+$",
('is_subscriber_viewable', models.BooleanField(verbose_name='is subscriber viewable', default=True)), "Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.",
], )
options={ ],
'abstract': False, ),
}, ),
managers=[ (
('objects', django.contrib.auth.models.UserManager()), "first_name",
models.CharField(max_length=64, verbose_name="first name"),
),
(
"last_name",
models.CharField(max_length=64, verbose_name="last name"),
),
(
"email",
models.EmailField(
unique=True, max_length=254, verbose_name="email address"
),
),
(
"date_of_birth",
models.DateField(
null=True, verbose_name="date of birth", blank=True
),
),
(
"nick_name",
models.CharField(
max_length=64, null=True, verbose_name="nick name", blank=True
),
),
(
"is_staff",
models.BooleanField(
help_text="Designates whether the user can log into this admin site.",
verbose_name="staff status",
default=False,
),
),
(
"is_active",
models.BooleanField(
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
verbose_name="active",
default=True,
),
),
(
"date_joined",
models.DateField(auto_now_add=True, verbose_name="date joined"),
),
(
"last_update",
models.DateField(verbose_name="last update", auto_now=True),
),
(
"is_superuser",
models.BooleanField(
help_text="Designates whether this user is a superuser. ",
verbose_name="superuser",
default=False,
),
),
(
"sex",
models.CharField(
choices=[("MAN", "Man"), ("WOMAN", "Woman")],
max_length=10,
default="MAN",
verbose_name="sex",
),
),
(
"tshirt_size",
models.CharField(
choices=[
("-", "-"),
("XS", "XS"),
("S", "S"),
("M", "M"),
("L", "L"),
("XL", "XL"),
("XXL", "XXL"),
("XXXL", "XXXL"),
],
max_length=5,
default="-",
verbose_name="tshirt size",
),
),
(
"role",
models.CharField(
choices=[
("STUDENT", "Student"),
("ADMINISTRATIVE", "Administrative agent"),
("TEACHER", "Teacher"),
("AGENT", "Agent"),
("DOCTOR", "Doctor"),
("FORMER STUDENT", "Former student"),
("SERVICE", "Service"),
],
max_length=15,
blank=True,
verbose_name="role",
default="",
),
),
(
"department",
models.CharField(
choices=[
("TC", "TC"),
("IMSI", "IMSI"),
("IMAP", "IMAP"),
("INFO", "INFO"),
("GI", "GI"),
("E", "E"),
("EE", "EE"),
("GESC", "GESC"),
("GMC", "GMC"),
("MC", "MC"),
("EDIM", "EDIM"),
("HUMA", "Humanities"),
("NA", "N/A"),
],
max_length=15,
blank=True,
verbose_name="department",
default="NA",
),
),
(
"dpt_option",
models.CharField(
max_length=32, blank=True, verbose_name="dpt option", default=""
),
),
(
"semester",
models.CharField(
max_length=5, blank=True, verbose_name="semester", default=""
),
),
(
"quote",
models.CharField(
max_length=256, blank=True, verbose_name="quote", default=""
),
),
(
"school",
models.CharField(
max_length=80, blank=True, verbose_name="school", default=""
),
),
(
"promo",
models.IntegerField(
null=True,
verbose_name="promo",
validators=[core.models.validate_promo],
blank=True,
),
),
(
"forum_signature",
models.TextField(
max_length=256,
blank=True,
verbose_name="forum signature",
default="",
),
),
(
"second_email",
models.EmailField(
max_length=254,
null=True,
verbose_name="second email address",
blank=True,
),
),
(
"phone",
phonenumber_field.modelfields.PhoneNumberField(
max_length=128, null=True, verbose_name="phone", blank=True
),
),
(
"parent_phone",
phonenumber_field.modelfields.PhoneNumberField(
max_length=128,
null=True,
verbose_name="parent phone",
blank=True,
),
),
(
"address",
models.CharField(
max_length=128, blank=True, verbose_name="address", default=""
),
),
(
"parent_address",
models.CharField(
max_length=128,
blank=True,
verbose_name="parent address",
default="",
),
),
(
"is_subscriber_viewable",
models.BooleanField(
verbose_name="is subscriber viewable", default=True
),
),
], ],
options={"abstract": False},
managers=[("objects", django.contrib.auth.models.UserManager())],
), ),
migrations.CreateModel( migrations.CreateModel(
name='Group', name="Group",
fields=[ fields=[
('group_ptr', models.OneToOneField(primary_key=True, parent_link=True, serialize=False, to='auth.Group', auto_created=True)), (
('is_meta', models.BooleanField(help_text='Whether a group is a meta group or not', verbose_name='meta group status', default=False)), "group_ptr",
('description', models.CharField(max_length=60, verbose_name='description')), models.OneToOneField(
primary_key=True,
parent_link=True,
serialize=False,
to="auth.Group",
auto_created=True,
),
),
(
"is_meta",
models.BooleanField(
help_text="Whether a group is a meta group or not",
verbose_name="meta group status",
default=False,
),
),
(
"description",
models.CharField(max_length=60, verbose_name="description"),
),
], ],
bases=('auth.group',), bases=("auth.group",),
), ),
migrations.CreateModel( migrations.CreateModel(
name='Page', name="Page",
fields=[ fields=[
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), (
('name', models.CharField(max_length=30, verbose_name='page name')), "id",
('_full_name', models.CharField(max_length=255, blank=True, verbose_name='page name')), models.AutoField(
('edit_groups', models.ManyToManyField(related_name='editable_page', to='core.Group', blank=True, verbose_name='edit group')), primary_key=True,
('owner_group', models.ForeignKey(default=1, related_name='owned_page', verbose_name='owner group', to='core.Group')), serialize=False,
('parent', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, null=True, related_name='children', verbose_name='parent', to='core.Page', blank=True)), verbose_name="ID",
('view_groups', models.ManyToManyField(related_name='viewable_page', to='core.Group', blank=True, verbose_name='view group')), auto_created=True,
),
),
("name", models.CharField(max_length=30, verbose_name="page name")),
(
"_full_name",
models.CharField(
max_length=255, blank=True, verbose_name="page name"
),
),
(
"edit_groups",
models.ManyToManyField(
related_name="editable_page",
to="core.Group",
blank=True,
verbose_name="edit group",
),
),
(
"owner_group",
models.ForeignKey(
default=1,
related_name="owned_page",
verbose_name="owner group",
to="core.Group",
),
),
(
"parent",
models.ForeignKey(
on_delete=django.db.models.deletion.SET_NULL,
null=True,
related_name="children",
verbose_name="parent",
to="core.Page",
blank=True,
),
),
(
"view_groups",
models.ManyToManyField(
related_name="viewable_page",
to="core.Group",
blank=True,
verbose_name="view group",
),
),
], ],
options={ options={
'permissions': (('change_prop_page', "Can change the page's properties (groups, ...)"), ('view_page', 'Can view the page')), "permissions": (
(
"change_prop_page",
"Can change the page's properties (groups, ...)",
),
("view_page", "Can view the page"),
)
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='PageRev', name="PageRev",
fields=[ fields=[
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), (
('revision', models.IntegerField(verbose_name='revision')), "id",
('title', models.CharField(max_length=255, blank=True, verbose_name='page title')), models.AutoField(
('content', models.TextField(blank=True, verbose_name='page content')), primary_key=True,
('date', models.DateTimeField(verbose_name='date', auto_now=True)), serialize=False,
('author', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='page_rev')), verbose_name="ID",
('page', models.ForeignKey(to='core.Page', related_name='revisions')), auto_created=True,
),
),
("revision", models.IntegerField(verbose_name="revision")),
(
"title",
models.CharField(
max_length=255, blank=True, verbose_name="page title"
),
),
("content", models.TextField(blank=True, verbose_name="page content")),
("date", models.DateTimeField(verbose_name="date", auto_now=True)),
(
"author",
models.ForeignKey(
to=settings.AUTH_USER_MODEL, related_name="page_rev"
),
),
("page", models.ForeignKey(to="core.Page", related_name="revisions")),
], ],
options={ options={"ordering": ["date"]},
'ordering': ['date'],
},
), ),
migrations.CreateModel( migrations.CreateModel(
name='Preferences', name="Preferences",
fields=[ fields=[
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), (
('show_my_stats', models.BooleanField(help_text='Show your account statistics to others', verbose_name='define if we show a users stats', default=False)), "id",
('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, related_name='preferences')), models.AutoField(
primary_key=True,
serialize=False,
verbose_name="ID",
auto_created=True,
),
),
(
"show_my_stats",
models.BooleanField(
help_text="Show your account statistics to others",
verbose_name="define if we show a users stats",
default=False,
),
),
(
"user",
models.OneToOneField(
to=settings.AUTH_USER_MODEL, related_name="preferences"
),
),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name='SithFile', name="SithFile",
fields=[ fields=[
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), (
('name', models.CharField(max_length=30, verbose_name='file name')), "id",
('file', models.FileField(upload_to=core.models.get_directory, null=True, verbose_name='file', blank=True)), models.AutoField(
('is_folder', models.BooleanField(verbose_name='is folder', default=True)), primary_key=True,
('mime_type', models.CharField(max_length=30, verbose_name='mime type')), serialize=False,
('size', models.IntegerField(default=0, verbose_name='size')), verbose_name="ID",
('date', models.DateTimeField(verbose_name='date', auto_now=True)), auto_created=True,
('edit_groups', models.ManyToManyField(related_name='editable_files', to='core.Group', blank=True, verbose_name='edit group')), ),
('owner', models.ForeignKey(verbose_name='owner', to=settings.AUTH_USER_MODEL, related_name='owned_files')), ),
('parent', models.ForeignKey(null=True, related_name='children', verbose_name='parent', to='core.SithFile', blank=True)), ("name", models.CharField(max_length=30, verbose_name="file name")),
('view_groups', models.ManyToManyField(related_name='viewable_files', to='core.Group', blank=True, verbose_name='view group')), (
"file",
models.FileField(
upload_to=core.models.get_directory,
null=True,
verbose_name="file",
blank=True,
),
),
(
"is_folder",
models.BooleanField(verbose_name="is folder", default=True),
),
(
"mime_type",
models.CharField(max_length=30, verbose_name="mime type"),
),
("size", models.IntegerField(default=0, verbose_name="size")),
("date", models.DateTimeField(verbose_name="date", auto_now=True)),
(
"edit_groups",
models.ManyToManyField(
related_name="editable_files",
to="core.Group",
blank=True,
verbose_name="edit group",
),
),
(
"owner",
models.ForeignKey(
verbose_name="owner",
to=settings.AUTH_USER_MODEL,
related_name="owned_files",
),
),
(
"parent",
models.ForeignKey(
null=True,
related_name="children",
verbose_name="parent",
to="core.SithFile",
blank=True,
),
),
(
"view_groups",
models.ManyToManyField(
related_name="viewable_files",
to="core.Group",
blank=True,
verbose_name="view group",
),
),
], ],
options={ options={"verbose_name": "file"},
'verbose_name': 'file',
},
), ),
migrations.AddField( migrations.AddField(
model_name='user', model_name="user",
name='avatar_pict', name="avatar_pict",
field=models.OneToOneField(blank=True, on_delete=django.db.models.deletion.SET_NULL, null=True, related_name='avatar_of', verbose_name='avatar', to='core.SithFile'), field=models.OneToOneField(
blank=True,
on_delete=django.db.models.deletion.SET_NULL,
null=True,
related_name="avatar_of",
verbose_name="avatar",
to="core.SithFile",
),
), ),
migrations.AddField( migrations.AddField(
model_name='user', model_name="user",
name='home', name="home",
field=models.OneToOneField(blank=True, null=True, related_name='home_of', verbose_name='home', to='core.SithFile'), field=models.OneToOneField(
blank=True,
null=True,
related_name="home_of",
verbose_name="home",
to="core.SithFile",
),
), ),
migrations.AddField( migrations.AddField(
model_name='user', model_name="user",
name='profile_pict', name="profile_pict",
field=models.OneToOneField(blank=True, on_delete=django.db.models.deletion.SET_NULL, null=True, related_name='profile_of', verbose_name='profile', to='core.SithFile'), field=models.OneToOneField(
blank=True,
on_delete=django.db.models.deletion.SET_NULL,
null=True,
related_name="profile_of",
verbose_name="profile",
to="core.SithFile",
),
), ),
migrations.AddField( migrations.AddField(
model_name='user', model_name="user",
name='scrub_pict', name="scrub_pict",
field=models.OneToOneField(blank=True, on_delete=django.db.models.deletion.SET_NULL, null=True, related_name='scrub_of', verbose_name='scrub', to='core.SithFile'), field=models.OneToOneField(
blank=True,
on_delete=django.db.models.deletion.SET_NULL,
null=True,
related_name="scrub_of",
verbose_name="scrub",
to="core.SithFile",
),
), ),
migrations.CreateModel( migrations.CreateModel(
name='MetaGroup', name="MetaGroup",
fields=[ fields=[],
], options={"proxy": True},
options={ bases=("core.group",),
'proxy': True, managers=[("objects", core.models.MetaGroupManager())],
},
bases=('core.group',),
managers=[
('objects', core.models.MetaGroupManager()),
],
), ),
migrations.CreateModel( migrations.CreateModel(
name='RealGroup', name="RealGroup",
fields=[ fields=[],
], options={"proxy": True},
options={ bases=("core.group",),
'proxy': True, managers=[("objects", core.models.RealGroupManager())],
},
bases=('core.group',),
managers=[
('objects', core.models.RealGroupManager()),
],
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name='page', name="page", unique_together=set([("name", "parent")])
unique_together=set([('name', 'parent')]),
), ),
migrations.AddField( migrations.AddField(
model_name='user', model_name="user",
name='groups', name="groups",
field=models.ManyToManyField(to='core.RealGroup', blank=True, related_name='users'), field=models.ManyToManyField(
to="core.RealGroup", blank=True, related_name="users"
),
), ),
] ]

View File

@ -6,14 +6,12 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0001_initial")]
('core', '0001_initial'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='sithfile', model_name="sithfile",
name='name', name="name",
field=models.CharField(verbose_name='file name', max_length=256), field=models.CharField(verbose_name="file name", max_length=256),
), )
] ]

View File

@ -7,14 +7,24 @@ import django.core.validators
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0002_auto_20160831_0144")]
('core', '0002_auto_20160831_0144'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='user', model_name="user",
name='username', name="username",
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, max_length=254, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.+-]+$', 'Enter a valid username. This value may contain only letters, numbers and ./+/-/_ characters.')], help_text='Required. 254 characters or fewer. Letters, digits and ./+/-/_ only.', verbose_name='username'), field=models.CharField(
), error_messages={"unique": "A user with that username already exists."},
max_length=254,
unique=True,
validators=[
django.core.validators.RegexValidator(
"^[\\w.+-]+$",
"Enter a valid username. This value may contain only letters, numbers and ./+/-/_ characters.",
)
],
help_text="Required. 254 characters or fewer. Letters, digits and ./+/-/_ only.",
verbose_name="username",
),
)
] ]

View File

@ -7,14 +7,14 @@ from django.conf import settings
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0003_auto_20160902_1914")]
('core', '0003_auto_20160902_1914'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='user', model_name="user",
name='godfathers', name="godfathers",
field=models.ManyToManyField(to=settings.AUTH_USER_MODEL, related_name='godchildren', blank=True), field=models.ManyToManyField(
), to=settings.AUTH_USER_MODEL, related_name="godchildren", blank=True
),
)
] ]

View File

@ -7,19 +7,26 @@ from django.conf import settings
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0004_user_godfathers")]
('core', '0004_user_godfathers'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='page', model_name="page",
name='lock_timeout', name="lock_timeout",
field=models.DateTimeField(verbose_name='lock_timeout', null=True, blank=True, default=None), field=models.DateTimeField(
verbose_name="lock_timeout", null=True, blank=True, default=None
),
), ),
migrations.AddField( migrations.AddField(
model_name='page', model_name="page",
name='lock_user', name="lock_user",
field=models.ForeignKey(verbose_name='lock user', default=None, blank=True, to=settings.AUTH_USER_MODEL, null=True, related_name='locked_pages'), field=models.ForeignKey(
verbose_name="lock user",
default=None,
blank=True,
to=settings.AUTH_USER_MODEL,
null=True,
related_name="locked_pages",
),
), ),
] ]

View File

@ -6,14 +6,12 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0005_auto_20161105_1035")]
('core', '0005_auto_20161105_1035'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='sithfile', model_name="sithfile",
name='is_moderated', name="is_moderated",
field=models.BooleanField(verbose_name='is moderated', default=False), field=models.BooleanField(verbose_name="is moderated", default=False),
), )
] ]

View File

@ -6,14 +6,12 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0006_auto_20161108_1703")]
('core', '0006_auto_20161108_1703'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='sithfile', model_name="sithfile",
name='asked_for_removal', name="asked_for_removal",
field=models.BooleanField(default=False, verbose_name='asked for removal'), field=models.BooleanField(default=False, verbose_name="asked for removal"),
), )
] ]

View File

@ -8,24 +8,39 @@ import core.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0008_sithfile_asked_for_removal")]
('core', '0008_sithfile_asked_for_removal'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='sithfile', model_name="sithfile",
name='compressed', name="compressed",
field=models.FileField(upload_to=core.models.get_compressed_directory, null=True, verbose_name='compressed file', blank=True), field=models.FileField(
upload_to=core.models.get_compressed_directory,
null=True,
verbose_name="compressed file",
blank=True,
),
), ),
migrations.AddField( migrations.AddField(
model_name='sithfile', model_name="sithfile",
name='thumbnail', name="thumbnail",
field=models.FileField(upload_to=core.models.get_thumbnail_directory, null=True, verbose_name='thumbnail', blank=True), field=models.FileField(
upload_to=core.models.get_thumbnail_directory,
null=True,
verbose_name="thumbnail",
blank=True,
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='user', model_name="user",
name='home', name="home",
field=models.OneToOneField(verbose_name='home', related_name='home_of', on_delete=django.db.models.deletion.SET_NULL, null=True, to='core.SithFile', blank=True), field=models.OneToOneField(
verbose_name="home",
related_name="home_of",
on_delete=django.db.models.deletion.SET_NULL,
null=True,
to="core.SithFile",
blank=True,
),
), ),
] ]

View File

@ -6,14 +6,12 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0009_auto_20161120_1155")]
('core', '0009_auto_20161120_1155'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='sithfile', model_name="sithfile",
name='is_in_sas', name="is_in_sas",
field=models.BooleanField(verbose_name='is in the SAS', default=False), field=models.BooleanField(verbose_name="is in the SAS", default=False),
), )
] ]

View File

@ -8,29 +8,47 @@ import core.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0010_sithfile_is_in_sas")]
('core', '0010_sithfile_is_in_sas'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='sithfile', model_name="sithfile",
name='compressed', name="compressed",
field=models.FileField(verbose_name='compressed file', upload_to=core.models.get_compressed_directory, null=True, blank=True, max_length=256), field=models.FileField(
verbose_name="compressed file",
upload_to=core.models.get_compressed_directory,
null=True,
blank=True,
max_length=256,
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='sithfile', model_name="sithfile",
name='date', name="date",
field=models.DateTimeField(verbose_name='date', default=django.utils.timezone.now), field=models.DateTimeField(
verbose_name="date", default=django.utils.timezone.now
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='sithfile', model_name="sithfile",
name='file', name="file",
field=models.FileField(verbose_name='file', upload_to=core.models.get_directory, null=True, blank=True, max_length=256), field=models.FileField(
verbose_name="file",
upload_to=core.models.get_directory,
null=True,
blank=True,
max_length=256,
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='sithfile', model_name="sithfile",
name='thumbnail', name="thumbnail",
field=models.FileField(verbose_name='thumbnail', upload_to=core.models.get_thumbnail_directory, null=True, blank=True, max_length=256), field=models.FileField(
verbose_name="thumbnail",
upload_to=core.models.get_thumbnail_directory,
null=True,
blank=True,
max_length=256,
),
), ),
] ]

View File

@ -8,20 +8,49 @@ import django.utils.timezone
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0011_auto_20161124_0848")]
('core', '0011_auto_20161124_0848'),
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Notification', name="Notification",
fields=[ fields=[
('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)), (
('url', models.CharField(max_length=255, verbose_name='url')), "id",
('text', models.CharField(max_length=512, verbose_name='text')), models.AutoField(
('type', models.CharField(max_length=16, choices=[('FILE_MODERATION', 'File moderation'), ('SAS_MODERATION', 'SAS moderation'), ('NEW_PICTURES', 'New pictures')], verbose_name='text', null=True, blank=True)), primary_key=True,
('date', models.DateTimeField(verbose_name='date', default=django.utils.timezone.now)), verbose_name="ID",
('user', models.ForeignKey(related_name='notifications', to=settings.AUTH_USER_MODEL)), auto_created=True,
serialize=False,
),
),
("url", models.CharField(max_length=255, verbose_name="url")),
("text", models.CharField(max_length=512, verbose_name="text")),
(
"type",
models.CharField(
max_length=16,
choices=[
("FILE_MODERATION", "File moderation"),
("SAS_MODERATION", "SAS moderation"),
("NEW_PICTURES", "New pictures"),
],
verbose_name="text",
null=True,
blank=True,
),
),
(
"date",
models.DateTimeField(
verbose_name="date", default=django.utils.timezone.now
),
),
(
"user",
models.ForeignKey(
related_name="notifications", to=settings.AUTH_USER_MODEL
),
),
], ],
), )
] ]

View File

@ -6,23 +6,18 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0012_notification")]
('core', '0012_notification'),
]
operations = [ operations = [
migrations.RemoveField( migrations.RemoveField(model_name="notification", name="text"),
model_name='notification', migrations.AddField(
name='text', model_name="notification",
name="param",
field=models.CharField(verbose_name="param", default="", max_length=128),
), ),
migrations.AddField( migrations.AddField(
model_name='notification', model_name="notification",
name='param', name="viewed",
field=models.CharField(verbose_name='param', default='', max_length=128), field=models.BooleanField(verbose_name="viewed", default=False),
),
migrations.AddField(
model_name='notification',
name='viewed',
field=models.BooleanField(verbose_name='viewed', default=False),
), ),
] ]

View File

@ -6,14 +6,24 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0013_auto_20161209_2338")]
('core', '0013_auto_20161209_2338'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='notification', model_name="notification",
name='type', name="type",
field=models.CharField(verbose_name='type', max_length=32, default='GENERIC', choices=[('FILE_MODERATION', 'New files to be moderated'), ('SAS_MODERATION', 'New pictures/album to be moderated in the SAS'), ('NEW_PICTURES', "You've been identified on some pictures"), ('REFILLING', 'You just refilled of %s'), ('SELLING', 'You just bought %s'), ('GENERIC', 'You have a notification')]), field=models.CharField(
), verbose_name="type",
max_length=32,
default="GENERIC",
choices=[
("FILE_MODERATION", "New files to be moderated"),
("SAS_MODERATION", "New pictures/album to be moderated in the SAS"),
("NEW_PICTURES", "You've been identified on some pictures"),
("REFILLING", "You just refilled of %s"),
("SELLING", "You just bought %s"),
("GENERIC", "You have a notification"),
],
),
)
] ]

View File

@ -7,15 +7,18 @@ from django.conf import settings
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0014_auto_20161210_0009")]
('core', '0014_auto_20161210_0009'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='sithfile', model_name="sithfile",
name='moderator', name="moderator",
field=models.ForeignKey(related_name='moderated_files', verbose_name='owner', default=0, to=settings.AUTH_USER_MODEL), field=models.ForeignKey(
related_name="moderated_files",
verbose_name="owner",
default=0,
to=settings.AUTH_USER_MODEL,
),
preserve_default=False, preserve_default=False,
), )
] ]

View File

@ -7,14 +7,18 @@ from django.conf import settings
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0015_sithfile_moderator")]
('core', '0015_sithfile_moderator'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='sithfile', model_name="sithfile",
name='moderator', name="moderator",
field=models.ForeignKey(related_name='moderated_files', blank=True, null=True, to=settings.AUTH_USER_MODEL, verbose_name='owner'), field=models.ForeignKey(
), related_name="moderated_files",
blank=True,
null=True,
to=settings.AUTH_USER_MODEL,
verbose_name="owner",
),
)
] ]

View File

@ -6,14 +6,12 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0016_auto_20161212_1922")]
('core', '0016_auto_20161212_1922'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='user', model_name="user",
name='last_update', name="last_update",
field=models.DateTimeField(verbose_name='last update', auto_now=True), field=models.DateTimeField(verbose_name="last update", auto_now=True),
), )
] ]

View File

@ -6,14 +6,25 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0017_auto_20161220_1626")]
('core', '0017_auto_20161220_1626'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='notification', model_name="notification",
name='type', name="type",
field=models.CharField(choices=[('NEWS_MODERATION', 'A fresh new to be moderated'), ('FILE_MODERATION', 'New files to be moderated'), ('SAS_MODERATION', 'New pictures/album to be moderated in the SAS'), ('NEW_PICTURES', "You've been identified on some pictures"), ('REFILLING', 'You just refilled of %s'), ('SELLING', 'You just bought %s'), ('GENERIC', 'You have a notification')], default='GENERIC', max_length=32, verbose_name='type'), field=models.CharField(
), choices=[
("NEWS_MODERATION", "A fresh new to be moderated"),
("FILE_MODERATION", "New files to be moderated"),
("SAS_MODERATION", "New pictures/album to be moderated in the SAS"),
("NEW_PICTURES", "You've been identified on some pictures"),
("REFILLING", "You just refilled of %s"),
("SELLING", "You just bought %s"),
("GENERIC", "You have a notification"),
],
default="GENERIC",
max_length=32,
verbose_name="type",
),
)
] ]

View File

@ -6,14 +6,14 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0018_auto_20161224_0211")]
('core', '0018_auto_20161224_0211'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='preferences', model_name="preferences",
name='receive_weekmail', name="receive_weekmail",
field=models.BooleanField(default=False, verbose_name='do you want to receive the weekmail'), field=models.BooleanField(
), default=False, verbose_name="do you want to receive the weekmail"
),
)
] ]

View File

@ -7,18 +7,22 @@ import django.core.validators
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0019_preferences_receive_weekmail")]
('core', '0019_preferences_receive_weekmail'),
]
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(name="group", options={"ordering": ["name"]}),
name='group',
options={'ordering': ['name']},
),
migrations.AlterField( migrations.AlterField(
model_name='page', model_name="page",
name='name', name="name",
field=models.CharField(validators=[django.core.validators.RegexValidator('^[A-z.+-]+$', 'Enter a valid page name. This value may contain only unaccented letters, numbers and ./+/-/_ characters.')], max_length=30, verbose_name='page unix name'), field=models.CharField(
validators=[
django.core.validators.RegexValidator(
"^[A-z.+-]+$",
"Enter a valid page name. This value may contain only unaccented letters, numbers and ./+/-/_ characters.",
)
],
max_length=30,
verbose_name="page unix name",
),
), ),
] ]

View File

@ -6,14 +6,26 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0020_auto_20170324_0917")]
('core', '0020_auto_20170324_0917'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='notification', model_name="notification",
name='type', name="type",
field=models.CharField(verbose_name='type', default='GENERIC', max_length=32, choices=[('MAILING_MODERATION', 'A new mailing list neet to be moderated'), ('NEWS_MODERATION', 'A fresh new to be moderated'), ('FILE_MODERATION', 'New files to be moderated'), ('SAS_MODERATION', 'New pictures/album to be moderated in the SAS'), ('NEW_PICTURES', "You've been identified on some pictures"), ('REFILLING', 'You just refilled of %s'), ('SELLING', 'You just bought %s'), ('GENERIC', 'You have a notification')]), field=models.CharField(
), verbose_name="type",
default="GENERIC",
max_length=32,
choices=[
("MAILING_MODERATION", "A new mailing list neet to be moderated"),
("NEWS_MODERATION", "A fresh new to be moderated"),
("FILE_MODERATION", "New files to be moderated"),
("SAS_MODERATION", "New pictures/album to be moderated in the SAS"),
("NEW_PICTURES", "You've been identified on some pictures"),
("REFILLING", "You just refilled of %s"),
("SELLING", "You just bought %s"),
("GENERIC", "You have a notification"),
],
),
)
] ]

View File

@ -6,14 +6,26 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0021_auto_20170822_1529")]
('core', '0021_auto_20170822_1529'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='notification', model_name="notification",
name='type', name="type",
field=models.CharField(choices=[('MAILING_MODERATION', 'A new mailing list needs to be moderated'), ('NEWS_MODERATION', 'A fresh new to be moderated'), ('FILE_MODERATION', 'New files to be moderated'), ('SAS_MODERATION', 'New pictures/album to be moderated in the SAS'), ('NEW_PICTURES', "You've been identified on some pictures"), ('REFILLING', 'You just refilled of %s'), ('SELLING', 'You just bought %s'), ('GENERIC', 'You have a notification')], default='GENERIC', max_length=32, verbose_name='type'), field=models.CharField(
), choices=[
("MAILING_MODERATION", "A new mailing list needs to be moderated"),
("NEWS_MODERATION", "A fresh new to be moderated"),
("FILE_MODERATION", "New files to be moderated"),
("SAS_MODERATION", "New pictures/album to be moderated in the SAS"),
("NEW_PICTURES", "You've been identified on some pictures"),
("REFILLING", "You just refilled of %s"),
("SELLING", "You just bought %s"),
("GENERIC", "You have a notification"),
],
default="GENERIC",
max_length=32,
verbose_name="type",
),
)
] ]

View File

@ -7,29 +7,35 @@ from django.conf import settings
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0022_auto_20170822_2232")]
('core', '0022_auto_20170822_2232'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='preferences', model_name="preferences",
name='notify_on_click', name="notify_on_click",
field=models.BooleanField(verbose_name='get a notification for every click', default=False), field=models.BooleanField(
verbose_name="get a notification for every click", default=False
),
), ),
migrations.AddField( migrations.AddField(
model_name='preferences', model_name="preferences",
name='notify_on_refill', name="notify_on_refill",
field=models.BooleanField(verbose_name='get a notification for every refilling', default=False), field=models.BooleanField(
verbose_name="get a notification for every refilling", default=False
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='preferences', model_name="preferences",
name='show_my_stats', name="show_my_stats",
field=models.BooleanField(verbose_name='show your stats to others', default=False), field=models.BooleanField(
verbose_name="show your stats to others", default=False
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='preferences', model_name="preferences",
name='user', name="user",
field=models.OneToOneField(related_name='_preferences', to=settings.AUTH_USER_MODEL), field=models.OneToOneField(
related_name="_preferences", to=settings.AUTH_USER_MODEL
),
), ),
] ]

View File

@ -6,14 +6,26 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0023_auto_20170902_1226")]
('core', '0023_auto_20170902_1226'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='notification', model_name="notification",
name='type', name="type",
field=models.CharField(choices=[('MAILING_MODERATION', 'A new mailing list needs to be moderated'), ('NEWS_MODERATION', 'There are %s fresh news to be moderated'), ('FILE_MODERATION', 'New files to be moderated'), ('SAS_MODERATION', 'New pictures/album to be moderated in the SAS'), ('NEW_PICTURES', "You've been identified on some pictures"), ('REFILLING', 'You just refilled of %s'), ('SELLING', 'You just bought %s'), ('GENERIC', 'You have a notification')], verbose_name='type', default='GENERIC', max_length=32), field=models.CharField(
), choices=[
("MAILING_MODERATION", "A new mailing list needs to be moderated"),
("NEWS_MODERATION", "There are %s fresh news to be moderated"),
("FILE_MODERATION", "New files to be moderated"),
("SAS_MODERATION", "New pictures/album to be moderated in the SAS"),
("NEW_PICTURES", "You've been identified on some pictures"),
("REFILLING", "You just refilled of %s"),
("SELLING", "You just bought %s"),
("GENERIC", "You have a notification"),
],
verbose_name="type",
default="GENERIC",
max_length=32,
),
)
] ]

View File

@ -7,14 +7,21 @@ import django.core.validators
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0024_auto_20170906_1317")]
('core', '0024_auto_20170906_1317'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='page', model_name="page",
name='name', name="name",
field=models.CharField(max_length=30, verbose_name='page unix name', validators=[django.core.validators.RegexValidator('^[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]$', 'Enter a valid page name. This value may contain only unaccented letters, numbers and ./+/-/_ characters.')]), field=models.CharField(
), max_length=30,
verbose_name="page unix name",
validators=[
django.core.validators.RegexValidator(
"^[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]$",
"Enter a valid page name. This value may contain only unaccented letters, numbers and ./+/-/_ characters.",
)
],
),
)
] ]

View File

@ -6,14 +6,29 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0025_auto_20170919_1521")]
('core', '0025_auto_20170919_1521'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='notification', model_name="notification",
name='type', name="type",
field=models.CharField(choices=[('MAILING_MODERATION', 'A new mailing list needs to be moderated'), ('NEWS_MODERATION', 'There are %s fresh news to be moderated'), ('FILE_MODERATION', 'New files to be moderated'), ('SAS_MODERATION', 'There are %s pictures to be moderated in the SAS'), ('NEW_PICTURES', "You've been identified on some pictures"), ('REFILLING', 'You just refilled of %s'), ('SELLING', 'You just bought %s'), ('GENERIC', 'You have a notification')], verbose_name='type', max_length=32, default='GENERIC'), field=models.CharField(
), choices=[
("MAILING_MODERATION", "A new mailing list needs to be moderated"),
("NEWS_MODERATION", "There are %s fresh news to be moderated"),
("FILE_MODERATION", "New files to be moderated"),
(
"SAS_MODERATION",
"There are %s pictures to be moderated in the SAS",
),
("NEW_PICTURES", "You've been identified on some pictures"),
("REFILLING", "You just refilled of %s"),
("SELLING", "You just bought %s"),
("GENERIC", "You have a notification"),
],
verbose_name="type",
max_length=32,
default="GENERIC",
),
)
] ]

View File

@ -8,18 +8,34 @@ import django.utils.timezone
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0026_auto_20170926_1512")]
('core', '0026_auto_20170926_1512'),
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Gift', name="Gift",
fields=[ fields=[
('id', models.AutoField(primary_key=True, auto_created=True, verbose_name='ID', serialize=False)), (
('label', models.CharField(max_length=255, verbose_name='label')), "id",
('date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date')), models.AutoField(
('user', models.ForeignKey(related_name='gifts', to=settings.AUTH_USER_MODEL)), primary_key=True,
auto_created=True,
verbose_name="ID",
serialize=False,
),
),
("label", models.CharField(max_length=255, verbose_name="label")),
(
"date",
models.DateTimeField(
default=django.utils.timezone.now, verbose_name="date"
),
),
(
"user",
models.ForeignKey(
related_name="gifts", to=settings.AUTH_USER_MODEL
),
),
], ],
), )
] ]

View File

@ -6,14 +6,30 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0027_gift")]
('core', '0027_gift'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='notification', model_name="notification",
name='type', name="type",
field=models.CharField(default='GENERIC', verbose_name='type', max_length=32, choices=[('POSTER_MODERATION', 'A new poster needs to be moderated'), ('MAILING_MODERATION', 'A new mailing list needs to be moderated'), ('NEWS_MODERATION', 'There are %s fresh news to be moderated'), ('FILE_MODERATION', 'New files to be moderated'), ('SAS_MODERATION', 'There are %s pictures to be moderated in the SAS'), ('NEW_PICTURES', "You've been identified on some pictures"), ('REFILLING', 'You just refilled of %s'), ('SELLING', 'You just bought %s'), ('GENERIC', 'You have a notification')]), field=models.CharField(
), default="GENERIC",
verbose_name="type",
max_length=32,
choices=[
("POSTER_MODERATION", "A new poster needs to be moderated"),
("MAILING_MODERATION", "A new mailing list needs to be moderated"),
("NEWS_MODERATION", "There are %s fresh news to be moderated"),
("FILE_MODERATION", "New files to be moderated"),
(
"SAS_MODERATION",
"There are %s pictures to be moderated in the SAS",
),
("NEW_PICTURES", "You've been identified on some pictures"),
("REFILLING", "You just refilled of %s"),
("SELLING", "You just bought %s"),
("GENERIC", "You have a notification"),
],
),
)
] ]

View File

@ -7,14 +7,17 @@ import core.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("core", "0028_auto_20171216_2044")]
('core', '0028_auto_20171216_2044'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='page', model_name="page",
name='owner_group', name="owner_group",
field=models.ForeignKey(verbose_name='owner group', default=core.models.Page.get_default_owner_group, related_name='owned_page', to='core.Group'), field=models.ForeignKey(
), verbose_name="owner group",
default=core.models.Page.get_default_owner_group,
related_name="owned_page",
to="core.Group",
),
)
] ]

File diff suppressed because it is too large Load Diff

View File

@ -46,5 +46,5 @@ class PsqlRunOnly(migrations.RunSQL):
""" """
def _run_sql(self, schema_editor, sqls): def _run_sql(self, schema_editor, sqls):
if connection.vendor == 'postgresql': if connection.vendor == "postgresql":
super(PsqlRunOnly, self)._run_sql(schema_editor, sqls) super(PsqlRunOnly, self)._run_sql(schema_editor, sqls)

View File

@ -34,21 +34,20 @@ class ScssFinder(FileSystemFinder):
""" """
Find static *.css files compiled on the fly Find static *.css files compiled on the fly
""" """
locations = [] locations = []
def __init__(self, apps=None, *args, **kwargs): def __init__(self, apps=None, *args, **kwargs):
location = settings.STATIC_ROOT location = settings.STATIC_ROOT
if not os.path.isdir(location): if not os.path.isdir(location):
return return
self.locations = [ self.locations = [("", location)]
('', location),
]
self.storages = OrderedDict() self.storages = OrderedDict()
filesystem_storage = FileSystemStorage(location=location) filesystem_storage = FileSystemStorage(location=location)
filesystem_storage.prefix = self.locations[0][0] filesystem_storage.prefix = self.locations[0][0]
self.storages[location] = filesystem_storage self.storages[location] = filesystem_storage
def find(self, path, all=False): def find(self, path, all=False):
if path.endswith('.css'): if path.endswith(".css"):
return super(ScssFinder, self).find(path, all) return super(ScssFinder, self).find(path, all)
return [] return []

View File

@ -39,7 +39,8 @@ class ScssProcessor(object):
Else : give the path of the corresponding css supposed to already be compiled Else : give the path of the corresponding css supposed to already be compiled
Don't forget to use compilestatics to compile scss for production Don't forget to use compilestatics to compile scss for production
""" """
prefix = iri_to_uri(getattr(settings, 'STATIC_URL', '/static/'))
prefix = iri_to_uri(getattr(settings, "STATIC_URL", "/static/"))
storage = ScssFileStorage() storage = ScssFileStorage()
scss_extensions = [".scss"] scss_extensions = [".scss"]
@ -63,7 +64,7 @@ class ScssProcessor(object):
"include_paths": settings.SASS_INCLUDE_FOLDERS, "include_paths": settings.SASS_INCLUDE_FOLDERS,
} }
if settings.SASS_PRECISION: if settings.SASS_PRECISION:
compile_args['precision'] = settings.SASS_PRECISION compile_args["precision"] = settings.SASS_PRECISION
content = sass.compile(**compile_args) content = sass.compile(**compile_args)
content = force_bytes(content) content = force_bytes(content)

View File

@ -21,4 +21,3 @@
# Place - Suite 330, Boston, MA 02111-1307, USA. # Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #

View File

@ -38,11 +38,11 @@ register = template.Library()
@register.filter(is_safe=False) @register.filter(is_safe=False)
@stringfilter @stringfilter
def markdown(text): def markdown(text):
return mark_safe("<div class=\"markdown\">%s</div>" % md(text)) return mark_safe('<div class="markdown">%s</div>' % md(text))
@register.filter(name='phonenumber')
def phonenumber(value, country='FR', @register.filter(name="phonenumber")
format=phonenumbers.PhoneNumberFormat.NATIONAL): def phonenumber(value, country="FR", format=phonenumbers.PhoneNumberFormat.NATIONAL):
""" """
This filter is kindly borrowed from https://github.com/foundertherapy/django-phonenumber-filter This filter is kindly borrowed from https://github.com/foundertherapy/django-phonenumber-filter
""" """
@ -53,13 +53,37 @@ def phonenumber(value, country='FR',
except phonenumbers.NumberParseException as e: except phonenumbers.NumberParseException as e:
return value return value
@register.filter() @register.filter()
@stringfilter @stringfilter
def datetime_format_python_to_PHP(python_format_string): def datetime_format_python_to_PHP(python_format_string):
""" """
Given a python datetime format string, attempts to convert it to the nearest PHP datetime format string possible. Given a python datetime format string, attempts to convert it to the nearest PHP datetime format string possible.
""" """
python2PHP = {"%a": "D", "%a": "D", "%A": "l", "%b": "M", "%B": "F", "%c": "", "%d": "d", "%H": "H", "%I": "h", "%j": "z", "%m": "m", "%M": "i", "%p": "A", "%S": "s", "%U": "", "%w": "w", "%W": "W", "%x": "", "%X": "", "%y": "y", "%Y": "Y", "%Z": "e"} python2PHP = {
"%a": "D",
"%a": "D",
"%A": "l",
"%b": "M",
"%B": "F",
"%c": "",
"%d": "d",
"%H": "H",
"%I": "h",
"%j": "z",
"%m": "m",
"%M": "i",
"%p": "A",
"%S": "s",
"%U": "",
"%w": "w",
"%W": "W",
"%x": "",
"%X": "",
"%y": "y",
"%Y": "Y",
"%Z": "e",
}
php_format_string = python_format_string php_format_string = python_format_string
for py, php in python2PHP.items(): for py, php in python2PHP.items():

View File

@ -49,203 +49,261 @@ class UserRegistrationTest(TestCase):
Should register a user correctly Should register a user correctly
""" """
c = Client() c = Client()
response = c.post(reverse('core:register'), {'first_name': 'Guy', response = c.post(
'last_name': 'Carlier', reverse("core:register"),
'email': 'guy@git.an', {
'date_of_birth': '12/6/1942', "first_name": "Guy",
'password1': 'plop', "last_name": "Carlier",
'password2': 'plop', "email": "guy@git.an",
'captcha_0': 'dummy-value', "date_of_birth": "12/6/1942",
'captcha_1': 'PASSED' "password1": "plop",
}) "password2": "plop",
"captcha_0": "dummy-value",
"captcha_1": "PASSED",
},
)
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('TEST_REGISTER_USER_FORM_OK' in str(response.content)) self.assertTrue("TEST_REGISTER_USER_FORM_OK" in str(response.content))
def test_register_user_form_fail_password(self): def test_register_user_form_fail_password(self):
""" """
Should not register a user correctly Should not register a user correctly
""" """
c = Client() c = Client()
response = c.post(reverse('core:register'), {'first_name': 'Guy', response = c.post(
'last_name': 'Carlier', reverse("core:register"),
'email': 'bibou@git.an', {
'date_of_birth': '12/6/1942', "first_name": "Guy",
'password1': 'plop', "last_name": "Carlier",
'password2': 'plop2', "email": "bibou@git.an",
'captcha_0': 'dummy-value', "date_of_birth": "12/6/1942",
'captcha_1': 'PASSED' "password1": "plop",
}) "password2": "plop2",
"captcha_0": "dummy-value",
"captcha_1": "PASSED",
},
)
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content))
def test_register_user_form_fail_email(self): def test_register_user_form_fail_email(self):
""" """
Should not register a user correctly Should not register a user correctly
""" """
c = Client() c = Client()
response = c.post(reverse('core:register'), {'first_name': 'Guy', response = c.post(
'last_name': 'Carlier', reverse("core:register"),
'email': 'bibou.git.an', {
'date_of_birth': '12/6/1942', "first_name": "Guy",
'password1': 'plop', "last_name": "Carlier",
'password2': 'plop', "email": "bibou.git.an",
'captcha_0': 'dummy-value', "date_of_birth": "12/6/1942",
'captcha_1': 'PASSED' "password1": "plop",
}) "password2": "plop",
"captcha_0": "dummy-value",
"captcha_1": "PASSED",
},
)
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content))
def test_register_user_form_fail_missing_name(self): def test_register_user_form_fail_missing_name(self):
""" """
Should not register a user correctly Should not register a user correctly
""" """
c = Client() c = Client()
response = c.post(reverse('core:register'), {'first_name': 'Guy', response = c.post(
'last_name': '', reverse("core:register"),
'email': 'bibou@git.an', {
'date_of_birth': '12/6/1942', "first_name": "Guy",
'password1': 'plop', "last_name": "",
'password2': 'plop', "email": "bibou@git.an",
'captcha_0': 'dummy-value', "date_of_birth": "12/6/1942",
'captcha_1': 'PASSED' "password1": "plop",
}) "password2": "plop",
"captcha_0": "dummy-value",
"captcha_1": "PASSED",
},
)
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content))
def test_register_user_form_fail_missing_date_of_birth(self): def test_register_user_form_fail_missing_date_of_birth(self):
""" """
Should not register a user correctly Should not register a user correctly
""" """
c = Client() c = Client()
response = c.post(reverse('core:register'), {'first_name': '', response = c.post(
'last_name': 'Carlier', reverse("core:register"),
'email': 'bibou@git.an', {
'date_of_birth': '', "first_name": "",
'password1': 'plop', "last_name": "Carlier",
'password2': 'plop', "email": "bibou@git.an",
'captcha_0': 'dummy-value', "date_of_birth": "",
'captcha_1': 'PASSED' "password1": "plop",
}) "password2": "plop",
"captcha_0": "dummy-value",
"captcha_1": "PASSED",
},
)
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content))
def test_register_user_form_fail_missing_first_name(self): def test_register_user_form_fail_missing_first_name(self):
""" """
Should not register a user correctly Should not register a user correctly
""" """
c = Client() c = Client()
response = c.post(reverse('core:register'), {'first_name': '', response = c.post(
'last_name': 'Carlier', reverse("core:register"),
'email': 'bibou@git.an', {
'date_of_birth': '12/6/1942', "first_name": "",
'password1': 'plop', "last_name": "Carlier",
'password2': 'plop', "email": "bibou@git.an",
'captcha_0': 'dummy-value', "date_of_birth": "12/6/1942",
'captcha_1': 'PASSED' "password1": "plop",
}) "password2": "plop",
"captcha_0": "dummy-value",
"captcha_1": "PASSED",
},
)
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content))
def test_register_user_form_fail_wrong_captcha(self): def test_register_user_form_fail_wrong_captcha(self):
""" """
Should not register a user correctly Should not register a user correctly
""" """
c = Client() c = Client()
response = c.post(reverse('core:register'), {'first_name': 'Bibou', response = c.post(
'last_name': 'Carlier', reverse("core:register"),
'email': 'bibou@git.an', {
'date_of_birth': '12/6/1942', "first_name": "Bibou",
'password1': 'plop', "last_name": "Carlier",
'password2': 'plop', "email": "bibou@git.an",
'captcha_0': 'dummy-value', "date_of_birth": "12/6/1942",
'captcha_1': 'WRONG_CAPTCHA' "password1": "plop",
}) "password2": "plop",
"captcha_0": "dummy-value",
"captcha_1": "WRONG_CAPTCHA",
},
)
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content))
def test_register_user_form_fail_already_exists(self): def test_register_user_form_fail_already_exists(self):
""" """
Should not register a user correctly Should not register a user correctly
""" """
c = Client() c = Client()
c.post(reverse('core:register'), {'first_name': 'Guy', c.post(
'last_name': 'Carlier', reverse("core:register"),
'email': 'bibou@git.an', {
'date_of_birth': '12/6/1942', "first_name": "Guy",
'password1': 'plop', "last_name": "Carlier",
'password2': 'plop', "email": "bibou@git.an",
'captcha_0': 'dummy-value', "date_of_birth": "12/6/1942",
'captcha_1': 'PASSED' "password1": "plop",
}) "password2": "plop",
response = c.post(reverse('core:register'), {'first_name': 'Bibou', "captcha_0": "dummy-value",
'last_name': 'Carlier', "captcha_1": "PASSED",
'email': 'bibou@git.an', },
'date_of_birth': '12/6/1942', )
'password1': 'plop', response = c.post(
'password2': 'plop', reverse("core:register"),
'captcha_0': 'dummy-value', {
'captcha_1': 'PASSED' "first_name": "Bibou",
}) "last_name": "Carlier",
"email": "bibou@git.an",
"date_of_birth": "12/6/1942",
"password1": "plop",
"password2": "plop",
"captcha_0": "dummy-value",
"captcha_1": "PASSED",
},
)
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content))
def test_login_success(self): def test_login_success(self):
""" """
Should login a user correctly Should login a user correctly
""" """
c = Client() c = Client()
c.post(reverse('core:register'), {'first_name': 'Guy', c.post(
'last_name': 'Carlier', reverse("core:register"),
'email': 'bibou@git.an', {
'date_of_birth': '12/6/1942', "first_name": "Guy",
'password1': 'plop', "last_name": "Carlier",
'password2': 'plop', "email": "bibou@git.an",
'captcha_0': 'dummy-value', "date_of_birth": "12/6/1942",
'captcha_1': 'PASSED' "password1": "plop",
}) "password2": "plop",
response = c.post(reverse('core:login'), {'username': 'gcarlier', 'password': 'plop'}) "captcha_0": "dummy-value",
"captcha_1": "PASSED",
},
)
response = c.post(
reverse("core:login"), {"username": "gcarlier", "password": "plop"}
)
self.assertTrue(response.status_code == 302) self.assertTrue(response.status_code == 302)
#self.assertTrue('Hello, world' in str(response.content)) # self.assertTrue('Hello, world' in str(response.content))
def test_login_fail(self): def test_login_fail(self):
""" """
Should not login a user correctly Should not login a user correctly
""" """
c = Client() c = Client()
c.post(reverse('core:register'), {'first_name': 'Guy', c.post(
'last_name': 'Carlier', reverse("core:register"),
'email': 'bibou@git.an', {
'date_of_birth': '12/6/1942', "first_name": "Guy",
'password1': 'plop', "last_name": "Carlier",
'password2': 'plop', "email": "bibou@git.an",
'captcha_0': 'dummy-value', "date_of_birth": "12/6/1942",
'captcha_1': 'PASSED' "password1": "plop",
}) "password2": "plop",
response = c.post(reverse('core:login'), {'username': 'gcarlier', 'password': 'guy'}) "captcha_0": "dummy-value",
"captcha_1": "PASSED",
},
)
response = c.post(
reverse("core:login"), {"username": "gcarlier", "password": "guy"}
)
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue("""<p>Votre nom d\\'utilisateur et votre mot de passe ne correspondent pas. Merci de r\\xc3\\xa9essayer.</p>""" in str(response.content)) self.assertTrue(
"""<p>Votre nom d\\'utilisateur et votre mot de passe ne correspondent pas. Merci de r\\xc3\\xa9essayer.</p>"""
in str(response.content)
)
class MarkdownTest(TestCase): class MarkdownTest(TestCase):
def test_full_markdown_syntax(self): def test_full_markdown_syntax(self):
root_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) root_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
with open(os.path.join(root_path) + '/doc/SYNTAX.md', 'r') as md_file: with open(os.path.join(root_path) + "/doc/SYNTAX.md", "r") as md_file:
md = md_file.read() md = md_file.read()
with open(os.path.join(root_path) + '/doc/SYNTAX.html', 'r') as html_file: with open(os.path.join(root_path) + "/doc/SYNTAX.html", "r") as html_file:
html = html_file.read() html = html_file.read()
result = markdown(md) result = markdown(md)
self.assertTrue(result == html) self.assertTrue(result == html)
class PageHandlingTest(TestCase): class PageHandlingTest(TestCase):
def setUp(self): def setUp(self):
try: try:
Group.objects.create(name="root") Group.objects.create(name="root")
u = User(username='root', last_name="", first_name="Bibou", u = User(
email="ae.info@utbm.fr", username="root",
date_of_birth="1942-06-12", last_name="",
is_superuser=True, is_staff=True) first_name="Bibou",
email="ae.info@utbm.fr",
date_of_birth="1942-06-12",
is_superuser=True,
is_staff=True,
)
u.set_password("plop") u.set_password("plop")
u.save() u.save()
self.client.login(username='root', password='plop') self.client.login(username="root", password="plop")
except Exception as e: except Exception as e:
print(e) print(e)
@ -253,12 +311,10 @@ class PageHandlingTest(TestCase):
""" """
Should create a page correctly Should create a page correctly
""" """
self.client.post(reverse('core:page_new'), { self.client.post(
'parent': '', reverse("core:page_new"), {"parent": "", "name": "guy", "owner_group": 1}
'name': 'guy', )
'owner_group': 1, response = self.client.get(reverse("core:page", kwargs={"page_name": "guy"}))
})
response = self.client.get(reverse('core:page', kwargs={'page_name': 'guy'}))
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('<a href="/page/guy/hist">' in str(response.content)) self.assertTrue('<a href="/page/guy/hist">' in str(response.content))
@ -266,17 +322,16 @@ class PageHandlingTest(TestCase):
""" """
Should create a page correctly Should create a page correctly
""" """
self.client.post(reverse('core:page_new'), { self.client.post(
'parent': '', reverse("core:page_new"), {"parent": "", "name": "guy", "owner_group": "1"}
'name': 'guy', )
'owner_group': '1', response = self.client.post(
}) reverse("core:page_new"),
response = self.client.post(reverse('core:page_new'), { {"parent": "1", "name": "bibou", "owner_group": "1"},
'parent': '1', )
'name': 'bibou', response = self.client.get(
'owner_group': '1', reverse("core:page", kwargs={"page_name": "guy/bibou"})
}) )
response = self.client.get(reverse('core:page', kwargs={'page_name': 'guy/bibou'}))
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('<a href="/page/guy/bibou/">' in str(response.content)) self.assertTrue('<a href="/page/guy/bibou/">' in str(response.content))
@ -286,17 +341,24 @@ class PageHandlingTest(TestCase):
""" """
parent = Page(name="guy", owner_group=Group.objects.filter(id=1).first()) parent = Page(name="guy", owner_group=Group.objects.filter(id=1).first())
parent.save(force_lock=True) parent.save(force_lock=True)
page = Page(name="bibou", owner_group=Group.objects.filter(id=1).first(), parent=parent) page = Page(
name="bibou", owner_group=Group.objects.filter(id=1).first(), parent=parent
)
page.save(force_lock=True) page.save(force_lock=True)
response = self.client.get(reverse('core:page', kwargs={'page_name': 'guy/bibou'})) response = self.client.get(
reverse("core:page", kwargs={"page_name": "guy/bibou"})
)
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('<a href="/page/guy/bibou/edit">\\xc3\\x89diter</a>' in str(response.content)) self.assertTrue(
'<a href="/page/guy/bibou/edit">\\xc3\\x89diter</a>'
in str(response.content)
)
def test_access_page_not_found(self): def test_access_page_not_found(self):
""" """
Should not display a page correctly Should not display a page correctly
""" """
response = self.client.get(reverse('core:page', kwargs={'page_name': 'swagg'})) response = self.client.get(reverse("core:page", kwargs={"page_name": "swagg"}))
response = self.client.get("/page/swagg/") response = self.client.get("/page/swagg/")
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('<a href="/page/create?page=swagg">' in str(response.content)) self.assertTrue('<a href="/page/create?page=swagg">' in str(response.content))
@ -305,15 +367,14 @@ class PageHandlingTest(TestCase):
""" """
Should format the markdown and escape html correctly Should format the markdown and escape html correctly
""" """
self.client.post(reverse('core:page_new'), { self.client.post(
'parent': '', reverse("core:page_new"), {"parent": "", "name": "guy", "owner_group": "1"}
'name': 'guy', )
'owner_group': '1', self.client.post(
}) reverse("core:page_edit", kwargs={"page_name": "guy"}),
self.client.post(reverse('core:page_edit', kwargs={'page_name': 'guy'}), { {
'title': 'Bibou', "title": "Bibou",
'content': "content": """Guy *bibou*
'''Guy *bibou*
http://git.an http://git.an
@ -322,13 +383,18 @@ http://git.an
<guy>Bibou</guy> <guy>Bibou</guy>
<script>alert('Guy');</script> <script>alert('Guy');</script>
''', """,
}) },
response = self.client.get(reverse('core:page', kwargs={'page_name': 'guy'})) )
response = self.client.get(reverse("core:page", kwargs={"page_name": "guy"}))
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('<p>Guy <em>bibou</em></p>\\n<p><a href="http://git.an">http://git.an</a></p>\\n' + self.assertTrue(
'<h1>Swag</h1>\\n&lt;guy&gt;Bibou&lt;/guy&gt;' + '<p>Guy <em>bibou</em></p>\\n<p><a href="http://git.an">http://git.an</a></p>\\n'
"&lt;script&gt;alert(\\'Guy\\');&lt;/script&gt;" in str(response.content)) + "<h1>Swag</h1>\\n&lt;guy&gt;Bibou&lt;/guy&gt;"
+ "&lt;script&gt;alert(\\'Guy\\');&lt;/script&gt;"
in str(response.content)
)
# TODO: many tests on the pages: # TODO: many tests on the pages:
# - renaming a page # - renaming a page
@ -341,23 +407,33 @@ class FileHandlingTest(TestCase):
try: try:
call_command("populate") call_command("populate")
self.subscriber = User.objects.filter(username="subscriber").first() self.subscriber = User.objects.filter(username="subscriber").first()
self.client.login(username='subscriber', password='plop') self.client.login(username="subscriber", password="plop")
except Exception as e: except Exception as e:
print(e) print(e)
def test_create_folder_home(self): def test_create_folder_home(self):
response = self.client.post(reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}), response = self.client.post(
{"folder_name": "GUY_folder_test"}) reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}),
{"folder_name": "GUY_folder_test"},
)
self.assertTrue(response.status_code == 302) self.assertTrue(response.status_code == 302)
response = self.client.get(reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id})) response = self.client.get(
reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id})
)
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue("GUY_folder_test</a>" in str(response.content)) self.assertTrue("GUY_folder_test</a>" in str(response.content))
def test_upload_file_home(self): def test_upload_file_home(self):
with open("/bin/ls", "rb") as f: with open("/bin/ls", "rb") as f:
response = self.client.post(reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}), response = self.client.post(
{"file_field": f}) reverse(
"core:file_detail", kwargs={"file_id": self.subscriber.home.id}
),
{"file_field": f},
)
self.assertTrue(response.status_code == 302) self.assertTrue(response.status_code == 302)
response = self.client.get(reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id})) response = self.client.get(
reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id})
)
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue("ls</a>" in str(response.content)) self.assertTrue("ls</a>" in str(response.content))

View File

@ -28,73 +28,181 @@ from django.conf.urls import url
from core.views import * from core.views import *
urlpatterns = [ urlpatterns = [
url(r'^$', index, name='index'), url(r"^$", index, name="index"),
url(r'^to_markdown$', ToMarkdownView.as_view(), name='to_markdown'), url(r"^to_markdown$", ToMarkdownView.as_view(), name="to_markdown"),
url(r'^notifications$', NotificationList.as_view(), name='notification_list'), url(r"^notifications$", NotificationList.as_view(), name="notification_list"),
url(r'^notification/(?P<notif_id>[0-9]+)$', notification, name='notification'), url(r"^notification/(?P<notif_id>[0-9]+)$", notification, name="notification"),
# Search # Search
url(r'^search/$', search_view, name='search'), url(r"^search/$", search_view, name="search"),
url(r'^search_json/$', search_json, name='search_json'), url(r"^search_json/$", search_json, name="search_json"),
url(r'^search_user/$', search_user_json, name='search_user'), url(r"^search_user/$", search_user_json, name="search_user"),
# Login and co # Login and co
url(r'^login/$', login, name='login'), url(r"^login/$", login, name="login"),
url(r'^logout/$', logout, name='logout'), url(r"^logout/$", logout, name="logout"),
url(r'^password_change/$', password_change, name='password_change'), url(r"^password_change/$", password_change, name="password_change"),
url(r'^password_change/(?P<user_id>[0-9]+)$', password_root_change, name='password_root_change'), url(
url(r'^password_change/done$', password_change_done, name='password_change_done'), r"^password_change/(?P<user_id>[0-9]+)$",
url(r'^password_reset/$', password_reset, name='password_reset'), password_root_change,
url(r'^password_reset/done$', password_reset_done, name='password_reset_done'), name="password_root_change",
url(r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', password_reset_confirm, name='password_reset_confirm'), ),
url(r'^reset/done/$', password_reset_complete, name='password_reset_complete'), url(r"^password_change/done$", password_change_done, name="password_change_done"),
url(r'^register$', register, name='register'), url(r"^password_reset/$", password_reset, name="password_reset"),
url(r"^password_reset/done$", password_reset_done, name="password_reset_done"),
url(
r"^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$",
password_reset_confirm,
name="password_reset_confirm",
),
url(r"^reset/done/$", password_reset_complete, name="password_reset_complete"),
url(r"^register$", register, name="register"),
# Group handling # Group handling
url(r'^group/$', GroupListView.as_view(), name='group_list'), url(r"^group/$", GroupListView.as_view(), name="group_list"),
url(r'^group/new$', GroupCreateView.as_view(), name='group_new'), url(r"^group/new$", GroupCreateView.as_view(), name="group_new"),
url(r'^group/(?P<group_id>[0-9]+)/$', GroupEditView.as_view(), name='group_edit'), url(r"^group/(?P<group_id>[0-9]+)/$", GroupEditView.as_view(), name="group_edit"),
url(r'^group/(?P<group_id>[0-9]+)/delete$', GroupDeleteView.as_view(), name='group_delete'), url(
r"^group/(?P<group_id>[0-9]+)/delete$",
GroupDeleteView.as_view(),
name="group_delete",
),
# User views # User views
url(r'^user/$', UserListView.as_view(), name='user_list'), url(r"^user/$", UserListView.as_view(), name="user_list"),
url(r'^user/(?P<user_id>[0-9]+)/mini$', UserMiniView.as_view(), name='user_profile_mini'), url(
url(r'^user/(?P<user_id>[0-9]+)/$', UserView.as_view(), name='user_profile'), r"^user/(?P<user_id>[0-9]+)/mini$",
url(r'^user/(?P<user_id>[0-9]+)/pictures$', UserPicturesView.as_view(), name='user_pictures'), UserMiniView.as_view(),
url(r'^user/(?P<user_id>[0-9]+)/godfathers$', UserGodfathersView.as_view(), name='user_godfathers'), name="user_profile_mini",
url(r'^user/(?P<user_id>[0-9]+)/godfathers/tree$', UserGodfathersTreeView.as_view(), name='user_godfathers_tree'), ),
url(r'^user/(?P<user_id>[0-9]+)/godfathers/tree/pict$', UserGodfathersTreePictureView.as_view(), name='user_godfathers_tree_pict'), url(r"^user/(?P<user_id>[0-9]+)/$", UserView.as_view(), name="user_profile"),
url(r'^user/(?P<user_id>[0-9]+)/godfathers/(?P<godfather_id>[0-9]+)/(?P<is_father>(True)|(False))/delete$', DeleteUserGodfathers, name='user_godfathers_delete'), url(
url(r'^user/(?P<user_id>[0-9]+)/edit$', UserUpdateProfileView.as_view(), name='user_edit'), r"^user/(?P<user_id>[0-9]+)/pictures$",
url(r'^user/(?P<user_id>[0-9]+)/profile_upload$', UserUploadProfilePictView.as_view(), name='user_profile_upload'), UserPicturesView.as_view(),
url(r'^user/(?P<user_id>[0-9]+)/clubs$', UserClubView.as_view(), name='user_clubs'), name="user_pictures",
url(r'^user/(?P<user_id>[0-9]+)/prefs$', UserPreferencesView.as_view(), name='user_prefs'), ),
url(r'^user/(?P<user_id>[0-9]+)/groups$', UserUpdateGroupView.as_view(), name='user_groups'), url(
url(r'^user/tools/$', UserToolsView.as_view(), name='user_tools'), r"^user/(?P<user_id>[0-9]+)/godfathers$",
url(r'^user/(?P<user_id>[0-9]+)/account$', UserAccountView.as_view(), name='user_account'), UserGodfathersView.as_view(),
url(r'^user/(?P<user_id>[0-9]+)/account/(?P<year>[0-9]+)/(?P<month>[0-9]+)$', UserAccountDetailView.as_view(), name='user_account_detail'), name="user_godfathers",
url(r'^user/(?P<user_id>[0-9]+)/stats$', UserStatsView.as_view(), name='user_stats'), ),
url(r'^user/(?P<user_id>[0-9]+)/gift/create$', GiftCreateView.as_view(), name='user_gift_create'), url(
url(r'^user/(?P<user_id>[0-9]+)/gift/delete/(?P<gift_id>[0-9]+)/$', GiftDeleteView.as_view(), name='user_gift_delete'), r"^user/(?P<user_id>[0-9]+)/godfathers/tree$",
UserGodfathersTreeView.as_view(),
name="user_godfathers_tree",
),
url(
r"^user/(?P<user_id>[0-9]+)/godfathers/tree/pict$",
UserGodfathersTreePictureView.as_view(),
name="user_godfathers_tree_pict",
),
url(
r"^user/(?P<user_id>[0-9]+)/godfathers/(?P<godfather_id>[0-9]+)/(?P<is_father>(True)|(False))/delete$",
DeleteUserGodfathers,
name="user_godfathers_delete",
),
url(
r"^user/(?P<user_id>[0-9]+)/edit$",
UserUpdateProfileView.as_view(),
name="user_edit",
),
url(
r"^user/(?P<user_id>[0-9]+)/profile_upload$",
UserUploadProfilePictView.as_view(),
name="user_profile_upload",
),
url(r"^user/(?P<user_id>[0-9]+)/clubs$", UserClubView.as_view(), name="user_clubs"),
url(
r"^user/(?P<user_id>[0-9]+)/prefs$",
UserPreferencesView.as_view(),
name="user_prefs",
),
url(
r"^user/(?P<user_id>[0-9]+)/groups$",
UserUpdateGroupView.as_view(),
name="user_groups",
),
url(r"^user/tools/$", UserToolsView.as_view(), name="user_tools"),
url(
r"^user/(?P<user_id>[0-9]+)/account$",
UserAccountView.as_view(),
name="user_account",
),
url(
r"^user/(?P<user_id>[0-9]+)/account/(?P<year>[0-9]+)/(?P<month>[0-9]+)$",
UserAccountDetailView.as_view(),
name="user_account_detail",
),
url(
r"^user/(?P<user_id>[0-9]+)/stats$", UserStatsView.as_view(), name="user_stats"
),
url(
r"^user/(?P<user_id>[0-9]+)/gift/create$",
GiftCreateView.as_view(),
name="user_gift_create",
),
url(
r"^user/(?P<user_id>[0-9]+)/gift/delete/(?P<gift_id>[0-9]+)/$",
GiftDeleteView.as_view(),
name="user_gift_delete",
),
# File views # File views
# url(r'^file/add/(?P<popup>popup)?$', FileCreateView.as_view(), name='file_new'), # url(r'^file/add/(?P<popup>popup)?$', FileCreateView.as_view(), name='file_new'),
url(r'^file/(?P<popup>popup)?$', FileListView.as_view(), name='file_list'), url(r"^file/(?P<popup>popup)?$", FileListView.as_view(), name="file_list"),
url(r'^file/(?P<file_id>[0-9]+)/(?P<popup>popup)?$', FileView.as_view(), name='file_detail'), url(
url(r'^file/(?P<file_id>[0-9]+)/edit/(?P<popup>popup)?$', FileEditView.as_view(), name='file_edit'), r"^file/(?P<file_id>[0-9]+)/(?P<popup>popup)?$",
url(r'^file/(?P<file_id>[0-9]+)/prop/(?P<popup>popup)?$', FileEditPropView.as_view(), name='file_prop'), FileView.as_view(),
url(r'^file/(?P<file_id>[0-9]+)/delete/(?P<popup>popup)?$', FileDeleteView.as_view(), name='file_delete'), name="file_detail",
url(r'^file/moderation$', FileModerationView.as_view(), name='file_moderation'), ),
url(r'^file/(?P<file_id>[0-9]+)/moderate$', FileModerateView.as_view(), name='file_moderate'), url(
url(r'^file/(?P<file_id>[0-9]+)/download$', send_file, name='download'), r"^file/(?P<file_id>[0-9]+)/edit/(?P<popup>popup)?$",
FileEditView.as_view(),
name="file_edit",
),
url(
r"^file/(?P<file_id>[0-9]+)/prop/(?P<popup>popup)?$",
FileEditPropView.as_view(),
name="file_prop",
),
url(
r"^file/(?P<file_id>[0-9]+)/delete/(?P<popup>popup)?$",
FileDeleteView.as_view(),
name="file_delete",
),
url(r"^file/moderation$", FileModerationView.as_view(), name="file_moderation"),
url(
r"^file/(?P<file_id>[0-9]+)/moderate$",
FileModerateView.as_view(),
name="file_moderate",
),
url(r"^file/(?P<file_id>[0-9]+)/download$", send_file, name="download"),
# Page views # Page views
url(r'^page/$', PageListView.as_view(), name='page_list'), url(r"^page/$", PageListView.as_view(), name="page_list"),
url(r'^page/create$', PageCreateView.as_view(), name='page_new'), url(r"^page/create$", PageCreateView.as_view(), name="page_new"),
url(r'^page/(?P<page_id>[0-9]*)/delete$', PageDeleteView.as_view(), name='page_delete'), url(
url(r'^page/(?P<page_name>([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/edit$', PageEditView.as_view(), name='page_edit'), r"^page/(?P<page_id>[0-9]*)/delete$",
url(r'^page/(?P<page_name>([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/prop$', PagePropView.as_view(), name='page_prop'), PageDeleteView.as_view(),
url(r'^page/(?P<page_name>([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/hist$', PageHistView.as_view(), name='page_hist'), name="page_delete",
url(r'^page/(?P<page_name>([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/rev/(?P<rev>[0-9]+)/', PageRevView.as_view(), name='page_rev'), ),
url(r'^page/(?P<page_name>([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/$', PageView.as_view(), name='page'), url(
r"^page/(?P<page_name>([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/edit$",
PageEditView.as_view(),
name="page_edit",
),
url(
r"^page/(?P<page_name>([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/prop$",
PagePropView.as_view(),
name="page_prop",
),
url(
r"^page/(?P<page_name>([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/hist$",
PageHistView.as_view(),
name="page_hist",
),
url(
r"^page/(?P<page_name>([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/rev/(?P<rev>[0-9]+)/",
PageRevView.as_view(),
name="page_rev",
),
url(
r"^page/(?P<page_name>([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/$",
PageView.as_view(),
name="page",
),
] ]

View File

@ -30,6 +30,7 @@ from io import BytesIO
from datetime import date from datetime import date
from PIL import ExifTags from PIL import ExifTags
# from exceptions import IOError # from exceptions import IOError
import PIL import PIL
@ -71,9 +72,9 @@ def get_semester(d=date.today()):
def scale_dimension(width, height, long_edge): def scale_dimension(width, height, long_edge):
if width > height: if width > height:
ratio = long_edge * 1. / width ratio = long_edge * 1.0 / width
else: else:
ratio = long_edge * 1. / height ratio = long_edge * 1.0 / height
return int(width * ratio), int(height * ratio) return int(width * ratio), int(height * ratio)
@ -83,16 +84,28 @@ def resize_image(im, edge, format):
content = BytesIO() content = BytesIO()
im = im.resize((width, height), PIL.Image.ANTIALIAS) im = im.resize((width, height), PIL.Image.ANTIALIAS)
try: try:
im.save(fp=content, format=format.upper(), quality=90, optimize=True, progressive=True) im.save(
fp=content,
format=format.upper(),
quality=90,
optimize=True,
progressive=True,
)
except IOError: except IOError:
PIL.ImageFile.MAXBLOCK = im.size[0] * im.size[1] PIL.ImageFile.MAXBLOCK = im.size[0] * im.size[1]
im.save(fp=content, format=format.upper(), quality=90, optimize=True, progressive=True) im.save(
fp=content,
format=format.upper(),
quality=90,
optimize=True,
progressive=True,
)
return ContentFile(content.getvalue()) return ContentFile(content.getvalue())
def exif_auto_rotate(image): def exif_auto_rotate(image):
for orientation in ExifTags.TAGS.keys(): for orientation in ExifTags.TAGS.keys():
if ExifTags.TAGS[orientation] == 'Orientation': if ExifTags.TAGS[orientation] == "Orientation":
break break
exif = dict(image._getexif().items()) exif = dict(image._getexif().items())
@ -108,54 +121,66 @@ def exif_auto_rotate(image):
def doku_to_markdown(text): def doku_to_markdown(text):
"""This is a quite correct doku translator""" """This is a quite correct doku translator"""
text = re.sub(r'([^:]|^)\/\/(.*?)\/\/', r'*\2*', text) # Italic (prevents protocol:// conflict) text = re.sub(
text = re.sub(r'<del>(.*?)<\/del>', r'~~\1~~', text, flags=re.DOTALL) # Strike (may be multiline) r"([^:]|^)\/\/(.*?)\/\/", r"*\2*", text
text = re.sub(r'<sup>(.*?)<\/sup>', r'^\1^', text) # Superscript (multiline not supported, because almost never used) ) # Italic (prevents protocol:// conflict)
text = re.sub(r'<sub>(.*?)<\/sub>', r'_\1_', text) # Subscript (idem) text = re.sub(
r"<del>(.*?)<\/del>", r"~~\1~~", text, flags=re.DOTALL
) # Strike (may be multiline)
text = re.sub(
r"<sup>(.*?)<\/sup>", r"^\1^", text
) # Superscript (multiline not supported, because almost never used)
text = re.sub(r"<sub>(.*?)<\/sub>", r"_\1_", text) # Subscript (idem)
text = re.sub(r'^======(.*?)======', r'#\1', text, flags=re.MULTILINE) # Titles text = re.sub(r"^======(.*?)======", r"#\1", text, flags=re.MULTILINE) # Titles
text = re.sub(r'^=====(.*?)=====', r'##\1', text, flags=re.MULTILINE) text = re.sub(r"^=====(.*?)=====", r"##\1", text, flags=re.MULTILINE)
text = re.sub(r'^====(.*?)====', r'###\1', text, flags=re.MULTILINE) text = re.sub(r"^====(.*?)====", r"###\1", text, flags=re.MULTILINE)
text = re.sub(r'^===(.*?)===', r'####\1', text, flags=re.MULTILINE) text = re.sub(r"^===(.*?)===", r"####\1", text, flags=re.MULTILINE)
text = re.sub(r'^==(.*?)==', r'#####\1', text, flags=re.MULTILINE) text = re.sub(r"^==(.*?)==", r"#####\1", text, flags=re.MULTILINE)
text = re.sub(r'^=(.*?)=', r'######\1', text, flags=re.MULTILINE) text = re.sub(r"^=(.*?)=", r"######\1", text, flags=re.MULTILINE)
text = re.sub(r'<nowiki>', r'<nosyntax>', text) text = re.sub(r"<nowiki>", r"<nosyntax>", text)
text = re.sub(r'</nowiki>', r'</nosyntax>', text) text = re.sub(r"</nowiki>", r"</nosyntax>", text)
text = re.sub(r'<code>', r'```\n', text) text = re.sub(r"<code>", r"```\n", text)
text = re.sub(r'</code>', r'\n```', text) text = re.sub(r"</code>", r"\n```", text)
text = re.sub(r'article://', r'page://', text) text = re.sub(r"article://", r"page://", text)
text = re.sub(r'dfile://', r'file://', text) text = re.sub(r"dfile://", r"file://", text)
i = 1 i = 1
for fn in re.findall(r'\(\((.*?)\)\)', text): # Footnotes for fn in re.findall(r"\(\((.*?)\)\)", text): # Footnotes
text = re.sub(r'\(\((.*?)\)\)', r'[^%s]' % i, text, count=1) text = re.sub(r"\(\((.*?)\)\)", r"[^%s]" % i, text, count=1)
text += "\n[^%s]: %s\n" % (i, fn) text += "\n[^%s]: %s\n" % (i, fn)
i += 1 i += 1
text = re.sub(r'\\{2,}[\s]', r' \n', text) # Carriage return text = re.sub(r"\\{2,}[\s]", r" \n", text) # Carriage return
text = re.sub(r'\[\[(.*?)\|(.*?)\]\]', r'[\2](\1)', text) # Links text = re.sub(r"\[\[(.*?)\|(.*?)\]\]", r"[\2](\1)", text) # Links
text = re.sub(r'\[\[(.*?)\]\]', r'[\1](\1)', text) # Links 2 text = re.sub(r"\[\[(.*?)\]\]", r"[\1](\1)", text) # Links 2
text = re.sub(r'{{(.*?)\|(.*?)}}', r'![\2](\1 "\2")', text) # Images text = re.sub(r"{{(.*?)\|(.*?)}}", r'![\2](\1 "\2")', text) # Images
text = re.sub(r'{{(.*?)(\|(.*?))?}}', r'![\1](\1 "\1")', text) # Images 2 text = re.sub(r"{{(.*?)(\|(.*?))?}}", r'![\1](\1 "\1")', text) # Images 2
text = re.sub(r'{\[(.*?)(\|(.*?))?\]}', r'[\1](\1)', text) # Video (transform to classic links, since we can't integrate them) text = re.sub(
r"{\[(.*?)(\|(.*?))?\]}", r"[\1](\1)", text
) # Video (transform to classic links, since we can't integrate them)
text = re.sub(r'###(\d*?)###', r'[[[\1]]]', text) # Progress bar text = re.sub(r"###(\d*?)###", r"[[[\1]]]", text) # Progress bar
text = re.sub(r'(\n +[^* -][^\n]*(\n +[^* -][^\n]*)*)', r'```\1\n```', text, flags=re.DOTALL) # Block code without lists text = re.sub(
r"(\n +[^* -][^\n]*(\n +[^* -][^\n]*)*)", r"```\1\n```", text, flags=re.DOTALL
) # Block code without lists
text = re.sub(r'( +)-(.*)', r'1.\2', text) # Ordered lists text = re.sub(r"( +)-(.*)", r"1.\2", text) # Ordered lists
new_text = [] new_text = []
quote_level = 0 quote_level = 0
for line in text.splitlines(): # Tables and quotes for line in text.splitlines(): # Tables and quotes
enter = re.finditer(r'\[quote(=(.+?))?\]', line) enter = re.finditer(r"\[quote(=(.+?))?\]", line)
quit = re.finditer(r'\[/quote\]', line) quit = re.finditer(r"\[/quote\]", line)
if re.search(r'\A\s*\^(([^\^]*?)\^)*', line): # Table part if re.search(r"\A\s*\^(([^\^]*?)\^)*", line): # Table part
line = line.replace('^', '|') line = line.replace("^", "|")
new_text.append("> " * quote_level + line) new_text.append("> " * quote_level + line)
new_text.append("> " * quote_level + "|---|") # Don't keep the text alignement in tables it's really too complex for what it's worth new_text.append(
"> " * quote_level + "|---|"
) # Don't keep the text alignement in tables it's really too complex for what it's worth
elif enter or quit: # Quote part elif enter or quit: # Quote part
for quote in enter: # Enter quotes (support multiple at a time) for quote in enter: # Enter quotes (support multiple at a time)
quote_level += 1 quote_level += 1
@ -163,16 +188,20 @@ def doku_to_markdown(text):
new_text.append("> " * quote_level + "##### " + quote.group(2)) new_text.append("> " * quote_level + "##### " + quote.group(2))
except: except:
new_text.append("> " * quote_level) new_text.append("> " * quote_level)
line = line.replace(quote.group(0), '') line = line.replace(quote.group(0), "")
final_quote_level = quote_level # Store quote_level to use at the end, since it will be modified during quit iteration final_quote_level = (
quote_level
) # Store quote_level to use at the end, since it will be modified during quit iteration
final_newline = False final_newline = False
for quote in quit: # Quit quotes (support multiple at a time) for quote in quit: # Quit quotes (support multiple at a time)
line = line.replace(quote.group(0), '') line = line.replace(quote.group(0), "")
quote_level -= 1 quote_level -= 1
final_newline = True final_newline = True
new_text.append("> " * final_quote_level + line) # Finally append the line new_text.append("> " * final_quote_level + line) # Finally append the line
if final_newline: if final_newline:
new_text.append("\n") # Add a new line to ensure the separation between the quote and the following text new_text.append(
"\n"
) # Add a new line to ensure the separation between the quote and the following text
else: else:
new_text.append(line) new_text.append(line)
@ -181,24 +210,28 @@ def doku_to_markdown(text):
def bbcode_to_markdown(text): def bbcode_to_markdown(text):
"""This is a very basic BBcode translator""" """This is a very basic BBcode translator"""
text = re.sub(r'\[b\](.*?)\[\/b\]', r'**\1**', text, flags=re.DOTALL) # Bold text = re.sub(r"\[b\](.*?)\[\/b\]", r"**\1**", text, flags=re.DOTALL) # Bold
text = re.sub(r'\[i\](.*?)\[\/i\]', r'*\1*', text, flags=re.DOTALL) # Italic text = re.sub(r"\[i\](.*?)\[\/i\]", r"*\1*", text, flags=re.DOTALL) # Italic
text = re.sub(r'\[u\](.*?)\[\/u\]', r'__\1__', text, flags=re.DOTALL) # Underline text = re.sub(r"\[u\](.*?)\[\/u\]", r"__\1__", text, flags=re.DOTALL) # Underline
text = re.sub(r'\[s\](.*?)\[\/s\]', r'~~\1~~', text, flags=re.DOTALL) # Strike (may be multiline) text = re.sub(
text = re.sub(r'\[strike\](.*?)\[\/strike\]', r'~~\1~~', text, flags=re.DOTALL) # Strike 2 r"\[s\](.*?)\[\/s\]", r"~~\1~~", text, flags=re.DOTALL
) # Strike (may be multiline)
text = re.sub(
r"\[strike\](.*?)\[\/strike\]", r"~~\1~~", text, flags=re.DOTALL
) # Strike 2
text = re.sub(r'article://', r'page://', text) text = re.sub(r"article://", r"page://", text)
text = re.sub(r'dfile://', r'file://', text) text = re.sub(r"dfile://", r"file://", text)
text = re.sub(r'\[url=(.*?)\](.*)\[\/url\]', r'[\2](\1)', text) # Links text = re.sub(r"\[url=(.*?)\](.*)\[\/url\]", r"[\2](\1)", text) # Links
text = re.sub(r'\[url\](.*)\[\/url\]', r'\1', text) # Links 2 text = re.sub(r"\[url\](.*)\[\/url\]", r"\1", text) # Links 2
text = re.sub(r'\[img\](.*)\[\/img\]', r'![\1](\1 "\1")', text) # Images text = re.sub(r"\[img\](.*)\[\/img\]", r'![\1](\1 "\1")', text) # Images
new_text = [] new_text = []
quote_level = 0 quote_level = 0
for line in text.splitlines(): # Tables and quotes for line in text.splitlines(): # Tables and quotes
enter = re.finditer(r'\[quote(=(.+?))?\]', line) enter = re.finditer(r"\[quote(=(.+?))?\]", line)
quit = re.finditer(r'\[/quote\]', line) quit = re.finditer(r"\[/quote\]", line)
if enter or quit: # Quote part if enter or quit: # Quote part
for quote in enter: # Enter quotes (support multiple at a time) for quote in enter: # Enter quotes (support multiple at a time)
quote_level += 1 quote_level += 1
@ -206,16 +239,20 @@ def bbcode_to_markdown(text):
new_text.append("> " * quote_level + "##### " + quote.group(2)) new_text.append("> " * quote_level + "##### " + quote.group(2))
except: except:
new_text.append("> " * quote_level) new_text.append("> " * quote_level)
line = line.replace(quote.group(0), '') line = line.replace(quote.group(0), "")
final_quote_level = quote_level # Store quote_level to use at the end, since it will be modified during quit iteration final_quote_level = (
quote_level
) # Store quote_level to use at the end, since it will be modified during quit iteration
final_newline = False final_newline = False
for quote in quit: # Quit quotes (support multiple at a time) for quote in quit: # Quit quotes (support multiple at a time)
line = line.replace(quote.group(0), '') line = line.replace(quote.group(0), "")
quote_level -= 1 quote_level -= 1
final_newline = True final_newline = True
new_text.append("> " * final_quote_level + line) # Finally append the line new_text.append("> " * final_quote_level + line) # Finally append the line
if final_newline: if final_newline:
new_text.append("\n") # Add a new line to ensure the separation between the quote and the following text new_text.append(
"\n"
) # Add a new line to ensure the separation between the quote and the following text
else: else:
new_text.append(line) new_text.append(line)

View File

@ -26,43 +26,69 @@ import types
from django.shortcuts import render from django.shortcuts import render
from django.http import HttpResponseForbidden, HttpResponseNotFound from django.http import HttpResponseForbidden, HttpResponseNotFound
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist, ImproperlyConfigured from django.core.exceptions import (
PermissionDenied,
ObjectDoesNotExist,
ImproperlyConfigured,
)
from django.views.generic.base import View from django.views.generic.base import View
from django.db.models import Count from django.db.models import Count
from core.models import Group from core.models import Group
from core.views.forms import LoginForm from core.views.forms import LoginForm
def forbidden(request): def forbidden(request):
try: try:
return HttpResponseForbidden(render(request, "core/403.jinja", context={'next': request.path, 'form': return HttpResponseForbidden(
LoginForm(), 'popup': request.resolver_match.kwargs['popup'] or ""})) render(
request,
"core/403.jinja",
context={
"next": request.path,
"form": LoginForm(),
"popup": request.resolver_match.kwargs["popup"] or "",
},
)
)
except: except:
return HttpResponseForbidden(render(request, "core/403.jinja", context={'next': request.path, 'form': LoginForm()})) return HttpResponseForbidden(
render(
request,
"core/403.jinja",
context={"next": request.path, "form": LoginForm()},
)
)
def not_found(request): def not_found(request):
return HttpResponseNotFound(render(request, "core/404.jinja")) return HttpResponseNotFound(render(request, "core/404.jinja"))
def can_edit_prop(obj, user): def can_edit_prop(obj, user):
if obj is None or user.is_owner(obj): if obj is None or user.is_owner(obj):
return True return True
return False return False
def can_edit(obj, user): def can_edit(obj, user):
if obj is None or user.can_edit(obj): if obj is None or user.can_edit(obj):
return True return True
return can_edit_prop(obj, user) return can_edit_prop(obj, user)
def can_view(obj, user): def can_view(obj, user):
if obj is None or user.can_view(obj): if obj is None or user.can_view(obj):
return True return True
return can_edit(obj, user) return can_edit(obj, user)
class CanCreateMixin(View): class CanCreateMixin(View):
""" """
This view is made to protect any child view that would create an object, and thus, that can not be protected by any This view is made to protect any child view that would create an object, and thus, that can not be protected by any
of the following mixin of the following mixin
""" """
def dispatch(self, request, *arg, **kwargs): def dispatch(self, request, *arg, **kwargs):
res = super(CanCreateMixin, self).dispatch(request, *arg, **kwargs) res = super(CanCreateMixin, self).dispatch(request, *arg, **kwargs)
if not request.user.is_authenticated(): if not request.user.is_authenticated():
@ -75,6 +101,7 @@ class CanCreateMixin(View):
return super(CanCreateMixin, self).form_valid(form) return super(CanCreateMixin, self).form_valid(form)
raise PermissionDenied raise PermissionDenied
class CanEditPropMixin(View): class CanEditPropMixin(View):
""" """
This view is made to protect any child view that would be showing some properties of an object that are restricted This view is made to protect any child view that would be showing some properties of an object that are restricted
@ -82,64 +109,78 @@ class CanEditPropMixin(View):
In other word, you can make a view with this view as parent, and it would be retricted to the users that are in the In other word, you can make a view with this view as parent, and it would be retricted to the users that are in the
object's owner_group object's owner_group
""" """
def dispatch(self, request, *arg, **kwargs): def dispatch(self, request, *arg, **kwargs):
try: try:
self.object = self.get_object() self.object = self.get_object()
if can_edit_prop(self.object, request.user): if can_edit_prop(self.object, request.user):
return super(CanEditPropMixin, self).dispatch(request, *arg, **kwargs) return super(CanEditPropMixin, self).dispatch(request, *arg, **kwargs)
return forbidden(request) return forbidden(request)
except: pass except:
pass
# If we get here, it's a ListView # If we get here, it's a ListView
l_id = [o.id for o in self.get_queryset() if can_edit_prop(o, request.user)] l_id = [o.id for o in self.get_queryset() if can_edit_prop(o, request.user)]
if not l_id and self.get_queryset().count() != 0: if not l_id and self.get_queryset().count() != 0:
raise PermissionDenied raise PermissionDenied
self._get_queryset = self.get_queryset self._get_queryset = self.get_queryset
def get_qs(self2): def get_qs(self2):
return self2._get_queryset().filter(id__in=l_id) return self2._get_queryset().filter(id__in=l_id)
self.get_queryset = types.MethodType(get_qs, self) self.get_queryset = types.MethodType(get_qs, self)
return super(CanEditPropMixin, self).dispatch(request, *arg, **kwargs) return super(CanEditPropMixin, self).dispatch(request, *arg, **kwargs)
class CanEditMixin(View): class CanEditMixin(View):
""" """
This view makes exactly the same thing as its direct parent, but checks the group on the edit_groups field of the This view makes exactly the same thing as its direct parent, but checks the group on the edit_groups field of the
object object
""" """
def dispatch(self, request, *arg, **kwargs): def dispatch(self, request, *arg, **kwargs):
try: try:
self.object = self.get_object() self.object = self.get_object()
if can_edit(self.object, request.user): if can_edit(self.object, request.user):
return super(CanEditMixin, self).dispatch(request, *arg, **kwargs) return super(CanEditMixin, self).dispatch(request, *arg, **kwargs)
return forbidden(request) return forbidden(request)
except: pass except:
pass
# If we get here, it's a ListView # If we get here, it's a ListView
l_id = [o.id for o in self.get_queryset() if can_edit(o, request.user)] l_id = [o.id for o in self.get_queryset() if can_edit(o, request.user)]
if not l_id and self.get_queryset().count() != 0: if not l_id and self.get_queryset().count() != 0:
raise PermissionDenied raise PermissionDenied
self._get_queryset = self.get_queryset self._get_queryset = self.get_queryset
def get_qs(self2): def get_qs(self2):
return self2._get_queryset().filter(id__in=l_id) return self2._get_queryset().filter(id__in=l_id)
self.get_queryset = types.MethodType(get_qs, self) self.get_queryset = types.MethodType(get_qs, self)
return super(CanEditMixin, self).dispatch(request, *arg, **kwargs) return super(CanEditMixin, self).dispatch(request, *arg, **kwargs)
class CanViewMixin(View): class CanViewMixin(View):
""" """
This view still makes exactly the same thing as its direct parent, but checks the group on the view_groups field of This view still makes exactly the same thing as its direct parent, but checks the group on the view_groups field of
the object the object
""" """
def dispatch(self, request, *arg, **kwargs): def dispatch(self, request, *arg, **kwargs):
try: try:
self.object = self.get_object() self.object = self.get_object()
if can_view(self.object, request.user): if can_view(self.object, request.user):
return super(CanViewMixin, self).dispatch(request, *arg, **kwargs) return super(CanViewMixin, self).dispatch(request, *arg, **kwargs)
return forbidden(request) return forbidden(request)
except: pass except:
pass
# If we get here, it's a ListView # If we get here, it's a ListView
l_id = [o.id for o in self.get_queryset() if can_view(o, request.user)] l_id = [o.id for o in self.get_queryset() if can_view(o, request.user)]
if not l_id and self.get_queryset().count() != 0: if not l_id and self.get_queryset().count() != 0:
raise PermissionDenied raise PermissionDenied
self._get_queryset = self.get_queryset self._get_queryset = self.get_queryset
def get_qs(self2): def get_qs(self2):
return self2._get_queryset().filter(id__in=l_id) return self2._get_queryset().filter(id__in=l_id)
self.get_queryset = types.MethodType(get_qs, self) self.get_queryset = types.MethodType(get_qs, self)
return super(CanViewMixin, self).dispatch(request, *arg, **kwargs) return super(CanViewMixin, self).dispatch(request, *arg, **kwargs)
@ -148,6 +189,7 @@ class FormerSubscriberMixin(View):
""" """
This view check if the user was at least an old subscriber This view check if the user was at least an old subscriber
""" """
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not request.user.was_subscribed: if not request.user.was_subscribed:
raise PermissionDenied raise PermissionDenied
@ -158,6 +200,7 @@ class TabedViewMixin(View):
""" """
This view provide the basic functions for displaying tabs in the template This view provide the basic functions for displaying tabs in the template
""" """
def get_tabs_title(self): def get_tabs_title(self):
try: try:
return self.tabs_title return self.tabs_title
@ -178,38 +221,42 @@ class TabedViewMixin(View):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(TabedViewMixin, self).get_context_data(**kwargs) kwargs = super(TabedViewMixin, self).get_context_data(**kwargs)
kwargs['tabs_title'] = self.get_tabs_title() kwargs["tabs_title"] = self.get_tabs_title()
kwargs['current_tab'] = self.get_current_tab() kwargs["current_tab"] = self.get_current_tab()
kwargs['list_of_tabs'] = self.get_list_of_tabs() kwargs["list_of_tabs"] = self.get_list_of_tabs()
return kwargs return kwargs
class QuickNotifMixin: class QuickNotifMixin:
quick_notif_list = [] quick_notif_list = []
def dispatch(self, request, *arg, **kwargs): def dispatch(self, request, *arg, **kwargs):
self.quick_notif_list = [] # In some cases, the class can stay instanciated, so we need to reset the list self.quick_notif_list = (
[]
) # In some cases, the class can stay instanciated, so we need to reset the list
return super(QuickNotifMixin, self).dispatch(request, *arg, **kwargs) return super(QuickNotifMixin, self).dispatch(request, *arg, **kwargs)
def get_success_url(self): def get_success_url(self):
ret = super(QuickNotifMixin, self).get_success_url() ret = super(QuickNotifMixin, self).get_success_url()
try: try:
if '?' in ret: if "?" in ret:
ret += '&' + self.quick_notif_url_arg ret += "&" + self.quick_notif_url_arg
else: else:
ret += '?' + self.quick_notif_url_arg ret += "?" + self.quick_notif_url_arg
except: pass except:
pass
return ret return ret
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
"""Add quick notifications to context""" """Add quick notifications to context"""
kwargs = super(QuickNotifMixin, self).get_context_data(**kwargs) kwargs = super(QuickNotifMixin, self).get_context_data(**kwargs)
kwargs['quick_notifs'] = [] kwargs["quick_notifs"] = []
for n in self.quick_notif_list: for n in self.quick_notif_list:
kwargs['quick_notifs'].append(settings.SITH_QUICK_NOTIF[n]) kwargs["quick_notifs"].append(settings.SITH_QUICK_NOTIF[n])
for k,v in settings.SITH_QUICK_NOTIF.items(): for k, v in settings.SITH_QUICK_NOTIF.items():
for gk in self.request.GET.keys(): for gk in self.request.GET.keys():
if k == gk: if k == gk:
kwargs['quick_notifs'].append(v) kwargs["quick_notifs"].append(v)
return kwargs return kwargs
@ -218,5 +265,3 @@ from .page import *
from .files import * from .files import *
from .site import * from .site import *
from .group import * from .group import *

View File

@ -54,55 +54,93 @@ def send_file(request, file_id, file_class=SithFile, file_attr="file"):
f = file_class.objects.filter(id=file_id).first() f = file_class.objects.filter(id=file_id).first()
if f is None or not f.file: if f is None or not f.file:
return not_found(request) return not_found(request)
if not (can_view(f, request.user) or if not (
('counter_token' in request.session.keys() and can_view(f, request.user)
request.session['counter_token'] and # check if not null for counters that have no token set or (
Counter.objects.filter(token=request.session['counter_token']).exists()) "counter_token" in request.session.keys()
): and request.session["counter_token"]
and Counter.objects.filter( # check if not null for counters that have no token set
token=request.session["counter_token"]
).exists()
)
):
raise PermissionDenied raise PermissionDenied
name = f.__getattribute__(file_attr).name name = f.__getattribute__(file_attr).name
filepath = os.path.join(settings.MEDIA_ROOT, name) filepath = os.path.join(settings.MEDIA_ROOT, name)
with open(filepath.encode('utf-8'), 'rb') as filename: with open(filepath.encode("utf-8"), "rb") as filename:
wrapper = FileWrapper(filename) wrapper = FileWrapper(filename)
response = HttpResponse(wrapper, content_type=f.mime_type) response = HttpResponse(wrapper, content_type=f.mime_type)
response['Content-Length'] = os.path.getsize(filepath.encode('utf-8')) response["Content-Length"] = os.path.getsize(filepath.encode("utf-8"))
response['Content-Disposition'] = ('inline; filename="%s"' % f.name).encode('utf-8') response["Content-Disposition"] = ('inline; filename="%s"' % f.name).encode(
"utf-8"
)
return response return response
class AddFilesForm(forms.Form): class AddFilesForm(forms.Form):
folder_name = forms.CharField(label=_("Add a new folder"), max_length=30, required=False) folder_name = forms.CharField(
file_field = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}), label=_("Files"), label=_("Add a new folder"), max_length=30, required=False
required=False) )
file_field = forms.FileField(
widget=forms.ClearableFileInput(attrs={"multiple": True}),
label=_("Files"),
required=False,
)
def process(self, parent, owner, files): def process(self, parent, owner, files):
notif = False notif = False
try: try:
if self.cleaned_data['folder_name'] != "": if self.cleaned_data["folder_name"] != "":
folder = SithFile(parent=parent, name=self.cleaned_data['folder_name'], owner=owner) folder = SithFile(
parent=parent, name=self.cleaned_data["folder_name"], owner=owner
)
folder.clean() folder.clean()
folder.save() folder.save()
notif = True notif = True
except Exception as e: except Exception as e:
self.add_error(None, _("Error creating folder %(folder_name)s: %(msg)s") % self.add_error(
{'folder_name': self.cleaned_data['folder_name'], 'msg': repr(e)}) None,
_("Error creating folder %(folder_name)s: %(msg)s")
% {"folder_name": self.cleaned_data["folder_name"], "msg": repr(e)},
)
for f in files: for f in files:
new_file = SithFile(parent=parent, name=f.name, file=f, owner=owner, is_folder=False, new_file = SithFile(
mime_type=f.content_type, size=f._size) parent=parent,
name=f.name,
file=f,
owner=owner,
is_folder=False,
mime_type=f.content_type,
size=f._size,
)
try: try:
new_file.clean() new_file.clean()
new_file.save() new_file.save()
notif = True notif = True
except Exception as e: except Exception as e:
self.add_error(None, _("Error uploading file %(file_name)s: %(msg)s") % {'file_name': f, 'msg': repr(e)}) self.add_error(
None,
_("Error uploading file %(file_name)s: %(msg)s")
% {"file_name": f, "msg": repr(e)},
)
if notif: if notif:
for u in RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID).first().users.all(): for u in (
if not u.notifications.filter(type="FILE_MODERATION", viewed=False).exists(): RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID)
Notification(user=u, url=reverse("core:file_moderation"), type="FILE_MODERATION").save() .first()
.users.all()
):
if not u.notifications.filter(
type="FILE_MODERATION", viewed=False
).exists():
Notification(
user=u,
url=reverse("core:file_moderation"),
type="FILE_MODERATION",
).save()
class FileListView(ListView): class FileListView(ListView):
template_name = 'core/file_list.jinja' template_name = "core/file_list.jinja"
context_object_name = "file_list" context_object_name = "file_list"
def get_queryset(self): def get_queryset(self):
@ -110,81 +148,94 @@ class FileListView(ListView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(FileListView, self).get_context_data(**kwargs) kwargs = super(FileListView, self).get_context_data(**kwargs)
kwargs['popup'] = "" kwargs["popup"] = ""
if self.kwargs['popup']: if self.kwargs["popup"]:
kwargs['popup'] = 'popup' kwargs["popup"] = "popup"
return kwargs return kwargs
class FileEditView(CanEditMixin, UpdateView): class FileEditView(CanEditMixin, UpdateView):
model = SithFile model = SithFile
pk_url_kwarg = "file_id" pk_url_kwarg = "file_id"
template_name = 'core/file_edit.jinja' template_name = "core/file_edit.jinja"
context_object_name = "file" context_object_name = "file"
def get_form_class(self): def get_form_class(self):
fields = ['name', 'is_moderated'] fields = ["name", "is_moderated"]
if self.object.is_file: if self.object.is_file:
fields = ['file'] + fields fields = ["file"] + fields
return modelform_factory(SithFile, fields=fields) return modelform_factory(SithFile, fields=fields)
def get_success_url(self): def get_success_url(self):
if self.kwargs['popup']: if self.kwargs["popup"]:
return reverse('core:file_detail', kwargs={'file_id': self.object.id, 'popup': "popup"}) return reverse(
return reverse('core:file_detail', kwargs={'file_id': self.object.id, 'popup': ""}) "core:file_detail", kwargs={"file_id": self.object.id, "popup": "popup"}
)
return reverse(
"core:file_detail", kwargs={"file_id": self.object.id, "popup": ""}
)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(FileEditView, self).get_context_data(**kwargs) kwargs = super(FileEditView, self).get_context_data(**kwargs)
kwargs['popup'] = "" kwargs["popup"] = ""
if self.kwargs['popup']: if self.kwargs["popup"]:
kwargs['popup'] = 'popup' kwargs["popup"] = "popup"
return kwargs return kwargs
class FileEditPropForm(forms.ModelForm): class FileEditPropForm(forms.ModelForm):
class Meta: class Meta:
model = SithFile model = SithFile
fields = ['parent', 'owner', 'edit_groups', 'view_groups'] fields = ["parent", "owner", "edit_groups", "view_groups"]
parent = make_ajax_field(SithFile, 'parent', 'files', help_text="")
edit_groups = make_ajax_field(SithFile, 'edit_groups', 'groups', help_text="", label=_("edit group")) parent = make_ajax_field(SithFile, "parent", "files", help_text="")
view_groups = make_ajax_field(SithFile, 'view_groups', 'groups', help_text="", label=_("view group")) edit_groups = make_ajax_field(
SithFile, "edit_groups", "groups", help_text="", label=_("edit group")
)
view_groups = make_ajax_field(
SithFile, "view_groups", "groups", help_text="", label=_("view group")
)
recursive = forms.BooleanField(label=_("Apply rights recursively"), required=False) recursive = forms.BooleanField(label=_("Apply rights recursively"), required=False)
class FileEditPropView(CanEditPropMixin, UpdateView): class FileEditPropView(CanEditPropMixin, UpdateView):
model = SithFile model = SithFile
pk_url_kwarg = "file_id" pk_url_kwarg = "file_id"
template_name = 'core/file_edit.jinja' template_name = "core/file_edit.jinja"
context_object_name = "file" context_object_name = "file"
form_class = FileEditPropForm form_class = FileEditPropForm
def get_form(self, form_class=None): def get_form(self, form_class=None):
form = super(FileEditPropView, self).get_form(form_class) form = super(FileEditPropView, self).get_form(form_class)
form.fields['parent'].queryset = SithFile.objects.filter(is_folder=True) form.fields["parent"].queryset = SithFile.objects.filter(is_folder=True)
return form return form
def form_valid(self, form): def form_valid(self, form):
ret = super(FileEditPropView, self).form_valid(form) ret = super(FileEditPropView, self).form_valid(form)
if form.cleaned_data['recursive']: if form.cleaned_data["recursive"]:
self.object.apply_rights_recursively() self.object.apply_rights_recursively()
return ret return ret
def get_success_url(self): def get_success_url(self):
return reverse('core:file_detail', kwargs={'file_id': self.object.id, 'popup': self.kwargs['popup'] or ""}) return reverse(
"core:file_detail",
kwargs={"file_id": self.object.id, "popup": self.kwargs["popup"] or ""},
)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(FileEditPropView, self).get_context_data(**kwargs) kwargs = super(FileEditPropView, self).get_context_data(**kwargs)
kwargs['popup'] = "" kwargs["popup"] = ""
if self.kwargs['popup']: if self.kwargs["popup"]:
kwargs['popup'] = 'popup' kwargs["popup"] = "popup"
return kwargs return kwargs
class FileView(CanViewMixin, DetailView, FormMixin): class FileView(CanViewMixin, DetailView, FormMixin):
"""This class handle the upload of new files into a folder""" """This class handle the upload of new files into a folder"""
model = SithFile model = SithFile
pk_url_kwarg = "file_id" pk_url_kwarg = "file_id"
template_name = 'core/file_detail.jinja' template_name = "core/file_detail.jinja"
context_object_name = "file" context_object_name = "file"
form_class = AddFilesForm form_class = AddFilesForm
@ -201,79 +252,99 @@ class FileView(CanViewMixin, DetailView, FormMixin):
`object` is the SithFile object you want to put in the clipboard, or `object` is the SithFile object you want to put in the clipboard, or
where you want to paste the clipboard where you want to paste the clipboard
""" """
if 'delete' in request.POST.keys(): if "delete" in request.POST.keys():
for f_id in request.POST.getlist('file_list'): for f_id in request.POST.getlist("file_list"):
sf = SithFile.objects.filter(id=f_id).first() sf = SithFile.objects.filter(id=f_id).first()
if sf: if sf:
sf.delete() sf.delete()
if 'clear' in request.POST.keys(): if "clear" in request.POST.keys():
request.session['clipboard'] = [] request.session["clipboard"] = []
if 'cut' in request.POST.keys(): if "cut" in request.POST.keys():
for f_id in request.POST.getlist('file_list'): for f_id in request.POST.getlist("file_list"):
f_id = int(f_id) f_id = int(f_id)
if f_id in [c.id for c in object.children.all()] and f_id not in request.session['clipboard']: if (
request.session['clipboard'].append(f_id) f_id in [c.id for c in object.children.all()]
if 'paste' in request.POST.keys(): and f_id not in request.session["clipboard"]
for f_id in request.session['clipboard']: ):
request.session["clipboard"].append(f_id)
if "paste" in request.POST.keys():
for f_id in request.session["clipboard"]:
sf = SithFile.objects.filter(id=f_id).first() sf = SithFile.objects.filter(id=f_id).first()
if sf: if sf:
sf.move_to(object) sf.move_to(object)
request.session['clipboard'] = [] request.session["clipboard"] = []
request.session.modified = True request.session.modified = True
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.form = self.get_form() self.form = self.get_form()
if 'clipboard' not in request.session.keys(): if "clipboard" not in request.session.keys():
request.session['clipboard'] = [] request.session["clipboard"] = []
return super(FileView, self).get(request, *args, **kwargs) return super(FileView, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
if 'clipboard' not in request.session.keys(): if "clipboard" not in request.session.keys():
request.session['clipboard'] = [] request.session["clipboard"] = []
if request.user.can_edit(self.object): if request.user.can_edit(self.object):
# XXX this call can fail! # XXX this call can fail!
FileView.handle_clipboard(request, self.object) FileView.handle_clipboard(request, self.object)
self.form = self.get_form() # The form handle only the file upload self.form = self.get_form() # The form handle only the file upload
files = request.FILES.getlist('file_field') files = request.FILES.getlist("file_field")
if request.user.is_authenticated() and request.user.can_edit(self.object) and self.form.is_valid(): if (
request.user.is_authenticated()
and request.user.can_edit(self.object)
and self.form.is_valid()
):
self.form.process(parent=self.object, owner=request.user, files=files) self.form.process(parent=self.object, owner=request.user, files=files)
if self.form.is_valid(): if self.form.is_valid():
return super(FileView, self).form_valid(self.form) return super(FileView, self).form_valid(self.form)
return self.form_invalid(self.form) return self.form_invalid(self.form)
def get_success_url(self): def get_success_url(self):
return reverse('core:file_detail', kwargs={'file_id': self.object.id, 'popup': self.kwargs['popup'] or ""}) return reverse(
"core:file_detail",
kwargs={"file_id": self.object.id, "popup": self.kwargs["popup"] or ""},
)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(FileView, self).get_context_data(**kwargs) kwargs = super(FileView, self).get_context_data(**kwargs)
kwargs['popup'] = "" kwargs["popup"] = ""
kwargs['form'] = self.form kwargs["form"] = self.form
if self.kwargs['popup']: if self.kwargs["popup"]:
kwargs['popup'] = 'popup' kwargs["popup"] = "popup"
kwargs['clipboard'] = SithFile.objects.filter(id__in=self.request.session['clipboard']) kwargs["clipboard"] = SithFile.objects.filter(
id__in=self.request.session["clipboard"]
)
return kwargs return kwargs
class FileDeleteView(CanEditPropMixin, DeleteView): class FileDeleteView(CanEditPropMixin, DeleteView):
model = SithFile model = SithFile
pk_url_kwarg = "file_id" pk_url_kwarg = "file_id"
template_name = 'core/file_delete_confirm.jinja' template_name = "core/file_delete_confirm.jinja"
context_object_name = "file" context_object_name = "file"
def get_success_url(self): def get_success_url(self):
self.object.file.delete() # Doing it here or overloading delete() is the same, so let's do it here self.object.file.delete() # Doing it here or overloading delete() is the same, so let's do it here
if 'next' in self.request.GET.keys(): if "next" in self.request.GET.keys():
return self.request.GET['next'] return self.request.GET["next"]
if self.object.parent is None: if self.object.parent is None:
return reverse('core:file_list', kwargs={'popup': self.kwargs['popup'] or ""}) return reverse(
return reverse('core:file_detail', kwargs={'file_id': self.object.parent.id, 'popup': self.kwargs['popup'] or ""}) "core:file_list", kwargs={"popup": self.kwargs["popup"] or ""}
)
return reverse(
"core:file_detail",
kwargs={
"file_id": self.object.parent.id,
"popup": self.kwargs["popup"] or "",
},
)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(FileDeleteView, self).get_context_data(**kwargs) kwargs = super(FileDeleteView, self).get_context_data(**kwargs)
kwargs['popup'] = "" kwargs["popup"] = ""
if self.kwargs['popup']: if self.kwargs["popup"]:
kwargs['popup'] = 'popup' kwargs["popup"] = "popup"
return kwargs return kwargs
@ -282,7 +353,7 @@ class FileModerationView(TemplateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(FileModerationView, self).get_context_data(**kwargs) kwargs = super(FileModerationView, self).get_context_data(**kwargs)
kwargs['files'] = SithFile.objects.filter(is_moderated=False)[:100] kwargs["files"] = SithFile.objects.filter(is_moderated=False)[:100]
return kwargs return kwargs
@ -295,6 +366,6 @@ class FileModerateView(CanEditPropMixin, SingleObjectMixin):
self.object.is_moderated = True self.object.is_moderated = True
self.object.moderator = request.user self.object.moderator = request.user
self.object.save() self.object.save()
if 'next' in self.request.GET.keys(): if "next" in self.request.GET.keys():
return redirect(self.request.GET['next']) return redirect(self.request.GET["next"])
return redirect('core:file_moderation') return redirect("core:file_moderation")

Some files were not shown because too many files have changed in this diff Show More