mirror of
https://github.com/ae-utbm/sith.git
synced 2025-07-13 05:19:26 +00:00
Compare commits
78 Commits
features/m
...
feature/im
Author | SHA1 | Date | |
---|---|---|---|
46fa14ed12 | |||
18dffb0053 | |||
6e47d1471e | |||
b5146569e1 | |||
acde993352 | |||
8852ef990e | |||
87295ad9b7 | |||
5ab5ef681c | |||
c9e70889dd | |||
b30ee0a27a | |||
ef968f3673 | |||
96dede5077 | |||
66fcb76cb5 | |||
63c8e51137 | |||
12bec5c553 | |||
08460a6964 | |||
b5a40cfda9 | |||
c78953b036 | |||
427f7ceaff | |||
1055385bcc | |||
c1022642a2 | |||
06253f029c | |||
0501e6417a | |||
a198f5252d | |||
d83842af27 | |||
f605f7dcc6 | |||
8e7c025e47 | |||
1bfe929ab3 | |||
93cc2c883e | |||
44290a20a6 | |||
1f10a284f2 | |||
28f397574f | |||
6c1fa6de0b | |||
f0a08afd31 | |||
9e0b5b0b82 | |||
25c5a3297c | |||
5ea181829e | |||
0cf203669f | |||
559bfcac60 | |||
db8a1ed0ab | |||
16150905a0 | |||
9a376887ac | |||
773808fa59 | |||
c1e59a0676 | |||
05febc60bd | |||
a73fe598ef | |||
585923c827 | |||
394e17d599 | |||
59136850b8 | |||
d726f4b1e8 | |||
705b9b1e6a | |||
31e8ad8a3e | |||
99827e005b | |||
751c8a8bc6 | |||
37216cd16b | |||
dae68638cf | |||
7cadc0bc28 | |||
cce686f3a8 | |||
4fe46fbcef | |||
fe8b8f46aa | |||
7079761ffe | |||
f681c981c6 | |||
5d97146d14 | |||
7b56bd697d | |||
14cd268d69 | |||
754be1c9c9 | |||
da2c155254 | |||
ceb2888f82 | |||
26c94c9ec6 | |||
639197f4c8 | |||
faccc1367f | |||
22b83b0814 | |||
1d82e2a7d9 | |||
b8a72c57e1 | |||
9188565a86 | |||
4d7d22c337 | |||
b58116b023 | |||
fe9e5ce861 |
8
.github/actions/compile_messages/action.yml
vendored
Normal file
8
.github/actions/compile_messages/action.yml
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
name: "Compile messages"
|
||||||
|
description: "Compile the gettext translation messages"
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: Setup project
|
||||||
|
run: poetry run ./manage.py compilemessages
|
||||||
|
shell: bash
|
53
.github/actions/setup_project/action.yml
vendored
Normal file
53
.github/actions/setup_project/action.yml
vendored
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
name: "Setup project"
|
||||||
|
description: "Setup Python and Poetry"
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: Install apt packages
|
||||||
|
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||||
|
with:
|
||||||
|
packages: gettext libxapian-dev libgraphviz-dev
|
||||||
|
version: 1.0 # increment to reset cache
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install gettext libxapian-dev libgraphviz-dev
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Set up python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: "3.10"
|
||||||
|
|
||||||
|
- name: Load cached Poetry installation
|
||||||
|
id: cached-poetry
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ~/.local
|
||||||
|
key: poetry-0 # increment to reset cache
|
||||||
|
|
||||||
|
- name: Install Poetry
|
||||||
|
if: steps.cached-poetry.outputs.cache-hit != 'true'
|
||||||
|
shell: bash
|
||||||
|
run: curl -sSL https://install.python-poetry.org | python3 -
|
||||||
|
|
||||||
|
- name: Check pyproject.toml syntax
|
||||||
|
shell: bash
|
||||||
|
run: poetry check
|
||||||
|
|
||||||
|
- name: Load cached dependencies
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ~/.cache/pypoetry
|
||||||
|
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-poetry-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: poetry install -E testing -E docs
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Compile gettext messages
|
||||||
|
run: poetry run ./manage.py compilemessages
|
||||||
|
shell: bash
|
10
.github/actions/setup_xapian/action.yml
vendored
Normal file
10
.github/actions/setup_xapian/action.yml
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
name: "Setup xapian"
|
||||||
|
description: "Setup the xapian indexes"
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: Setup xapian index
|
||||||
|
run: |
|
||||||
|
mkdir -p /dev/shm/search_indexes
|
||||||
|
ln -s /dev/shm/search_indexes sith/search_indexes
|
||||||
|
shell: bash
|
43
.github/workflows/ci.yml
vendored
Normal file
43
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
name: Sith 3 CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- taiste
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- taiste
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
black:
|
||||||
|
name: Black format
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Setup Project
|
||||||
|
uses: ./.github/actions/setup_project
|
||||||
|
- run: poetry run black --check .
|
||||||
|
|
||||||
|
tests:
|
||||||
|
name: Run tests and generate coverage report
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- uses: ./.github/actions/setup_project
|
||||||
|
- uses: ./.github/actions/setup_xapian
|
||||||
|
- uses: ./.github/actions/compile_messages
|
||||||
|
- name: Run tests
|
||||||
|
run: poetry run coverage run ./manage.py test
|
||||||
|
- name: Generate coverage report
|
||||||
|
run: |
|
||||||
|
poetry run coverage report
|
||||||
|
poetry run coverage html
|
||||||
|
- name: Archive code coverage results
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: coverage-report
|
||||||
|
path: coverage_report
|
83
.github/workflows/unittests.yml
vendored
83
.github/workflows/unittests.yml
vendored
@ -1,83 +0,0 @@
|
|||||||
name: Sith3 CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches: [ master, taiste ]
|
|
||||||
push:
|
|
||||||
branches: [ master, taiste ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
unittests:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 30
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
# Skip unit testing if no diff on .py files
|
|
||||||
- name: Check file diff
|
|
||||||
uses: technote-space/get-diff-action@v6
|
|
||||||
id: git-diff
|
|
||||||
with:
|
|
||||||
PATTERNS: |
|
|
||||||
**/*.py
|
|
||||||
|
|
||||||
- name: Set up python
|
|
||||||
if: steps.git-diff.outputs.diff
|
|
||||||
uses: actions/setup-python@v4
|
|
||||||
with:
|
|
||||||
python-version: '3.8'
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
if: steps.git-diff.outputs.diff
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install gettext libxapian-dev libgraphviz-dev
|
|
||||||
|
|
||||||
- name: Install poetry
|
|
||||||
if: steps.git-diff.outputs.diff
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
python -m pip install poetry
|
|
||||||
|
|
||||||
- name: Checking pyproject.toml syntax
|
|
||||||
if: steps.git-diff.outputs.diff
|
|
||||||
run: poetry check
|
|
||||||
|
|
||||||
- name: Install project
|
|
||||||
if: steps.git-diff.outputs.diff
|
|
||||||
run: poetry install -E testing
|
|
||||||
|
|
||||||
- name: Setup xapian index
|
|
||||||
if: steps.git-diff.outputs.diff
|
|
||||||
run: |
|
|
||||||
mkdir -p /dev/shm/search_indexes
|
|
||||||
ln -s /dev/shm/search_indexes sith/search_indexes
|
|
||||||
|
|
||||||
- name: Setup project
|
|
||||||
if: steps.git-diff.outputs.diff
|
|
||||||
run: poetry run ./manage.py compilemessages
|
|
||||||
|
|
||||||
- name: Launch tests and generate coverage report
|
|
||||||
if: steps.git-diff.outputs.diff
|
|
||||||
run: |
|
|
||||||
poetry run coverage run ./manage.py test
|
|
||||||
poetry run coverage report
|
|
||||||
|
|
||||||
lint:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Set up python
|
|
||||||
uses: actions/setup-python@v4
|
|
||||||
with:
|
|
||||||
python-version: '3.8'
|
|
||||||
|
|
||||||
- name: Install black
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
python -m pip install black==22.6.0
|
|
||||||
|
|
||||||
- name: Check linting
|
|
||||||
run: black --check .
|
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,6 +11,7 @@ dist/
|
|||||||
env/
|
env/
|
||||||
doc/html
|
doc/html
|
||||||
data/
|
data/
|
||||||
|
galaxy/test_galaxy_state.json
|
||||||
/static/
|
/static/
|
||||||
sith/settings_custom.py
|
sith/settings_custom.py
|
||||||
sith/search_indexes/
|
sith/search_indexes/
|
||||||
|
@ -8,7 +8,6 @@ import django.db.models.deletion
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = []
|
dependencies = []
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -6,7 +6,6 @@ import django.db.models.deletion
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("club", "0001_initial"),
|
("club", "0001_initial"),
|
||||||
("accounting", "0001_initial"),
|
("accounting", "0001_initial"),
|
||||||
|
@ -6,7 +6,6 @@ import phonenumber_field.modelfields
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("accounting", "0002_auto_20160824_2152")]
|
dependencies = [("accounting", "0002_auto_20160824_2152")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -6,7 +6,6 @@ import django.db.models.deletion
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("accounting", "0003_auto_20160824_2203")]
|
dependencies = [("accounting", "0003_auto_20160824_2203")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("accounting", "0004_auto_20161005_1505")]
|
dependencies = [("accounting", "0004_auto_20161005_1505")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -66,7 +66,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
|
||||||
"""
|
"""
|
||||||
if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -117,7 +117,9 @@ class BankAccount(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
|
||||||
"""
|
"""
|
||||||
if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
if user.is_anonymous:
|
||||||
|
return False
|
||||||
|
if user.is_in_group(pk=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"]:
|
||||||
@ -154,7 +156,9 @@ 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
|
||||||
"""
|
"""
|
||||||
if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
if user.is_anonymous:
|
||||||
|
return False
|
||||||
|
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -225,7 +229,9 @@ class GeneralJournal(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
|
||||||
"""
|
"""
|
||||||
if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
if user.is_anonymous:
|
||||||
|
return False
|
||||||
|
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
||||||
return True
|
return True
|
||||||
if self.club_account.can_be_edited_by(user):
|
if self.club_account.can_be_edited_by(user):
|
||||||
return True
|
return True
|
||||||
@ -235,7 +241,7 @@ class GeneralJournal(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
|
||||||
"""
|
"""
|
||||||
if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
||||||
return True
|
return True
|
||||||
if self.club_account.can_be_edited_by(user):
|
if self.club_account.can_be_edited_by(user):
|
||||||
return True
|
return True
|
||||||
@ -414,7 +420,9 @@ class Operation(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
|
||||||
"""
|
"""
|
||||||
if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
if user.is_anonymous:
|
||||||
|
return False
|
||||||
|
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
||||||
return True
|
return True
|
||||||
if self.journal.closed:
|
if self.journal.closed:
|
||||||
return False
|
return False
|
||||||
@ -427,7 +435,7 @@ class Operation(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
|
||||||
"""
|
"""
|
||||||
if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
||||||
return True
|
return True
|
||||||
if self.journal.closed:
|
if self.journal.closed:
|
||||||
return False
|
return False
|
||||||
@ -483,7 +491,9 @@ class AccountingType(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
|
||||||
"""
|
"""
|
||||||
if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
if user.is_anonymous:
|
||||||
|
return False
|
||||||
|
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -554,6 +564,8 @@ class Label(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def is_owned_by(self, user):
|
def is_owned_by(self, user):
|
||||||
|
if user.is_anonymous:
|
||||||
|
return False
|
||||||
return self.club_account.is_owned_by(user)
|
return self.club_account.is_owned_by(user)
|
||||||
|
|
||||||
def can_be_edited_by(self, user):
|
def can_be_edited_by(self, user):
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<hr>
|
<hr>
|
||||||
<h2>{% trans %}Bank account: {% endtrans %}{{ object.name }}</h2>
|
<h2>{% trans %}Bank account: {% endtrans %}{{ object.name }}</h2>
|
||||||
{% if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) and not object.club_accounts.exists() %}
|
{% if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) and not object.club_accounts.exists() %}
|
||||||
<a href="{{ url('accounting:bank_delete', b_account_id=object.id) }}">{% trans %}Delete{% endtrans %}</a>
|
<a href="{{ url('accounting:bank_delete', b_account_id=object.id) }}">{% trans %}Delete{% endtrans %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<h4>{% trans %}Infos{% endtrans %}</h4>
|
<h4>{% trans %}Infos{% endtrans %}</h4>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<h4>
|
<h4>
|
||||||
{% trans %}Accounting{% endtrans %}
|
{% trans %}Accounting{% endtrans %}
|
||||||
</h4>
|
</h4>
|
||||||
{% if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %}
|
{% if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %}
|
||||||
<p><a href="{{ url('accounting:simple_type_list') }}">{% trans %}Manage simplified types{% endtrans %}</a></p>
|
<p><a href="{{ url('accounting:simple_type_list') }}">{% trans %}Manage simplified types{% endtrans %}</a></p>
|
||||||
<p><a href="{{ url('accounting:type_list') }}">{% trans %}Manage accounting types{% endtrans %}</a></p>
|
<p><a href="{{ url('accounting:type_list') }}">{% trans %}Manage accounting types{% endtrans %}</a></p>
|
||||||
<p><a href="{{ url('accounting:bank_new') }}">{% trans %}New bank account{% endtrans %}</a></p>
|
<p><a href="{{ url('accounting:bank_new') }}">{% trans %}New bank account{% endtrans %}</a></p>
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
{% if user.is_root and not object.journals.exists() %}
|
{% if user.is_root and not object.journals.exists() %}
|
||||||
<a href="{{ url('accounting:club_delete', c_account_id=object.id) }}">{% trans %}Delete{% endtrans %}</a>
|
<a href="{{ url('accounting:club_delete', c_account_id=object.id) }}">{% trans %}Delete{% endtrans %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %}
|
{% if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %}
|
||||||
<p><a href="{{ url('accounting:label_new') }}?parent={{ object.id }}">{% trans %}New label{% endtrans %}</a></p>
|
<p><a href="{{ url('accounting:label_new') }}?parent={{ object.id }}">{% trans %}New label{% endtrans %}</a></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p><a href="{{ url('accounting:label_list', clubaccount_id=object.id) }}">{% trans %}Label list{% endtrans %}</a></p>
|
<p><a href="{{ url('accounting:label_list', clubaccount_id=object.id) }}">{% trans %}Label list{% endtrans %}</a></p>
|
||||||
@ -56,7 +56,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<td> <a href="{{ url('accounting:journal_details', j_id=j.id) }}">{% trans %}View{% endtrans %}</a>
|
<td> <a href="{{ url('accounting:journal_details', j_id=j.id) }}">{% trans %}View{% endtrans %}</a>
|
||||||
<a href="{{ url('accounting:journal_edit', j_id=j.id) }}">{% trans %}Edit{% endtrans %}</a>
|
<a href="{{ url('accounting:journal_edit', j_id=j.id) }}">{% trans %}Edit{% endtrans %}</a>
|
||||||
{% if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) and j.operations.count() == 0 %}
|
{% if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) and j.operations.count() == 0 %}
|
||||||
<a href="{{ url('accounting:journal_delete', j_id=j.id) }}">{% trans %}Delete{% endtrans %}</a>
|
<a href="{{ url('accounting:journal_delete', j_id=j.id) }}">{% trans %}Delete{% endtrans %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
@ -6,11 +6,12 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="accounting">
|
<div id="accounting">
|
||||||
{% if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) or user.is_root %}
|
{% if user.is_root
|
||||||
|
or user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
|
||||||
|
%}
|
||||||
<p><a href="{{ url('accounting:co_new') }}">{% trans %}Create new company{% endtrans %}</a></p>
|
<p><a href="{{ url('accounting:co_new') }}">{% trans %}Create new company{% endtrans %}</a></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<br/>
|
||||||
</br>
|
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -84,10 +84,13 @@
|
|||||||
<td>-</td>
|
<td>-</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td>
|
<td>
|
||||||
{% if o.journal.club_account.bank_account.name != "AE TI" and o.journal.club_account.bank_account.name != "TI" or user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %}
|
{%
|
||||||
{% if not o.journal.closed %}
|
if o.journal.club_account.bank_account.name not in ["AE TI", "TI"]
|
||||||
<a href="{{ url('accounting:op_edit', op_id=o.id) }}">{% trans %}Edit{% endtrans %}</a>
|
or user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
|
||||||
{% endif %}
|
%}
|
||||||
|
{% if not o.journal.closed %}
|
||||||
|
<a href="{{ url('accounting:op_edit', op_id=o.id) }}">{% trans %}Edit{% endtrans %}</a>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td><a href="{{ url('accounting:op_pdf', op_id=o.id) }}">{% trans %}Generate{% endtrans %}</a></td>
|
<td><a href="{{ url('accounting:op_pdf', op_id=o.id) }}">{% trans %}Generate{% endtrans %}</a></td>
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<hr>
|
<hr>
|
||||||
<p><a href="{{ url('accounting:club_details', c_account_id=object.id) }}">{% trans %}Back to club account{% endtrans %}</a></p>
|
<p><a href="{{ url('accounting:club_details', c_account_id=object.id) }}">{% trans %}Back to club account{% endtrans %}</a></p>
|
||||||
{% if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %}
|
{% if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %}
|
||||||
<p><a href="{{ url('accounting:label_new') }}?parent={{ object.id }}">{% trans %}New label{% endtrans %}</a></p>
|
<p><a href="{{ url('accounting:label_new') }}?parent={{ object.id }}">{% trans %}New label{% endtrans %}</a></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if object.labels.all() %}
|
{% if object.labels.all() %}
|
||||||
@ -21,7 +21,7 @@
|
|||||||
<ul>
|
<ul>
|
||||||
{% for l in object.labels.all() %}
|
{% for l in object.labels.all() %}
|
||||||
<li><a href="{{ url('accounting:label_edit', label_id=l.id) }}">{{ l }}</a>
|
<li><a href="{{ url('accounting:label_edit', label_id=l.id) }}">{{ l }}</a>
|
||||||
{% if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %}
|
{% if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %}
|
||||||
-
|
-
|
||||||
<a href="{{ url('accounting:label_delete', label_id=l.id) }}">{% trans %}Delete{% endtrans %}</a>
|
<a href="{{ url('accounting:label_delete', label_id=l.id) }}">{% trans %}Delete{% endtrans %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -31,7 +31,6 @@ from accounting.models import (
|
|||||||
|
|
||||||
class RefoundAccountTest(TestCase):
|
class RefoundAccountTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
call_command("populate")
|
|
||||||
self.skia = User.objects.filter(username="skia").first()
|
self.skia = User.objects.filter(username="skia").first()
|
||||||
# reffil skia's account
|
# reffil skia's account
|
||||||
self.skia.customer.amount = 800
|
self.skia.customer.amount = 800
|
||||||
@ -73,7 +72,6 @@ class RefoundAccountTest(TestCase):
|
|||||||
|
|
||||||
class JournalTest(TestCase):
|
class JournalTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
call_command("populate")
|
|
||||||
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):
|
||||||
@ -101,7 +99,6 @@ class JournalTest(TestCase):
|
|||||||
|
|
||||||
class OperationTest(TestCase):
|
class OperationTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
call_command("populate")
|
|
||||||
self.tomorrow_formatted = (date.today() + timedelta(days=1)).strftime(
|
self.tomorrow_formatted = (date.today() + timedelta(days=1)).strftime(
|
||||||
"%d/%m/%Y"
|
"%d/%m/%Y"
|
||||||
)
|
)
|
||||||
|
@ -891,7 +891,7 @@ class RefoundAccountView(FormView):
|
|||||||
form_class = CloseCustomerAccountForm
|
form_class = CloseCustomerAccountForm
|
||||||
|
|
||||||
def permission(self, user):
|
def permission(self, user):
|
||||||
if user.is_root or user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
if user.is_root or user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
@ -24,7 +24,6 @@ from api.views import RightModelViewSet
|
|||||||
|
|
||||||
|
|
||||||
class CounterSerializer(serializers.ModelSerializer):
|
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(), read_only=True
|
child=serializers.IntegerField(), read_only=True
|
||||||
|
@ -24,7 +24,6 @@ from api.views import RightModelViewSet
|
|||||||
|
|
||||||
|
|
||||||
class LaunderettePlaceSerializer(serializers.ModelSerializer):
|
class LaunderettePlaceSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
machine_list = serializers.ListField(
|
machine_list = serializers.ListField(
|
||||||
child=serializers.IntegerField(), read_only=True
|
child=serializers.IntegerField(), read_only=True
|
||||||
)
|
)
|
||||||
|
@ -167,7 +167,6 @@ class SellingsForm(forms.Form):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, club, *args, **kwargs):
|
def __init__(self, club, *args, **kwargs):
|
||||||
|
|
||||||
super(SellingsForm, self).__init__(*args, **kwargs)
|
super(SellingsForm, self).__init__(*args, **kwargs)
|
||||||
self.fields["products"] = forms.ModelMultipleChoiceField(
|
self.fields["products"] = forms.ModelMultipleChoiceField(
|
||||||
club.products.order_by("name").filter(archived=False).all(),
|
club.products.order_by("name").filter(archived=False).all(),
|
||||||
@ -230,9 +229,7 @@ class ClubMemberForm(forms.Form):
|
|||||||
id__in=[
|
id__in=[
|
||||||
ms.user.id
|
ms.user.id
|
||||||
for ms in self.club_members
|
for ms in self.club_members
|
||||||
if ms.can_be_edited_by(
|
if ms.can_be_edited_by(self.request_user)
|
||||||
self.request_user, self.request_user_membership
|
|
||||||
)
|
|
||||||
]
|
]
|
||||||
).all(),
|
).all(),
|
||||||
label=_("Mark as old"),
|
label=_("Mark as old"),
|
||||||
|
@ -7,7 +7,6 @@ import django.db.models.deletion
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = []
|
dependencies = []
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -7,7 +7,6 @@ import django.db.models.deletion
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
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"),
|
||||||
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("club", "0002_auto_20160824_2152")]
|
dependencies = [("club", "0002_auto_20160824_2152")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -7,7 +7,6 @@ import django.db.models.deletion
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("club", "0003_auto_20160902_2042")]
|
dependencies = [("club", "0003_auto_20160902_2042")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -6,7 +6,6 @@ import django.db.models.deletion
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("club", "0004_auto_20160915_1057")]
|
dependencies = [("club", "0004_auto_20160915_1057")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -6,7 +6,6 @@ import django.utils.timezone
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("club", "0005_auto_20161120_1149")]
|
dependencies = [("club", "0005_auto_20161120_1149")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("club", "0006_auto_20161229_0040")]
|
dependencies = [("club", "0006_auto_20161229_0040")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("club", "0007_auto_20170324_0917")]
|
dependencies = [("club", "0007_auto_20170324_0917")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -9,7 +9,6 @@ import django.db.models.deletion
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
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"),
|
||||||
|
@ -19,7 +19,6 @@ def generate_club_pages(apps, schema_editor):
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0024_auto_20170906_1317"), ("club", "0010_club_logo")]
|
dependencies = [("core", "0024_auto_20170906_1317"), ("club", "0010_club_logo")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("club", "0009_auto_20170822_2232")]
|
dependencies = [("club", "0009_auto_20170822_2232")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -7,7 +7,6 @@ import django.db.models.deletion
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("club", "0010_auto_20170912_2028")]
|
dependencies = [("club", "0010_auto_20170912_2028")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
216
club/models.py
216
club/models.py
@ -22,10 +22,14 @@
|
|||||||
# Place - Suite 330, Boston, MA 02111-1307, USA.
|
# Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from django.core.cache import cache
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.core.exceptions import ValidationError, ObjectDoesNotExist
|
from django.core.exceptions import ValidationError, ObjectDoesNotExist
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
@ -72,6 +76,7 @@ class Club(models.Model):
|
|||||||
_("short description"), max_length=1000, default="", blank=True, null=True
|
_("short description"), max_length=1000, default="", blank=True, null=True
|
||||||
)
|
)
|
||||||
address = models.CharField(_("address"), max_length=254)
|
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():
|
def get_default_owner_group():
|
||||||
return settings.SITH_GROUP_ROOT_ID
|
return settings.SITH_GROUP_ROOT_ID
|
||||||
@ -122,12 +127,22 @@ class Club(models.Model):
|
|||||||
def clean(self):
|
def clean(self):
|
||||||
self.check_loop()
|
self.check_loop()
|
||||||
|
|
||||||
def _change_unixname(self, new_name):
|
def _change_unixname(self, old_name, new_name):
|
||||||
c = Club.objects.filter(unix_name=new_name).first()
|
c = Club.objects.filter(unix_name=new_name).first()
|
||||||
if c is None:
|
if c is None:
|
||||||
|
# Update all the groups names
|
||||||
|
Group.objects.filter(name=old_name).update(name=new_name)
|
||||||
|
Group.objects.filter(name=old_name + settings.SITH_BOARD_SUFFIX).update(
|
||||||
|
name=new_name + settings.SITH_BOARD_SUFFIX
|
||||||
|
)
|
||||||
|
Group.objects.filter(name=old_name + settings.SITH_MEMBER_SUFFIX).update(
|
||||||
|
name=new_name + settings.SITH_MEMBER_SUFFIX
|
||||||
|
)
|
||||||
|
|
||||||
if self.home:
|
if self.home:
|
||||||
self.home.name = new_name
|
self.home.name = new_name
|
||||||
self.home.save()
|
self.home.save()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise ValidationError(_("A club with that unix_name already exists"))
|
raise ValidationError(_("A club with that unix_name already exists"))
|
||||||
|
|
||||||
@ -171,29 +186,34 @@ class Club(models.Model):
|
|||||||
self.page.parent = self.parent.page
|
self.page.parent = self.parent.page
|
||||||
self.page.save(force_lock=True)
|
self.page.save(force_lock=True)
|
||||||
|
|
||||||
|
@transaction.atomic()
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
with transaction.atomic():
|
old = Club.objects.filter(id=self.id).first()
|
||||||
creation = False
|
creation = old is None
|
||||||
old = Club.objects.filter(id=self.id).first()
|
if not creation and old.unix_name != self.unix_name:
|
||||||
if not old:
|
self._change_unixname(self.unix_name)
|
||||||
creation = True
|
super(Club, self).save(*args, **kwargs)
|
||||||
else:
|
if creation:
|
||||||
if old.unix_name != self.unix_name:
|
board = MetaGroup(name=self.unix_name + settings.SITH_BOARD_SUFFIX)
|
||||||
self._change_unixname(self.unix_name)
|
board.save()
|
||||||
super(Club, self).save(*args, **kwargs)
|
member = MetaGroup(name=self.unix_name + settings.SITH_MEMBER_SUFFIX)
|
||||||
if creation:
|
member.save()
|
||||||
board = MetaGroup(name=self.unix_name + settings.SITH_BOARD_SUFFIX)
|
subscribers = Group.objects.filter(
|
||||||
board.save()
|
name=settings.SITH_MAIN_MEMBERS_GROUP
|
||||||
member = MetaGroup(name=self.unix_name + settings.SITH_MEMBER_SUFFIX)
|
).first()
|
||||||
member.save()
|
self.make_home()
|
||||||
subscribers = Group.objects.filter(
|
self.home.edit_groups.set([board])
|
||||||
name=settings.SITH_MAIN_MEMBERS_GROUP
|
self.home.view_groups.set([member, subscribers])
|
||||||
).first()
|
self.home.save()
|
||||||
self.make_home()
|
self.make_page()
|
||||||
self.home.edit_groups.set([board])
|
cache.set(f"sith_club_{self.unix_name}", self)
|
||||||
self.home.view_groups.set([member, subscribers])
|
|
||||||
self.home.save()
|
def delete(self, *args, **kwargs):
|
||||||
self.make_page()
|
super().delete(*args, **kwargs)
|
||||||
|
# Invalidate the cache of this club and of its memberships
|
||||||
|
for membership in self.members.ongoing().select_related("user"):
|
||||||
|
cache.delete(f"membership_{self.id}_{membership.user.id}")
|
||||||
|
cache.delete(f"sith_club_{self.unix_name}")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
@ -208,7 +228,9 @@ class Club(models.Model):
|
|||||||
"""
|
"""
|
||||||
Method to see if that object can be super edited by the given user
|
Method to see if that object can be super edited by the given user
|
||||||
"""
|
"""
|
||||||
return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP)
|
if user.is_anonymous:
|
||||||
|
return False
|
||||||
|
return user.is_board_member
|
||||||
|
|
||||||
def get_full_logo_url(self):
|
def get_full_logo_url(self):
|
||||||
return "https://%s%s" % (settings.SITH_URL, self.logo.url)
|
return "https://%s%s" % (settings.SITH_URL, self.logo.url)
|
||||||
@ -228,28 +250,89 @@ class Club(models.Model):
|
|||||||
return False
|
return False
|
||||||
return sub.was_subscribed
|
return sub.was_subscribed
|
||||||
|
|
||||||
_memberships = {}
|
def get_membership_for(self, user: User) -> Optional["Membership"]:
|
||||||
|
|
||||||
def get_membership_for(self, user):
|
|
||||||
"""
|
"""
|
||||||
Returns the current membership the given user
|
Return the current membership the given user.
|
||||||
|
The result is cached.
|
||||||
"""
|
"""
|
||||||
try:
|
if user.is_anonymous:
|
||||||
return Club._memberships[self.id][user.id]
|
return None
|
||||||
except:
|
membership = cache.get(f"membership_{self.id}_{user.id}")
|
||||||
m = self.members.filter(user=user.id).filter(end_date=None).first()
|
if membership == "not_member":
|
||||||
try:
|
return None
|
||||||
Club._memberships[self.id][user.id] = m
|
if membership is None:
|
||||||
except:
|
membership = self.members.filter(user=user, end_date=None).first()
|
||||||
Club._memberships[self.id] = {}
|
if membership is None:
|
||||||
Club._memberships[self.id][user.id] = m
|
cache.set(f"membership_{self.id}_{user.id}", "not_member")
|
||||||
return m
|
else:
|
||||||
|
cache.set(f"membership_{self.id}_{user.id}", membership)
|
||||||
|
return membership
|
||||||
|
|
||||||
def has_rights_in_club(self, user):
|
def has_rights_in_club(self, user):
|
||||||
m = self.get_membership_for(user)
|
m = self.get_membership_for(user)
|
||||||
return m is not None and m.role > settings.SITH_MAXIMUM_FREE_ROLE
|
return m is not None and m.role > settings.SITH_MAXIMUM_FREE_ROLE
|
||||||
|
|
||||||
|
|
||||||
|
class MembershipQuerySet(models.QuerySet):
|
||||||
|
def ongoing(self) -> "MembershipQuerySet":
|
||||||
|
"""
|
||||||
|
Filter all memberships which are not finished yet
|
||||||
|
"""
|
||||||
|
# noinspection PyTypeChecker
|
||||||
|
return self.filter(Q(end_date=None) | Q(end_date__gte=timezone.now()))
|
||||||
|
|
||||||
|
def board(self) -> "MembershipQuerySet":
|
||||||
|
"""
|
||||||
|
Filter all memberships where the user is/was in the board.
|
||||||
|
|
||||||
|
Be aware that users who were in the board in the past
|
||||||
|
are included, even if there are no more members.
|
||||||
|
|
||||||
|
If you want to get the users who are currently in the board,
|
||||||
|
mind combining this with the :meth:`ongoing` queryset method
|
||||||
|
"""
|
||||||
|
# noinspection PyTypeChecker
|
||||||
|
return self.filter(role__gt=settings.SITH_MAXIMUM_FREE_ROLE)
|
||||||
|
|
||||||
|
def update(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Work just like the default Django's update() method,
|
||||||
|
but add a cache refresh for the elements of the queryset.
|
||||||
|
|
||||||
|
Be aware that this adds a db query to retrieve the updated objects
|
||||||
|
"""
|
||||||
|
nb_rows = super().update(**kwargs)
|
||||||
|
if nb_rows > 0:
|
||||||
|
# if at least a row was affected, refresh the cache
|
||||||
|
for membership in self.all():
|
||||||
|
if membership.end_date is not None:
|
||||||
|
cache.set(
|
||||||
|
f"membership_{membership.club_id}_{membership.user_id}",
|
||||||
|
"not_member",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
cache.set(
|
||||||
|
f"membership_{membership.club_id}_{membership.user_id}",
|
||||||
|
membership,
|
||||||
|
)
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
"""
|
||||||
|
Work just like the default Django's delete() method,
|
||||||
|
but add a cache invalidation for the elements of the queryset
|
||||||
|
before the deletion.
|
||||||
|
|
||||||
|
Be aware that this adds a db query to retrieve the deleted element.
|
||||||
|
As this first query take place before the deletion operation,
|
||||||
|
it will be performed even if the deletion fails.
|
||||||
|
"""
|
||||||
|
ids = list(self.values_list("club_id", "user_id"))
|
||||||
|
nb_rows, _ = super().delete()
|
||||||
|
if nb_rows > 0:
|
||||||
|
for club_id, user_id in ids:
|
||||||
|
cache.set(f"membership_{club_id}_{user_id}", "not_member")
|
||||||
|
|
||||||
|
|
||||||
class Membership(models.Model):
|
class Membership(models.Model):
|
||||||
"""
|
"""
|
||||||
The Membership class makes the connection between User and Clubs
|
The Membership class makes the connection between User and Clubs
|
||||||
@ -289,6 +372,8 @@ class Membership(models.Model):
|
|||||||
_("description"), max_length=128, null=False, blank=True
|
_("description"), max_length=128, null=False, blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
objects = MembershipQuerySet.as_manager()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return (
|
return (
|
||||||
self.club.name
|
self.club.name
|
||||||
@ -303,24 +388,34 @@ class Membership(models.Model):
|
|||||||
"""
|
"""
|
||||||
Method to see if that object can be super edited by the given user
|
Method to see if that object can be super edited by the given user
|
||||||
"""
|
"""
|
||||||
return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP)
|
if user.is_anonymous:
|
||||||
|
return False
|
||||||
|
return user.is_board_member
|
||||||
|
|
||||||
def can_be_edited_by(self, user, membership=None):
|
def can_be_edited_by(self, user: User) -> bool:
|
||||||
"""
|
"""
|
||||||
Method to see if that object can be edited by the given user
|
Check if that object can be edited by the given user
|
||||||
"""
|
"""
|
||||||
if user.memberships:
|
if user.is_root or user.is_board_member:
|
||||||
if membership: # This is for optimisation purpose
|
return True
|
||||||
ms = membership
|
membership = self.club.get_membership_for(user)
|
||||||
else:
|
if membership is not None and membership.role >= self.role:
|
||||||
ms = user.memberships.filter(club=self.club, end_date=None).first()
|
return True
|
||||||
return (ms and ms.role >= self.role) or user.is_in_group(
|
return False
|
||||||
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})
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
if self.end_date is None:
|
||||||
|
cache.set(f"membership_{self.club_id}_{self.user_id}", self)
|
||||||
|
else:
|
||||||
|
cache.set(f"membership_{self.club_id}_{self.user_id}", "not_member")
|
||||||
|
|
||||||
|
def delete(self, *args, **kwargs):
|
||||||
|
super().delete(*args, **kwargs)
|
||||||
|
cache.delete(f"membership_{self.club_id}_{self.user_id}")
|
||||||
|
|
||||||
|
|
||||||
class Mailing(models.Model):
|
class Mailing(models.Model):
|
||||||
@ -373,14 +468,12 @@ class Mailing(models.Model):
|
|||||||
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_com_admin
|
||||||
|
|
||||||
def is_owned_by(self, user):
|
def is_owned_by(self, user):
|
||||||
return (
|
if user.is_anonymous:
|
||||||
user.is_in_group(self)
|
return False
|
||||||
or user.is_root
|
return user.is_root or user.is_com_admin
|
||||||
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)
|
||||||
@ -388,9 +481,8 @@ class Mailing(models.Model):
|
|||||||
def can_be_edited_by(self, user):
|
def can_be_edited_by(self, user):
|
||||||
return self.club.has_rights_in_club(user)
|
return self.club.has_rights_in_club(user)
|
||||||
|
|
||||||
def delete(self):
|
def delete(self, *args, **kwargs):
|
||||||
for sub in self.subscriptions.all():
|
self.subscriptions.all().delete()
|
||||||
sub.delete()
|
|
||||||
super(Mailing, self).delete()
|
super(Mailing, self).delete()
|
||||||
|
|
||||||
def fetch_format(self):
|
def fetch_format(self):
|
||||||
@ -463,10 +555,12 @@ class MailingSubscription(models.Model):
|
|||||||
super(MailingSubscription, self).clean()
|
super(MailingSubscription, self).clean()
|
||||||
|
|
||||||
def is_owned_by(self, user):
|
def is_owned_by(self, user):
|
||||||
|
if user.is_anonymous:
|
||||||
|
return False
|
||||||
return (
|
return (
|
||||||
self.mailing.club.has_rights_in_club(user)
|
self.mailing.club.has_rights_in_club(user)
|
||||||
or user.is_root
|
or user.is_root
|
||||||
or self.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
|
or self.user.is_com_admin
|
||||||
)
|
)
|
||||||
|
|
||||||
def can_be_edited_by(self, user):
|
def can_be_edited_by(self, user):
|
||||||
|
@ -13,13 +13,15 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<td>{% trans %}User{% endtrans %}</td>
|
<tr>
|
||||||
<td>{% trans %}Role{% endtrans %}</td>
|
<td>{% trans %}User{% endtrans %}</td>
|
||||||
<td>{% trans %}Description{% endtrans %}</td>
|
<td>{% trans %}Role{% endtrans %}</td>
|
||||||
<td>{% trans %}Since{% endtrans %}</td>
|
<td>{% trans %}Description{% endtrans %}</td>
|
||||||
{% if users_old %}
|
<td>{% trans %}Since{% endtrans %}</td>
|
||||||
<td>{% trans %}Mark as old{% endtrans %}</td>
|
{% if users_old %}
|
||||||
{% endif %}
|
<td>{% trans %}Mark as old{% endtrans %}</td>
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for m in members %}
|
{% for m in members %}
|
||||||
|
773
club/tests.py
773
club/tests.py
@ -13,378 +13,564 @@
|
|||||||
# OR WITHIN THE LOCAL FILE "LICENSE"
|
# OR WITHIN THE LOCAL FILE "LICENSE"
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.cache import cache
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.utils import timezone, html
|
from django.utils import timezone, html
|
||||||
|
from django.utils.timezone import now, localtime
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
|
|
||||||
|
|
||||||
from core.models import User
|
from core.models import User, AnonymousUser
|
||||||
from club.models import Club, Membership, Mailing
|
from club.models import Club, Membership, Mailing
|
||||||
from club.forms import MailingForm
|
from club.forms import MailingForm
|
||||||
from sith.settings import SITH_BAR_MANAGER
|
from sith.settings import SITH_BAR_MANAGER, SITH_MAIN_CLUB_ID
|
||||||
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
||||||
|
|
||||||
|
|
||||||
class ClubTest(TestCase):
|
class ClubTest(TestCase):
|
||||||
|
"""
|
||||||
|
Set up data for test cases related to clubs and membership
|
||||||
|
The generated dataset is the one created by the populate command,
|
||||||
|
plus the following modifications :
|
||||||
|
|
||||||
|
- `self.club` is a dummy club recreated for each test
|
||||||
|
- `self.club` has two board members : skia (role 3) and comptable (role 10)
|
||||||
|
- `self.club` has one regular member : richard
|
||||||
|
- `self.club` has one former member : sli (who had role 2)
|
||||||
|
- None of the `self.club` members are in the AE club.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
# subscribed users - initial members
|
||||||
|
cls.skia = User.objects.get(username="skia")
|
||||||
|
cls.richard = User.objects.get(username="rbatsbak")
|
||||||
|
cls.comptable = User.objects.get(username="comptable")
|
||||||
|
cls.sli = User.objects.get(username="sli")
|
||||||
|
|
||||||
|
# subscribed users - not initial members
|
||||||
|
cls.krophil = User.objects.get(username="krophil")
|
||||||
|
cls.subscriber = User.objects.get(username="subscriber")
|
||||||
|
|
||||||
|
# old subscriber
|
||||||
|
cls.old_subscriber = User.objects.get(username="old_subscriber")
|
||||||
|
|
||||||
|
# not subscribed
|
||||||
|
cls.public = User.objects.get(username="public")
|
||||||
|
|
||||||
|
cls.ae = Club.objects.filter(pk=SITH_MAIN_CLUB_ID)[0]
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
call_command("populate")
|
# by default, Skia is in the AE, which creates side effect
|
||||||
self.skia = User.objects.filter(username="skia").first()
|
self.skia.memberships.all().delete()
|
||||||
self.rbatsbak = User.objects.filter(username="rbatsbak").first()
|
|
||||||
self.guy = User.objects.filter(username="guy").first()
|
|
||||||
self.bdf = Club.objects.filter(unix_name=SITH_BAR_MANAGER["unix_name"]).first()
|
|
||||||
|
|
||||||
def test_create_add_user_to_club_from_root_ok(self):
|
# create a fake club
|
||||||
self.client.login(username="root", password="plop")
|
self.club = Club.objects.create(
|
||||||
self.client.post(
|
name="Fake Club",
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
unix_name="fake-club",
|
||||||
{"users": self.skia.id, "start_date": "12/06/2016", "role": 3},
|
address="5 rue de la République, 90000 Belfort",
|
||||||
)
|
)
|
||||||
response = self.client.get(
|
self.members_url = reverse(
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id})
|
"club:club_members", kwargs={"club_id": self.club.id}
|
||||||
)
|
)
|
||||||
self.assertTrue(response.status_code == 200)
|
a_month_ago = now() - timedelta(days=30)
|
||||||
self.assertTrue(
|
yesterday = now() - timedelta(days=1)
|
||||||
"S' Kia</a></td>\\n <td>Responsable info</td>"
|
Membership.objects.create(
|
||||||
in str(response.content)
|
club=self.club, user=self.skia, start_date=a_month_ago, role=3
|
||||||
|
)
|
||||||
|
Membership.objects.create(club=self.club, user=self.richard, role=1)
|
||||||
|
Membership.objects.create(
|
||||||
|
club=self.club, user=self.comptable, start_date=a_month_ago, role=10
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_create_add_multiple_user_to_club_from_root_ok(self):
|
# sli was a member but isn't anymore
|
||||||
|
Membership.objects.create(
|
||||||
|
club=self.club,
|
||||||
|
user=self.sli,
|
||||||
|
start_date=a_month_ago,
|
||||||
|
end_date=yesterday,
|
||||||
|
role=2,
|
||||||
|
)
|
||||||
|
cache.clear()
|
||||||
|
|
||||||
|
|
||||||
|
class MembershipQuerySetTest(ClubTest):
|
||||||
|
def test_ongoing(self):
|
||||||
|
"""
|
||||||
|
Test that the ongoing queryset method returns the memberships that
|
||||||
|
are not ended.
|
||||||
|
"""
|
||||||
|
current_members = self.club.members.ongoing()
|
||||||
|
expected = [
|
||||||
|
self.skia.memberships.get(club=self.club),
|
||||||
|
self.comptable.memberships.get(club=self.club),
|
||||||
|
self.richard.memberships.get(club=self.club),
|
||||||
|
]
|
||||||
|
self.assertEqual(len(current_members), len(expected))
|
||||||
|
for member in current_members:
|
||||||
|
self.assertIn(member, expected)
|
||||||
|
|
||||||
|
def test_board(self):
|
||||||
|
"""
|
||||||
|
Test that the board queryset method returns the memberships
|
||||||
|
of user in the club board
|
||||||
|
"""
|
||||||
|
board_members = list(self.club.members.board())
|
||||||
|
expected = [
|
||||||
|
self.skia.memberships.get(club=self.club),
|
||||||
|
self.comptable.memberships.get(club=self.club),
|
||||||
|
# sli is no more member, but he was in the board
|
||||||
|
self.sli.memberships.get(club=self.club),
|
||||||
|
]
|
||||||
|
self.assertEqual(len(board_members), len(expected))
|
||||||
|
for member in board_members:
|
||||||
|
self.assertIn(member, expected)
|
||||||
|
|
||||||
|
def test_ongoing_board(self):
|
||||||
|
"""
|
||||||
|
Test that combining ongoing and board returns users
|
||||||
|
who are currently board members of the club
|
||||||
|
"""
|
||||||
|
members = list(self.club.members.ongoing().board())
|
||||||
|
expected = [
|
||||||
|
self.skia.memberships.get(club=self.club),
|
||||||
|
self.comptable.memberships.get(club=self.club),
|
||||||
|
]
|
||||||
|
self.assertEqual(len(members), len(expected))
|
||||||
|
for member in members:
|
||||||
|
self.assertIn(member, expected)
|
||||||
|
|
||||||
|
def test_update_invalidate_cache(self):
|
||||||
|
"""
|
||||||
|
Test that the `update` queryset method properly invalidate cache
|
||||||
|
"""
|
||||||
|
mem_skia = self.skia.memberships.get(club=self.club)
|
||||||
|
cache.set(f"membership_{mem_skia.club_id}_{mem_skia.user_id}", mem_skia)
|
||||||
|
self.skia.memberships.update(end_date=localtime(now()).date())
|
||||||
|
self.assertEqual(
|
||||||
|
cache.get(f"membership_{mem_skia.club_id}_{mem_skia.user_id}"), "not_member"
|
||||||
|
)
|
||||||
|
|
||||||
|
mem_richard = self.richard.memberships.get(club=self.club)
|
||||||
|
cache.set(
|
||||||
|
f"membership_{mem_richard.club_id}_{mem_richard.user_id}", mem_richard
|
||||||
|
)
|
||||||
|
self.richard.memberships.update(role=5)
|
||||||
|
new_mem = self.richard.memberships.get(club=self.club)
|
||||||
|
self.assertNotEqual(new_mem, "not_member")
|
||||||
|
self.assertEqual(new_mem.role, 5)
|
||||||
|
|
||||||
|
def test_delete_invalidate_cache(self):
|
||||||
|
"""
|
||||||
|
Test that the `delete` queryset properly invalidate cache
|
||||||
|
"""
|
||||||
|
|
||||||
|
mem_skia = self.skia.memberships.get(club=self.club)
|
||||||
|
mem_comptable = self.comptable.memberships.get(club=self.club)
|
||||||
|
cache.set(f"membership_{mem_skia.club_id}_{mem_skia.user_id}", mem_skia)
|
||||||
|
cache.set(
|
||||||
|
f"membership_{mem_comptable.club_id}_{mem_comptable.user_id}", mem_comptable
|
||||||
|
)
|
||||||
|
|
||||||
|
# should delete the subscriptions of skia and comptable
|
||||||
|
self.club.members.ongoing().board().delete()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
cache.get(f"membership_{mem_skia.club_id}_{mem_skia.user_id}"), "not_member"
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
cache.get(f"membership_{mem_comptable.club_id}_{mem_comptable.user_id}"),
|
||||||
|
"not_member",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ClubModelTest(ClubTest):
|
||||||
|
def assert_membership_just_started(self, user: User, role: int):
|
||||||
|
"""
|
||||||
|
Assert that the given membership is active and started today
|
||||||
|
"""
|
||||||
|
membership = user.memberships.ongoing().filter(club=self.club).first()
|
||||||
|
self.assertIsNotNone(membership)
|
||||||
|
self.assertEqual(localtime(now()).date(), membership.start_date)
|
||||||
|
self.assertIsNone(membership.end_date)
|
||||||
|
self.assertEqual(membership.role, role)
|
||||||
|
self.assertEqual(membership.club.get_membership_for(user), membership)
|
||||||
|
member_group = self.club.unix_name + settings.SITH_MEMBER_SUFFIX
|
||||||
|
board_group = self.club.unix_name + settings.SITH_BOARD_SUFFIX
|
||||||
|
self.assertTrue(user.is_in_group(name=member_group))
|
||||||
|
self.assertTrue(user.is_in_group(name=board_group))
|
||||||
|
|
||||||
|
def assert_membership_just_ended(self, user: User):
|
||||||
|
"""
|
||||||
|
Assert that the given user have a membership which ended today
|
||||||
|
"""
|
||||||
|
today = localtime(now()).date()
|
||||||
|
self.assertIsNotNone(
|
||||||
|
user.memberships.filter(club=self.club, end_date=today).first()
|
||||||
|
)
|
||||||
|
self.assertIsNone(self.club.get_membership_for(user))
|
||||||
|
|
||||||
|
def test_access_unauthorized(self):
|
||||||
|
"""
|
||||||
|
Test that users who never subscribed and anonymous users
|
||||||
|
cannot see the page
|
||||||
|
"""
|
||||||
|
response = self.client.post(self.members_url)
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
self.client.login(username="public", password="plop")
|
||||||
|
response = self.client.post(self.members_url)
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
def test_display(self):
|
||||||
|
"""
|
||||||
|
Test that a GET request return a page where the requested
|
||||||
|
information are displayed.
|
||||||
|
"""
|
||||||
|
self.client.login(username=self.skia.username, password="plop")
|
||||||
|
response = self.client.get(self.members_url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
expected_html = (
|
||||||
|
"<table><thead><tr>"
|
||||||
|
"<td>Utilisateur</td><td>Rôle</td><td>Description</td>"
|
||||||
|
"<td>Depuis</td><td>Marquer comme ancien</td>"
|
||||||
|
"</tr></thead><tbody>"
|
||||||
|
)
|
||||||
|
memberships = self.club.members.ongoing().order_by("-role")
|
||||||
|
input_id = 0
|
||||||
|
for membership in memberships.select_related("user"):
|
||||||
|
user = membership.user
|
||||||
|
expected_html += (
|
||||||
|
f"<tr><td><a href=\"{reverse('core:user_profile', args=[user.id])}\">"
|
||||||
|
f"{user.get_display_name()}</a></td>"
|
||||||
|
f"<td>{settings.SITH_CLUB_ROLES[membership.role]}</td>"
|
||||||
|
f"<td>{membership.description}</td>"
|
||||||
|
f"<td>{membership.start_date}</td><td>"
|
||||||
|
)
|
||||||
|
if membership.role <= 3: # 3 is the role of skia
|
||||||
|
expected_html += (
|
||||||
|
'<input type="checkbox" name="users_old" '
|
||||||
|
f'value="{user.id}" '
|
||||||
|
f'id="id_users_old_{input_id}">'
|
||||||
|
)
|
||||||
|
input_id += 1
|
||||||
|
expected_html += "</td></tr>"
|
||||||
|
expected_html += "</tbody></table>"
|
||||||
|
self.assertInHTML(expected_html, response.content.decode())
|
||||||
|
|
||||||
|
def test_root_add_one_club_member(self):
|
||||||
|
"""
|
||||||
|
Test that root users can add members to clubs, one at a time
|
||||||
|
"""
|
||||||
self.client.login(username="root", password="plop")
|
self.client.login(username="root", password="plop")
|
||||||
self.client.post(
|
response = self.client.post(
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
self.members_url,
|
||||||
|
{"users": self.subscriber.id, "role": 3},
|
||||||
|
)
|
||||||
|
self.assertRedirects(response, self.members_url)
|
||||||
|
self.subscriber.refresh_from_db()
|
||||||
|
self.assert_membership_just_started(self.subscriber, role=3)
|
||||||
|
|
||||||
|
def test_root_add_multiple_club_member(self):
|
||||||
|
"""
|
||||||
|
Test that root users can add multiple members at once to clubs
|
||||||
|
"""
|
||||||
|
self.client.login(username="root", password="plop")
|
||||||
|
response = self.client.post(
|
||||||
|
self.members_url,
|
||||||
{
|
{
|
||||||
"users": "|%d|%d|" % (self.skia.id, self.rbatsbak.id),
|
"users": f"|{self.subscriber.id}|{self.krophil.id}|",
|
||||||
"start_date": "12/06/2016",
|
|
||||||
"role": 3,
|
"role": 3,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
response = self.client.get(
|
self.assertRedirects(response, self.members_url)
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id})
|
self.subscriber.refresh_from_db()
|
||||||
)
|
self.assert_membership_just_started(self.subscriber, role=3)
|
||||||
self.assertTrue(response.status_code == 200)
|
self.assert_membership_just_started(self.krophil, role=3)
|
||||||
content = str(response.content)
|
|
||||||
self.assertTrue(
|
|
||||||
"S' Kia</a></td>\\n <td>Responsable info</td>"
|
|
||||||
in content
|
|
||||||
)
|
|
||||||
self.assertTrue(
|
|
||||||
"Richard Batsbak</a></td>\\n <td>Responsable info</td>"
|
|
||||||
in content
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_create_add_user_to_club_from_root_fail_not_subscriber(self):
|
def test_add_unauthorized_members(self):
|
||||||
|
"""
|
||||||
|
Test that users who are not currently subscribed
|
||||||
|
cannot be members of clubs.
|
||||||
|
"""
|
||||||
self.client.login(username="root", password="plop")
|
self.client.login(username="root", password="plop")
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
self.members_url,
|
||||||
{"users": self.guy.id, "start_date": "12/06/2016", "role": 3},
|
{"users": self.public.id, "role": 1},
|
||||||
)
|
)
|
||||||
self.assertTrue(response.status_code == 200)
|
self.assertIsNone(self.public.memberships.filter(club=self.club).first())
|
||||||
self.assertTrue('<ul class="errorlist"><li>' in str(response.content))
|
self.assertTrue('<ul class="errorlist"><li>' in str(response.content))
|
||||||
response = self.client.get(
|
|
||||||
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):
|
response = self.client.post(
|
||||||
|
self.members_url,
|
||||||
|
{"users": self.old_subscriber.id, "role": 1},
|
||||||
|
)
|
||||||
|
self.assertIsNone(self.public.memberships.filter(club=self.club).first())
|
||||||
|
self.assertIsNone(self.club.get_membership_for(self.public))
|
||||||
|
self.assertTrue('<ul class="errorlist"><li>' in str(response.content))
|
||||||
|
|
||||||
|
def test_add_members_already_members(self):
|
||||||
|
"""
|
||||||
|
Test that users who are already members of a club
|
||||||
|
cannot be added again to this club
|
||||||
|
"""
|
||||||
self.client.login(username="root", password="plop")
|
self.client.login(username="root", password="plop")
|
||||||
|
current_membership = self.skia.memberships.ongoing().get(club=self.club)
|
||||||
|
nb_memberships = self.skia.memberships.count()
|
||||||
self.client.post(
|
self.client.post(
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
self.members_url,
|
||||||
{"users": self.skia.id, "start_date": "12/06/2016", "role": 3},
|
{"users": self.skia.id, "role": current_membership.role + 1},
|
||||||
)
|
|
||||||
response = self.client.get(
|
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id})
|
|
||||||
)
|
|
||||||
self.assertTrue(
|
|
||||||
"S' Kia</a></td>\\n <td>Responsable info</td>"
|
|
||||||
in str(response.content)
|
|
||||||
)
|
|
||||||
response = self.client.post(
|
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
|
||||||
{"users": self.skia.id, "start_date": "12/06/2016", "role": 4},
|
|
||||||
)
|
|
||||||
self.assertTrue(response.status_code == 200)
|
|
||||||
self.assertFalse(
|
|
||||||
"S' Kia</a></td>\\n <td>Secrétaire</td>"
|
|
||||||
in str(response.content)
|
|
||||||
)
|
)
|
||||||
|
self.skia.refresh_from_db()
|
||||||
|
self.assertEqual(nb_memberships, self.skia.memberships.count())
|
||||||
|
new_membership = self.skia.memberships.ongoing().get(club=self.club)
|
||||||
|
self.assertEqual(current_membership, new_membership)
|
||||||
|
self.assertEqual(self.club.get_membership_for(self.skia), new_membership)
|
||||||
|
|
||||||
def test_create_add_user_non_existent_to_club_from_root_fail(self):
|
def test_add_not_existing_users(self):
|
||||||
|
"""
|
||||||
|
Test that not existing users cannot be added in clubs.
|
||||||
|
If one user in the request is invalid, no membership creation at all
|
||||||
|
can take place.
|
||||||
|
"""
|
||||||
self.client.login(username="root", password="plop")
|
self.client.login(username="root", password="plop")
|
||||||
|
nb_memberships = self.club.members.count()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
self.members_url,
|
||||||
{"users": [9999], "start_date": "12/06/2016", "role": 3},
|
{"users": [9999], "role": 1},
|
||||||
)
|
)
|
||||||
self.assertTrue(response.status_code == 200)
|
self.assertContains(response, '<ul class="errorlist"><li>')
|
||||||
content = str(response.content)
|
self.club.refresh_from_db()
|
||||||
self.assertTrue('<ul class="errorlist"><li>' in content)
|
self.assertEqual(self.club.members.count(), nb_memberships)
|
||||||
self.assertFalse("<td>Responsable info</td>" in content)
|
|
||||||
self.client.login(username="root", password="plop")
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
self.members_url,
|
||||||
{
|
{
|
||||||
"users": "|%d|%d|" % (self.skia.id, 9999),
|
"users": f"|{self.subscriber.id}|{9999}|",
|
||||||
"start_date": "12/06/2016",
|
"start_date": "12/06/2016",
|
||||||
"role": 3,
|
"role": 3,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assertTrue(response.status_code == 200)
|
self.assertContains(response, '<ul class="errorlist"><li>')
|
||||||
content = str(response.content)
|
self.club.refresh_from_db()
|
||||||
self.assertTrue('<ul class="errorlist"><li>' in content)
|
self.assertEqual(self.club.members.count(), nb_memberships)
|
||||||
self.assertFalse("<td>Responsable info</td>" in content)
|
|
||||||
|
|
||||||
def test_create_add_user_to_club_from_skia_ok(self):
|
def test_president_add_members(self):
|
||||||
self.client.login(username="root", password="plop")
|
"""
|
||||||
self.client.post(
|
Test that the president of the club can add members
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
"""
|
||||||
{"users": self.skia.id, "start_date": "12/06/2016", "role": 10},
|
president = self.club.members.get(role=10).user
|
||||||
|
nb_club_membership = self.club.members.count()
|
||||||
|
nb_subscriber_memberships = self.subscriber.memberships.count()
|
||||||
|
self.client.login(username=president.username, password="plop")
|
||||||
|
response = self.client.post(
|
||||||
|
self.members_url,
|
||||||
|
{"users": self.subscriber.id, "role": 9},
|
||||||
)
|
)
|
||||||
self.client.login(username="skia", password="plop")
|
self.assertRedirects(response, self.members_url)
|
||||||
self.client.post(
|
self.club.refresh_from_db()
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
self.subscriber.refresh_from_db()
|
||||||
{"users": self.rbatsbak.id, "start_date": "12/06/2016", "role": 9},
|
self.assertEqual(self.club.members.count(), nb_club_membership + 1)
|
||||||
|
self.assertEqual(
|
||||||
|
self.subscriber.memberships.count(), nb_subscriber_memberships + 1
|
||||||
)
|
)
|
||||||
response = self.client.get(
|
self.assert_membership_just_started(self.subscriber, role=9)
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id})
|
|
||||||
|
def test_add_member_greater_role(self):
|
||||||
|
"""
|
||||||
|
Test that a member of the club member cannot create
|
||||||
|
a membership with a greater role than its own.
|
||||||
|
"""
|
||||||
|
self.client.login(username=self.skia.username, password="plop")
|
||||||
|
nb_memberships = self.club.members.count()
|
||||||
|
response = self.client.post(
|
||||||
|
self.members_url,
|
||||||
|
{"users": self.subscriber.id, "role": 10},
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertIn(
|
self.assertInHTML(
|
||||||
"""Richard Batsbak</a></td>\n <td>Vice-Président⸱e</td>""",
|
"<li>Vous n'avez pas la permission de faire cela</li>",
|
||||||
response.content.decode(),
|
response.content.decode(),
|
||||||
)
|
)
|
||||||
|
self.club.refresh_from_db()
|
||||||
|
self.assertEqual(nb_memberships, self.club.members.count())
|
||||||
|
self.assertIsNone(self.subscriber.memberships.filter(club=self.club).first())
|
||||||
|
|
||||||
def test_create_add_user_to_club_from_richard_fail(self):
|
def test_add_member_without_role(self):
|
||||||
self.client.login(username="root", password="plop")
|
"""
|
||||||
self.client.post(
|
Test that trying to add members without specifying their role fails
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
"""
|
||||||
{"users": self.rbatsbak.id, "start_date": "12/06/2016", "role": 3},
|
|
||||||
)
|
|
||||||
self.client.login(username="rbatsbak", password="plop")
|
|
||||||
response = self.client.post(
|
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
|
||||||
{"users": self.skia.id, "start_date": "12/06/2016", "role": 10},
|
|
||||||
)
|
|
||||||
self.assertTrue(response.status_code == 200)
|
|
||||||
self.assertTrue(
|
|
||||||
"<li>Vous n'avez pas la permission de faire cela</li>"
|
|
||||||
in str(response.content)
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_role_required_if_users_specified(self):
|
|
||||||
self.client.login(username="root", password="plop")
|
self.client.login(username="root", password="plop")
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
self.members_url,
|
||||||
{"users": self.rbatsbak.id, "start_date": "12/06/2016"},
|
{"users": self.subscriber.id, "start_date": "12/06/2016"},
|
||||||
)
|
)
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
'<ul class="errorlist"><li>Vous devez choisir un r' in str(response.content)
|
'<ul class="errorlist"><li>Vous devez choisir un r' in str(response.content)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_mark_old_user_to_club_from_skia_ok(self):
|
def test_end_membership_self(self):
|
||||||
self.client.login(username="root", password="plop")
|
"""
|
||||||
self.client.post(
|
Test that a member can end its own membership
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
"""
|
||||||
{
|
|
||||||
"users": "|%d|%d|" % (self.skia.id, self.rbatsbak.id),
|
|
||||||
"start_date": "12/06/2016",
|
|
||||||
"role": 3,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
self.client.login(username="skia", password="plop")
|
self.client.login(username="skia", password="plop")
|
||||||
response = self.client.post(
|
self.client.post(
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
self.members_url,
|
||||||
{"users_old": self.rbatsbak.id},
|
|
||||||
)
|
|
||||||
self.assertTrue(response.status_code == 302)
|
|
||||||
|
|
||||||
response = self.client.get(
|
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id})
|
|
||||||
)
|
|
||||||
self.assertTrue(response.status_code == 200)
|
|
||||||
content = str(response.content)
|
|
||||||
self.assertFalse(
|
|
||||||
"Richard Batsbak</a></td>\\n <td>Responsable info</td>"
|
|
||||||
in content
|
|
||||||
)
|
|
||||||
self.assertTrue(
|
|
||||||
"S' Kia</a></td>\\n <td>Responsable info</td>"
|
|
||||||
in content
|
|
||||||
)
|
|
||||||
|
|
||||||
# Skia is board member so he should be able to mark as old even without being in the club
|
|
||||||
response = self.client.post(
|
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
|
||||||
{"users_old": self.skia.id},
|
{"users_old": self.skia.id},
|
||||||
)
|
)
|
||||||
|
self.skia.refresh_from_db()
|
||||||
|
self.assert_membership_just_ended(self.skia)
|
||||||
|
|
||||||
|
def test_end_membership_lower_role(self):
|
||||||
|
"""
|
||||||
|
Test that board members of the club can end memberships
|
||||||
|
of users with lower roles
|
||||||
|
"""
|
||||||
|
# remainder : skia has role 3, comptable has role 10, richard has role 1
|
||||||
|
self.client.login(username=self.skia.username, password="plop")
|
||||||
|
response = self.client.post(
|
||||||
|
self.members_url,
|
||||||
|
{"users_old": self.richard.id},
|
||||||
|
)
|
||||||
|
self.assertRedirects(response, self.members_url)
|
||||||
|
self.club.refresh_from_db()
|
||||||
|
self.assert_membership_just_ended(self.richard)
|
||||||
|
|
||||||
|
def test_end_membership_higher_role(self):
|
||||||
|
"""
|
||||||
|
Test that board members of the club cannot end memberships
|
||||||
|
of users with higher roles
|
||||||
|
"""
|
||||||
|
membership = self.comptable.memberships.filter(club=self.club).first()
|
||||||
|
self.client.login(username=self.skia.username, password="plop")
|
||||||
|
self.client.post(
|
||||||
|
self.members_url,
|
||||||
|
{"users_old": self.comptable.id},
|
||||||
|
)
|
||||||
|
self.club.refresh_from_db()
|
||||||
|
new_membership = self.club.get_membership_for(self.comptable)
|
||||||
|
self.assertIsNotNone(new_membership)
|
||||||
|
self.assertEqual(new_membership, membership)
|
||||||
|
|
||||||
|
membership = self.comptable.memberships.filter(club=self.club).first()
|
||||||
|
self.assertIsNone(membership.end_date)
|
||||||
|
|
||||||
|
def test_end_membership_as_main_club_board(self):
|
||||||
|
"""
|
||||||
|
Test that board members of the main club can end the membership
|
||||||
|
of anyone
|
||||||
|
"""
|
||||||
|
# make subscriber a board member
|
||||||
|
self.subscriber.memberships.all().delete()
|
||||||
|
Membership.objects.create(club=self.ae, user=self.subscriber, role=3)
|
||||||
|
|
||||||
|
nb_memberships = self.club.members.count()
|
||||||
|
self.client.login(username=self.subscriber.username, password="plop")
|
||||||
|
response = self.client.post(
|
||||||
|
self.members_url,
|
||||||
|
{"users_old": self.comptable.id},
|
||||||
|
)
|
||||||
|
self.assertRedirects(response, self.members_url)
|
||||||
|
self.assert_membership_just_ended(self.comptable)
|
||||||
|
self.assertEqual(self.club.members.ongoing().count(), nb_memberships - 1)
|
||||||
|
|
||||||
|
def test_end_membership_as_root(self):
|
||||||
|
"""
|
||||||
|
Test that root users can end the membership of anyone
|
||||||
|
"""
|
||||||
|
nb_memberships = self.club.members.count()
|
||||||
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}),
|
|
||||||
{"users": self.rbatsbak.id, "start_date": "12/06/2016", "role": 3},
|
|
||||||
)
|
|
||||||
self.client.login(username="skia", password="plop")
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
self.members_url,
|
||||||
{"users_old": self.rbatsbak.id},
|
{"users_old": [self.comptable.id]},
|
||||||
)
|
|
||||||
self.assertFalse(
|
|
||||||
"Richard Batsbak</a></td>\\n <td>Responsable info</td>"
|
|
||||||
in str(response.content)
|
|
||||||
)
|
)
|
||||||
|
self.assertRedirects(response, self.members_url)
|
||||||
|
self.assert_membership_just_ended(self.comptable)
|
||||||
|
self.assertEqual(self.club.members.ongoing().count(), nb_memberships - 1)
|
||||||
|
self.assertEqual(self.club.members.count(), nb_memberships)
|
||||||
|
|
||||||
def test_mark_old_multiple_users_from_skia_ok(self):
|
def test_end_membership_as_foreigner(self):
|
||||||
self.client.login(username="root", password="plop")
|
"""
|
||||||
|
Test that users who are not in this club cannot end its memberships
|
||||||
|
"""
|
||||||
|
nb_memberships = self.club.members.count()
|
||||||
|
membership = self.richard.memberships.filter(club=self.club).first()
|
||||||
|
self.client.login(username="subscriber", password="root")
|
||||||
self.client.post(
|
self.client.post(
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
self.members_url,
|
||||||
{
|
{"users_old": [self.richard.id]},
|
||||||
"users": "|%d|%d|" % (self.skia.id, self.rbatsbak.id),
|
|
||||||
"start_date": "12/06/2016",
|
|
||||||
"role": 3,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
self.client.login(username="skia", password="plop")
|
# nothing should have changed
|
||||||
response = self.client.post(
|
new_mem = self.club.get_membership_for(self.richard)
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
self.assertIsNotNone(new_mem)
|
||||||
{"users_old": [self.rbatsbak.id, self.skia.id]},
|
self.assertEqual(self.club.members.count(), nb_memberships)
|
||||||
)
|
self.assertEqual(membership, new_mem)
|
||||||
self.assertTrue(response.status_code == 302)
|
|
||||||
|
|
||||||
response = self.client.get(
|
def test_delete_remove_from_meta_group(self):
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id})
|
"""
|
||||||
)
|
Test that when a club is deleted, all its members are removed from the
|
||||||
self.assertTrue(response.status_code == 200)
|
associated metagroup
|
||||||
content = str(response.content)
|
"""
|
||||||
self.assertFalse(
|
memberships = self.club.members.select_related("user")
|
||||||
"Richard Batsbak</a></td>\\n <td>Responsable info</td>"
|
users = [membership.user for membership in memberships]
|
||||||
in content
|
meta_group = self.club.unix_name + settings.SITH_MEMBER_SUFFIX
|
||||||
)
|
|
||||||
self.assertFalse(
|
|
||||||
"S' Kia</a></td>\\n <td>Responsable info</td>"
|
|
||||||
in content
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_mark_old_user_to_club_from_richard_ok(self):
|
self.club.delete()
|
||||||
self.client.login(username="root", password="plop")
|
for user in users:
|
||||||
self.client.post(
|
self.assertFalse(user.is_in_group(name=meta_group))
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
|
||||||
{
|
|
||||||
"users": "|%d|%d|" % (self.skia.id, self.rbatsbak.id),
|
|
||||||
"start_date": "12/06/2016",
|
|
||||||
"role": 3,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test with equal rights
|
def test_add_to_meta_group(self):
|
||||||
self.client.login(username="rbatsbak", password="plop")
|
"""
|
||||||
response = self.client.post(
|
Test that when a membership begins, the user is added to the meta group
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
"""
|
||||||
{"users_old": self.skia.id},
|
group_members = self.club.unix_name + settings.SITH_MEMBER_SUFFIX
|
||||||
)
|
board_members = self.club.unix_name + settings.SITH_BOARD_SUFFIX
|
||||||
self.assertTrue(response.status_code == 302)
|
self.assertFalse(self.subscriber.is_in_group(name=group_members))
|
||||||
|
self.assertFalse(self.subscriber.is_in_group(name=board_members))
|
||||||
|
Membership.objects.create(club=self.club, user=self.subscriber, role=3)
|
||||||
|
self.assertTrue(self.subscriber.is_in_group(name=group_members))
|
||||||
|
self.assertTrue(self.subscriber.is_in_group(name=board_members))
|
||||||
|
|
||||||
response = self.client.get(
|
def test_remove_from_meta_group(self):
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id})
|
"""
|
||||||
)
|
Test that when a membership ends, the user is removed from meta group
|
||||||
self.assertTrue(response.status_code == 200)
|
"""
|
||||||
content = str(response.content)
|
group_members = self.club.unix_name + settings.SITH_MEMBER_SUFFIX
|
||||||
self.assertTrue(
|
board_members = self.club.unix_name + settings.SITH_BOARD_SUFFIX
|
||||||
"Richard Batsbak</a></td>\\n <td>Responsable info</td>"
|
self.assertTrue(self.comptable.is_in_group(name=group_members))
|
||||||
in content
|
self.assertTrue(self.comptable.is_in_group(name=board_members))
|
||||||
)
|
self.comptable.memberships.update(end_date=localtime(now()))
|
||||||
self.assertFalse(
|
self.assertFalse(self.comptable.is_in_group(name=group_members))
|
||||||
"S' Kia</a></td>\\n <td>Responsable info</td>"
|
self.assertFalse(self.comptable.is_in_group(name=board_members))
|
||||||
in content
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test with lower rights
|
def test_club_owner(self):
|
||||||
self.client.post(
|
"""
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
Test that a club is owned only by board members of the main club
|
||||||
{"users": self.skia.id, "start_date": "12/06/2016", "role": 0},
|
"""
|
||||||
)
|
anonymous = AnonymousUser()
|
||||||
|
self.assertFalse(self.club.is_owned_by(anonymous))
|
||||||
|
self.assertFalse(self.club.is_owned_by(self.subscriber))
|
||||||
|
|
||||||
self.client.post(
|
# make sli a board member
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
self.sli.memberships.all().delete()
|
||||||
{"users_old": self.skia.id},
|
Membership(club=self.ae, user=self.sli, role=3).save()
|
||||||
)
|
self.assertTrue(self.club.is_owned_by(self.sli))
|
||||||
response = self.client.get(
|
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id})
|
|
||||||
)
|
|
||||||
self.assertTrue(response.status_code == 200)
|
|
||||||
content = str(response.content)
|
|
||||||
self.assertTrue(
|
|
||||||
"Richard Batsbak</a></td>\\n <td>Responsable info</td>"
|
|
||||||
in content
|
|
||||||
)
|
|
||||||
self.assertFalse(
|
|
||||||
"S' Kia</a></td>\\n <td>Curieux</td>" in content
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_mark_old_user_to_club_from_richard_fail(self):
|
|
||||||
self.client.login(username="root", password="plop")
|
|
||||||
self.client.post(
|
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
|
||||||
{"users": self.skia.id, "start_date": "12/06/2016", "role": 3},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test with richard outside of the club
|
|
||||||
self.client.login(username="rbatsbak", password="plop")
|
|
||||||
response = self.client.post(
|
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
|
||||||
{"users_old": self.skia.id},
|
|
||||||
)
|
|
||||||
self.assertTrue(response.status_code == 200)
|
|
||||||
|
|
||||||
response = self.client.get(
|
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id})
|
|
||||||
)
|
|
||||||
self.assertTrue(response.status_code == 200)
|
|
||||||
self.assertTrue(
|
|
||||||
"S' Kia</a></td>\\n <td>Responsable info</td>"
|
|
||||||
in str(response.content)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test with lower rights
|
|
||||||
self.client.post(
|
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
|
||||||
{"users": self.rbatsbak.id, "start_date": "12/06/2016", "role": 0},
|
|
||||||
)
|
|
||||||
|
|
||||||
self.client.post(
|
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
|
|
||||||
{"users_old": self.skia.id},
|
|
||||||
)
|
|
||||||
response = self.client.get(
|
|
||||||
reverse("club:club_members", kwargs={"club_id": self.bdf.id})
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
content = response.content.decode()
|
|
||||||
self.assertIn(
|
|
||||||
"Richard Batsbak</a></td>\n <td>Curieux⸱euse</td>",
|
|
||||||
content,
|
|
||||||
)
|
|
||||||
self.assertIn(
|
|
||||||
"S' Kia</a></td>\n <td>Responsable info</td>",
|
|
||||||
content,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MailingFormTest(TestCase):
|
class MailingFormTest(TestCase):
|
||||||
"""Perform validation tests for MailingForm"""
|
"""Perform validation tests for MailingForm"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
cls.skia = User.objects.filter(username="skia").first()
|
||||||
|
cls.rbatsbak = User.objects.filter(username="rbatsbak").first()
|
||||||
|
cls.krophil = User.objects.filter(username="krophil").first()
|
||||||
|
cls.comunity = User.objects.filter(username="comunity").first()
|
||||||
|
cls.bdf = Club.objects.filter(unix_name=SITH_BAR_MANAGER["unix_name"]).first()
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
call_command("populate")
|
|
||||||
self.skia = User.objects.filter(username="skia").first()
|
|
||||||
self.rbatsbak = User.objects.filter(username="rbatsbak").first()
|
|
||||||
self.krophil = User.objects.filter(username="krophil").first()
|
|
||||||
self.comunity = User.objects.filter(username="comunity").first()
|
|
||||||
self.bdf = Club.objects.filter(unix_name=SITH_BAR_MANAGER["unix_name"]).first()
|
|
||||||
Membership(
|
Membership(
|
||||||
user=self.rbatsbak,
|
user=self.rbatsbak,
|
||||||
club=self.bdf,
|
club=self.bdf,
|
||||||
@ -699,7 +885,6 @@ class ClubSellingViewTest(TestCase):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
call_command("populate")
|
|
||||||
self.ae = Club.objects.filter(unix_name="ae").first()
|
self.ae = Club.objects.filter(unix_name="ae").first()
|
||||||
|
|
||||||
def test_page_not_internal_error(self):
|
def test_page_not_internal_error(self):
|
||||||
|
@ -306,9 +306,7 @@ class ClubMembersView(ClubTabsMixin, CanViewMixin, DetailFormView):
|
|||||||
return resp
|
return resp
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
self.members = (
|
self.members = self.get_object().members.ongoing().order_by("-role")
|
||||||
self.get_object().members.filter(end_date=None).order_by("-role").all()
|
|
||||||
)
|
|
||||||
return super(ClubMembersView, self).dispatch(request, *args, **kwargs)
|
return super(ClubMembersView, self).dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_success_url(self, **kwargs):
|
def get_success_url(self, **kwargs):
|
||||||
@ -443,7 +441,6 @@ class ClubSellingCSVView(ClubSellingView):
|
|||||||
return row
|
return row
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
|
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
kwargs = self.get_context_data(**kwargs)
|
kwargs = self.get_context_data(**kwargs)
|
||||||
|
|
||||||
@ -706,7 +703,6 @@ class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView):
|
|||||||
|
|
||||||
|
|
||||||
class MailingDeleteView(CanEditMixin, DeleteView):
|
class MailingDeleteView(CanEditMixin, DeleteView):
|
||||||
|
|
||||||
model = Mailing
|
model = Mailing
|
||||||
template_name = "core/delete_confirm.jinja"
|
template_name = "core/delete_confirm.jinja"
|
||||||
pk_url_kwarg = "mailing_id"
|
pk_url_kwarg = "mailing_id"
|
||||||
@ -724,7 +720,6 @@ class MailingDeleteView(CanEditMixin, DeleteView):
|
|||||||
|
|
||||||
|
|
||||||
class MailingSubscriptionDeleteView(CanEditMixin, DeleteView):
|
class MailingSubscriptionDeleteView(CanEditMixin, DeleteView):
|
||||||
|
|
||||||
model = MailingSubscription
|
model = MailingSubscription
|
||||||
template_name = "core/delete_confirm.jinja"
|
template_name = "core/delete_confirm.jinja"
|
||||||
pk_url_kwarg = "mailing_subscription_id"
|
pk_url_kwarg = "mailing_subscription_id"
|
||||||
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = []
|
dependencies = []
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -7,7 +7,6 @@ import django.db.models.deletion
|
|||||||
|
|
||||||
|
|
||||||
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),
|
||||||
|
@ -7,7 +7,6 @@ import django.db.models.deletion
|
|||||||
|
|
||||||
|
|
||||||
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),
|
||||||
|
@ -8,7 +8,6 @@ import django.db.models.deletion
|
|||||||
|
|
||||||
|
|
||||||
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),
|
||||||
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("com", "0004_auto_20171221_1614")]
|
dependencies = [("com", "0004_auto_20171221_1614")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -6,7 +6,6 @@ from django.db import migrations
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("com", "0005_auto_20180318_2227")]
|
dependencies = [("com", "0005_auto_20180318_2227")]
|
||||||
|
|
||||||
operations = [migrations.RemoveField(model_name="sith", name="index_page")]
|
operations = [migrations.RemoveField(model_name="sith", name="index_page")]
|
||||||
|
@ -50,7 +50,9 @@ class Sith(models.Model):
|
|||||||
version = utils.get_git_revision_short_hash()
|
version = utils.get_git_revision_short_hash()
|
||||||
|
|
||||||
def is_owned_by(self, user):
|
def is_owned_by(self, user):
|
||||||
return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
|
if user.is_anonymous:
|
||||||
|
return False
|
||||||
|
return user.is_com_admin
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "⛩ Sith ⛩"
|
return "⛩ Sith ⛩"
|
||||||
@ -92,13 +94,15 @@ class News(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
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
|
if user.is_anonymous:
|
||||||
|
return False
|
||||||
|
return user.is_com_admin or user == self.author
|
||||||
|
|
||||||
def can_be_edited_by(self, user):
|
def can_be_edited_by(self, user):
|
||||||
return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
|
return user.is_com_admin
|
||||||
|
|
||||||
def can_be_viewed_by(self, user):
|
def can_be_viewed_by(self, user):
|
||||||
return self.is_moderated or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
|
return self.is_moderated or user.is_com_admin
|
||||||
|
|
||||||
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})
|
||||||
@ -243,7 +247,9 @@ class Weekmail(models.Model):
|
|||||||
return "Weekmail %s (sent: %s) - %s" % (self.id, self.sent, self.title)
|
return "Weekmail %s (sent: %s) - %s" % (self.id, self.sent, self.title)
|
||||||
|
|
||||||
def is_owned_by(self, user):
|
def is_owned_by(self, user):
|
||||||
return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
|
if user.is_anonymous:
|
||||||
|
return False
|
||||||
|
return user.is_com_admin
|
||||||
|
|
||||||
|
|
||||||
class WeekmailArticle(models.Model):
|
class WeekmailArticle(models.Model):
|
||||||
@ -271,7 +277,9 @@ class WeekmailArticle(models.Model):
|
|||||||
rank = models.IntegerField(_("rank"), default=-1)
|
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)
|
if user.is_anonymous:
|
||||||
|
return False
|
||||||
|
return user.is_com_admin
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s - %s (%s)" % (self.title, self.author, self.club)
|
return "%s - %s (%s)" % (self.title, self.author, self.club)
|
||||||
@ -287,7 +295,9 @@ class Screen(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def is_owned_by(self, user):
|
def is_owned_by(self, user):
|
||||||
return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
|
if user.is_anonymous:
|
||||||
|
return False
|
||||||
|
return user.is_com_admin
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s" % (self.name)
|
return "%s" % (self.name)
|
||||||
@ -340,12 +350,12 @@ 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(
|
if user.is_anonymous:
|
||||||
settings.SITH_GROUP_COM_ADMIN_ID
|
return False
|
||||||
) or Club.objects.filter(id__in=user.clubs_with_rights)
|
return user.is_com_admin or len(user.clubs_with_rights) > 0
|
||||||
|
|
||||||
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_com_admin
|
||||||
|
|
||||||
def get_display_name(self):
|
def get_display_name(self):
|
||||||
return self.club.get_display_name()
|
return self.club.get_display_name()
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
<p>{% trans %}Author: {% endtrans %}{{ user_profile_link(news.author) }}</p>
|
<p>{% trans %}Author: {% endtrans %}{{ user_profile_link(news.author) }}</p>
|
||||||
{% if news.moderator %}
|
{% if news.moderator %}
|
||||||
<p>{% trans %}Moderator: {% endtrans %}{{ user_profile_link(news.moderator) }}</p>
|
<p>{% trans %}Moderator: {% endtrans %}{{ user_profile_link(news.moderator) }}</p>
|
||||||
{% elif user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) %}
|
{% elif user.is_com_admin %}
|
||||||
<p> <a href="{{ url('com:news_moderate', news_id=news.id) }}">{% trans %}Moderate{% endtrans %}</a></p>
|
<p> <a href="{{ url('com:news_moderate', news_id=news.id) }}">{% trans %}Moderate{% endtrans %}</a></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if user.can_edit(news) %}
|
{% if user.can_edit(news) %}
|
||||||
|
@ -49,7 +49,7 @@
|
|||||||
<p>{{ form.club.errors }}<label for="{{ form.club.name }}">{{ form.club.label }}</label> {{ form.club }}</p>
|
<p>{{ form.club.errors }}<label for="{{ form.club.name }}">{{ form.club.label }}</label> {{ form.club }}</p>
|
||||||
<p>{{ form.summary.errors }}<label for="{{ form.summary.name }}">{{ form.summary.label }}</label> {{ form.summary }}</p>
|
<p>{{ form.summary.errors }}<label for="{{ form.summary.name }}">{{ form.summary.label }}</label> {{ form.summary }}</p>
|
||||||
<p>{{ form.content.errors }}<label for="{{ form.content.name }}">{{ form.content.label }}</label> {{ form.content }}</p>
|
<p>{{ form.content.errors }}<label for="{{ form.content.name }}">{{ form.content.label }}</label> {{ form.content }}</p>
|
||||||
{% if user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) %}
|
{% if user.is_com_admin %}
|
||||||
<p>{{ form.automoderation.errors }}<label for="{{ form.automoderation.name }}">{{ form.automoderation.label }}</label>
|
<p>{{ form.automoderation.errors }}<label for="{{ form.automoderation.name }}">{{ form.automoderation.label }}</label>
|
||||||
{{ form.automoderation }}</p>
|
{{ form.automoderation }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) %}
|
{% if user.is_com_admin %}
|
||||||
<div id="news_admin">
|
<div id="news_admin">
|
||||||
<a class="button" href="{{ url('com:news_admin_list') }}">{% trans %}Administrate news{% endtrans %}</a>
|
<a class="button" href="{{ url('com:news_admin_list') }}">{% trans %}Administrate news{% endtrans %}</a>
|
||||||
</div>
|
</div>
|
||||||
|
127
com/tests.py
127
com/tests.py
@ -13,22 +13,21 @@
|
|||||||
# OR WITHIN THE LOCAL FILE "LICENSE"
|
# OR WITHIN THE LOCAL FILE "LICENSE"
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from django.utils import html
|
from django.utils import html
|
||||||
|
from django.utils.timezone import localtime, now
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
from club.models import Club, Membership
|
||||||
from core.models import User, RealGroup
|
from com.models import Sith, News, Weekmail, WeekmailArticle, Poster
|
||||||
|
from core.models import User, RealGroup, AnonymousUser
|
||||||
|
|
||||||
|
|
||||||
class ComAlertTest(TestCase):
|
class ComAlertTest(TestCase):
|
||||||
def setUp(self):
|
|
||||||
call_command("populate")
|
|
||||||
|
|
||||||
def test_page_is_working(self):
|
def test_page_is_working(self):
|
||||||
self.client.login(username="comunity", password="plop")
|
self.client.login(username="comunity", password="plop")
|
||||||
response = self.client.get(reverse("com:alert_edit"))
|
response = self.client.get(reverse("com:alert_edit"))
|
||||||
@ -37,9 +36,6 @@ class ComAlertTest(TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class ComInfoTest(TestCase):
|
class ComInfoTest(TestCase):
|
||||||
def setUp(self):
|
|
||||||
call_command("populate")
|
|
||||||
|
|
||||||
def test_page_is_working(self):
|
def test_page_is_working(self):
|
||||||
self.client.login(username="comunity", password="plop")
|
self.client.login(username="comunity", password="plop")
|
||||||
response = self.client.get(reverse("com:info_edit"))
|
response = self.client.get(reverse("com:info_edit"))
|
||||||
@ -48,14 +44,16 @@ class ComInfoTest(TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class ComTest(TestCase):
|
class ComTest(TestCase):
|
||||||
def setUp(self):
|
@classmethod
|
||||||
call_command("populate")
|
def setUpTestData(cls):
|
||||||
self.skia = User.objects.filter(username="skia").first()
|
cls.skia = User.objects.filter(username="skia").first()
|
||||||
self.com_group = RealGroup.objects.filter(
|
cls.com_group = RealGroup.objects.filter(
|
||||||
id=settings.SITH_GROUP_COM_ADMIN_ID
|
id=settings.SITH_GROUP_COM_ADMIN_ID
|
||||||
).first()
|
).first()
|
||||||
self.skia.groups.set([self.com_group])
|
cls.skia.groups.set([cls.com_group])
|
||||||
self.skia.save()
|
cls.skia.save()
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
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):
|
||||||
@ -114,3 +112,102 @@ class ComTest(TestCase):
|
|||||||
_("You need an up to date subscription to access this content")
|
_("You need an up to date subscription to access this content")
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SithTest(TestCase):
|
||||||
|
def test_sith_owner(self):
|
||||||
|
"""
|
||||||
|
Test that the sith instance is owned by com admins
|
||||||
|
and nobody else
|
||||||
|
"""
|
||||||
|
sith: Sith = Sith.objects.first()
|
||||||
|
|
||||||
|
com_admin = User.objects.get(username="comunity")
|
||||||
|
self.assertTrue(sith.is_owned_by(com_admin))
|
||||||
|
|
||||||
|
anonymous = AnonymousUser()
|
||||||
|
self.assertFalse(sith.is_owned_by(anonymous))
|
||||||
|
|
||||||
|
sli = User.objects.get(username="sli")
|
||||||
|
self.assertFalse(sith.is_owned_by(sli))
|
||||||
|
|
||||||
|
|
||||||
|
class NewsTest(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
cls.com_admin = User.objects.get(username="comunity")
|
||||||
|
new = News.objects.create(
|
||||||
|
title="dummy new",
|
||||||
|
summary="This is a dummy new",
|
||||||
|
content="Look at that beautiful dummy new",
|
||||||
|
author=User.objects.get(username="subscriber"),
|
||||||
|
club=Club.objects.first(),
|
||||||
|
)
|
||||||
|
cls.new = new
|
||||||
|
cls.author = new.author
|
||||||
|
cls.sli = User.objects.get(username="sli")
|
||||||
|
cls.anonymous = AnonymousUser()
|
||||||
|
|
||||||
|
def test_news_owner(self):
|
||||||
|
"""
|
||||||
|
Test that news are owned by com admins
|
||||||
|
or by their author but nobody else
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.assertTrue(self.new.is_owned_by(self.com_admin))
|
||||||
|
self.assertTrue(self.new.is_owned_by(self.author))
|
||||||
|
self.assertFalse(self.new.is_owned_by(self.anonymous))
|
||||||
|
self.assertFalse(self.new.is_owned_by(self.sli))
|
||||||
|
|
||||||
|
|
||||||
|
class WeekmailArticleTest(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
cls.com_admin = User.objects.get(username="comunity")
|
||||||
|
author = User.objects.get(username="subscriber")
|
||||||
|
cls.article = WeekmailArticle.objects.create(
|
||||||
|
weekmail=Weekmail.objects.create(),
|
||||||
|
author=author,
|
||||||
|
title="title",
|
||||||
|
content="Some content",
|
||||||
|
club=Club.objects.first(),
|
||||||
|
)
|
||||||
|
cls.author = author
|
||||||
|
cls.sli = User.objects.get(username="sli")
|
||||||
|
cls.anonymous = AnonymousUser()
|
||||||
|
|
||||||
|
def test_weekmail_owner(self):
|
||||||
|
"""
|
||||||
|
Test that weekmails are owned only by com admins
|
||||||
|
"""
|
||||||
|
self.assertTrue(self.article.is_owned_by(self.com_admin))
|
||||||
|
self.assertFalse(self.article.is_owned_by(self.author))
|
||||||
|
self.assertFalse(self.article.is_owned_by(self.anonymous))
|
||||||
|
self.assertFalse(self.article.is_owned_by(self.sli))
|
||||||
|
|
||||||
|
|
||||||
|
class PosterTest(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
cls.com_admin = User.objects.get(username="comunity")
|
||||||
|
cls.poster = Poster.objects.create(
|
||||||
|
name="dummy",
|
||||||
|
file=SimpleUploadedFile("dummy.jpg", b"azertyuiop"),
|
||||||
|
club=Club.objects.first(),
|
||||||
|
date_begin=localtime(now()),
|
||||||
|
)
|
||||||
|
cls.sli = User.objects.get(username="sli")
|
||||||
|
cls.sli.memberships.all().delete()
|
||||||
|
Membership(user=cls.sli, club=Club.objects.first(), role=5).save()
|
||||||
|
cls.susbcriber = User.objects.get(username="subscriber")
|
||||||
|
cls.anonymous = AnonymousUser()
|
||||||
|
|
||||||
|
def test_poster_owner(self):
|
||||||
|
"""
|
||||||
|
Test that poster are owned by com admins and board members in clubs
|
||||||
|
"""
|
||||||
|
self.assertTrue(self.poster.is_owned_by(self.com_admin))
|
||||||
|
self.assertFalse(self.poster.is_owned_by(self.anonymous))
|
||||||
|
|
||||||
|
self.assertFalse(self.poster.is_owned_by(self.susbcriber))
|
||||||
|
self.assertTrue(self.poster.is_owned_by(self.sli))
|
||||||
|
15
com/views.py
15
com/views.py
@ -146,7 +146,7 @@ class ComTabsMixin(TabedViewMixin):
|
|||||||
|
|
||||||
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_com_admin:
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
return super(IsComAdminMixin, self).dispatch(request, *args, **kwargs)
|
return super(IsComAdminMixin, self).dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
@ -283,9 +283,7 @@ class NewsEditView(CanEditMixin, UpdateView):
|
|||||||
|
|
||||||
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(
|
if form.cleaned_data["automoderation"] and self.request.user.is_com_admin:
|
||||||
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()
|
||||||
@ -333,9 +331,7 @@ 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(
|
if form.cleaned_data["automoderation"] and self.request.user.is_com_admin:
|
||||||
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()
|
||||||
@ -617,10 +613,7 @@ class MailingListAdminView(ComTabsMixin, ListView):
|
|||||||
current_tab = "mailings"
|
current_tab = "mailings"
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
if not (
|
if not (request.user.is_com_admin or request.user.is_root):
|
||||||
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)
|
||||||
|
|
||||||
|
14
core/apps.py
14
core/apps.py
@ -25,6 +25,7 @@
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
from django.core.cache import cache
|
||||||
from django.core.signals import request_started
|
from django.core.signals import request_started
|
||||||
|
|
||||||
|
|
||||||
@ -33,26 +34,17 @@ class SithConfig(AppConfig):
|
|||||||
verbose_name = "Core app of the Sith"
|
verbose_name = "Core app of the Sith"
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
from core.models import User
|
|
||||||
from club.models import Club
|
|
||||||
from forum.models import Forum
|
from forum.models import Forum
|
||||||
|
import core.signals
|
||||||
|
|
||||||
def clear_cached_groups(**kwargs):
|
cache.clear()
|
||||||
User._group_ids = {}
|
|
||||||
User._group_name = {}
|
|
||||||
|
|
||||||
def clear_cached_memberships(**kwargs):
|
def clear_cached_memberships(**kwargs):
|
||||||
User._club_memberships = {}
|
|
||||||
Club._memberships = {}
|
|
||||||
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,
|
clear_cached_memberships,
|
||||||
weak=False,
|
weak=False,
|
||||||
dispatch_uid="clear_cached_memberships",
|
dispatch_uid="clear_cached_memberships",
|
||||||
)
|
)
|
||||||
# TODO: there may be a need to add more cache clearing
|
|
||||||
|
@ -39,6 +39,5 @@ class Command(compilemessages.Command):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
|
||||||
os.chdir("sith")
|
os.chdir("sith")
|
||||||
super(Command, self).handle(*args, **options)
|
super(Command, self).handle(*args, **options)
|
||||||
|
@ -60,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):
|
||||||
@ -68,7 +68,6 @@ class Command(BaseCommand):
|
|||||||
os.remove(file)
|
os.remove(file)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
|
||||||
if os.path.isdir(settings.STATIC_ROOT):
|
if os.path.isdir(settings.STATIC_ROOT):
|
||||||
print("---- Compiling scss files ---")
|
print("---- Compiling scss files ---")
|
||||||
self.exec_on_folder(settings.STATIC_ROOT, self.compilescss)
|
self.exec_on_folder(settings.STATIC_ROOT, self.compilescss)
|
||||||
|
@ -155,12 +155,10 @@ class Command(BaseCommand):
|
|||||||
Counter(name="Eboutic", club=main_club, type="EBOUTIC").save()
|
Counter(name="Eboutic", club=main_club, type="EBOUTIC").save()
|
||||||
Counter(name="AE", club=main_club, type="OFFICE").save()
|
Counter(name="AE", club=main_club, type="OFFICE").save()
|
||||||
|
|
||||||
home_root.view_groups.set(
|
ae_members = Group.objects.get(name=settings.SITH_MAIN_MEMBERS_GROUP)
|
||||||
[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first()]
|
|
||||||
)
|
home_root.view_groups.set([ae_members])
|
||||||
club_root.view_groups.set(
|
club_root.view_groups.set([ae_members])
|
||||||
[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first()]
|
|
||||||
)
|
|
||||||
home_root.save()
|
home_root.save()
|
||||||
club_root.save()
|
club_root.save()
|
||||||
|
|
||||||
@ -210,6 +208,8 @@ Welcome to the wiki page!
|
|||||||
|
|
||||||
# Here we add a lot of test datas, that are not necessary for the Sith, but that provide a basic development environment
|
# Here we add a lot of test datas, that are not necessary for the Sith, but that provide a basic development environment
|
||||||
if not options["prod"]:
|
if not options["prod"]:
|
||||||
|
self.now = timezone.now().replace(hour=12)
|
||||||
|
|
||||||
# Adding user Skia
|
# Adding user Skia
|
||||||
skia = User(
|
skia = User(
|
||||||
username="skia",
|
username="skia",
|
||||||
@ -220,9 +220,7 @@ Welcome to the wiki page!
|
|||||||
)
|
)
|
||||||
skia.set_password("plop")
|
skia.set_password("plop")
|
||||||
skia.save()
|
skia.save()
|
||||||
skia.view_groups = [
|
skia.view_groups = [ae_members.id]
|
||||||
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
|
|
||||||
]
|
|
||||||
skia.save()
|
skia.save()
|
||||||
skia_profile_path = (
|
skia_profile_path = (
|
||||||
root_path
|
root_path
|
||||||
@ -261,9 +259,7 @@ Welcome to the wiki page!
|
|||||||
)
|
)
|
||||||
public.set_password("plop")
|
public.set_password("plop")
|
||||||
public.save()
|
public.save()
|
||||||
public.view_groups = [
|
public.view_groups = [ae_members.id]
|
||||||
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
|
|
||||||
]
|
|
||||||
public.save()
|
public.save()
|
||||||
# Adding user Subscriber
|
# Adding user Subscriber
|
||||||
subscriber = User(
|
subscriber = User(
|
||||||
@ -277,9 +273,7 @@ Welcome to the wiki page!
|
|||||||
)
|
)
|
||||||
subscriber.set_password("plop")
|
subscriber.set_password("plop")
|
||||||
subscriber.save()
|
subscriber.save()
|
||||||
subscriber.view_groups = [
|
subscriber.view_groups = [ae_members.id]
|
||||||
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
|
|
||||||
]
|
|
||||||
subscriber.save()
|
subscriber.save()
|
||||||
# Adding user old Subscriber
|
# Adding user old Subscriber
|
||||||
old_subscriber = User(
|
old_subscriber = User(
|
||||||
@ -293,9 +287,7 @@ Welcome to the wiki page!
|
|||||||
)
|
)
|
||||||
old_subscriber.set_password("plop")
|
old_subscriber.set_password("plop")
|
||||||
old_subscriber.save()
|
old_subscriber.save()
|
||||||
old_subscriber.view_groups = [
|
old_subscriber.view_groups = [ae_members.id]
|
||||||
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
|
|
||||||
]
|
|
||||||
old_subscriber.save()
|
old_subscriber.save()
|
||||||
# Adding user Counter admin
|
# Adding user Counter admin
|
||||||
counter = User(
|
counter = User(
|
||||||
@ -309,9 +301,7 @@ Welcome to the wiki page!
|
|||||||
)
|
)
|
||||||
counter.set_password("plop")
|
counter.set_password("plop")
|
||||||
counter.save()
|
counter.save()
|
||||||
counter.view_groups = [
|
counter.view_groups = [ae_members.id]
|
||||||
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
|
|
||||||
]
|
|
||||||
counter.groups.set(
|
counter.groups.set(
|
||||||
[
|
[
|
||||||
Group.objects.filter(id=settings.SITH_GROUP_COUNTER_ADMIN_ID)
|
Group.objects.filter(id=settings.SITH_GROUP_COUNTER_ADMIN_ID)
|
||||||
@ -332,9 +322,7 @@ Welcome to the wiki page!
|
|||||||
)
|
)
|
||||||
comptable.set_password("plop")
|
comptable.set_password("plop")
|
||||||
comptable.save()
|
comptable.save()
|
||||||
comptable.view_groups = [
|
comptable.view_groups = [ae_members.id]
|
||||||
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
|
|
||||||
]
|
|
||||||
comptable.groups.set(
|
comptable.groups.set(
|
||||||
[
|
[
|
||||||
Group.objects.filter(id=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
|
Group.objects.filter(id=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
|
||||||
@ -355,9 +343,7 @@ Welcome to the wiki page!
|
|||||||
)
|
)
|
||||||
u.set_password("plop")
|
u.set_password("plop")
|
||||||
u.save()
|
u.save()
|
||||||
u.view_groups = [
|
u.view_groups = [ae_members.id]
|
||||||
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
|
|
||||||
]
|
|
||||||
u.save()
|
u.save()
|
||||||
# Adding user Richard Batsbak
|
# Adding user Richard Batsbak
|
||||||
richard = User(
|
richard = User(
|
||||||
@ -394,9 +380,7 @@ Welcome to the wiki page!
|
|||||||
richard_profile.save()
|
richard_profile.save()
|
||||||
richard.profile_pict = richard_profile
|
richard.profile_pict = richard_profile
|
||||||
richard.save()
|
richard.save()
|
||||||
richard.view_groups = [
|
richard.view_groups = [ae_members.id]
|
||||||
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
|
|
||||||
]
|
|
||||||
richard.save()
|
richard.save()
|
||||||
# Adding syntax help page
|
# Adding syntax help page
|
||||||
p = Page(name="Aide_sur_la_syntaxe")
|
p = Page(name="Aide_sur_la_syntaxe")
|
||||||
@ -428,7 +412,7 @@ Welcome to the wiki page!
|
|||||||
default_subscription = "un-semestre"
|
default_subscription = "un-semestre"
|
||||||
# Root
|
# Root
|
||||||
s = Subscription(
|
s = Subscription(
|
||||||
member=User.objects.filter(pk=root.pk).first(),
|
member=root,
|
||||||
subscription_type=default_subscription,
|
subscription_type=default_subscription,
|
||||||
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0],
|
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0],
|
||||||
)
|
)
|
||||||
@ -528,7 +512,7 @@ Welcome to the wiki page!
|
|||||||
Club(
|
Club(
|
||||||
name="Woenzel'UT", unix_name="woenzel", address="Woenzel", parent=guyut
|
name="Woenzel'UT", unix_name="woenzel", address="Woenzel", parent=guyut
|
||||||
).save()
|
).save()
|
||||||
Membership(user=skia, club=main_club, role=3, description="").save()
|
Membership(user=skia, club=main_club, role=3).save()
|
||||||
troll = Club(
|
troll = Club(
|
||||||
name="Troll Penché",
|
name="Troll Penché",
|
||||||
unix_name="troll",
|
unix_name="troll",
|
||||||
@ -855,9 +839,7 @@ Welcome to the wiki page!
|
|||||||
)
|
)
|
||||||
sli.set_password("plop")
|
sli.set_password("plop")
|
||||||
sli.save()
|
sli.save()
|
||||||
sli.view_groups = [
|
sli.view_groups = [ae_members.id]
|
||||||
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
|
|
||||||
]
|
|
||||||
sli.save()
|
sli.save()
|
||||||
sli_profile_path = (
|
sli_profile_path = (
|
||||||
root_path
|
root_path
|
||||||
@ -934,7 +916,7 @@ Welcome to the wiki page!
|
|||||||
Membership(
|
Membership(
|
||||||
user=comunity,
|
user=comunity,
|
||||||
club=bar_club,
|
club=bar_club,
|
||||||
start_date=timezone.now(),
|
start_date=self.now,
|
||||||
role=settings.SITH_CLUB_ROLES_ID["Board member"],
|
role=settings.SITH_CLUB_ROLES_ID["Board member"],
|
||||||
).save()
|
).save()
|
||||||
# Adding user tutu
|
# Adding user tutu
|
||||||
@ -1093,7 +1075,7 @@ Welcome to the wiki page!
|
|||||||
ForumTopic(forum=hall)
|
ForumTopic(forum=hall)
|
||||||
|
|
||||||
# News
|
# News
|
||||||
friday = timezone.now()
|
friday = self.now
|
||||||
while friday.weekday() != 4:
|
while friday.weekday() != 4:
|
||||||
friday += timedelta(hours=6)
|
friday += timedelta(hours=6)
|
||||||
friday.replace(hour=20, minute=0, second=0)
|
friday.replace(hour=20, minute=0, second=0)
|
||||||
@ -1111,8 +1093,8 @@ Welcome to the wiki page!
|
|||||||
n.save()
|
n.save()
|
||||||
NewsDate(
|
NewsDate(
|
||||||
news=n,
|
news=n,
|
||||||
start_date=timezone.now() + timedelta(hours=70),
|
start_date=self.now + timedelta(hours=70),
|
||||||
end_date=timezone.now() + timedelta(hours=72),
|
end_date=self.now + timedelta(hours=72),
|
||||||
).save()
|
).save()
|
||||||
n = News(
|
n = News(
|
||||||
title="Repas barman",
|
title="Repas barman",
|
||||||
@ -1128,8 +1110,8 @@ Welcome to the wiki page!
|
|||||||
n.save()
|
n.save()
|
||||||
NewsDate(
|
NewsDate(
|
||||||
news=n,
|
news=n,
|
||||||
start_date=timezone.now() + timedelta(hours=72),
|
start_date=self.now + timedelta(hours=72),
|
||||||
end_date=timezone.now() + timedelta(hours=84),
|
end_date=self.now + timedelta(hours=84),
|
||||||
).save()
|
).save()
|
||||||
n = News(
|
n = News(
|
||||||
title="Repas fromager",
|
title="Repas fromager",
|
||||||
@ -1144,8 +1126,8 @@ Welcome to the wiki page!
|
|||||||
n.save()
|
n.save()
|
||||||
NewsDate(
|
NewsDate(
|
||||||
news=n,
|
news=n,
|
||||||
start_date=timezone.now() + timedelta(hours=96),
|
start_date=self.now + timedelta(hours=96),
|
||||||
end_date=timezone.now() + timedelta(hours=100),
|
end_date=self.now + timedelta(hours=100),
|
||||||
).save()
|
).save()
|
||||||
n = News(
|
n = News(
|
||||||
title="SdF",
|
title="SdF",
|
||||||
@ -1161,7 +1143,7 @@ Welcome to the wiki page!
|
|||||||
NewsDate(
|
NewsDate(
|
||||||
news=n,
|
news=n,
|
||||||
start_date=friday + timedelta(hours=24 * 7 + 1),
|
start_date=friday + timedelta(hours=24 * 7 + 1),
|
||||||
end_date=timezone.now() + timedelta(hours=24 * 7 + 9),
|
end_date=self.now + timedelta(hours=24 * 7 + 9),
|
||||||
).save()
|
).save()
|
||||||
# Weekly
|
# Weekly
|
||||||
n = News(
|
n = News(
|
||||||
@ -1292,28 +1274,28 @@ Welcome to the wiki page!
|
|||||||
club=troll,
|
club=troll,
|
||||||
role=9,
|
role=9,
|
||||||
description="Padawan Troll",
|
description="Padawan Troll",
|
||||||
start_date=timezone.now() - timedelta(days=17),
|
start_date=self.now - timedelta(days=17),
|
||||||
).save()
|
).save()
|
||||||
Membership(
|
Membership(
|
||||||
user=krophil,
|
user=krophil,
|
||||||
club=troll,
|
club=troll,
|
||||||
role=10,
|
role=10,
|
||||||
description="Maitre Troll",
|
description="Maitre Troll",
|
||||||
start_date=timezone.now() - timedelta(days=200),
|
start_date=self.now - timedelta(days=200),
|
||||||
).save()
|
).save()
|
||||||
Membership(
|
Membership(
|
||||||
user=skia,
|
user=skia,
|
||||||
club=troll,
|
club=troll,
|
||||||
role=2,
|
role=2,
|
||||||
description="Grand Ancien Troll",
|
description="Grand Ancien Troll",
|
||||||
start_date=timezone.now() - timedelta(days=400),
|
start_date=self.now - timedelta(days=400),
|
||||||
end_date=timezone.now() - timedelta(days=86),
|
end_date=self.now - timedelta(days=86),
|
||||||
).save()
|
).save()
|
||||||
Membership(
|
Membership(
|
||||||
user=richard,
|
user=richard,
|
||||||
club=troll,
|
club=troll,
|
||||||
role=2,
|
role=2,
|
||||||
description="",
|
description="",
|
||||||
start_date=timezone.now() - timedelta(days=200),
|
start_date=self.now - timedelta(days=200),
|
||||||
end_date=timezone.now() - timedelta(days=100),
|
end_date=self.now - timedelta(days=100),
|
||||||
).save()
|
).save()
|
||||||
|
@ -22,9 +22,6 @@ from django.core.management import call_command
|
|||||||
class Command(BaseCommand):
|
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):
|
|
||||||
parser.add_argument("--prod", action="store_true")
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
root_path = os.path.dirname(
|
root_path = os.path.dirname(
|
||||||
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||||
@ -40,7 +37,4 @@ class Command(BaseCommand):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
repr(e)
|
repr(e)
|
||||||
call_command("migrate")
|
call_command("migrate")
|
||||||
if options["prod"]:
|
call_command("populate")
|
||||||
call_command("populate", "--prod")
|
|
||||||
else:
|
|
||||||
call_command("populate")
|
|
||||||
|
@ -12,7 +12,6 @@ import django.db.models.deletion
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("auth", "0006_require_contenttypes_0002")]
|
dependencies = [("auth", "0006_require_contenttypes_0002")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0001_initial")]
|
dependencies = [("core", "0001_initial")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -6,7 +6,6 @@ import django.core.validators
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0002_auto_20160831_0144")]
|
dependencies = [("core", "0002_auto_20160831_0144")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -6,7 +6,6 @@ from django.conf import settings
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0003_auto_20160902_1914")]
|
dependencies = [("core", "0003_auto_20160902_1914")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -7,7 +7,6 @@ import django.db.models.deletion
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0004_user_godfathers")]
|
dependencies = [("core", "0004_user_godfathers")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0005_auto_20161105_1035")]
|
dependencies = [("core", "0005_auto_20161105_1035")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0006_auto_20161108_1703")]
|
dependencies = [("core", "0006_auto_20161108_1703")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -7,7 +7,6 @@ import core.models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0008_sithfile_asked_for_removal")]
|
dependencies = [("core", "0008_sithfile_asked_for_removal")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0009_auto_20161120_1155")]
|
dependencies = [("core", "0009_auto_20161120_1155")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -7,7 +7,6 @@ import core.models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0010_sithfile_is_in_sas")]
|
dependencies = [("core", "0010_sithfile_is_in_sas")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -8,7 +8,6 @@ import django.db.models.deletion
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0011_auto_20161124_0848")]
|
dependencies = [("core", "0011_auto_20161124_0848")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0012_notification")]
|
dependencies = [("core", "0012_notification")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0013_auto_20161209_2338")]
|
dependencies = [("core", "0013_auto_20161209_2338")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -7,7 +7,6 @@ import django.db.models.deletion
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0014_auto_20161210_0009")]
|
dependencies = [("core", "0014_auto_20161210_0009")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -7,7 +7,6 @@ import django.db.models.deletion
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0015_sithfile_moderator")]
|
dependencies = [("core", "0015_sithfile_moderator")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0016_auto_20161212_1922")]
|
dependencies = [("core", "0016_auto_20161212_1922")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0017_auto_20161220_1626")]
|
dependencies = [("core", "0017_auto_20161220_1626")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0018_auto_20161224_0211")]
|
dependencies = [("core", "0018_auto_20161224_0211")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -6,7 +6,6 @@ import django.core.validators
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0019_preferences_receive_weekmail")]
|
dependencies = [("core", "0019_preferences_receive_weekmail")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0020_auto_20170324_0917")]
|
dependencies = [("core", "0020_auto_20170324_0917")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0021_auto_20170822_1529")]
|
dependencies = [("core", "0021_auto_20170822_1529")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -7,7 +7,6 @@ import django.db.models.deletion
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0022_auto_20170822_2232")]
|
dependencies = [("core", "0022_auto_20170822_2232")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0023_auto_20170902_1226")]
|
dependencies = [("core", "0023_auto_20170902_1226")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -6,7 +6,6 @@ import django.core.validators
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0024_auto_20170906_1317")]
|
dependencies = [("core", "0024_auto_20170906_1317")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0025_auto_20170919_1521")]
|
dependencies = [("core", "0025_auto_20170919_1521")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -8,7 +8,6 @@ import django.db.models.deletion
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0026_auto_20170926_1512")]
|
dependencies = [("core", "0026_auto_20170926_1512")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0027_gift")]
|
dependencies = [("core", "0027_gift")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -7,7 +7,6 @@ import django.db.models.deletion
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0028_auto_20171216_2044")]
|
dependencies = [("core", "0028_auto_20171216_2044")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -6,7 +6,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0029_auto_20180426_2013")]
|
dependencies = [("core", "0029_auto_20180426_2013")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -6,7 +6,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0030_auto_20190704_1500")]
|
dependencies = [("core", "0030_auto_20190704_1500")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -6,7 +6,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0031_auto_20190906_1615")]
|
dependencies = [("core", "0031_auto_20190906_1615")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -5,7 +5,6 @@ from django.db import migrations
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0032_auto_20190909_0043")]
|
dependencies = [("core", "0032_auto_20190909_0043")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -6,7 +6,6 @@ import django.db.models.deletion
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("core", "0033_auto_20191006_0049"),
|
("core", "0033_auto_20191006_0049"),
|
||||||
]
|
]
|
||||||
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("core", "0034_operationlog"),
|
("core", "0034_operationlog"),
|
||||||
]
|
]
|
||||||
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0035_auto_20200216_1743")]
|
dependencies = [("core", "0035_auto_20200216_1743")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0036_auto_20211001_0248")]
|
dependencies = [("core", "0036_auto_20211001_0248")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
341
core/models.py
341
core/models.py
@ -23,12 +23,12 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
import importlib
|
import importlib
|
||||||
|
from typing import Union, Optional, List
|
||||||
|
|
||||||
from django.db import models
|
from django.core.cache import cache
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
from django.contrib.auth.models import (
|
from django.contrib.auth.models import (
|
||||||
AbstractBaseUser,
|
AbstractBaseUser,
|
||||||
PermissionsMixin,
|
|
||||||
UserManager,
|
UserManager,
|
||||||
Group as AuthGroup,
|
Group as AuthGroup,
|
||||||
GroupManager as AuthGroupManager,
|
GroupManager as AuthGroupManager,
|
||||||
@ -40,7 +40,7 @@ from django.core import validators
|
|||||||
from django.core.exceptions import ValidationError, PermissionDenied
|
from django.core.exceptions import ValidationError, PermissionDenied
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import transaction
|
from django.db import models, transaction
|
||||||
from django.contrib.staticfiles.storage import staticfiles_storage
|
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
@ -50,7 +50,7 @@ from core import utils
|
|||||||
|
|
||||||
from phonenumber_field.modelfields import PhoneNumberField
|
from phonenumber_field.modelfields import PhoneNumberField
|
||||||
|
|
||||||
from datetime import datetime, timedelta, date
|
from datetime import timedelta, date
|
||||||
|
|
||||||
import unicodedata
|
import unicodedata
|
||||||
|
|
||||||
@ -90,14 +90,24 @@ class Group(AuthGroup):
|
|||||||
"""
|
"""
|
||||||
return reverse("core:group_list")
|
return reverse("core:group_list")
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
cache.set(f"sith_group_{self.id}", self)
|
||||||
|
cache.set(f"sith_group_{self.name.replace(' ', '_')}", self)
|
||||||
|
|
||||||
|
def delete(self, *args, **kwargs):
|
||||||
|
super().delete(*args, **kwargs)
|
||||||
|
cache.delete(f"sith_group_{self.id}")
|
||||||
|
cache.delete(f"sith_group_{self.name.replace(' ', '_')}")
|
||||||
|
|
||||||
|
|
||||||
class MetaGroup(Group):
|
class MetaGroup(Group):
|
||||||
"""
|
"""
|
||||||
MetaGroups are dynamically created groups.
|
MetaGroups are dynamically created groups.
|
||||||
Generaly used with clubs where creating a club creates two groups:
|
Generally used with clubs where creating a club creates two groups:
|
||||||
|
|
||||||
* club-SITH_BOARD_SUFFIX
|
* club-SITH_BOARD_SUFFIX
|
||||||
* club-SITH_MEMBER_SUFFIX
|
* club-SITH_MEMBER_SUFFIX
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#: Assign a manager in a way that MetaGroup.objects only return groups with is_meta=False
|
#: Assign a manager in a way that MetaGroup.objects only return groups with is_meta=False
|
||||||
@ -110,6 +120,32 @@ class MetaGroup(Group):
|
|||||||
super(MetaGroup, self).__init__(*args, **kwargs)
|
super(MetaGroup, self).__init__(*args, **kwargs)
|
||||||
self.is_meta = True
|
self.is_meta = True
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def associated_club(self):
|
||||||
|
"""
|
||||||
|
Return the group associated with this meta group
|
||||||
|
|
||||||
|
The result of this function is cached
|
||||||
|
|
||||||
|
:return: The associated club if it exists, else None
|
||||||
|
:rtype: club.models.Club | None
|
||||||
|
"""
|
||||||
|
from club.models import Club
|
||||||
|
|
||||||
|
if self.name.endswith(settings.SITH_BOARD_SUFFIX):
|
||||||
|
# replace this with str.removesuffix as soon as Python
|
||||||
|
# is upgraded to 3.10
|
||||||
|
club_name = self.name[: -len(settings.SITH_BOARD_SUFFIX)]
|
||||||
|
elif self.name.endswith(settings.SITH_MEMBER_SUFFIX):
|
||||||
|
club_name = self.name[: -len(settings.SITH_MEMBER_SUFFIX)]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
club = cache.get(f"sith_club_{club_name}")
|
||||||
|
if club is None:
|
||||||
|
club = Club.objects.filter(unix_name=club_name).first()
|
||||||
|
cache.set(f"sith_club_{club_name}", club)
|
||||||
|
return club
|
||||||
|
|
||||||
|
|
||||||
class RealGroup(Group):
|
class RealGroup(Group):
|
||||||
"""
|
"""
|
||||||
@ -134,6 +170,43 @@ def validate_promo(value):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_group(*, pk: int = None, name: str = None) -> Optional[Group]:
|
||||||
|
"""
|
||||||
|
Search for a group by its primary key or its name.
|
||||||
|
Either one of the two must be set.
|
||||||
|
|
||||||
|
The result is cached for the default duration (should be 5 minutes).
|
||||||
|
|
||||||
|
:param pk: The primary key of the group
|
||||||
|
:param name: The name of the group
|
||||||
|
:return: The group if it exists, else None
|
||||||
|
:raises ValueError: If no group matches the criteria
|
||||||
|
"""
|
||||||
|
if pk is None and name is None:
|
||||||
|
raise ValueError("Either pk or name must be set")
|
||||||
|
if name is not None:
|
||||||
|
name = name.replace(" ", "_") # avoid errors with memcached backend
|
||||||
|
pk_or_name: Union[str, int] = pk if pk is not None else name
|
||||||
|
group = cache.get(f"sith_group_{pk_or_name}")
|
||||||
|
if group == "not_found":
|
||||||
|
# Using None as a cache value is a little bit tricky,
|
||||||
|
# so we use a special string to represent None
|
||||||
|
return None
|
||||||
|
elif group is not None:
|
||||||
|
return group
|
||||||
|
# if this point is reached, the group is not in cache
|
||||||
|
if pk is not None:
|
||||||
|
group = Group.objects.filter(pk=pk).first()
|
||||||
|
else:
|
||||||
|
group = Group.objects.filter(name=name).first()
|
||||||
|
if group is not None:
|
||||||
|
cache.set(f"sith_group_{group.id}", group)
|
||||||
|
cache.set(f"sith_group_{group.name.replace(' ', '_')}", group)
|
||||||
|
else:
|
||||||
|
cache.set(f"sith_group_{pk_or_name}", "not_found")
|
||||||
|
return group
|
||||||
|
|
||||||
|
|
||||||
class User(AbstractBaseUser):
|
class User(AbstractBaseUser):
|
||||||
"""
|
"""
|
||||||
Defines the base user class, useable in every app
|
Defines the base user class, useable in every app
|
||||||
@ -295,7 +368,6 @@ class User(AbstractBaseUser):
|
|||||||
objects = UserManager()
|
objects = UserManager()
|
||||||
|
|
||||||
USERNAME_FIELD = "username"
|
USERNAME_FIELD = "username"
|
||||||
# REQUIRED_FIELDS = ['email']
|
|
||||||
|
|
||||||
def promo_has_logo(self):
|
def promo_has_logo(self):
|
||||||
return utils.file_exist("./core/static/core/img/promo_%02d.png" % self.promo)
|
return utils.file_exist("./core/static/core/img/promo_%02d.png" % self.promo)
|
||||||
@ -336,94 +408,72 @@ class User(AbstractBaseUser):
|
|||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
_club_memberships = {}
|
def is_in_group(self, *, pk: int = None, name: str = None) -> bool:
|
||||||
_group_names = {}
|
"""
|
||||||
_group_ids = {}
|
Check if this user is in the given group.
|
||||||
|
Either a group id or a group name must be provided.
|
||||||
|
If both are passed, only the id will be considered.
|
||||||
|
|
||||||
def is_in_group(self, group_name):
|
The group will be fetched using the given parameter.
|
||||||
"""If the user is in the group passed in argument (as string or by id)"""
|
If no group is found, return False.
|
||||||
group_id = 0
|
If a group is found, check if this user is in the latter.
|
||||||
g = None
|
|
||||||
if isinstance(group_name, int): # Handle the case where group_name is an ID
|
:return: True if the user is the group, else False
|
||||||
if group_name in User._group_ids.keys():
|
"""
|
||||||
g = User._group_ids[group_name]
|
if pk is not None:
|
||||||
else:
|
group: Optional[Group] = get_group(pk=pk)
|
||||||
g = Group.objects.filter(id=group_name).first()
|
elif name is not None:
|
||||||
User._group_ids[group_name] = g
|
group: Optional[Group] = get_group(name=name)
|
||||||
else:
|
|
||||||
if group_name in User._group_names.keys():
|
|
||||||
g = User._group_names[group_name]
|
|
||||||
else:
|
|
||||||
g = Group.objects.filter(name=group_name).first()
|
|
||||||
User._group_names[group_name] = g
|
|
||||||
if g:
|
|
||||||
group_name = g.name
|
|
||||||
group_id = g.id
|
|
||||||
else:
|
else:
|
||||||
|
raise ValueError("You must either provide the id or the name of the group")
|
||||||
|
if group is None:
|
||||||
return False
|
return False
|
||||||
if group_id == settings.SITH_GROUP_PUBLIC_ID:
|
if group.id == settings.SITH_GROUP_PUBLIC_ID:
|
||||||
return True
|
return True
|
||||||
if group_id == settings.SITH_GROUP_SUBSCRIBERS_ID:
|
if group.id == settings.SITH_GROUP_SUBSCRIBERS_ID:
|
||||||
return self.is_subscribed
|
return self.is_subscribed
|
||||||
if group_id == settings.SITH_GROUP_OLD_SUBSCRIBERS_ID:
|
if group.id == settings.SITH_GROUP_OLD_SUBSCRIBERS_ID:
|
||||||
return self.was_subscribed
|
return self.was_subscribed
|
||||||
if (
|
if group.id == settings.SITH_GROUP_ROOT_ID:
|
||||||
group_name == settings.SITH_MAIN_MEMBERS_GROUP
|
return self.is_root
|
||||||
): # We check the subscription if asked
|
if group.is_meta:
|
||||||
return self.is_subscribed
|
# check if this group is associated with a club
|
||||||
if group_name[-len(settings.SITH_BOARD_SUFFIX) :] == settings.SITH_BOARD_SUFFIX:
|
group.__class__ = MetaGroup
|
||||||
name = group_name[: -len(settings.SITH_BOARD_SUFFIX)]
|
club = group.associated_club
|
||||||
if name in User._club_memberships.keys():
|
if club is None:
|
||||||
mem = User._club_memberships[name]
|
return False
|
||||||
else:
|
membership = club.get_membership_for(self)
|
||||||
from club.models import Club
|
if membership is None:
|
||||||
|
return False
|
||||||
c = Club.objects.filter(unix_name=name).first()
|
if group.name.endswith(settings.SITH_MEMBER_SUFFIX):
|
||||||
mem = c.get_membership_for(self)
|
|
||||||
User._club_memberships[name] = mem
|
|
||||||
if mem:
|
|
||||||
return mem.role > settings.SITH_MAXIMUM_FREE_ROLE
|
|
||||||
return False
|
|
||||||
if (
|
|
||||||
group_name[-len(settings.SITH_MEMBER_SUFFIX) :]
|
|
||||||
== settings.SITH_MEMBER_SUFFIX
|
|
||||||
):
|
|
||||||
name = group_name[: -len(settings.SITH_MEMBER_SUFFIX)]
|
|
||||||
if name in User._club_memberships.keys():
|
|
||||||
mem = User._club_memberships[name]
|
|
||||||
else:
|
|
||||||
from club.models import Club
|
|
||||||
|
|
||||||
c = Club.objects.filter(unix_name=name).first()
|
|
||||||
mem = c.get_membership_for(self)
|
|
||||||
User._club_memberships[name] = mem
|
|
||||||
if mem:
|
|
||||||
return True
|
return True
|
||||||
return False
|
return membership.role > settings.SITH_MAXIMUM_FREE_ROLE
|
||||||
if group_id == settings.SITH_GROUP_ROOT_ID and self.is_superuser:
|
return group in self.cached_groups
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cached_groups(self) -> List[Group]:
|
||||||
|
"""
|
||||||
|
Get the list of groups this user is in.
|
||||||
|
The result is cached for the default duration (should be 5 minutes)
|
||||||
|
:return: A list of all the groups this user is in
|
||||||
|
"""
|
||||||
|
groups = cache.get(f"user_{self.id}_groups")
|
||||||
|
if groups is None:
|
||||||
|
groups = list(self.groups.all())
|
||||||
|
cache.set(f"user_{self.id}_groups", groups)
|
||||||
|
return groups
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def is_root(self) -> bool:
|
||||||
|
if self.is_superuser:
|
||||||
return True
|
return True
|
||||||
return group_name in self.cached_groups_names
|
root_id = settings.SITH_GROUP_ROOT_ID
|
||||||
|
return any(g.id == root_id for g in self.cached_groups)
|
||||||
@cached_property
|
|
||||||
def cached_groups_names(self):
|
|
||||||
return [g.name for g in self.groups.all()]
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def is_root(self):
|
|
||||||
return (
|
|
||||||
self.is_superuser
|
|
||||||
or self.groups.filter(id=settings.SITH_GROUP_ROOT_ID).exists()
|
|
||||||
)
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def is_board_member(self):
|
def is_board_member(self):
|
||||||
from club.models import Club
|
main_club = settings.SITH_MAIN_CLUB["unix_name"]
|
||||||
|
return self.is_in_group(name=main_club + settings.SITH_BOARD_SUFFIX)
|
||||||
return (
|
|
||||||
Club.objects.filter(unix_name=settings.SITH_MAIN_CLUB["unix_name"])
|
|
||||||
.first()
|
|
||||||
.has_rights_in_club(self)
|
|
||||||
)
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def can_read_subscription_history(self):
|
def can_read_subscription_history(self):
|
||||||
@ -434,8 +484,8 @@ class User(AbstractBaseUser):
|
|||||||
|
|
||||||
for club in Club.objects.filter(
|
for club in Club.objects.filter(
|
||||||
id__in=settings.SITH_CAN_READ_SUBSCRIPTION_HISTORY
|
id__in=settings.SITH_CAN_READ_SUBSCRIPTION_HISTORY
|
||||||
).all():
|
):
|
||||||
if club.has_rights_in_club(self):
|
if club in self.clubs_with_rights:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -443,10 +493,8 @@ class User(AbstractBaseUser):
|
|||||||
def can_create_subscription(self):
|
def can_create_subscription(self):
|
||||||
from club.models import Club
|
from club.models import Club
|
||||||
|
|
||||||
for club in Club.objects.filter(
|
for club in Club.objects.filter(id__in=settings.SITH_CAN_CREATE_SUBSCRIPTIONS):
|
||||||
id__in=settings.SITH_CAN_CREATE_SUBSCRIPTIONS
|
if club in self.clubs_with_rights:
|
||||||
).all():
|
|
||||||
if club.has_rights_in_club(self):
|
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -464,11 +512,11 @@ class User(AbstractBaseUser):
|
|||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def is_banned_alcohol(self):
|
def is_banned_alcohol(self):
|
||||||
return self.is_in_group(settings.SITH_GROUP_BANNED_ALCOHOL_ID)
|
return self.is_in_group(pk=settings.SITH_GROUP_BANNED_ALCOHOL_ID)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def is_banned_counter(self):
|
def is_banned_counter(self):
|
||||||
return self.is_in_group(settings.SITH_GROUP_BANNED_COUNTER_ID)
|
return self.is_in_group(pk=settings.SITH_GROUP_BANNED_COUNTER_ID)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def age(self) -> int:
|
def age(self) -> int:
|
||||||
@ -598,9 +646,9 @@ class User(AbstractBaseUser):
|
|||||||
"""
|
"""
|
||||||
if hasattr(obj, "is_owned_by") and obj.is_owned_by(self):
|
if hasattr(obj, "is_owned_by") and obj.is_owned_by(self):
|
||||||
return True
|
return True
|
||||||
if hasattr(obj, "owner_group") and self.is_in_group(obj.owner_group.name):
|
if hasattr(obj, "owner_group") and self.is_in_group(pk=obj.owner_group.id):
|
||||||
return True
|
return True
|
||||||
if self.is_superuser or self.is_in_group(settings.SITH_GROUP_ROOT_ID):
|
if self.is_root:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -611,8 +659,8 @@ class User(AbstractBaseUser):
|
|||||||
if hasattr(obj, "can_be_edited_by") and obj.can_be_edited_by(self):
|
if hasattr(obj, "can_be_edited_by") and obj.can_be_edited_by(self):
|
||||||
return True
|
return True
|
||||||
if hasattr(obj, "edit_groups"):
|
if hasattr(obj, "edit_groups"):
|
||||||
for g in obj.edit_groups.all():
|
for pk in obj.edit_groups.values_list("pk", flat=True):
|
||||||
if self.is_in_group(g.name):
|
if self.is_in_group(pk=pk):
|
||||||
return True
|
return True
|
||||||
if isinstance(obj, User) and obj == self:
|
if isinstance(obj, User) and obj == self:
|
||||||
return True
|
return True
|
||||||
@ -627,15 +675,15 @@ class User(AbstractBaseUser):
|
|||||||
if hasattr(obj, "can_be_viewed_by") and obj.can_be_viewed_by(self):
|
if hasattr(obj, "can_be_viewed_by") and obj.can_be_viewed_by(self):
|
||||||
return True
|
return True
|
||||||
if hasattr(obj, "view_groups"):
|
if hasattr(obj, "view_groups"):
|
||||||
for g in obj.view_groups.all():
|
for pk in obj.view_groups.values_list("pk", flat=True):
|
||||||
if self.is_in_group(g.name):
|
if self.is_in_group(pk=pk):
|
||||||
return True
|
return True
|
||||||
if self.can_edit(obj):
|
if self.can_edit(obj):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def can_be_edited_by(self, user):
|
def can_be_edited_by(self, user):
|
||||||
return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) or user.is_root
|
return user.is_root or user.is_board_member
|
||||||
|
|
||||||
def can_be_viewed_by(self, user):
|
def can_be_viewed_by(self, user):
|
||||||
return (user.was_subscribed and self.is_subscriber_viewable) or user.is_root
|
return (user.was_subscribed and self.is_subscriber_viewable) or user.is_root
|
||||||
@ -656,10 +704,6 @@ class User(AbstractBaseUser):
|
|||||||
escape(self.get_display_name()),
|
escape(self.get_display_name()),
|
||||||
)
|
)
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def subscribed(self):
|
|
||||||
return self.is_in_group(settings.SITH_MAIN_MEMBERS_GROUP)
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def preferences(self):
|
def preferences(self):
|
||||||
try:
|
try:
|
||||||
@ -682,17 +726,16 @@ class User(AbstractBaseUser):
|
|||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def clubs_with_rights(self):
|
def clubs_with_rights(self):
|
||||||
return [
|
"""
|
||||||
m.club.id
|
:return: the list of clubs where the user has rights
|
||||||
for m in self.memberships.filter(
|
:rtype: list[club.models.Club]
|
||||||
models.Q(end_date__isnull=True) | models.Q(end_date__gte=timezone.now())
|
"""
|
||||||
).all()
|
memberships = self.memberships.ongoing().board().select_related("club")
|
||||||
if m.club.has_rights_in_club(self)
|
return [m.club for m in memberships]
|
||||||
]
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def is_com_admin(self):
|
def is_com_admin(self):
|
||||||
return self.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
|
return self.is_in_group(pk=settings.SITH_GROUP_COM_ADMIN_ID)
|
||||||
|
|
||||||
|
|
||||||
class AnonymousUser(AuthAnonymousUser):
|
class AnonymousUser(AuthAnonymousUser):
|
||||||
@ -747,21 +790,18 @@ class AnonymousUser(AuthAnonymousUser):
|
|||||||
def favorite_topics(self):
|
def favorite_topics(self):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
def is_in_group(self, group_name):
|
def is_in_group(self, *, pk: int = None, name: str = None) -> bool:
|
||||||
"""
|
"""
|
||||||
The anonymous user is only the public group
|
The anonymous user is only in the public group
|
||||||
"""
|
"""
|
||||||
group_id = 0
|
allowed_id = settings.SITH_GROUP_PUBLIC_ID
|
||||||
if isinstance(group_name, int): # Handle the case where group_name is an ID
|
if pk is not None:
|
||||||
g = Group.objects.filter(id=group_name).first()
|
return pk == allowed_id
|
||||||
if g:
|
elif name is not None:
|
||||||
group_name = g.name
|
group = get_group(name=name)
|
||||||
group_id = g.id
|
return group is not None and group.id == allowed_id
|
||||||
else:
|
else:
|
||||||
return False
|
raise ValueError("You must either provide the id or the name of the group")
|
||||||
if group_id == settings.SITH_GROUP_PUBLIC_ID:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def is_owner(self, obj):
|
def is_owner(self, obj):
|
||||||
return False
|
return False
|
||||||
@ -879,14 +919,44 @@ class SithFile(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("file")
|
verbose_name = _("file")
|
||||||
|
|
||||||
def is_owned_by(self, user):
|
def can_be_managed_by(self, user: User) -> bool:
|
||||||
if hasattr(self, "profile_of") and user.is_in_group(
|
"""
|
||||||
settings.SITH_MAIN_BOARD_GROUP
|
Tell if the user can manage the file (edit, delete, etc.) or not.
|
||||||
|
Apply the following rules:
|
||||||
|
- If the file is not in the SAS nor in the profiles directory, it can be "managed" by anyone -> return True
|
||||||
|
- If the file is in the SAS, only the SAS admins (or roots) can manage it -> return True if the user is in the SAS admin group or is a root
|
||||||
|
- If the file is in the profiles directory, only the roots can manage it -> return True if the user is a root
|
||||||
|
|
||||||
|
:returns: True if the file is managed by the SAS or within the profiles directory, False otherwise
|
||||||
|
"""
|
||||||
|
|
||||||
|
# If the file is not in the SAS nor in the profiles directory, it can be "managed" by anyone
|
||||||
|
profiles_dir = SithFile.objects.filter(name="profiles").first()
|
||||||
|
if not self.is_in_sas and not profiles_dir in self.get_parent_list():
|
||||||
|
return True
|
||||||
|
|
||||||
|
# If the file is in the SAS, only the SAS admins (or roots) can manage it
|
||||||
|
if self.is_in_sas and (
|
||||||
|
user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID) or user.is_root
|
||||||
):
|
):
|
||||||
return True
|
return True
|
||||||
if user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID):
|
|
||||||
|
# If the file is in the profiles directory, only the roots can manage it
|
||||||
|
if profiles_dir in self.get_parent_list() and (
|
||||||
|
user.is_root or user.is_board_member
|
||||||
|
):
|
||||||
return True
|
return True
|
||||||
if self.is_in_sas and user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID):
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_owned_by(self, user):
|
||||||
|
if user.is_anonymous:
|
||||||
|
return False
|
||||||
|
if hasattr(self, "profile_of") and user.is_board_member:
|
||||||
|
return True
|
||||||
|
if user.is_com_admin:
|
||||||
|
return True
|
||||||
|
if self.is_in_sas and user.is_in_group(pk=settings.SITH_GROUP_SAS_ADMIN_ID):
|
||||||
return True
|
return True
|
||||||
return user.id == self.owner.id
|
return user.id == self.owner.id
|
||||||
|
|
||||||
@ -956,7 +1026,7 @@ class SithFile(models.Model):
|
|||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
sas = SithFile.objects.filter(id=settings.SITH_SAS_ROOT_DIR_ID).first()
|
sas = SithFile.objects.filter(id=settings.SITH_SAS_ROOT_DIR_ID).first()
|
||||||
self.is_in_sas = sas in self.get_parent_list()
|
self.is_in_sas = sas in self.get_parent_list() or self == sas
|
||||||
copy_rights = False
|
copy_rights = False
|
||||||
if self.id is None:
|
if self.id is None:
|
||||||
copy_rights = True
|
copy_rights = True
|
||||||
@ -1090,12 +1160,6 @@ class SithFile(models.Model):
|
|||||||
|
|
||||||
return Album.objects.filter(id=self.id).first()
|
return Album.objects.filter(id=self.id).first()
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
if self.is_folder:
|
|
||||||
return _("Folder: ") + self.name
|
|
||||||
else:
|
|
||||||
return _("File: ") + self.name
|
|
||||||
|
|
||||||
def get_parent_list(self):
|
def get_parent_list(self):
|
||||||
l = []
|
l = []
|
||||||
p = self.parent
|
p = self.parent
|
||||||
@ -1176,6 +1240,7 @@ class Page(models.Model):
|
|||||||
# Attention: this field may not be valid until you call save(). It's made for fast query, but don't rely on it when
|
# Attention: this field may not be valid until you call save(). It's made for fast query, but don't rely on it when
|
||||||
# playing with a Page object, use get_full_name() instead!
|
# playing with a Page object, use get_full_name() instead!
|
||||||
_full_name = models.CharField(_("page name"), max_length=255, blank=True)
|
_full_name = models.CharField(_("page name"), max_length=255, blank=True)
|
||||||
|
|
||||||
# This function prevents generating migration upon settings change
|
# This function prevents generating migration upon settings change
|
||||||
def get_default_owner_group():
|
def get_default_owner_group():
|
||||||
return settings.SITH_GROUP_ROOT_ID
|
return settings.SITH_GROUP_ROOT_ID
|
||||||
@ -1492,6 +1557,8 @@ class Gift(models.Model):
|
|||||||
return self.label
|
return self.label
|
||||||
|
|
||||||
def is_owned_by(self, user):
|
def is_owned_by(self, user):
|
||||||
|
if user.is_anonymous:
|
||||||
|
return False
|
||||||
return user.is_board_member or user.is_root
|
return user.is_board_member or user.is_root
|
||||||
|
|
||||||
|
|
||||||
|
17
core/signals.py
Normal file
17
core/signals.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from django.core.cache import cache
|
||||||
|
from django.db.models.signals import m2m_changed
|
||||||
|
from django.dispatch import receiver
|
||||||
|
|
||||||
|
from core.models import User
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(m2m_changed, sender=User.groups.through, dispatch_uid="user_groups_changed")
|
||||||
|
def user_groups_changed(sender, instance: User, **kwargs):
|
||||||
|
"""
|
||||||
|
Clear the cached clubs of the user
|
||||||
|
"""
|
||||||
|
# As a m2m relationship doesn't live within the model
|
||||||
|
# but rather on an intermediary table, there is no
|
||||||
|
# model method to override, meaning we must use
|
||||||
|
# a signal to invalidate the cache when a user is removed from a club
|
||||||
|
cache.delete(f"user_{instance.id}_groups")
|
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 434 KiB |
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user