2 Commits

Author SHA1 Message Date
858199e476 Modification of the weekmail template 2022-09-25 22:56:39 +02:00
f6ecbd899d Add of some modifications in the weekmail model 2022-09-25 22:55:39 +02:00
428 changed files with 13608 additions and 15558 deletions

View File

@ -1,8 +0,0 @@
name: "Compile messages"
description: "Compile the gettext translation messages"
runs:
using: composite
steps:
- name: Setup project
run: poetry run ./manage.py compilemessages
shell: bash

View File

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

View File

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

View File

@ -1,18 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"
# Raise pull requests for version updates
# to pip against the `develop` branch
target-branch: "taiste"
reviewers:
- "ae-utbm/developpers-v3"
commit-message:
prefix: "[UPDATE] "

View File

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

View File

@ -33,11 +33,11 @@ jobs:
# See https://github.com/ae-utbm/sith3/wiki/GitHub-Actions#deployment-action # See https://github.com/ae-utbm/sith3/wiki/GitHub-Actions#deployment-action
script: | script: |
export PATH="/home/sith/.local/bin:$PATH" export PATH="$HOME/.poetry/bin:$PATH"
pushd ${{secrets.SITH_PATH}} pushd ${{secrets.SITH_PATH}}
git pull git pull
poetry install poetry update
poetry run ./manage.py migrate poetry run ./manage.py migrate
echo "yes" | poetry run ./manage.py collectstatic echo "yes" | poetry run ./manage.py collectstatic
poetry run ./manage.py compilestatic poetry run ./manage.py compilestatic

View File

@ -1,62 +0,0 @@
name: Sith3 taiste
on:
push:
branches: [ taiste ]
jobs:
deployment:
runs-on: ubuntu-latest
environment: taiste
timeout-minutes: 30
steps:
- name: SSH Remote Commands
uses: appleboy/ssh-action@dce9d565de8d876c11d93fa4fe677c0285a66d78
with:
# Proxy
proxy_host : ${{secrets.PROXY_HOST}}
proxy_port : ${{secrets.PROXY_PORT}}
proxy_username : ${{secrets.PROXY_USER}}
proxy_passphrase: ${{secrets.PROXY_PASSPHRASE}}
proxy_key: ${{secrets.PROXY_KEY}}
# Serveur web
host: ${{secrets.HOST}}
port : ${{secrets.PORT}}
username : ${{secrets.USER}}
key: ${{secrets.KEY}}
script_stop: true
# See https://github.com/ae-utbm/sith3/wiki/GitHub-Actions#deployment-action
script: |
export PATH="$HOME/.poetry/bin:$PATH"
pushd ${{secrets.SITH_PATH}}
git pull
poetry install
poetry run ./manage.py migrate
echo "yes" | poetry run ./manage.py collectstatic
poetry run ./manage.py compilestatic
poetry run ./manage.py compilemessages
sudo systemctl restart uwsgi
sentry:
runs-on: ubuntu-latest
environment: taiste
timeout-minutes: 30
needs: deployment
steps:
- uses: actions/checkout@v3
- name: Sentry Release
uses: getsentry/action-release@v1.2.0
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
SENTRY_URL: ${{ secrets.SENTRY_URL }}
with:
environment: taiste

83
.github/workflows/unittests.yml vendored Normal file
View File

@ -0,0 +1,83 @@
name: Sith3 CI
on:
pull_request:
branches: [ master ]
push:
branches: [ master ]
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 .

2
.gitignore vendored
View File

@ -7,11 +7,9 @@ db.sqlite3
pyrightconfig.json pyrightconfig.json
dist/ dist/
.vscode/ .vscode/
.idea/
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/

View File

@ -16,4 +16,3 @@ Zar <antoine.charmeau@utbm.fr> <antoine.charmeau@laposte.net>
root <root@localhost.localdomain> root <root@localhost.localdomain>
tleb <tleb@openmailbox.org> <theo.lebrun@live.fr> tleb <tleb@openmailbox.org> <theo.lebrun@live.fr>
tleb <tleb@openmailbox.org> <theo.lebrun@utbm.fr> tleb <tleb@openmailbox.org> <theo.lebrun@utbm.fr>
Maréchal <thgirod@hotmail.com>

View File

@ -18,7 +18,7 @@ formats: all
# Optionally set the version of Python and requirements required to build your docs # Optionally set the version of Python and requirements required to build your docs
python: python:
version: "3.8" version: 3.8
install: install:
- method: pip - method: pip
path: . path: .

21
LICENSE.old Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Skia
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -9,7 +9,7 @@
<img src="https://img.shields.io/readthedocs/sith-ae?logo=readthedocs&style=for-the-badge"> <img src="https://img.shields.io/readthedocs/sith-ae?logo=readthedocs&style=for-the-badge">
</a> </a>
<a href="https://discord.gg/XK9WfPsUFm"> <a href="https://discord.gg/XK9WfPsUFm">
<img src="https://img.shields.io/discord/971448179075731476?label=Discord&logo=discord&style=for-the-badge"> <img src="https://img.shields.io/discord/889796155523874847?label=Discord&logo=discord&style=for-the-badge">
</a> </a>
</p> </p>
@ -37,4 +37,5 @@
</li> </li>
</ul> </ul>
> This project is licensed under GNU GPL, see the LICENSE file at the top of the repository for more details. > This project is licenced under GNU GPL, see the LICENSE file at the top of the repository for more details.

View File

@ -1,15 +1,23 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #

View File

@ -1,16 +1,24 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #

View File

@ -8,6 +8,7 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [] dependencies = []
operations = [ operations = [

View File

@ -6,6 +6,7 @@ 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"),

View File

@ -6,6 +6,7 @@ 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 = [

View File

@ -6,6 +6,7 @@ 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 = [

View File

@ -5,6 +5,7 @@ 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 = [

View File

@ -1,16 +1,24 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #
@ -66,7 +74,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(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID): if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
return True return True
return False return False
@ -117,9 +125,7 @@ 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_anonymous: if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
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"]:
@ -156,9 +162,7 @@ class ClubAccount(models.Model):
""" """
Method to see if that object can be edited by the given user Method to see if that object can be edited by the given user
""" """
if user.is_anonymous: if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
return False
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
return True return True
return False return False
@ -229,9 +233,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_anonymous: if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
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
@ -241,7 +243,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(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID): if user.is_in_group(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
@ -420,9 +422,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_anonymous: if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
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
@ -435,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(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID): if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
return True return True
if self.journal.closed: if self.journal.closed:
return False return False
@ -491,9 +491,7 @@ 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_anonymous: if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
return False
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
return True return True
return False return False
@ -564,8 +562,6 @@ 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):

View File

@ -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(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) and not object.club_accounts.exists() %} {% if user.is_in_group(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>

View File

@ -9,7 +9,7 @@
<h4> <h4>
{% trans %}Accounting{% endtrans %} {% trans %}Accounting{% endtrans %}
</h4> </h4>
{% if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %} {% if user.is_in_group(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>

View File

@ -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(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %} {% if user.is_in_group(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(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) and j.operations.count() == 0 %} {% if user.is_in_group(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>

View File

@ -6,12 +6,11 @@
{% block content %} {% block content %}
<div id="accounting"> <div id="accounting">
{% if user.is_root {% if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) or 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>

View File

@ -84,13 +84,10 @@
<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 o.journal.club_account.bank_account.name not in ["AE TI", "TI"] {% if not o.journal.closed %}
or user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) <a href="{{ url('accounting:op_edit', op_id=o.id) }}">{% trans %}Edit{% endtrans %}</a>
%} {% 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>

View File

@ -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(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %} {% if user.is_in_group(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(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %} {% if user.is_in_group(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 %}

View File

@ -1,16 +1,24 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #
@ -31,6 +39,7 @@ 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
@ -72,6 +81,7 @@ 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):
@ -99,6 +109,7 @@ 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"
) )

View File

@ -1,140 +1,154 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #
from django.urls import path from django.urls import re_path
from accounting.views import * from accounting.views import *
urlpatterns = [ urlpatterns = [
# Accounting types # Accounting types
path( re_path(
"simple_type/", r"^simple_type$",
SimplifiedAccountingTypeListView.as_view(), SimplifiedAccountingTypeListView.as_view(),
name="simple_type_list", name="simple_type_list",
), ),
path( re_path(
"simple_type/create/", r"^simple_type/create$",
SimplifiedAccountingTypeCreateView.as_view(), SimplifiedAccountingTypeCreateView.as_view(),
name="simple_type_new", name="simple_type_new",
), ),
path( re_path(
"simple_type/<int:type_id>/edit/", r"^simple_type/(?P<type_id>[0-9]+)/edit$",
SimplifiedAccountingTypeEditView.as_view(), SimplifiedAccountingTypeEditView.as_view(),
name="simple_type_edit", name="simple_type_edit",
), ),
# Accounting types # Accounting types
path("type/", AccountingTypeListView.as_view(), name="type_list"), re_path(r"^type$", AccountingTypeListView.as_view(), name="type_list"),
path("type/create/", AccountingTypeCreateView.as_view(), name="type_new"), re_path(r"^type/create$", AccountingTypeCreateView.as_view(), name="type_new"),
path( re_path(
"type/<int:type_id>/edit/", r"^type/(?P<type_id>[0-9]+)/edit$",
AccountingTypeEditView.as_view(), AccountingTypeEditView.as_view(),
name="type_edit", name="type_edit",
), ),
# Bank accounts # Bank accounts
path("", BankAccountListView.as_view(), name="bank_list"), re_path(r"^$", BankAccountListView.as_view(), name="bank_list"),
path("bank/create", BankAccountCreateView.as_view(), name="bank_new"), re_path(r"^bank/create$", BankAccountCreateView.as_view(), name="bank_new"),
path( re_path(
"bank/<int:b_account_id>/", r"^bank/(?P<b_account_id>[0-9]+)$",
BankAccountDetailView.as_view(), BankAccountDetailView.as_view(),
name="bank_details", name="bank_details",
), ),
path( re_path(
"bank/<int:b_account_id>/edit/", r"^bank/(?P<b_account_id>[0-9]+)/edit$",
BankAccountEditView.as_view(), BankAccountEditView.as_view(),
name="bank_edit", name="bank_edit",
), ),
path( re_path(
"bank/<int:b_account_id>/delete/", r"^bank/(?P<b_account_id>[0-9]+)/delete$",
BankAccountDeleteView.as_view(), BankAccountDeleteView.as_view(),
name="bank_delete", name="bank_delete",
), ),
# Club accounts # Club accounts
path("club/create/", ClubAccountCreateView.as_view(), name="club_new"), re_path(r"^club/create$", ClubAccountCreateView.as_view(), name="club_new"),
path( re_path(
"club/<int:c_account_id>/", r"^club/(?P<c_account_id>[0-9]+)$",
ClubAccountDetailView.as_view(), ClubAccountDetailView.as_view(),
name="club_details", name="club_details",
), ),
path( re_path(
"club/<int:c_account_id>/edit/", r"^club/(?P<c_account_id>[0-9]+)/edit$",
ClubAccountEditView.as_view(), ClubAccountEditView.as_view(),
name="club_edit", name="club_edit",
), ),
path( re_path(
"club/<int:c_account_id>/delete/", r"^club/(?P<c_account_id>[0-9]+)/delete$",
ClubAccountDeleteView.as_view(), ClubAccountDeleteView.as_view(),
name="club_delete", name="club_delete",
), ),
# Journals # Journals
path("journal/create/", JournalCreateView.as_view(), name="journal_new"), re_path(r"^journal/create$", JournalCreateView.as_view(), name="journal_new"),
path( re_path(
"journal/<int:j_id>/", r"^journal/(?P<j_id>[0-9]+)$",
JournalDetailView.as_view(), JournalDetailView.as_view(),
name="journal_details", name="journal_details",
), ),
path( re_path(
"journal/<int:j_id>/edit/", r"^journal/(?P<j_id>[0-9]+)/edit$",
JournalEditView.as_view(), JournalEditView.as_view(),
name="journal_edit", name="journal_edit",
), ),
path( re_path(
"journal/<int:j_id>/delete/", r"^journal/(?P<j_id>[0-9]+)/delete$",
JournalDeleteView.as_view(), JournalDeleteView.as_view(),
name="journal_delete", name="journal_delete",
), ),
path( re_path(
"journal/<int:j_id>/statement/nature/", r"^journal/(?P<j_id>[0-9]+)/statement/nature$",
JournalNatureStatementView.as_view(), JournalNatureStatementView.as_view(),
name="journal_nature_statement", name="journal_nature_statement",
), ),
path( re_path(
"journal/<int:j_id>/statement/person/", r"^journal/(?P<j_id>[0-9]+)/statement/person$",
JournalPersonStatementView.as_view(), JournalPersonStatementView.as_view(),
name="journal_person_statement", name="journal_person_statement",
), ),
path( re_path(
"journal/<int:j_id>/statement/accounting/", r"^journal/(?P<j_id>[0-9]+)/statement/accounting$",
JournalAccountingStatementView.as_view(), JournalAccountingStatementView.as_view(),
name="journal_accounting_statement", name="journal_accounting_statement",
), ),
# Operations # Operations
path( re_path(
"operation/create/<int:j_id>/", r"^operation/create/(?P<j_id>[0-9]+)$",
OperationCreateView.as_view(), OperationCreateView.as_view(),
name="op_new", name="op_new",
), ),
path("operation/<int:op_id>/", OperationEditView.as_view(), name="op_edit"), re_path(
path("operation/<int:op_id>/pdf/", OperationPDFView.as_view(), name="op_pdf"), r"^operation/(?P<op_id>[0-9]+)$", OperationEditView.as_view(), name="op_edit"
),
re_path(
r"^operation/(?P<op_id>[0-9]+)/pdf$", OperationPDFView.as_view(), name="op_pdf"
),
# Companies # Companies
path("company/list/", CompanyListView.as_view(), name="co_list"), re_path(r"^company/list$", CompanyListView.as_view(), name="co_list"),
path("company/create/", CompanyCreateView.as_view(), name="co_new"), re_path(r"^company/create$", CompanyCreateView.as_view(), name="co_new"),
path("company/<int:co_id>/", CompanyEditView.as_view(), name="co_edit"), re_path(r"^company/(?P<co_id>[0-9]+)$", CompanyEditView.as_view(), name="co_edit"),
# Labels # Labels
path("label/new/", LabelCreateView.as_view(), name="label_new"), re_path(r"^label/new$", LabelCreateView.as_view(), name="label_new"),
path( re_path(
"label/<int:clubaccount_id>/", r"^label/(?P<clubaccount_id>[0-9]+)$",
LabelListView.as_view(), LabelListView.as_view(),
name="label_list", name="label_list",
), ),
path("label/<int:label_id>/edit/", LabelEditView.as_view(), name="label_edit"), re_path(
path( r"^label/(?P<label_id>[0-9]+)/edit$", LabelEditView.as_view(), name="label_edit"
"label/<int:label_id>/delete/", ),
re_path(
r"^label/(?P<label_id>[0-9]+)/delete$",
LabelDeleteView.as_view(), LabelDeleteView.as_view(),
name="label_delete", name="label_delete",
), ),
# User account # User account
path("refound/account/", RefoundAccountView.as_view(), name="refound_account"), re_path(r"^refound/account$", RefoundAccountView.as_view(), name="refound_account"),
] ]

View File

@ -1,16 +1,24 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #
@ -891,7 +899,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(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID): if user.is_root or user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
return True return True
else: else:
raise PermissionDenied raise PermissionDenied

View File

@ -1,15 +1,23 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #

View File

@ -1,16 +1,24 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #

View File

@ -1,16 +1,24 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #

View File

@ -1,16 +1,24 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #

View File

@ -1,16 +1,24 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #

View File

@ -1,16 +1,24 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #

View File

@ -1,16 +1,24 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #

View File

@ -1,16 +1,24 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #

View File

@ -1,16 +1,24 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #
@ -24,6 +32,7 @@ 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

View File

@ -1,16 +1,24 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #

View File

@ -1,16 +1,24 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #
@ -24,6 +32,7 @@ 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
) )

View File

@ -1,16 +1,24 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #

View File

@ -1,15 +1,23 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #

View File

@ -1,36 +1,31 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #
from ajax_select import make_ajax_form
from django.contrib import admin from django.contrib import admin
from club.models import Club, Membership from club.models import Club, Membership
@admin.register(Club) admin.site.register(Club)
class ClubAdmin(admin.ModelAdmin): admin.site.register(Membership)
list_display = ("name", "unix_name", "parent", "is_active")
@admin.register(Membership)
class MembershipAdmin(admin.ModelAdmin):
list_display = ("user", "club", "role", "start_date", "end_date")
search_fields = (
"user__username",
"user__first_name",
"user__last_name",
"club__name",
)
form = make_ajax_form(Membership, {"user": "users"})

View File

@ -167,6 +167,7 @@ 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(),
@ -229,7 +230,9 @@ 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(self.request_user) if ms.can_be_edited_by(
self.request_user, self.request_user_membership
)
] ]
).all(), ).all(),
label=_("Mark as old"), label=_("Mark as old"),

View File

@ -7,6 +7,7 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [] dependencies = []
operations = [ operations = [

View File

@ -7,6 +7,7 @@ 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"),

View File

@ -5,6 +5,7 @@ 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 = [

View File

@ -7,6 +7,7 @@ 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 = [

View File

@ -6,6 +6,7 @@ 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 = [

View File

@ -6,6 +6,7 @@ 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 = [

View File

@ -5,6 +5,7 @@ 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 = [

View File

@ -5,6 +5,7 @@ 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 = [

View File

@ -9,6 +9,7 @@ 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"),

View File

@ -19,6 +19,7 @@ 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 = [

View File

@ -5,6 +5,7 @@ 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 = [

View File

@ -7,6 +7,7 @@ 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 = [

View File

@ -22,14 +22,10 @@
# 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
@ -76,7 +72,6 @@ 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
@ -127,22 +122,12 @@ class Club(models.Model):
def clean(self): def clean(self):
self.check_loop() self.check_loop()
def _change_unixname(self, old_name, new_name): def _change_unixname(self, 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"))
@ -186,34 +171,29 @@ 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):
old = Club.objects.filter(id=self.id).first() with transaction.atomic():
creation = old is None creation = False
if not creation and old.unix_name != self.unix_name: old = Club.objects.filter(id=self.id).first()
self._change_unixname(self.unix_name) if not old:
super(Club, self).save(*args, **kwargs) creation = True
if creation: else:
board = MetaGroup(name=self.unix_name + settings.SITH_BOARD_SUFFIX) if old.unix_name != self.unix_name:
board.save() self._change_unixname(self.unix_name)
member = MetaGroup(name=self.unix_name + settings.SITH_MEMBER_SUFFIX) super(Club, self).save(*args, **kwargs)
member.save() if creation:
subscribers = Group.objects.filter( board = MetaGroup(name=self.unix_name + settings.SITH_BOARD_SUFFIX)
name=settings.SITH_MAIN_MEMBERS_GROUP board.save()
).first() member = MetaGroup(name=self.unix_name + settings.SITH_MEMBER_SUFFIX)
self.make_home() member.save()
self.home.edit_groups.set([board]) subscribers = Group.objects.filter(
self.home.view_groups.set([member, subscribers]) name=settings.SITH_MAIN_MEMBERS_GROUP
self.home.save() ).first()
self.make_page() self.make_home()
cache.set(f"sith_club_{self.unix_name}", self) self.home.edit_groups.set([board])
self.home.view_groups.set([member, subscribers])
def delete(self, *args, **kwargs): self.home.save()
super().delete(*args, **kwargs) self.make_page()
# 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
@ -228,9 +208,7 @@ 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
""" """
if user.is_anonymous: return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP)
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)
@ -250,89 +228,28 @@ class Club(models.Model):
return False return False
return sub.was_subscribed return sub.was_subscribed
def get_membership_for(self, user: User) -> Optional["Membership"]: _memberships = {}
def get_membership_for(self, user):
""" """
Return the current membership the given user. Returns the current membership the given user
The result is cached.
""" """
if user.is_anonymous: try:
return None return Club._memberships[self.id][user.id]
membership = cache.get(f"membership_{self.id}_{user.id}") except:
if membership == "not_member": m = self.members.filter(user=user.id).filter(end_date=None).first()
return None try:
if membership is None: Club._memberships[self.id][user.id] = m
membership = self.members.filter(user=user, end_date=None).first() except:
if membership is None: Club._memberships[self.id] = {}
cache.set(f"membership_{self.id}_{user.id}", "not_member") Club._memberships[self.id][user.id] = m
else: return m
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
@ -372,8 +289,6 @@ 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
@ -388,34 +303,24 @@ 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
""" """
if user.is_anonymous: return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP)
return False
return user.is_board_member
def can_be_edited_by(self, user: User) -> bool: def can_be_edited_by(self, user, membership=None):
""" """
Check 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_root or user.is_board_member: if user.memberships:
return True if membership: # This is for optimisation purpose
membership = self.club.get_membership_for(user) ms = membership
if membership is not None and membership.role >= self.role: else:
return True ms = user.memberships.filter(club=self.club, end_date=None).first()
return False return (ms and ms.role >= self.role) or user.is_in_group(
settings.SITH_MAIN_BOARD_GROUP
)
return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP)
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):
@ -468,12 +373,14 @@ 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_com_admin return user.is_root or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
def is_owned_by(self, user): def is_owned_by(self, user):
if user.is_anonymous: return (
return False user.is_in_group(self)
return user.is_root or user.is_com_admin or user.is_root
or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
)
def can_view(self, user): def can_view(self, user):
return self.club.has_rights_in_club(user) return self.club.has_rights_in_club(user)
@ -481,8 +388,9 @@ 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, *args, **kwargs): def delete(self):
self.subscriptions.all().delete() for sub in self.subscriptions.all():
sub.delete()
super(Mailing, self).delete() super(Mailing, self).delete()
def fetch_format(self): def fetch_format(self):
@ -555,12 +463,10 @@ 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_com_admin or self.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
) )
def can_be_edited_by(self, user): def can_be_edited_by(self, user):

View File

@ -13,15 +13,13 @@
{% endif %} {% endif %}
<table> <table>
<thead> <thead>
<tr> <td>{% trans %}User{% endtrans %}</td>
<td>{% trans %}User{% endtrans %}</td> <td>{% trans %}Role{% endtrans %}</td>
<td>{% trans %}Role{% endtrans %}</td> <td>{% trans %}Description{% endtrans %}</td>
<td>{% trans %}Description{% endtrans %}</td> <td>{% trans %}Since{% endtrans %}</td>
<td>{% trans %}Since{% endtrans %}</td> {% if users_old %}
{% if users_old %} <td>{% trans %}Mark as old{% endtrans %}</td>
<td>{% trans %}Mark as old{% endtrans %}</td> {% endif %}
{% endif %}
</tr>
</thead> </thead>
<tbody> <tbody>
{% for m in members %} {% for m in members %}

View File

@ -1,576 +1,396 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #
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, AnonymousUser from core.models import User
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, 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):
# by default, Skia is in the AE, which creates side effect call_command("populate")
self.skia.memberships.all().delete() self.skia = User.objects.filter(username="skia").first()
self.rbatsbak = User.objects.filter(username="rbatsbak").first()
self.guy = User.objects.filter(username="guy").first()
self.bdf = Club.objects.filter(unix_name="bdf").first()
# create a fake club def test_create_add_user_to_club_from_root_ok(self):
self.club = Club.objects.create(
name="Fake Club",
unix_name="fake-club",
address="5 rue de la République, 90000 Belfort",
)
self.members_url = reverse(
"club:club_members", kwargs={"club_id": self.club.id}
)
a_month_ago = now() - timedelta(days=30)
yesterday = now() - timedelta(days=1)
Membership.objects.create(
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
)
# 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")
response = self.client.post(
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": f"|{self.subscriber.id}|{self.krophil.id}|",
"role": 3,
},
)
self.assertRedirects(response, self.members_url)
self.subscriber.refresh_from_db()
self.assert_membership_just_started(self.subscriber, role=3)
self.assert_membership_just_started(self.krophil, role=3)
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")
response = self.client.post(
self.members_url,
{"users": self.public.id, "role": 1},
)
self.assertIsNone(self.public.memberships.filter(club=self.club).first())
self.assertTrue('<ul class="errorlist"><li>' in str(response.content))
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")
current_membership = self.skia.memberships.ongoing().get(club=self.club)
nb_memberships = self.skia.memberships.count()
self.client.post( self.client.post(
self.members_url, reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
{"users": self.skia.id, "role": current_membership.role + 1}, {"users": self.skia.id, "start_date": "12/06/2016", "role": 3},
)
response = self.client.get(
reverse("club:club_members", kwargs={"club_id": self.bdf.id})
)
self.assertTrue(response.status_code == 200)
self.assertTrue(
"S&#39; Kia</a></td>\\n <td>Responsable info</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_add_not_existing_users(self): def test_create_add_multiple_user_to_club_from_root_ok(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() self.client.post(
response = self.client.post( reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
self.members_url,
{"users": [9999], "role": 1},
)
self.assertContains(response, '<ul class="errorlist"><li>')
self.club.refresh_from_db()
self.assertEqual(self.club.members.count(), nb_memberships)
response = self.client.post(
self.members_url,
{ {
"users": f"|{self.subscriber.id}|{9999}|", "users": "|%d|%d|" % (self.skia.id, self.rbatsbak.id),
"start_date": "12/06/2016", "start_date": "12/06/2016",
"role": 3, "role": 3,
}, },
) )
self.assertContains(response, '<ul class="errorlist"><li>') response = self.client.get(
self.club.refresh_from_db() reverse("club:club_members", kwargs={"club_id": self.bdf.id})
self.assertEqual(self.club.members.count(), nb_memberships) )
self.assertTrue(response.status_code == 200)
content = str(response.content)
self.assertTrue(
"S&#39; 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_president_add_members(self): def test_create_add_user_to_club_from_root_fail_not_subscriber(self):
"""
Test that the president of the club can add members
"""
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.assertRedirects(response, self.members_url)
self.club.refresh_from_db()
self.subscriber.refresh_from_db()
self.assertEqual(self.club.members.count(), nb_club_membership + 1)
self.assertEqual(
self.subscriber.memberships.count(), nb_subscriber_memberships + 1
)
self.assert_membership_just_started(self.subscriber, role=9)
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.assertInHTML(
"<li>Vous n'avez pas la permission de faire cela</li>",
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_add_member_without_role(self):
"""
Test that trying to add members without specifying their role fails
"""
self.client.login(username="root", password="plop") self.client.login(username="root", password="plop")
response = self.client.post( response = self.client.post(
self.members_url, reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
{"users": self.subscriber.id, "start_date": "12/06/2016"}, {"users": self.guy.id, "start_date": "12/06/2016", "role": 3},
)
self.assertTrue(response.status_code == 200)
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):
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},
)
response = self.client.get(
reverse("club:club_members", kwargs={"club_id": self.bdf.id})
)
self.assertTrue(
"S&#39; 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&#39; Kia</a></td>\\n <td>Secrétaire</td>"
in str(response.content)
)
def test_create_add_user_non_existent_to_club_from_root_fail(self):
self.client.login(username="root", password="plop")
response = self.client.post(
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
{"users": [9999], "start_date": "12/06/2016", "role": 3},
)
self.assertTrue(response.status_code == 200)
content = str(response.content)
self.assertTrue('<ul class="errorlist"><li>' in content)
self.assertFalse("<td>Responsable info</td>" in content)
self.client.login(username="root", password="plop")
response = self.client.post(
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
{
"users": "|%d|%d|" % (self.skia.id, 9999),
"start_date": "12/06/2016",
"role": 3,
},
)
self.assertTrue(response.status_code == 200)
content = str(response.content)
self.assertTrue('<ul class="errorlist"><li>' in content)
self.assertFalse("<td>Responsable info</td>" in content)
def test_create_add_user_to_club_from_skia_ok(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": 10},
)
self.client.login(username="skia", 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": 9},
)
response = self.client.get(
reverse("club:club_members", kwargs={"club_id": self.bdf.id})
)
self.assertEqual(response.status_code, 200)
self.assertIn(
"""Richard Batsbak</a></td>\n <td>Vice-Président⸱e</td>""",
response.content.decode(),
)
def test_create_add_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.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&#x27;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")
response = self.client.post(
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
{"users": self.rbatsbak.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_end_membership_self(self): def test_mark_old_user_to_club_from_skia_ok(self):
""" self.client.login(username="root", password="plop")
Test that a member can end its own membership
"""
self.client.login(username="skia", password="plop")
self.client.post( self.client.post(
self.members_url, 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")
response = self.client.post(
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
{"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&#39; 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")
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)
self.assertEqual(self.club.members.count(), nb_memberships)
def test_end_membership_as_foreigner(self):
"""
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(
self.members_url, reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
{"users_old": [self.richard.id]}, {"users": self.rbatsbak.id, "start_date": "12/06/2016", "role": 3},
)
self.client.login(username="skia", password="plop")
response = self.client.post(
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
{"users_old": self.rbatsbak.id},
)
self.assertFalse(
"Richard Batsbak</a></td>\\n <td>Responsable info</td>"
in str(response.content)
) )
# nothing should have changed
new_mem = self.club.get_membership_for(self.richard)
self.assertIsNotNone(new_mem)
self.assertEqual(self.club.members.count(), nb_memberships)
self.assertEqual(membership, new_mem)
def test_delete_remove_from_meta_group(self): def test_mark_old_multiple_users_from_skia_ok(self):
""" self.client.login(username="root", password="plop")
Test that when a club is deleted, all its members are removed from the self.client.post(
associated metagroup reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
""" {
memberships = self.club.members.select_related("user") "users": "|%d|%d|" % (self.skia.id, self.rbatsbak.id),
users = [membership.user for membership in memberships] "start_date": "12/06/2016",
meta_group = self.club.unix_name + settings.SITH_MEMBER_SUFFIX "role": 3,
},
)
self.client.login(username="skia", password="plop")
response = self.client.post(
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
{"users_old": [self.rbatsbak.id, self.skia.id]},
)
self.assertTrue(response.status_code == 302)
self.club.delete() response = self.client.get(
for user in users: reverse("club:club_members", kwargs={"club_id": self.bdf.id})
self.assertFalse(user.is_in_group(name=meta_group)) )
self.assertTrue(response.status_code == 200)
content = str(response.content)
self.assertFalse(
"Richard Batsbak</a></td>\\n <td>Responsable info</td>"
in content
)
self.assertFalse(
"S&#39; Kia</a></td>\\n <td>Responsable info</td>"
in content
)
def test_add_to_meta_group(self): def test_mark_old_user_to_club_from_richard_ok(self):
""" self.client.login(username="root", password="plop")
Test that when a membership begins, the user is added to the meta group self.client.post(
""" reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
group_members = self.club.unix_name + settings.SITH_MEMBER_SUFFIX {
board_members = self.club.unix_name + settings.SITH_BOARD_SUFFIX "users": "|%d|%d|" % (self.skia.id, self.rbatsbak.id),
self.assertFalse(self.subscriber.is_in_group(name=group_members)) "start_date": "12/06/2016",
self.assertFalse(self.subscriber.is_in_group(name=board_members)) "role": 3,
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))
def test_remove_from_meta_group(self): # Test with equal rights
""" self.client.login(username="rbatsbak", password="plop")
Test that when a membership ends, the user is removed from meta group response = self.client.post(
""" reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
group_members = self.club.unix_name + settings.SITH_MEMBER_SUFFIX {"users_old": self.skia.id},
board_members = self.club.unix_name + settings.SITH_BOARD_SUFFIX )
self.assertTrue(self.comptable.is_in_group(name=group_members)) self.assertTrue(response.status_code == 302)
self.assertTrue(self.comptable.is_in_group(name=board_members))
self.comptable.memberships.update(end_date=localtime(now()))
self.assertFalse(self.comptable.is_in_group(name=group_members))
self.assertFalse(self.comptable.is_in_group(name=board_members))
def test_club_owner(self): response = self.client.get(
""" reverse("club:club_members", kwargs={"club_id": self.bdf.id})
Test that a club is owned only by board members of the main club )
""" self.assertTrue(response.status_code == 200)
anonymous = AnonymousUser() content = str(response.content)
self.assertFalse(self.club.is_owned_by(anonymous)) self.assertTrue(
self.assertFalse(self.club.is_owned_by(self.subscriber)) "Richard Batsbak</a></td>\\n <td>Responsable info</td>"
in content
)
self.assertFalse(
"S&#39; Kia</a></td>\\n <td>Responsable info</td>"
in content
)
# make sli a board member # Test with lower rights
self.sli.memberships.all().delete() self.client.post(
Membership(club=self.ae, user=self.sli, role=3).save() reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
self.assertTrue(self.club.is_owned_by(self.sli)) {"users": self.skia.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.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&#39; 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&#39; 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&#39; 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="bdf").first()
Membership( Membership(
user=self.rbatsbak, user=self.rbatsbak,
club=self.bdf, club=self.bdf,
@ -885,6 +705,7 @@ 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):

View File

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

View File

@ -306,7 +306,9 @@ class ClubMembersView(ClubTabsMixin, CanViewMixin, DetailFormView):
return resp return resp
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
self.members = self.get_object().members.ongoing().order_by("-role") self.members = (
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):
@ -441,6 +443,7 @@ 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)
@ -703,6 +706,7 @@ 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"
@ -720,6 +724,7 @@ 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"

View File

@ -1,15 +1,23 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #

View File

@ -1,49 +1,43 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #
from ajax_select import make_ajax_form
from django.contrib import admin from django.contrib import admin
from haystack.admin import SearchModelAdmin from haystack.admin import SearchModelAdmin
from com.models import * from com.models import *
@admin.register(News)
class NewsAdmin(SearchModelAdmin): class NewsAdmin(SearchModelAdmin):
list_display = ("title", "type", "club", "author") search_fields = ["title", "summary", "content"]
search_fields = ("title", "summary", "content")
form = make_ajax_form(
News,
{
"author": "users",
"moderator": "users",
},
)
@admin.register(Poster)
class PosterAdmin(SearchModelAdmin):
list_display = ("name", "club", "date_begin", "date_end", "moderator")
form = make_ajax_form(Poster, {"moderator": "users"})
@admin.register(Weekmail)
class WeekmailAdmin(SearchModelAdmin): class WeekmailAdmin(SearchModelAdmin):
list_display = ("title", "sent") search_fields = ["title"]
search_fields = ("title",)
admin.site.register(Sith) admin.site.register(Sith)
admin.site.register(News, NewsAdmin)
admin.site.register(Weekmail, WeekmailAdmin)
admin.site.register(Screen) admin.site.register(Screen)
admin.site.register(Poster)

View File

@ -5,6 +5,7 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [] dependencies = []
operations = [ operations = [

View File

@ -7,6 +7,7 @@ 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),

View File

@ -7,6 +7,7 @@ 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),

View File

@ -8,6 +8,7 @@ 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),

View File

@ -5,6 +5,7 @@ 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 = [

View File

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

View File

@ -36,7 +36,6 @@ from django.core.exceptions import ValidationError
from django.utils import timezone from django.utils import timezone
from core import utils
from core.models import User, Preferences, RealGroup, Notification, SithFile from core.models import User, Preferences, RealGroup, Notification, SithFile
from club.models import Club from club.models import Club
@ -47,12 +46,9 @@ class Sith(models.Model):
alert_msg = models.TextField(_("alert message"), default="", blank=True) alert_msg = models.TextField(_("alert message"), default="", blank=True)
info_msg = models.TextField(_("info message"), default="", blank=True) info_msg = models.TextField(_("info message"), default="", blank=True)
weekmail_destinations = models.TextField(_("weekmail destinations"), default="") weekmail_destinations = models.TextField(_("weekmail destinations"), default="")
version = utils.get_git_revision_short_hash()
def is_owned_by(self, user): def is_owned_by(self, user):
if user.is_anonymous: return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
return False
return user.is_com_admin
def __str__(self): def __str__(self):
return "⛩ Sith ⛩" return "⛩ Sith ⛩"
@ -65,6 +61,11 @@ NEWS_TYPES = [
("CALL", _("Call")), ("CALL", _("Call")),
] ]
WEEKMAIL_TYPE = [
("WEEKMAIL", _("Weekmail")),
("INVITATION", _("Invitation")),
]
class News(models.Model): class News(models.Model):
"""The news class""" """The news class"""
@ -94,15 +95,13 @@ class News(models.Model):
) )
def is_owned_by(self, user): def is_owned_by(self, user):
if user.is_anonymous: return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) or user == self.author
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_com_admin return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
def can_be_viewed_by(self, user): def can_be_viewed_by(self, user):
return self.is_moderated or user.is_com_admin return self.is_moderated or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
def get_absolute_url(self): def get_absolute_url(self):
return reverse("com:news_detail", kwargs={"news_id": self.id}) return reverse("com:news_detail", kwargs={"news_id": self.id})
@ -184,6 +183,9 @@ class Weekmail(models.Model):
protip = models.TextField(_("protip"), blank=True) protip = models.TextField(_("protip"), blank=True)
conclusion = models.TextField(_("conclusion"), blank=True) conclusion = models.TextField(_("conclusion"), blank=True)
sent = models.BooleanField(_("sent"), default=False) sent = models.BooleanField(_("sent"), default=False)
type = models.CharField(
_("type"), max_length=16, choices=WEEKMAIL_TYPE, default="WEEKMAIL"
)
class Meta: class Meta:
ordering = ["-id"] ordering = ["-id"]
@ -221,6 +223,17 @@ class Weekmail(models.Model):
None, "com/weekmail_renderer_text.jinja", context={"weekmail": self} None, "com/weekmail_renderer_text.jinja", context={"weekmail": self}
).content.decode("utf-8") ).content.decode("utf-8")
def switch_type(self):
"""
Switch the type of weekmail we are sending :
- a simple weekmail
- or an invitation
"""
if self.type == "INVITATION":
self.type = "WEEKMAIL"
else:
self.type = "INVITATION"
def render_html(self): def render_html(self):
""" """
Renders an HTML version of the mail with images and fancy CSS. Renders an HTML version of the mail with images and fancy CSS.
@ -233,10 +246,15 @@ class Weekmail(models.Model):
""" """
Return an absolute link to the banner. Return an absolute link to the banner.
""" """
if self.type == "INVITATION":
return (
"http://" + settings.SITH_URL + static("com/img/invitation_bannerP22.png")
)
return ( return (
"http://" + settings.SITH_URL + static("com/img/weekmail_bannerV2P22.png") "http://" + settings.SITH_URL + static("com/img/weekmail_bannerV2P22.png")
) )
def get_footer(self): def get_footer(self):
""" """
Return an absolute link to the footer. Return an absolute link to the footer.
@ -247,9 +265,7 @@ 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):
if user.is_anonymous: return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
return False
return user.is_com_admin
class WeekmailArticle(models.Model): class WeekmailArticle(models.Model):
@ -277,9 +293,7 @@ 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):
if user.is_anonymous: return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
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)
@ -295,9 +309,7 @@ class Screen(models.Model):
) )
def is_owned_by(self, user): def is_owned_by(self, user):
if user.is_anonymous: return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
return False
return user.is_com_admin
def __str__(self): def __str__(self):
return "%s" % (self.name) return "%s" % (self.name)
@ -350,12 +362,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):
if user.is_anonymous: return user.is_in_group(
return False settings.SITH_GROUP_COM_ADMIN_ID
return user.is_com_admin or len(user.clubs_with_rights) > 0 ) or Club.objects.filter(id__in=user.clubs_with_rights)
def can_be_moderated_by(self, user): def can_be_moderated_by(self, user):
return user.is_com_admin return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
def get_display_name(self): def get_display_name(self):
return self.club.get_display_name() return self.club.get_display_name()

View File

@ -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_com_admin %} {% elif user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) %}
<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) %}

View File

@ -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_com_admin %} {% if user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) %}
<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 %}

View File

@ -6,15 +6,15 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{% if user.is_com_admin %} {% if user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) %}
<div id="news_admin"> <div id="news_admin">
<a class="button" href="{{ url('com:news_admin_list') }}">{% trans %}Administrate news{% endtrans %}</a> <a href="{{ url('com:news_admin_list') }}">{% trans %}Administrate news{% endtrans %}</a>
</div> </div>
<br>
{% endif %} {% endif %}
<div id="news"> <div id="news">
<div id="left_column" class="news_column"> <div id="left_column" class="news_column">
{% for news in object_list.filter(type="NOTICE") %} {% for news in object_list.filter(type="NOTICE") %}
<section class="news_notice"> <section class="news_notice">
<h4><a href="{{ url('com:news_detail', news_id=news.id) }}">{{ news.title }}</a></h4> <h4><a href="{{ url('com:news_detail', news_id=news.id) }}">{{ news.title }}</a></h4>
@ -97,15 +97,6 @@
</section> </section>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
<h3>{% trans %}All coming events{% endtrans %}</h3>
<iframe
src="https://embed.styledcalendar.com/#2mF2is8CEXhr4ADcX6qN"
title="Styled Calendar"
class="styled-calendar-container"
style="width: 100%; border: none; height: 1060px"
data-cy="calendar-embed-iframe">
</iframe>
</div> </div>
<div id="right_column" class="news_column"> <div id="right_column" class="news_column">

View File

@ -24,7 +24,7 @@
<div id="progress_bar"></div> <div id="progress_bar"></div>
</div> </div>
<script src="{{ static('core/js/jquery-3.6.2.min.js') }}"></script> <script src="{{ static('core/js/jquery-3.1.0.min.js') }}"></script>
<script src="{{ static('com/js/slideshow.js') }}"></script> <script src="{{ static('com/js/slideshow.js') }}"></script>
</body> </body>
</html> </html>

View File

@ -10,6 +10,7 @@
<p><a href="{{ url('com:weekmail_preview') }}">{% trans %}Preview{% endtrans %}</a></p> <p><a href="{{ url('com:weekmail_preview') }}">{% trans %}Preview{% endtrans %}</a></p>
<p><a href="{{ url('com:weekmail_preview') }}?send=true">{% trans %}Send{% endtrans %}</a></p> <p><a href="{{ url('com:weekmail_preview') }}?send=true">{% trans %}Send{% endtrans %}</a></p>
<p><a href="{{ url('com:weekmail_article') }}">{% trans %}New article{% endtrans %}</a></p> <p><a href="{{ url('com:weekmail_article') }}">{% trans %}New article{% endtrans %}</a></p>
<p><a href="{{ url('com:weekmail') }}" onclick="{{weekmail.switch_type()}}">{% trans %}Switch invitation/weekmail{% endtrans %}</a></p>
<h4>{% trans %}Articles in no weekmail yet{% endtrans %}</h4> <h4>{% trans %}Articles in no weekmail yet{% endtrans %}</h4>
<table> <table>
<thead> <thead>

View File

@ -1,33 +1,42 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #
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 com.models import Sith, News, Weekmail, WeekmailArticle, Poster from core.models import User, RealGroup
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"))
@ -36,6 +45,9 @@ 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"))
@ -44,16 +56,14 @@ class ComInfoTest(TestCase):
class ComTest(TestCase): class ComTest(TestCase):
@classmethod def setUp(self):
def setUpTestData(cls): call_command("populate")
cls.skia = User.objects.filter(username="skia").first() self.skia = User.objects.filter(username="skia").first()
cls.com_group = RealGroup.objects.filter( self.com_group = RealGroup.objects.filter(
id=settings.SITH_GROUP_COM_ADMIN_ID id=settings.SITH_GROUP_COM_ADMIN_ID
).first() ).first()
cls.skia.groups.set([cls.com_group]) self.skia.groups.set([self.com_group])
cls.skia.save() self.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):
@ -72,7 +82,7 @@ class ComTest(TestCase):
self.assertContains( self.assertContains(
r, r,
"""<div id="alert_box"> """<div id="alert_box">
<div class="markdown"><h3>ALERTE!</h3> <div class="markdown"><h3>ALERTE!</h3>
<p><strong>Caaaataaaapuuuulte!!!!</strong></p>""", <p><strong>Caaaataaaapuuuulte!!!!</strong></p>""",
) )
@ -90,7 +100,7 @@ class ComTest(TestCase):
self.assertContains( self.assertContains(
r, r,
"""<div id="info_box"> """<div id="info_box">
<div class="markdown"><h3>INFO: <strong>Caaaataaaapuuuulte!!!!</strong></h3>""", <div class="markdown"><h3>INFO: <strong>Caaaataaaapuuuulte!!!!</strong></h3>""",
) )
def test_birthday_non_subscribed_user(self): def test_birthday_non_subscribed_user(self):
@ -112,102 +122,3 @@ 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))

View File

@ -1,111 +1,125 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #
from django.urls import path from django.urls import re_path
from club.views import MailingDeleteView
from com.views import * from com.views import *
from club.views import MailingDeleteView
urlpatterns = [ urlpatterns = [
path("sith/edit/alert/", AlertMsgEditView.as_view(), name="alert_edit"), re_path(r"^sith/edit/alert$", AlertMsgEditView.as_view(), name="alert_edit"),
path("sith/edit/info/", InfoMsgEditView.as_view(), name="info_edit"), re_path(r"^sith/edit/info$", InfoMsgEditView.as_view(), name="info_edit"),
path( re_path(
"sith/edit/weekmail_destinations/", r"^sith/edit/weekmail_destinations$",
WeekmailDestinationEditView.as_view(), WeekmailDestinationEditView.as_view(),
name="weekmail_destinations", name="weekmail_destinations",
), ),
path("weekmail/", WeekmailEditView.as_view(), name="weekmail"), re_path(r"^weekmail$", WeekmailEditView.as_view(), name="weekmail"),
path("weekmail/preview/", WeekmailPreviewView.as_view(), name="weekmail_preview"), re_path(
path( r"^weekmail/preview$", WeekmailPreviewView.as_view(), name="weekmail_preview"
"weekmail/new_article/", ),
re_path(
r"^weekmail/new_article$",
WeekmailArticleCreateView.as_view(), WeekmailArticleCreateView.as_view(),
name="weekmail_article", name="weekmail_article",
), ),
path( re_path(
"weekmail/article/<int:article_id>/delete/", r"^weekmail/article/(?P<article_id>[0-9]+)/delete$",
WeekmailArticleDeleteView.as_view(), WeekmailArticleDeleteView.as_view(),
name="weekmail_article_delete", name="weekmail_article_delete",
), ),
path( re_path(
"weekmail/article/<int:article_id>/edit/", r"^weekmail/article/(?P<article_id>[0-9]+)/edit$",
WeekmailArticleEditView.as_view(), WeekmailArticleEditView.as_view(),
name="weekmail_article_edit", name="weekmail_article_edit",
), ),
path("news/", NewsListView.as_view(), name="news_list"), re_path(r"^news$", NewsListView.as_view(), name="news_list"),
path("news/admin/", NewsAdminListView.as_view(), name="news_admin_list"), re_path(r"^news/admin$", NewsAdminListView.as_view(), name="news_admin_list"),
path("news/create/", NewsCreateView.as_view(), name="news_new"), re_path(r"^news/create$", NewsCreateView.as_view(), name="news_new"),
path( re_path(
"news/<int:news_id>/delete/", r"^news/(?P<news_id>[0-9]+)/delete$",
NewsDeleteView.as_view(), NewsDeleteView.as_view(),
name="news_delete", name="news_delete",
), ),
path( re_path(
"news/<int:news_id>/moderate/", r"^news/(?P<news_id>[0-9]+)/moderate$",
NewsModerateView.as_view(), NewsModerateView.as_view(),
name="news_moderate", name="news_moderate",
), ),
path("news/<int:news_id>/edit/", NewsEditView.as_view(), name="news_edit"), re_path(
path("news/<int:news_id>/", NewsDetailView.as_view(), name="news_detail"), r"^news/(?P<news_id>[0-9]+)/edit$", NewsEditView.as_view(), name="news_edit"
path("mailings/", MailingListAdminView.as_view(), name="mailing_admin"), ),
path( re_path(
"mailings/<int:mailing_id>/moderate/", r"^news/(?P<news_id>[0-9]+)$", NewsDetailView.as_view(), name="news_detail"
),
re_path(r"^mailings$", MailingListAdminView.as_view(), name="mailing_admin"),
re_path(
r"^mailings/(?P<mailing_id>[0-9]+)/moderate$",
MailingModerateView.as_view(), MailingModerateView.as_view(),
name="mailing_moderate", name="mailing_moderate",
), ),
path( re_path(
"mailings/<int:mailing_id>/delete/", r"^mailings/(?P<mailing_id>[0-9]+)/delete$",
MailingDeleteView.as_view(redirect_page="com:mailing_admin"), MailingDeleteView.as_view(redirect_page="com:mailing_admin"),
name="mailing_delete", name="mailing_delete",
), ),
path("poster/", PosterListView.as_view(), name="poster_list"), re_path(r"^poster$", PosterListView.as_view(), name="poster_list"),
path("poster/create/", PosterCreateView.as_view(), name="poster_create"), re_path(r"^poster/create$", PosterCreateView.as_view(), name="poster_create"),
path( re_path(
"poster/<int:poster_id>/edit/", r"^poster/(?P<poster_id>[0-9]+)/edit$",
PosterEditView.as_view(), PosterEditView.as_view(),
name="poster_edit", name="poster_edit",
), ),
path( re_path(
"poster/<int:poster_id>/delete/", r"^poster/(?P<poster_id>[0-9]+)/delete$",
PosterDeleteView.as_view(), PosterDeleteView.as_view(),
name="poster_delete", name="poster_delete",
), ),
path( re_path(
"poster/moderate/", r"^poster/moderate$",
PosterModerateListView.as_view(), PosterModerateListView.as_view(),
name="poster_moderate_list", name="poster_moderate_list",
), ),
path( re_path(
"poster/<int:object_id>/moderate/", r"^poster/(?P<object_id>[0-9]+)/moderate$",
PosterModerateView.as_view(), PosterModerateView.as_view(),
name="poster_moderate", name="poster_moderate",
), ),
path("screen/", ScreenListView.as_view(), name="screen_list"), re_path(r"^screen$", ScreenListView.as_view(), name="screen_list"),
path("screen/create/", ScreenCreateView.as_view(), name="screen_create"), re_path(r"^screen/create$", ScreenCreateView.as_view(), name="screen_create"),
path( re_path(
"screen/<int:screen_id>/slideshow/", r"^screen/(?P<screen_id>[0-9]+)/slideshow$",
ScreenSlideshowView.as_view(), ScreenSlideshowView.as_view(),
name="screen_slideshow", name="screen_slideshow",
), ),
path( re_path(
"screen/<int:screen_id>/edit/", r"^screen/(?P<screen_id>[0-9]+)/edit$",
ScreenEditView.as_view(), ScreenEditView.as_view(),
name="screen_edit", name="screen_edit",
), ),
path( re_path(
"screen/<int:screen_id>/delete/", r"^screen/(?P<screen_id>[0-9]+)/delete$",
ScreenDeleteView.as_view(), ScreenDeleteView.as_view(),
name="screen_delete", name="screen_delete",
), ),

View File

@ -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_com_admin: if not (request.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)):
raise PermissionDenied raise PermissionDenied
return super(IsComAdminMixin, self).dispatch(request, *args, **kwargs) return super(IsComAdminMixin, self).dispatch(request, *args, **kwargs)
@ -283,7 +283,9 @@ 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_com_admin: if form.cleaned_data["automoderation"] and self.request.user.is_in_group(
settings.SITH_GROUP_COM_ADMIN_ID
):
self.object.moderator = self.request.user self.object.moderator = self.request.user
self.object.is_moderated = True self.object.is_moderated = True
self.object.save() self.object.save()
@ -331,7 +333,9 @@ 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_com_admin: if form.cleaned_data["automoderation"] and self.request.user.is_in_group(
settings.SITH_GROUP_COM_ADMIN_ID
):
self.object.moderator = self.request.user self.object.moderator = self.request.user
self.object.is_moderated = True self.object.is_moderated = True
self.object.save() self.object.save()
@ -613,7 +617,10 @@ class MailingListAdminView(ComTabsMixin, ListView):
current_tab = "mailings" current_tab = "mailings"
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not (request.user.is_com_admin or request.user.is_root): if not (
request.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
or request.user.is_root
):
raise PermissionDenied raise PermissionDenied
return super(MailingListAdminView, self).dispatch(request, *args, **kwargs) return super(MailingListAdminView, self).dispatch(request, *args, **kwargs)

View File

@ -1,15 +1,23 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #

View File

@ -1,34 +1,40 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #
from django.contrib import admin from django.contrib import admin
from ajax_select import make_ajax_form from ajax_select import make_ajax_form
from core.models import User, Page, RealGroup, MetaGroup, SithFile from core.models import User, Page, RealGroup, SithFile
from django.contrib.auth.models import Group as AuthGroup from django.contrib.auth.models import Group as AuthGroup
from haystack.admin import SearchModelAdmin from haystack.admin import SearchModelAdmin
admin.site.unregister(AuthGroup) admin.site.unregister(AuthGroup)
admin.site.register(MetaGroup)
admin.site.register(RealGroup) admin.site.register(RealGroup)
@admin.register(User)
class UserAdmin(SearchModelAdmin): class UserAdmin(SearchModelAdmin):
list_display = ("first_name", "last_name", "username", "email", "nick_name") list_display = ["first_name", "last_name", "username", "email", "nick_name"]
form = make_ajax_form( form = make_ajax_form(
User, User,
{ {
@ -42,9 +48,11 @@ class UserAdmin(SearchModelAdmin):
search_fields = ["first_name", "last_name", "username"] search_fields = ["first_name", "last_name", "username"]
admin.site.register(User, UserAdmin)
@admin.register(Page) @admin.register(Page)
class PageAdmin(admin.ModelAdmin): class PageAdmin(admin.ModelAdmin):
list_display = ("name", "_full_name", "owner_group")
form = make_ajax_form( form = make_ajax_form(
Page, Page,
{ {
@ -58,12 +66,4 @@ class PageAdmin(admin.ModelAdmin):
@admin.register(SithFile) @admin.register(SithFile)
class SithFileAdmin(admin.ModelAdmin): class SithFileAdmin(admin.ModelAdmin):
list_display = ("name", "owner", "size", "date", "is_in_sas") form = make_ajax_form(SithFile, {"parent": "files"}) # ManyToManyField
form = make_ajax_form(
SithFile,
{
"parent": "files",
"owner": "users",
"moderator": "users",
},
) # ManyToManyField

View File

@ -25,7 +25,6 @@
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
@ -34,17 +33,26 @@ 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
cache.clear() def clear_cached_groups(**kwargs):
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

View File

@ -1,35 +0,0 @@
from core.models import Page
class FourDigitYearConverter:
regex = "[0-9]{4}"
def to_python(self, value):
return int(value)
def to_url(self, value):
return str(value).zfill(4)
class TwoDigitMonthConverter:
regex = "[0-9]{2}"
def to_python(self, value):
return int(value)
def to_url(self, value):
return str(value).zfill(2)
class BooleanStringConverter:
"""
Converter whose regex match either True or False
"""
regex = r"(True)|(False)"
def to_python(self, value):
return str(value) == "True"
def to_url(self, value):
return str(value)

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

View File

@ -1,16 +1,24 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #
@ -20,9 +28,8 @@ from ajax_select import register, LookupChannel
from core.views.site import search_user from core.views.site import search_user
from core.models import User, Group, SithFile from core.models import User, Group, SithFile
from club.models import Club from club.models import Club
from counter.models import Product, Counter, Customer from counter.models import Product, Counter
from accounting.models import ClubAccount, Company from accounting.models import ClubAccount, Company
from eboutic.models import BasketItem
def check_token(request): def check_token(request):
@ -53,21 +60,6 @@ class UsersLookup(RightManagedLookupChannel):
return item.get_display_name() return item.get_display_name()
@register("customers")
class CustomerLookup(RightManagedLookupChannel):
model = Customer
def get_query(self, q, request):
users = search_user(q)
return [user.customer for user in users]
def format_match(self, obj):
return obj.user.get_mini_item()
def format_item_display(self, obj):
return f"{obj.user.get_display_name()} ({obj.account_id})"
@register("groups") @register("groups")
class GroupsLookup(RightManagedLookupChannel): class GroupsLookup(RightManagedLookupChannel):
model = Group model = Group

View File

@ -1,15 +1,23 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #

View File

@ -1,15 +1,23 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2023 © AE UTBM # Copyright 2016,2017
# ae@utbm.fr / ae.info@utbm.fr # - Skia <skia@libskia.so>
# #
# This file is part of the website of the UTBM Student Association (AE UTBM), # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# https://ae.utbm.fr. # http://ae.utbm.fr.
# #
# You can find the source code of the website at https://github.com/ae-utbm/sith3 # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# #
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # This program is distributed in the hope that it will be useful, but WITHOUT
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# OR WITHIN THE LOCAL FILE "LICENSE" # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #

View File

@ -6,15 +6,8 @@ from django.core.management.base import BaseCommand
# see https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string # see https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
# added "v?" # added "v?"
# Please note that this does not match the version of the three.js library.
# Hence, you shall have to check this one by yourself
semver_regex = re.compile( semver_regex = re.compile(
r"^v?" """^v?(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"""
r"(?P<major>\d+)"
r"\.(?P<minor>\d+)"
r"\.(?P<patch>\d+)"
r"(?:-(?P<prerelease>(?:\d+|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:\d+|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?"
r"(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
) )

View File

@ -39,5 +39,6 @@ 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)

View File

@ -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,6 +68,7 @@ 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)

View File

@ -1,7 +1,7 @@
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2016,2017,2023 # Copyright 2016,2017
# - Skia <skia@hya.sk> # - Skia <skia@libskia.so>
# #
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr. # http://ae.utbm.fr.
@ -25,9 +25,7 @@
import os import os
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from io import StringIO, BytesIO from io import StringIO, BytesIO
from pathlib import Path
from django.contrib.auth.models import Permission
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.core.management import call_command from django.core.management import call_command
from django.conf import settings from django.conf import settings
@ -55,7 +53,6 @@ from com.models import Sith, Weekmail, News, NewsDate
from election.models import Election, Role, Candidature, ElectionList from election.models import Election, Role, Candidature, ElectionList
from forum.models import Forum, ForumTopic from forum.models import Forum, ForumTopic
from pedagogy.models import UV from pedagogy.models import UV
from sas.models import Album, Picture, PeoplePictureRelation
class Command(BaseCommand): class Command(BaseCommand):
@ -73,8 +70,10 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
os.environ["DJANGO_COLORS"] = "nocolor" os.environ["DJANGO_COLORS"] = "nocolor"
Site(id=4000, domain=settings.SITH_URL, name=settings.SITH_NAME).save() Site(id=4000, domain=settings.SITH_URL, name=settings.SITH_NAME).save()
root_path = Path(__file__).parent.parent.parent.parent root_path = os.path.dirname(
root_group, _ = Group.objects.get_or_create(name="Root") os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
)
Group(name="Root").save()
Group(name="Public").save() Group(name="Public").save()
Group(name="Subscribers").save() Group(name="Subscribers").save()
Group(name="Old subscribers").save() Group(name="Old subscribers").save()
@ -84,15 +83,10 @@ class Command(BaseCommand):
Group(name="Banned from buying alcohol").save() Group(name="Banned from buying alcohol").save()
Group(name="Banned from counters").save() Group(name="Banned from counters").save()
Group(name="Banned to subscribe").save() Group(name="Banned to subscribe").save()
sas_admin, _ = Group.objects.get_or_create(name="SAS admin") Group(name="SAS admin").save()
Group(name="Forum admin").save() Group(name="Forum admin").save()
Group(name="Pedagogy admin").save() Group(name="Pedagogy admin").save()
self.reset_index("core", "auth") self.reset_index("core", "auth")
change_billing = Permission.objects.get(codename="change_billinginfo")
add_billing = Permission.objects.get(codename="add_billinginfo")
root_group.permissions.add(change_billing, add_billing)
root = User( root = User(
id=0, id=0,
username="root", username="root",
@ -119,8 +113,7 @@ class Command(BaseCommand):
club_root = SithFile(parent=None, name="clubs", is_folder=True, owner=root) club_root = SithFile(parent=None, name="clubs", is_folder=True, owner=root)
club_root.save() club_root.save()
sas = SithFile(parent=None, name="SAS", is_folder=True, owner=root) SithFile(parent=None, name="SAS", is_folder=True, owner=root).save()
sas.save()
main_club = Club( main_club = Club(
id=1, id=1,
name=settings.SITH_MAIN_CLUB["name"], name=settings.SITH_MAIN_CLUB["name"],
@ -155,10 +148,12 @@ 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()
ae_members = Group.objects.get(name=settings.SITH_MAIN_MEMBERS_GROUP) home_root.view_groups.set(
[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first()]
home_root.view_groups.set([ae_members]) )
club_root.view_groups.set([ae_members]) club_root.view_groups.set(
[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first()]
)
home_root.save() home_root.save()
club_root.save() club_root.save()
@ -208,8 +203,6 @@ 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,17 +213,11 @@ Welcome to the wiki page!
) )
skia.set_password("plop") skia.set_password("plop")
skia.save() skia.save()
skia.view_groups = [ae_members.id] skia.view_groups = [
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
]
skia.save() skia.save()
skia_profile_path = ( skia_profile_path = os.path.join(root_path, "core/fixtures/images/3.jpg")
root_path
/ "core"
/ "fixtures"
/ "images"
/ "sas"
/ "Family"
/ "skia.jpg"
)
with open(skia_profile_path, "rb") as f: with open(skia_profile_path, "rb") as f:
name = str(skia.id) + "_profile.jpg" name = str(skia.id) + "_profile.jpg"
skia_profile = SithFile( skia_profile = SithFile(
@ -240,7 +227,7 @@ Welcome to the wiki page!
owner=skia, owner=skia,
is_folder=False, is_folder=False,
mime_type="image/jpeg", mime_type="image/jpeg",
size=skia_profile_path.stat().st_size, size=os.path.getsize(skia_profile_path),
) )
skia_profile.file.name = name skia_profile.file.name = name
skia_profile.save() skia_profile.save()
@ -259,7 +246,9 @@ Welcome to the wiki page!
) )
public.set_password("plop") public.set_password("plop")
public.save() public.save()
public.view_groups = [ae_members.id] public.view_groups = [
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(
@ -273,7 +262,9 @@ Welcome to the wiki page!
) )
subscriber.set_password("plop") subscriber.set_password("plop")
subscriber.save() subscriber.save()
subscriber.view_groups = [ae_members.id] subscriber.view_groups = [
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(
@ -287,7 +278,9 @@ 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 = [ae_members.id] old_subscriber.view_groups = [
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(
@ -301,7 +294,9 @@ Welcome to the wiki page!
) )
counter.set_password("plop") counter.set_password("plop")
counter.save() counter.save()
counter.view_groups = [ae_members.id] counter.view_groups = [
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)
@ -322,7 +317,9 @@ Welcome to the wiki page!
) )
comptable.set_password("plop") comptable.set_password("plop")
comptable.save() comptable.save()
comptable.view_groups = [ae_members.id] comptable.view_groups = [
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)
@ -343,49 +340,28 @@ Welcome to the wiki page!
) )
u.set_password("plop") u.set_password("plop")
u.save() u.save()
u.view_groups = [ae_members.id] u.view_groups = [
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( r = User(
username="rbatsbak", username="rbatsbak",
last_name="Batsbak", last_name="Batsbak",
first_name="Richard", first_name="Richard",
email="richard@git.an", email="richard@git.an",
date_of_birth="1982-06-12", date_of_birth="1982-06-12",
) )
richard.set_password("plop") r.set_password("plop")
richard.save() r.save()
richard.godfathers.add(comptable) r.view_groups = [
richard_profile_path = ( Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
root_path ]
/ "core" r.save()
/ "fixtures"
/ "images"
/ "sas"
/ "Family"
/ "richard.jpg"
)
with open(richard_profile_path, "rb") as f:
name = f"{richard.id}_profile.jpg"
richard_profile = SithFile(
parent=profiles_root,
name=name,
file=resize_image(Image.open(BytesIO(f.read())), 400, "JPEG"),
owner=richard,
is_folder=False,
mime_type="image/jpeg",
size=richard_profile_path.stat().st_size,
)
richard_profile.file.name = name
richard_profile.save()
richard.profile_pict = richard_profile
richard.save()
richard.view_groups = [ae_members.id]
richard.save()
# Adding syntax help page # Adding syntax help page
p = Page(name="Aide_sur_la_syntaxe") p = Page(name="Aide_sur_la_syntaxe")
p.save(force_lock=True) p.save(force_lock=True)
with open(root_path / "doc" / "SYNTAX.md", "r") as rm: with open(os.path.join(root_path) + "/doc/SYNTAX.md", "r") as rm:
PageRev( PageRev(
page=p, title="Aide sur la syntaxe", author=skia, content=rm.read() page=p, title="Aide sur la syntaxe", author=skia, content=rm.read()
).save() ).save()
@ -412,7 +388,7 @@ Welcome to the wiki page!
default_subscription = "un-semestre" default_subscription = "un-semestre"
# Root # Root
s = Subscription( s = Subscription(
member=root, member=User.objects.filter(pk=root.pk).first(),
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],
) )
@ -460,7 +436,7 @@ Welcome to the wiki page!
s.save() s.save()
# Richard # Richard
s = Subscription( s = Subscription(
member=User.objects.filter(pk=richard.pk).first(), member=User.objects.filter(pk=r.pk).first(),
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],
) )
@ -512,7 +488,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).save() Membership(user=skia, club=main_club, role=3, description="").save()
troll = Club( troll = Club(
name="Troll Penché", name="Troll Penché",
unix_name="troll", unix_name="troll",
@ -529,10 +505,8 @@ Welcome to the wiki page!
refound.save() refound.save()
# Counters # Counters
subscribers = Group.objects.get(name="Subscribers")
old_subscribers = Group.objects.get(name="Old subscribers")
Customer(user=skia, account_id="6568j", amount=0).save() Customer(user=skia, account_id="6568j", amount=0).save()
Customer(user=richard, account_id="4000k", amount=0).save() Customer(user=r, account_id="4000k", amount=0).save()
p = ProductType(name="Bières bouteilles") p = ProductType(name="Bières bouteilles")
p.save() p.save()
c = ProductType(name="Cotisations") c = ProductType(name="Cotisations")
@ -551,9 +525,6 @@ Welcome to the wiki page!
club=main_club, club=main_club,
) )
cotis.save() cotis.save()
cotis.buying_groups.add(subscribers)
cotis.buying_groups.add(old_subscribers)
cotis.save()
cotis2 = Product( cotis2 = Product(
name="Cotis 2 semestres", name="Cotis 2 semestres",
code="2SCOTIZ", code="2SCOTIZ",
@ -564,9 +535,6 @@ Welcome to the wiki page!
club=main_club, club=main_club,
) )
cotis2.save() cotis2.save()
cotis2.buying_groups.add(subscribers)
cotis2.buying_groups.add(old_subscribers)
cotis2.save()
refill = Product( refill = Product(
name="Rechargement 15 €", name="Rechargement 15 €",
code="15REFILL", code="15REFILL",
@ -577,8 +545,6 @@ Welcome to the wiki page!
club=main_club, club=main_club,
) )
refill.save() refill.save()
refill.buying_groups.add(subscribers)
refill.save()
barb = Product( barb = Product(
name="Barbar", name="Barbar",
code="BARB", code="BARB",
@ -587,11 +553,8 @@ Welcome to the wiki page!
selling_price="1.7", selling_price="1.7",
special_selling_price="1.6", special_selling_price="1.6",
club=main_club, club=main_club,
limit_age=18,
) )
barb.save() barb.save()
barb.buying_groups.add(subscribers)
barb.save()
cble = Product( cble = Product(
name="Chimay Bleue", name="Chimay Bleue",
code="CBLE", code="CBLE",
@ -600,11 +563,8 @@ Welcome to the wiki page!
selling_price="1.7", selling_price="1.7",
special_selling_price="1.6", special_selling_price="1.6",
club=main_club, club=main_club,
limit_age=18,
) )
cble.save() cble.save()
cble.buying_groups.add(subscribers)
cble.save()
cons = Product( cons = Product(
name="Consigne Eco-cup", name="Consigne Eco-cup",
code="CONS", code="CONS",
@ -614,6 +574,7 @@ Welcome to the wiki page!
special_selling_price="1", special_selling_price="1",
club=main_club, club=main_club,
) )
cons.id = 1152
cons.save() cons.save()
dcons = Product( dcons = Product(
name="Déconsigne Eco-cup", name="Déconsigne Eco-cup",
@ -624,8 +585,9 @@ Welcome to the wiki page!
special_selling_price="-1", special_selling_price="-1",
club=main_club, club=main_club,
) )
dcons.id = 1151
dcons.save() dcons.save()
cors = Product( Product(
name="Corsendonk", name="Corsendonk",
code="CORS", code="CORS",
product_type=p, product_type=p,
@ -633,12 +595,8 @@ Welcome to the wiki page!
selling_price="1.7", selling_price="1.7",
special_selling_price="1.6", special_selling_price="1.6",
club=main_club, club=main_club,
limit_age=18, ).save()
) Product(
cors.save()
cors.buying_groups.add(subscribers)
cors.save()
carolus = Product(
name="Carolus", name="Carolus",
code="CARO", code="CARO",
product_type=p, product_type=p,
@ -646,11 +604,7 @@ Welcome to the wiki page!
selling_price="1.7", selling_price="1.7",
special_selling_price="1.6", special_selling_price="1.6",
club=main_club, club=main_club,
limit_age=18, ).save()
)
carolus.save()
carolus.buying_groups.add(subscribers)
carolus.save()
mde = Counter.objects.filter(name="MDE").first() mde = Counter.objects.filter(name="MDE").first()
mde.products.add(barb) mde.products.add(barb)
mde.products.add(cble) mde.products.add(cble)
@ -839,17 +793,11 @@ Welcome to the wiki page!
) )
sli.set_password("plop") sli.set_password("plop")
sli.save() sli.save()
sli.view_groups = [ae_members.id] sli.view_groups = [
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
]
sli.save() sli.save()
sli_profile_path = ( sli_profile_path = os.path.join(root_path, "core/fixtures/images/5.jpg")
root_path
/ "core"
/ "fixtures"
/ "images"
/ "sas"
/ "Family"
/ "sli.jpg"
)
with open(sli_profile_path, "rb") as f: with open(sli_profile_path, "rb") as f:
name = str(sli.id) + "_profile.jpg" name = str(sli.id) + "_profile.jpg"
sli_profile = SithFile( sli_profile = SithFile(
@ -859,7 +807,7 @@ Welcome to the wiki page!
owner=sli, owner=sli,
is_folder=False, is_folder=False,
mime_type="image/jpeg", mime_type="image/jpeg",
size=sli_profile_path.stat().st_size, size=os.path.getsize(sli_profile_path),
) )
sli_profile.file.name = name sli_profile.file.name = name
sli_profile.save() sli_profile.save()
@ -875,15 +823,7 @@ Welcome to the wiki page!
) )
krophil.set_password("plop") krophil.set_password("plop")
krophil.save() krophil.save()
krophil_profile_path = ( krophil_profile_path = os.path.join(root_path, "core/fixtures/images/6.jpg")
root_path
/ "core"
/ "fixtures"
/ "images"
/ "sas"
/ "Family"
/ "krophil.jpg"
)
with open(krophil_profile_path, "rb") as f: with open(krophil_profile_path, "rb") as f:
name = str(krophil.id) + "_profile.jpg" name = str(krophil.id) + "_profile.jpg"
krophil_profile = SithFile( krophil_profile = SithFile(
@ -893,7 +833,7 @@ Welcome to the wiki page!
owner=krophil, owner=krophil,
is_folder=False, is_folder=False,
mime_type="image/jpeg", mime_type="image/jpeg",
size=krophil_profile_path.stat().st_size, size=os.path.getsize(krophil_profile_path),
) )
krophil_profile.file.name = name krophil_profile.file.name = name
krophil_profile.save() krophil_profile.save()
@ -916,7 +856,7 @@ Welcome to the wiki page!
Membership( Membership(
user=comunity, user=comunity,
club=bar_club, club=bar_club,
start_date=self.now, start_date=timezone.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
@ -1075,7 +1015,7 @@ Welcome to the wiki page!
ForumTopic(forum=hall) ForumTopic(forum=hall)
# News # News
friday = self.now friday = timezone.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)
@ -1093,8 +1033,8 @@ Welcome to the wiki page!
n.save() n.save()
NewsDate( NewsDate(
news=n, news=n,
start_date=self.now + timedelta(hours=70), start_date=timezone.now() + timedelta(hours=70),
end_date=self.now + timedelta(hours=72), end_date=timezone.now() + timedelta(hours=72),
).save() ).save()
n = News( n = News(
title="Repas barman", title="Repas barman",
@ -1110,8 +1050,8 @@ Welcome to the wiki page!
n.save() n.save()
NewsDate( NewsDate(
news=n, news=n,
start_date=self.now + timedelta(hours=72), start_date=timezone.now() + timedelta(hours=72),
end_date=self.now + timedelta(hours=84), end_date=timezone.now() + timedelta(hours=84),
).save() ).save()
n = News( n = News(
title="Repas fromager", title="Repas fromager",
@ -1126,8 +1066,8 @@ Welcome to the wiki page!
n.save() n.save()
NewsDate( NewsDate(
news=n, news=n,
start_date=self.now + timedelta(hours=96), start_date=timezone.now() + timedelta(hours=96),
end_date=self.now + timedelta(hours=100), end_date=timezone.now() + timedelta(hours=100),
).save() ).save()
n = News( n = News(
title="SdF", title="SdF",
@ -1143,7 +1083,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=self.now + timedelta(hours=24 * 7 + 9), end_date=timezone.now() + timedelta(hours=24 * 7 + 9),
).save() ).save()
# Weekly # Weekly
n = News( n = News(
@ -1196,106 +1136,3 @@ Welcome to the wiki page!
hours_THE=121, hours_THE=121,
hours_TE=4, hours_TE=4,
).save() ).save()
# SAS
skia.groups.add(sas_admin.id)
sas_fixtures_path = root_path / "core" / "fixtures" / "images" / "sas"
for f in sas_fixtures_path.glob("*"):
if f.is_dir():
album = Album(
parent=sas,
name=f.name,
owner=root,
is_folder=True,
is_in_sas=True,
is_moderated=True,
)
album.clean()
album.save()
for p in f.iterdir():
pict = Picture(
parent=album,
name=p.name,
file=resize_image(
Image.open(BytesIO(p.read_bytes())), 1000, "JPEG"
),
owner=root,
is_folder=False,
is_in_sas=True,
is_moderated=True,
mime_type="image/jpeg",
size=p.stat().st_size,
)
pict.file.name = p.name
pict.clean()
pict.generate_thumbnails()
pict.save()
p = Picture.objects.get(name="skia.jpg")
PeoplePictureRelation(user=skia, picture=p).save()
p = Picture.objects.get(name="sli.jpg")
PeoplePictureRelation(user=sli, picture=p).save()
p = Picture.objects.get(name="krophil.jpg")
PeoplePictureRelation(user=krophil, picture=p).save()
p = Picture.objects.get(name="skia_sli.jpg")
PeoplePictureRelation(user=skia, picture=p).save()
PeoplePictureRelation(user=sli, picture=p).save()
p = Picture.objects.get(name="skia_sli_krophil.jpg")
PeoplePictureRelation(user=skia, picture=p).save()
PeoplePictureRelation(user=sli, picture=p).save()
PeoplePictureRelation(user=krophil, picture=p).save()
p = Picture.objects.get(name="richard.jpg")
PeoplePictureRelation(user=richard, picture=p).save()
with open(skia_profile_path, "rb") as f:
name = str(skia.id) + "_profile.jpg"
skia_profile = SithFile(
parent=profiles_root,
name=name,
file=resize_image(Image.open(BytesIO(f.read())), 400, "JPEG"),
owner=skia,
is_folder=False,
mime_type="image/jpeg",
size=skia_profile_path.stat().st_size,
)
skia_profile.file.name = name
skia_profile.save()
skia.profile_pict = skia_profile
skia.save()
# Create some additional data for galaxy to work with
root.godfathers.add(skia)
skia.godfathers.add(root)
sli.godfathers.add(skia)
richard.godchildren.add(subscriber)
richard.godchildren.add(public)
Membership(
user=sli,
club=troll,
role=9,
description="Padawan Troll",
start_date=self.now - timedelta(days=17),
).save()
Membership(
user=krophil,
club=troll,
role=10,
description="Maitre Troll",
start_date=self.now - timedelta(days=200),
).save()
Membership(
user=skia,
club=troll,
role=2,
description="Grand Ancien Troll",
start_date=self.now - timedelta(days=400),
end_date=self.now - timedelta(days=86),
).save()
Membership(
user=richard,
club=troll,
role=2,
description="",
start_date=self.now - timedelta(days=200),
end_date=self.now - timedelta(days=100),
).save()

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