627 Commits

Author SHA1 Message Date
46fa14ed12 Updated FontAwesome from 4.7.0 to 6.4.0 2023-05-11 14:56:04 +02:00
18dffb0053 Used icons instead of text & updated layout accordingly 2023-05-11 14:54:51 +02:00
6e47d1471e Fix rebase 2023-05-11 14:53:59 +02:00
b5146569e1 Updated FontAwesome from 4.7.0 to 6.4.0 2023-05-11 14:51:44 +02:00
acde993352 Used icons instead of text & updated layout accordingly 2023-05-11 14:47:49 +02:00
8852ef990e [UPDATE] Bump libsass from 0.21.0 to 0.22.0 (#640)
Bumps [libsass](https://github.com/sass/libsass-python) from 0.21.0 to 0.22.0.
- [Release notes](https://github.com/sass/libsass-python/releases)
- [Changelog](https://github.com/sass/libsass-python/blob/main/docs/changes.rst)
- [Commits](https://github.com/sass/libsass-python/compare/0.21.0...0.22.0)

---
updated-dependencies:
- dependency-name: libsass
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-11 14:19:19 +02:00
87295ad9b7 Galaxy improvements (#628)
* galaxy: improve logging and performance reporting

* galaxy: add a full galaxy state test

* galaxy: optimize user self score computation

* galaxy: add 'generate_galaxy_test_data' command for development at scale

* galaxy: big refactor

Main changes:
  - Multiple Galaxy objects can now exist at the same time in DB. This allows for ruling a new galaxy while still
    displaying the old one.
  - The criteria to quickly know whether a user is a possible citizen is now a simple query on picture count. This
    avoids a very complicated query to database, that could often result in huge working memory load. With this change,
    it should be possible to run the galaxy even on a vanilla Postgres that didn't receive fine tuning for the Sith's
    galaxy.

* galaxy: template: make the galaxy graph work and be usable with a lot of stars

- Display focused star and its connections clearly
- Display star label faintly by default for other stars to avoid overloading the graph
- Hide non-focused lanes
- Avoid clicks on non-highlighted, too far stars
- Make the canva adapt its width to initial screen size, doesn't work dynamically

* galaxy: better docstrings

* galaxy: use bulk_create whenever possible

This is a big performance gain, especially for the tests.

Examples:

----

`./manage.py test galaxy.tests.GalaxyTest.test_full_galaxy_state`

Measurements averaged over 3 run on *my machine*™:
Before: 2min15s
After: 1m41s

----

`./manage.py generate_galaxy_test_data --user-pack-count 1`

Before: 48s
After: 25s

----

`./manage.py rule_galaxy` (for 600 citizen, corresponding to 1 user-pack)

Before: 14m4s
After: 12m34s

* core: populate: use a less ambiguous 'timezone.now()'

When running the tests around midnight, the day is changing, leading to some values being offset to the next day
depending on the timezone, and making some tests to fail. This ensure to use a less ambiguous `now` when populating
the database.

* write more extensive documentation

- add documentation to previously documented classes and functions and refactor some of the documented one, in accordance to the PEP257 and ReStructuredText standards ;
- add some type hints ;
- use a NamedTuple for the `Galaxy.compute_users_score` method instead of a raw tuple. Also change a little bit the logic in the function which call the latter ;
- add some additional parameter checks on a few functions ;
- change a little bit the logic of the log level setting for the galaxy related commands.

* galaxy: tests: split Model and View for more efficient data usage

---------

Co-authored-by: maréchal <thgirod@hotmail.com>
2023-05-10 12:47:02 +02:00
5ab5ef681c Remove duplicated css 2023-05-09 23:08:32 +02:00
c9e70889dd Merge branch 'master' into taiste 2023-05-09 22:37:49 +02:00
b30ee0a27a [FIX] Correction de bugs (#617)
* Fix #600

* Fix #602

* Fixes & améliorations du nouveau CSS (#616)

* Fix #604

* should fix #605

* Fix #608

* Update core/views/site.py

Co-Authored-By: thomas girod <56346771+imperosol@users.noreply.github.com>

* Added back the permission denied

* Should fix #609

* Fix failing test when 2 user are merged

* Should fix #610

* Should fix #627

* Should fix #109

Block les URLs suivantes lorsque le fichier se trouve dans le dir `profiles` ou `SAS` :
- `/file/<id>/`
- `/file/<id>/[delete|prop|edit]`

> Les urls du SAS restent accessiblent pour les roots & les admins SAS
> Les urls de profiles sont uniquement accessiblent aux roots

* Fix root dir of SAS being unnaccessible for sas admins

⚠️ need to edit the SAS directory & save it (no changes required in sas directory properties)

* Remove overwritten code

* Should fix duplicated albums in user profile (wtf)

* Fix typo

* Extended profiles picture access to board members

* Should fix #607

* Fix keyboard navigation not working properly

* Fix user tagged pictures section inside python rather than in the template

* Update utils.py

* Apply suggested changes

* Fix #604

* Fix #608

* Added back the permission denied

* Should fix duplicated albums in user profile (wtf)

* Fix user tagged pictures section inside python rather than in the template

* Apply suggested changes

---------

Co-authored-by: thomas girod <56346771+imperosol@users.noreply.github.com>
2023-05-02 13:07:36 +02:00
ef968f3673 Better usage of cache for groups and clubs related operations (#634)
* Better usage of cache for group retrieval

* Cache clearing on object deletion or update

* replace signals by save and delete override

* add is_anonymous check in is_owned_by

Add in many is_owned_by(self, user) methods that user is not anonymous. Since many of those functions do db queries, this should reduce a little bit the load of the db.

* Stricter usage of User.is_in_group

Constrain the parameters that can be passed to the function to make sure only a str or an int can be used. Also force to explicitly specify if the group id or the group name is used.

* write test and correct bugs

* remove forgotten populate commands

* Correct test
2023-05-02 12:36:59 +02:00
96dede5077 Speed up tests (#638) 2023-05-02 11:00:23 +02:00
66fcb76cb5 Merge pull request #635 from ae-utbm/dependabot/pip/taiste/sentry-sdk-1.21.0
[UPDATE] Bump sentry-sdk from 1.19.1 to 1.21.0
2023-04-26 22:17:13 +02:00
63c8e51137 [UPDATE] Bump sentry-sdk from 1.19.1 to 1.21.0
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 1.19.1 to 1.21.0.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/1.19.1...1.21.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-26 09:08:06 +00:00
12bec5c553 [UPDATE] Bump django-countries from 7.5 to 7.5.1 (#624)
Bumps [django-countries](https://github.com/SmileyChris/django-countries) from 7.5 to 7.5.1.
- [Release notes](https://github.com/SmileyChris/django-countries/releases)
- [Changelog](https://github.com/SmileyChris/django-countries/blob/main/CHANGES.rst)
- [Commits](https://github.com/SmileyChris/django-countries/compare/v7.5...v7.5.1)

---
updated-dependencies:
- dependency-name: django-countries
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-26 02:48:04 +02:00
08460a6964 update link for poetry install 2023-04-22 22:29:43 +02:00
b5a40cfda9 Mise à jour de Black vers la version 23.3 (#629) 2023-04-22 15:32:31 +02:00
c78953b036 [UPDATE] Bump cryptography from 37.0.4 to 40.0.1 (#594)
* [UPDATE] Bump cryptography from 37.0.4 to 40.0.1

Bumps [cryptography](https://github.com/pyca/cryptography) from 37.0.4 to 40.0.1.
- [Release notes](https://github.com/pyca/cryptography/releases)
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/37.0.4...40.0.1)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Updated pyOpenSSL to match cryptography requirements

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Julien Constant <julienconstant190@gmail.com>
2023-04-20 14:43:46 +02:00
427f7ceaff [UPDATE] Bump django-debug-toolbar from 3.8.1 to 4.0.0 (#593)
Bumps [django-debug-toolbar](https://github.com/jazzband/django-debug-toolbar) from 3.8.1 to 4.0.0.
- [Release notes](https://github.com/jazzband/django-debug-toolbar/releases)
- [Changelog](https://github.com/jazzband/django-debug-toolbar/blob/main/docs/changes.rst)
- [Commits](https://github.com/jazzband/django-debug-toolbar/compare/3.8.1...4.0.0)

---
updated-dependencies:
- dependency-name: django-debug-toolbar
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-09 21:37:48 +02:00
1055385bcc [UPDATE] Bump dict2xml from 1.7.2 to 1.7.3 (#592)
Bumps [dict2xml](https://github.com/delfick/python-dict2xml) from 1.7.2 to 1.7.3.
- [Release notes](https://github.com/delfick/python-dict2xml/releases)
- [Commits](https://github.com/delfick/python-dict2xml/compare/release-1.7.2...release-1.7.3)

---
updated-dependencies:
- dependency-name: dict2xml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...
2023-04-09 11:38:58 +02:00
c1022642a2 [FIX] Fixes supplémentaires pour la màj de mars (#622)
- Les photos de l'onglet de la page utilisateur utilise désormais leur version thumbnail au lieu de leur version HD
- Une des classes du CSS du SAS a été renommée car elle empiétait sur une class de la navbar
- Le profil utilisateur a été revu pour ajouter plus d'espacement entre le tableau des cotisations et le numéro de cotisants
- Les images de forum & blouse sont de nouveau cliquable pour les afficher en grands
- Sur mobile, lorsqu'on cliquait sur le premier élément de la navbar, ce dernier avait un overlay avec des angles arrondis
- Sur mobile, les utilisateurs avec des images de profils non carrées dépassait dans l'onglet Famille
2023-04-08 20:59:43 +02:00
910a6f8b34 [FIX] Fixes supplémentaires pour la màj de mars (#622)
- Les photos de l'onglet de la page utilisateur utilise désormais leur version thumbnail au lieu de leur version HD
- Une des classes du CSS du SAS a été renommée car elle empiétait sur une class de la navbar
- Le profil utilisateur a été revu pour ajouter plus d'espacement entre le tableau des cotisations et le numéro de cotisants
- Les images de forum & blouse sont de nouveau cliquable pour les afficher en grands
- Sur mobile, lorsqu'on cliquait sur le premier élément de la navbar, ce dernier avait un overlay avec des angles arrondis
- Sur mobile, les utilisateurs avec des images de profils non carrées dépassait dans l'onglet Famille
2023-04-08 20:58:55 +02:00
06253f029c [UPDATE] Bump sentry-sdk from 1.12.1 to 1.19.1 (#620) 2023-04-08 18:50:31 +00:00
fa6527b24f [FIX] Deuxième vague de fixes pour la mise à jour de mars (#619) 2023-04-06 16:09:29 +02:00
0501e6417a Merge branch 'master' into taiste 2023-04-05 20:02:12 +02:00
a198f5252d Fixes & améliorations du nouveau CSS (#616) 2023-04-05 18:03:43 +02:00
d83842af27 Fix problème de cache dans le SAS & améliore le CSS du SAS
Co-authored-by: Bartuccio Antoine <klmp200@users.noreply.github.com>
2023-04-05 14:32:32 +02:00
f605f7dcc6 Fixes pour la mise à jour de mars (#598) 2023-04-04 22:55:26 +02:00
e638bc04ed Fixes pour la mise à jour de mars (#598) 2023-04-04 22:50:19 +02:00
4830c3ea2d Mise à jour de mars (#586)
---------

Co-authored-by: Thomas Girod <thgirod@hotmail.com>
Co-authored-by: Théo DURR <git@theodurr.fr>
2023-04-04 19:17:44 +02:00
8e7c025e47 [FIX] Broken link in readme and license fix (& update) (#591) 2023-04-04 18:39:45 +02:00
1bfe929ab3 [CSS] Follow up of #578 (#589) 2023-04-04 15:21:09 +02:00
93cc2c883e Bump django from 3.2.16 to 3.2.18 (#574) 2023-04-04 10:16:55 +02:00
44290a20a6 Create dependabot.yml (#587) 2023-04-03 17:18:16 +02:00
1f10a284f2 Added GA/Clubs Google Calendar to main page (#585)
* Added GA/Clubs google calendar to main page

* Made tables full width
2023-04-03 15:54:12 +02:00
28f397574f Amélioration des pages utilisateurs pour les petits écrans (#578, #520)
- Refonte de l'organisation des pages utilisateurs (principalement du front)
  - Page des parrains/fillots
  - Page d'édition du profil
  - Page du profil
  - Page des outils
  - Page des préférences
  - Page des stats utilisateurs

- Refonte du CSS / organisation de la navbar principale (en haut de l'écran)
- Refonte du CSS de la navbar bleu clair (le menu)
- Refonte du CSS du SAS :
  - Page de photo
  - Page d'albums
2023-03-30 14:38:40 +02:00
6c1fa6de0b remove-useless-queries-counter-stats (#519) 2023-03-24 15:32:05 +01:00
f0a08afd31 Merge branch 'repair_NaN_bug_on_click' into taiste 2023-03-10 10:50:26 +01:00
982fc09908 Repair NaN bug for autocomplete on counter click (#583)
* Repair NaN bug for autocomplete on counter click
2023-03-10 10:49:14 +01:00
9e0b5b0b82 Merge branch 'partenariat-eurocks' into taiste 2023-03-09 17:14:34 +01:00
b12e8dc147 [PARTENARIAT] Ajout vitrine d'achat billets eurockéennes 2023 (#582)
* Added eurocks links to eboutic
2023-03-09 17:13:45 +01:00
25c5a3297c Repair NaN bug for autocomplete on counter click 2023-03-09 14:21:12 +01:00
dd3ad42eb5 Mise à jour de février (#581)
Co-authored-by: Thomas Girod <thgirod@hotmail.com>
Co-authored-by: Julien Constant <julienconstant190@gmail.com>
Co-authored-by: Skia <skia@hya.sk>
2023-03-09 13:39:33 +01:00
5ea181829e Edited unit tests
This test caused a breach in security due to the alert block displaying sensitive data.
2023-03-08 20:47:59 +01:00
0cf203669f fix wording
Co-authored-by: Théo DURR <git@theodurr.fr>
2023-03-08 20:35:24 +01:00
559bfcac60 fix typo 2023-03-08 20:13:12 +01:00
db8a1ed0ab Added eurocks links to eboutic 2023-03-08 20:10:54 +01:00
16150905a0 Fixed broken test 2023-03-08 14:11:10 +01:00
9a376887ac Update 404.jinja 2023-03-08 13:08:23 +01:00
773808fa59 Disabled Galaxy button & Removed 404 exception display 2023-03-08 12:50:52 +01:00
c1e59a0676 Disabled galaxy feature (only visually) 2023-03-07 21:32:37 +01:00
05febc60bd Merge branch 'master' into taiste 2023-03-04 16:35:41 +01:00
a73fe598ef repair user merging tool (#498) 2023-03-04 15:01:08 +01:00
b7f20fed6c Galaxy (#575)
Co-authored-by: Skia <florent.jacquet@eshard.com>
2023-03-02 15:11:23 +01:00
585923c827 Add galaxy (#562)
* style.scss: lint

* style.scss: add 'th' padding

* core: populate: add much more data for development

* Add galaxy
2023-02-07 12:08:25 +01:00
394e17d599 resolved importError (#565) 2023-01-13 02:22:53 +01:00
59136850b8 Merge pull request #530 from ae-utbm/redirection_for_barmen
redirect the user directly on counter when barman
2023-01-11 23:24:45 +01:00
d726f4b1e8 Merge pull request #499 from ae-utbm/unify-account-creation
Unify account id creation
2023-01-11 13:26:00 +01:00
705b9b1e6a Passage de vue à Alpine pour les comptoirs (#561)
Vue, c'est cool, mais avec Django c'est un peu chiant à utiliser. Alpine a l'avantage d'être plus léger et d'avoir une syntaxe qui ne ressemble pas à celle de Jinja (ce qui évite d'avoir à mettre des {% raw %} partout).
2023-01-10 22:26:46 +01:00
31e8ad8a3e redirect directly on counter if user is barman 2023-01-10 17:37:26 +01:00
99827e005b upgrade re_path to path (#533) 2023-01-09 22:07:03 +01:00
751c8a8bc6 unify account_id creation 2023-01-09 21:40:38 +01:00
73305c0b28 Implémentation 3DSv2 + résolution bugs eboutic + amélioration pages admin (#558)
Eboutic :
- Implémentation de la norme 3DSecure v2 pour les paiement par carte bancaire
- Amélioration générale de l'interface utilisateur
- Résolution du problème avec les caractères spéciaux dans le panier sur Safari
- Réparation du cookie du panier de l'eboutic qui n'était pas fonctionnel

Autre :
- Mise à jour de la documentation
- Mise à jour des dépendances Javascript
- Suppression du code inutilisé dans `subscription/models.py`
- Amélioration des pages administrateur (back-office Django)

Co-authored-by: thomas girod <56346771+imperosol@users.noreply.github.com>
Co-authored-by: Théo DURR <git@theodurr.fr>
Co-authored-by: Julien Constant <julienconstant190@gmail.com>
2023-01-09 20:53:12 +01:00
37216cd16b Updated lock file according to pyproject 2023-01-09 19:29:04 +01:00
dae68638cf Merge branch 'master' into taiste 2023-01-09 19:15:00 +01:00
7cadc0bc28 Update doc/start/install.rst 2023-01-09 19:04:43 +01:00
cce686f3a8 Update doc/about/tech.rst 2023-01-09 19:04:32 +01:00
4fe46fbcef [FIX] 3DSv2 - Echappement du XML et modif tables (#543)
* Fixed wrong HMAC signature generation
* Updated migration files

Co-authored-by: Julien Constant <julienconstant190@gmail.com>
2023-01-09 17:46:34 +01:00
fe8b8f46aa Fix 3DSv2 implementation (#542)
* Fixed wrong HMAC signature generation

* Fix xml du panier

Co-authored-by: Julien Constant <julienconstant190@gmail.com>
2023-01-06 20:02:45 +01:00
310f1a2283 [FEATURE] Ajout du logo de la promo 23 & Amélioration des anciens logos (#541) 2023-01-05 18:37:13 +01:00
7079761ffe Merge pull request #540 from ae-utbm/3dsv2-encore-un-patch
remove csrf_token
2022-12-26 18:55:41 +01:00
f681c981c6 remove csrf_token 2022-12-26 18:51:04 +01:00
5d97146d14 Merge pull request #531 from ae-utbm/remove_useless_tests
remove useless tests
2022-12-22 13:01:56 +01:00
7b56bd697d Merge pull request #536 from ae-utbm/refactor_admin
enhance admin pages
2022-12-21 17:10:51 +01:00
14cd268d69 Merge pull request #532 from ae-utbm/update-doc
update documentation
2022-12-21 16:02:17 +01:00
754be1c9c9 Update doc/about/tech.rst
Co-authored-by: Julien Constant <49886317+Juknum@users.noreply.github.com>
2022-12-20 21:17:52 +01:00
da2c155254 update documentation 2022-12-20 17:26:28 +01:00
ceb2888f82 enhance admin pages 2022-12-19 20:55:33 +01:00
ce3e2bb32b Revert "Bump cryptography from 37.0.4 to 38.0.3 (#515)" (#529)
This reverts commit 7b6eed9a47.
2022-12-17 14:05:53 +01:00
26c94c9ec6 [Eboutic] Fix double quote issue & improved user experience on small screen (#522)
* Fix #511 Regex issue with escaped double quotes

* Fix basket being when reloading the page (when cookie != "")

+ Added JSDoc
+ Cleaned some code

* Fix #509 Improved user experience on small screens

* Fix css class not being added back when reloading page

* CSS Fixes (see description)

+ Fixed overlaping item title with the cart emoji on small screen
+ Fixed minimal size of the basket on small screen (full width)

* Added darkened background circle to items with no image

* Fix issue were the basket could be None


* Edited CSS to have bette img ratio & the 🛒 icon

Adapt, Improve, Overcome

* Moved basket down on small screen size
2022-12-16 00:37:07 +01:00
7b6eed9a47 Bump cryptography from 37.0.4 to 38.0.3 (#515)
Bumps [cryptography](https://github.com/pyca/cryptography) from 37.0.4 to 38.0.3.
- [Release notes](https://github.com/pyca/cryptography/releases)
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/37.0.4...38.0.3)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-16 00:35:46 +01:00
639197f4c8 update some dependencies (#523) 2022-12-15 23:57:31 +01:00
13bae8d2fa Update deploy.yml (#527) 2022-12-15 23:55:29 +01:00
6b2027550c Revert "Dépendance poetry manquante (setuptools)" (#526)
This reverts commit 022b365bb2.
2022-12-15 23:18:41 +01:00
022b365bb2 Dépendance poetry manquante (setuptools) 2022-12-15 22:36:56 +01:00
d8867fc9ea Edited workflows (#521)
Résoud le soucis lié à dependabot.

Le problème venait du fait que l'on faisait un poetry update et non un poetry Install. Un update écrit dans poetry.lock, alors qu'un Install lit ce fichier. C'est là toute la différence.

Cette PR change donc les workflows.

Laisser ce bot apporte beaucoup de sécurité, vu qu'il nous prévient des changement, et aussi des vulnérabilités au niveau des dépendances.
2022-12-15 19:02:29 +01:00
118c58b5fa Bump django from 3.2.15 to 3.2.16 (#514)
Bumps [django](https://github.com/django/django) from 3.2.15 to 3.2.16.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.2.15...3.2.16)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-14 14:11:58 +01:00
faccc1367f Fix le panier de l'Eboutic pour Safari (#518)
Co-authored-by: Théo DURR <git@theodurr.fr>
Co-authored-by: thomas girod <56346771+imperosol@users.noreply.github.com>
2022-12-14 08:38:41 +01:00
22b83b0814 remove useless tests 2022-12-12 22:56:06 +01:00
1d82e2a7d9 Change country id to ISO 3166 1 numeric for 3DSV2 (#510) 2022-12-12 22:54:31 +01:00
823bd578f2 Fix election page css (#508)
* Fix election candidate overflowing texte
* Fix "Show Less" invisible still occurring in production (wtf)
* Fix program text still overflowing still occurring in production (wtf)

Bonus:
* Fix miss-aligned links in navbar
2022-12-12 21:22:18 +01:00
3e5c36b39e Fix election candidate overflowing texte (#506)
Le texte affiché lorsqu'on appuyait sur "Show more" dépassait horizontalement (programme des candidats)
2022-12-12 20:27:47 +01:00
8fb0897160 add a sentry capture for when eboutic basket regex validation fails. (#504)
Co-authored-by: Théo DURR <git@theodurr.fr>
2022-12-11 10:56:02 +01:00
b8a72c57e1 escape html characters on xml (#505) 2022-12-10 20:41:35 +01:00
6a0a8e8ab4 Edited bar manager club to pdf (pdfesti) (#503)
* Edited bar manager club to pdf (pdfesti)
* Fixed unit tests
2022-12-08 18:26:41 +01:00
9188565a86 Merge pull request #501 from ae-utbm/ci-cl-edit
Ci cl edit
2022-12-01 11:14:56 +01:00
4d7d22c337 edit yml to avoid git conflict when deploying on test 2022-12-01 10:07:03 +01:00
b58116b023 Merge pull request #500 from ae-utbm/eboutic-3DSv2-patch
integration of 3D secure v2 for eboutic bank payment
2022-11-30 23:10:53 +01:00
fe9e5ce861 integration of 3D secure v2 for eboutic bank payment 2022-11-30 22:52:56 +01:00
e43d53e564 Merge pull request #497 from ae-utbm/eboutic-patch
Correct wrong et_autoanswer url
2022-11-16 23:47:24 +01:00
d4a5039efc correct wrong et_autoanswer url 2022-11-16 23:33:22 +01:00
35506e0175 Revert "Merge pull request #496 from ae-utbm/eboutic-patch"
This reverts commit 1c27831f92, reversing
changes made to b92580943a.
2022-11-16 23:31:34 +01:00
1c27831f92 Merge pull request #496 from ae-utbm/eboutic-patch
hide bank payment button during investigation for bug
2022-11-16 22:17:04 +01:00
cdbf07a835 hide bank payment button during investigation for bug 2022-11-16 22:14:16 +01:00
b92580943a Merge pull request #495 from ae-utbm/eboutic-patch
Eboutic patch
2022-11-16 20:57:32 +01:00
60eff1000f second patch on eboutic 2022-11-16 20:41:24 +01:00
96510b270d bux fixing on new eboutic (#493)
- les alt des images des produits n'étaient pas bonnes
- les noms de produits avec une apostrophe dedans n'étaient pas cliquables
2022-11-16 19:08:58 +01:00
1281104d96 Merge pull request #490 from ae-utbm/taiste
Eboutic
2022-11-16 17:04:13 +01:00
3c1724fa81 Add warning message when user has no birthdate 2022-11-15 21:07:50 +01:00
1630af4fbd Merge pull request #489 from TheRolfFR/remove-pinktober
Removing pinktober for AE
2022-11-14 21:04:58 +01:00
e76e2b1537 Removing pinktober for AE 2022-11-14 20:40:19 +01:00
6c276dc596 resolved crash when user has no birthdate 2022-11-12 13:59:58 +01:00
d3c115e3f9 Merge pull request #477 from imperosol/eboutic
Refonte de la boutique en ligne
2022-10-31 16:28:56 +01:00
c245ef7149 refonte de la boutique en ligne 2022-10-31 16:15:16 +01:00
8b09ba2924 refonte de la boutique en ligne 2022-10-30 12:33:21 +01:00
52eb310f95 Merge pull request #486 from ae-utbm/taiste-ci-cl
add yaml config files for test deployment
2022-10-30 11:04:38 +01:00
5bff38fc7b add yaml config files for test deployment 2022-10-28 01:41:53 +02:00
2813a59323 Revert "add yaml config files for taiste deployment"
This reverts commit 89d6db4208.
2022-10-28 01:35:46 +02:00
eef33fa263 Revert "revert push to wrong branch"
This reverts commit 241d3cea53.
2022-10-28 01:35:34 +02:00
241d3cea53 revert push to wrong branch 2022-10-28 01:10:00 +02:00
89d6db4208 add yaml config files for taiste deployment 2022-10-28 01:08:12 +02:00
e0ad288cf4 Suppression des appels à la db de l'ancien site (#483) 2022-10-19 16:26:30 +02:00
f4d7fae8ca Merge pull request #482 from ae-utbm/pinktober-patch-patch
Patch to fix the pinktober patch
2022-10-13 20:56:00 +02:00
95a7493fc1 Patch to fix the pinktober patch 2022-10-13 20:50:18 +02:00
8243dbcbef mend pinktober logo (#481)
* mend pinktober logo

* Increased contrast for links

Co-authored-by: Théo DURR <git@theodurr.fr>
2022-10-12 08:47:06 +02:00
c3a4071627 Edited navbar for pinktober (#480)
Design by @TheRolfFR
2022-10-11 21:55:19 +02:00
cef3f22e0d Merge pull request #476 from ae-utbm/lsacienne/change_banner_to_invitation
Return to classical weekmail banner
2022-09-26 08:18:15 +02:00
c206b965ad Return to classical weekmail banner 2022-09-25 21:44:56 +02:00
e868946fd7 Merge pull request #475 from imperosol/patch-1
update link for poetry install
2022-09-25 17:42:06 +02:00
254044c36b Merge pull request #474 from ae-utbm/lsacienne/change_banner_to_invitation
💄 Modification of banner
2022-09-25 17:26:48 +02:00
c695d6f7a0 update link for poetry install 2022-09-25 12:06:29 +02:00
feef855f01 💄 Modification of banner 2022-09-21 22:12:35 +02:00
b3a48ca5af Merge pull request #471 from ae-utbm/remove-gitlab-files
Added back the **git** .mailmap file
2022-09-11 23:54:03 +02:00
f3a52d094e Merge pull request #472 from ae-utbm/lsacienne/change_banner
💄 Modification of banner
2022-09-11 23:37:43 +02:00
2901bd919f 💄 Modification of banner 2022-09-11 23:17:29 +02:00
0396a5bf2b Added back the **git** .mailmap file 2022-09-09 13:59:01 +02:00
b48ad16f04 Merge pull request #470 from ae-utbm/remove-gitlab-files
Remove old GitLab files
2022-09-02 20:13:20 +02:00
7cc6250860 Delete thank_you.md 2022-09-02 19:53:41 +02:00
ae2e4b518d Removed old GitLab files & may fix auto_assign for reviewers 2022-09-02 19:49:28 +02:00
e9b9f3a62b Merge pull request #469 from ae-utbm/remove-calendar-page
Switched Calendar link to Elections list link (as it was unused)
2022-09-02 19:43:47 +02:00
3321669726 Switched Calendar link to Elections list link (as it was unused) 2022-09-02 19:34:16 +02:00
21fc85670e hot fix: Updated variable names & comments (#461)
- Fixed a wrong condition on the users subscribing history `read` permission.
- The comments are clearer and mentions how to specify clubs by their id.
2022-08-31 20:53:08 +02:00
18a5ad6541 Merge pull request #460 from ae-utbm/integration-subscriptions 2022-08-31 18:51:40 +02:00
71c5456225 Selected club members can now see subscriptions 2022-08-31 18:39:49 +02:00
50e04164a2 Merge pull request #457 from ae-utbm/455-sentry-modal
Fixed some mess done on settings.py
2022-08-27 22:06:13 +02:00
3b1d71f317 Merge pull request #458 from ae-utbm/actions
Editing workflow process
2022-08-27 22:00:54 +02:00
65c2689578 Editing workflow process
Sentry new release only triggers when deployment is successful
2022-08-27 21:56:46 +02:00
b45673f04a Update settings.py 2022-08-27 21:54:20 +02:00
cb6e037f5e Fixed some mess done on settings.py 2022-08-27 21:52:16 +02:00
5e6d60bb3a Merge pull request #456 from ae-utbm/455-sentry-modal
Updated sentry modal SDK
2022-08-27 21:47:59 +02:00
64f8d9bad3 Function name refactor
So the name is clearer
2022-08-27 21:36:45 +02:00
05b86e1f7a Black again 2022-08-27 21:23:49 +02:00
700fed860d Code refactor and comments 2022-08-27 21:22:31 +02:00
820bf6279b Modal window is now autocompleted if user is logged in 2022-08-27 20:14:31 +02:00
b97ce81dd2 Fixed black lint 2022-08-27 19:48:23 +02:00
f4dfd8f99c settings.DEBUG variable sets the sentry env to development
DSN still needs to be specified manually
2022-08-27 19:46:26 +02:00
29139bf360 SENTRY_ENV can now be overriden in settings.py 2022-08-27 18:58:12 +02:00
4f9c2724f5 Updated sentry modal SDK
Specified default environment for issues
2022-08-27 18:46:22 +02:00
7a914f5e94 Merge pull request #451 from ae-utbm/django-3.2-migration
Edited deprecated code
2022-08-27 00:17:36 +02:00
121d04e1d5 Merge branch 'master' into django-3.2-migration 2022-08-27 00:03:58 +02:00
fc6cdba8e2 Merge pull request #452 from ae-utbm/actions
Going back to actions again
2022-08-26 23:33:24 +02:00
7f39ead159 This should work now 2022-08-26 23:32:43 +02:00
1da82ac2dd Another regex 2022-08-26 23:31:33 +02:00
f2dcc39c14 No inspiration 2022-08-26 23:28:08 +02:00
705dc56153 Testing another regex 2022-08-26 23:26:37 +02:00
02047b62d7 Edited random file 2022-08-26 23:21:34 +02:00
895d4b33a6 Going back to actions again 2022-08-26 23:19:29 +02:00
142cb3316e Edited deprecated code
Fixes #449

See : https://docs.djangoproject.com/en/3.2/ref/forms/api/\#notes-on-field-ordering
2022-08-26 22:33:21 +02:00
997fcc9fff Merge pull request #448 from ae-utbm/actions 2022-08-26 22:26:33 +02:00
ec65ca11d6 Added sentry release action (See: #444) 2022-08-26 21:33:18 +02:00
0198027544 I forgot sth 2022-08-26 17:11:20 +02:00
69e0550d4f Merge pull request #447 from ae-utbm/actions
Implemented diff file for CI
2022-08-26 17:09:13 +02:00
9a1a5635e2 Implemented file diff (see: #445) 2022-08-26 17:04:09 +02:00
863f9ff77e Added some safety to deploy script 2022-08-26 16:39:49 +02:00
4146c4c5cb Merge pull request #443 from ae-utbm/actions
Unit tests do not run on master push
2022-08-26 16:24:23 +02:00
b3ad5c5df9 Unit tests do not run on master push
They are only trigerred on PRs
2022-08-26 16:12:17 +02:00
9388e2dc88 Merge pull request #442 from ae-utbm/actions
Actions should work now
2022-08-26 14:54:42 +02:00
56dec9eaa1 Added auto assign for PR 2022-08-26 14:43:51 +02:00
596126f4f4 Actions seem to be operationnal 2022-08-26 14:39:10 +02:00
8646b2c8f7 Rollback to previous version (see: https://github.com/appleboy/ssh-action/issues/174) 2022-08-26 14:10:37 +02:00
c81bb1fb90 Merge pull request #440 from ae-utbm/links-update
Updated links before moving to GitHub
2022-08-26 14:05:12 +02:00
d17a52a8d6 Updated links before moving to GitHub 2022-08-26 14:04:05 +02:00
55e0eecc0b SSH Connection now works 2022-08-26 13:58:45 +02:00
496adc17ea Updated links & moved to a markdown file 2022-08-26 13:58:21 +02:00
ab43d7d2df Testing things 2022-08-26 13:53:17 +02:00
13f0bfe546 Enabled debug 2022-08-26 13:48:40 +02:00
83a384145b Fixed spelling 2022-08-26 13:43:13 +02:00
8a923761a5 Specified environment 2022-08-26 13:26:41 +02:00
6e4a99eba3 Added sample deploy action 2022-08-26 13:20:57 +02:00
0470aa185e Merge pull request #441 from ae-utbm/actions
First try for CI/CD using actions
2022-08-25 22:47:34 +02:00
273371db8b Updated for merging into master 2022-08-25 22:46:30 +02:00
ed3aa0c328 Removed real tests during actions deployment 2022-08-25 22:45:33 +02:00
acfff6b103 Edited master to actions for testing purposes 2022-08-25 22:30:32 +02:00
ada4579193 Created deploy workflow & made a dry run 2022-08-25 22:23:13 +02:00
3a17c3079e 0/10 en dictée 2022-08-25 21:23:48 +02:00
26e46de8e1 Je sais pas écrire 2022-08-25 21:21:12 +02:00
111bcc8e60 Fixed permission issue on apt-get 2022-08-25 21:19:23 +02:00
cdaa204ba2 Added bullshit 2022-08-25 21:17:19 +02:00
e85511fcb9 Initial unit testing action 2022-08-25 21:14:04 +02:00
Sli
35c120a29f Merge branch 'download-all-my-pictures' into 'master'
Fix 'download all my picture button' being displayed in all albums sections

This MR fix the following issue where the download all button is displayed in each album (hence it's for all photos & not only by album)
![image](/uploads/c888e2bf8715d18cd2ea26e63f9fac28/image.png)

See merge request ae-utbm/Sith!320
2022-08-09 17:09:48 +00:00
Sli
7c4c1bc387 Fix permissions on download pictures feature 2022-08-09 18:11:20 +02:00
6e77edcf67 Fix 'download all my picture button' being displayed in all albums sections 2022-08-09 17:57:02 +02:00
effed9c760 Merge branch 'download-all-my-pictures' into 'master'
Add feature to download all of your pictures as a user


See merge request ae-utbm/Sith!319
2022-08-09 13:51:04 +00:00
Sli
0e5c8b53b0 Add missing translations and update doc 2022-08-07 16:45:18 +02:00
Sli
47a332445c Add feature to download all of your pictures as a user 2022-08-07 16:08:56 +02:00
Sli
c904b2d827 Merge branch 'fix/broken-js' into 'master'
Fix broken forms


See merge request ae-utbm/Sith!318
2022-08-06 12:53:30 +00:00
Sli
f56263d6bd Fix broken forms 2022-08-06 14:28:35 +02:00
0c2494cb34 Merge branch 'django-3.2' into 'master'
Upgrade to django 3.2

* Upgrade dependencies
* Fix ugettext
* Fix bad urls

See merge request ae-utbm/Sith!316
2022-08-05 18:46:24 +00:00
9e5743a64c Merge branch 'defer-script-and-font-awesom' into 'master'
Update de base.jinja

Defer des balises script. Ajout de preload sur l'import de fontawesome. Changement de certains commentaires html en commentaires jinja.

Le deux premiers points devraient permettre de gagner un temps non-négligeable au chargement de la page.

See merge request ae-utbm/Sith!317
2022-08-05 18:12:29 +00:00
b5241ec75e Defer des balises script. Ajout de preload sur fa. 2022-08-05 13:22:09 +00:00
Sli
4f00224f0d Update dependencies, apply black and fix wrong default SITH_COUNTER_OFFICES values 2022-08-04 18:42:29 +02:00
Sli
320a896610 Fix tests and broken forms 2022-08-04 17:20:21 +02:00
Sli
08924c5e05 Fix wrong url and set default auto field 2022-08-04 00:38:50 +02:00
Sli
98bfc308a7 Minimal working version
* Upgrade dependencies
* Fix ugettext
* Fix bad urls
2022-08-04 00:28:09 +02:00
Sli
dee24fbc9c Fix deprecation warnings 2022-08-03 21:48:37 +02:00
2556427c7d Merge branch 'lsacienne/invitation_banner2' into 'master'
💄 Change banner to invitation banner

We must set an invitation banner again. For the next one, we should create a new feature with a new button to avoid doing this switch every time.

See merge request ae-utbm/Sith!314
2022-07-04 19:23:28 +00:00
a2b35e5bba 💄 Change banner to invitation banner 2022-07-04 14:03:50 +02:00
3e8f1acb96 Merge branch 'election-css' into 'master'
Improved Elections CSS for the table

- Everything can be seen without scrolling sideways (unless you're on a small screen)
- Each column makes the same size
- Candidate description/program is now below its profile picture
- If the candidate does not have any profile picture, the default one is shown
- The Edit/Delete message has been replaced with their corresponding emojis (they takes fewer spaces and doesn't need to be translated)
- Modified links at the bottom to look like buttons

<details><summary>Before</summary>
![image](/uploads/fd42e2fa027786612582d41c97090277/image.png)
</details>

<details><summary>This MR (root)</summary>
![image](/uploads/8350518422392f971d98f3c7ee48a558/image.png)
</details>

<details><summary>This MR (lambda user)</summary>
![image](/uploads/e6b66730e47556ea21230e89d2d06f83/image.png)
</details>

<details><summary>When a candidate is selected</summary>
![image](/uploads/adde527405fb321ba2023c36e06f4dc3/image.png)
</details>

See merge request ae-utbm/Sith!313
2022-06-15 19:13:47 +00:00
85788977fe Moved file to correct place & improved CSS a bit 2022-06-15 15:32:16 +02:00
066ca5bada This shouldn't be unminified 2022-06-15 01:57:57 +02:00
41369f738e Improved Elections CSS for the table 2022-06-15 01:42:17 +02:00
67377b3cbf Merge branch 'lsacienne/change_weekmail_banner_P22_08_06_2022' into 'master'
Change the invitation banner in weekmail to regular weekmail banner

We now have the weekmail banner and not the invitation banner

See merge request ae-utbm/Sith!312
2022-06-14 09:18:26 +00:00
ac3d668655 💄 CHange the invitation banner in weekmail
We now have the weekmail banner and not the invitation banner
2022-06-08 22:05:24 +02:00
c57b15e159 Merge branch 'lsacienne/change_weekmail_banner_P22' into 'master'
Modification of the banner and footer for the Special General Meeting

There will be a special general meeting next week so we modify the banner to fit with this event.

See merge request ae-utbm/Sith!311
2022-06-01 21:14:38 +00:00
66efb8012e ♻️ Fix black pipeline 2022-06-01 22:46:12 +02:00
cad0c0dadb 💄 Modification of the banner and footer
for the special invitation
2022-06-01 22:40:52 +02:00
b32c90ed5d Add of weekmail footer 2022-06-01 22:39:44 +02:00
4d361dc67b Add of weekmail banner in 2 versions 2022-06-01 22:39:17 +02:00
2b170d91f7 Add of Invitation banner 2022-06-01 22:38:44 +02:00
9e074d6ca6 Merge branch 'service_desk_reply' into 'master'
Update .gitlab/service_desk_templates/thank_you.md


See merge request ae-utbm/Sith!310
2022-05-26 13:46:54 +00:00
b655b2695b Update .gitlab/service_desk_templates/thank_you.md 2022-05-26 08:41:37 +00:00
366aeed2ba Merge branch 'lsacienne/refilling_authorized_for_Bdf_ae' into 'master'
Add authorization to refill to the counters AE & BdF

Since the FIMU is coming, there is a necessity to allow access to physical refilling to the people who will manage the stands.

Therefore, We should authorize the refilling on the BdF and AE counter.

See merge request ae-utbm/Sith!309
2022-05-22 09:56:56 +00:00
454ae5f9e3 Add authorization to refill to the counters AE & BdF 2022-05-22 09:56:53 +00:00
b811114425 fix black pipeline 2022-05-21 21:53:25 +02:00
712e7c8939 Add of verification on the counter 2022-05-21 12:23:34 +02:00
713cd92141 Modification of the settings to fit better with the code 2022-05-21 12:23:23 +02:00
4154b499b1 Add of a new settings for the counters AE & BdF 2022-05-21 09:45:28 +02:00
253f204225 Merge branch '125-fix-family-tree' into 'master'
Ajout de pygraphviz en dépendance

Closes #125

On change également la version minimale de python (`3.7` -> `3.8`)

Closes #125

See merge request ae-utbm/Sith!306
2022-05-08 12:09:38 +00:00
7241f3eb1d Ajout de pygraphviz en dépendance 2022-05-08 12:09:37 +00:00
2422f60898 Merge branch 'lsacienne/refilling_only_for_ae_member' into 'master'
Adds a Restriction for refilling

As it was asked by many members of the AE. I added a restriction applied to the barmens.
In fact, we oftenly loose money due to the physic refilling.
The goal with this change is to only allow **the members of the AE** to refill with physic money.

See merge request ae-utbm/Sith!303
2022-05-05 21:53:57 +00:00
ba6599fa56 Add of tests 2022-05-05 23:24:08 +02:00
f2666f6fb0 Replace the query by a function which already
existed
2022-05-02 00:04:00 +02:00
b33839191d Fix black pipeline 2022-04-28 13:16:03 +02:00
ee3e375dde Post request management 2022-04-28 11:13:07 +02:00
5b0f7ca21b Merge branch 'skia/deploy_in_ci' into 'master'
gitlab-ci: deploy with Gitlab CI/CD

This MR is a proof-of-concept for deploying the Sith using Gitlab CI/CD. It leverage the CI variable to use a private key that is deployed for the `sith` user of `ae-web`. The `prod.sh` script shall do the rest.

TODO before merge:
* [x] Ensure the private key variable is protected (currently done, but may change during development to be used on this branch)
* [x] Remove this branch from the `only:refs` list
* [x] Change `test_prod.sh` for the real script

See merge request ae/Sith!293
2022-04-27 18:21:49 +00:00
f581d91730 gitlab-ci: deploy with Gitlab CI/CD 2022-04-27 18:21:48 +00:00
bbf362691b Change to use settings instead of hardcoding 2022-04-27 15:38:55 +02:00
15e2c8c7b3 Fix the balck pipeline 2022-04-27 15:38:14 +02:00
f838127730 Merge branch 'aile-master-patch-00174' into 'master'
Update badges and links on the readme


See merge request ae/Sith!305
2022-04-27 13:13:08 +00:00
d4c0bb3b0e Fix pipeline
Signed-off-by: Théo DURR <03ht@theodurr.fr>
2022-04-27 14:52:33 +02:00
b81aee3f1c Update badges and links 2022-04-27 09:50:38 +00:00
c6caf5dbce Add of restriction for refilling 2022-04-20 14:01:33 +02:00
7acc59f2cd Merge branch 'lsacienne/refilling_date' into 'master'
Add of date in the counter/refilling_list view

I only add a new field in the counter/refilling_list view which will *normally* display the date of each refilling.

See merge request ae/Sith!302
2022-04-19 10:28:09 +00:00
757ff7ead7 Add of date in the counter/refilling_list view 2022-04-19 12:02:22 +02:00
bc2fe16b74 Merge branch '117-django-2-2-not-compatible-with-psycopg-2-9' into 'master'
Resolve "Django 2.2 not compatible with psycopg 2.9"
Closes #117

See merge request ae/Sith!299
2022-04-18 20:21:19 +00:00
35363d9ee7 Resolve "Django 2.2 not compatible with psycopg 2.9" 2022-04-18 20:21:18 +00:00
52106db6fd Merge branch '118-black-pipeline-is-broken' into 'master'
Resolve "Black pipeline is broken"

Closes #118

Closes #118

See merge request ae/Sith!300
2022-04-18 18:33:39 +00:00
c4b1829e78 Resolve "Black pipeline is broken" 2022-04-18 18:33:36 +00:00
489a9378c5 Merge branch 'poetry' into 'master'
Add missing dependencies and improve pipeline

* Use black version specified in requirements for checking with black
* Check if pyproject.toml file is valid at CI level
* Build documentation in CI
* Add missing postgres dependencie

See merge request ae/Sith!284
2022-03-26 21:27:22 +00:00
Sli
28ae109b32 Add missing dependencies and improve pipeline 2022-03-26 21:27:20 +00:00
e7a6a94ff2 Merge branch 'doc-windows-install' into 'master'
Added WSL Windows doc for the project install

Added steps to install the project on Windows using WSL :)

See merge request ae/Sith!291
2022-03-03 18:18:55 +00:00
234556a172 Merge branch 'skia/fix_eboutic' into 'master'
Multiple fixes

* Bump `black` and fix issues
* `club`: fix tests broken by inclusive translation
* `gitlab-ci`: use `poetry`, as `pip` was broken anyway
* `eboutic`: et_autoanswer: don't require 'Auto' to proceed checking the request: As described in the [doc](https://www.paybox.com/espace-integrateur-documentation/la-solution-paybox-system/gestion-de-la-reponse/), `Auto` may be missing if the payment failed. Thus, it's not required to proceed checking the bank's answer.

See merge request ae/Sith!296
2022-03-02 16:21:10 +00:00
e4ddceabea club: fix tests with inclusive translation 2022-02-28 14:50:24 +01:00
05dd3ad642 gitlab-ci: use poetry 2022-02-28 10:34:15 +01:00
6c5db61a97 eboutic: et_autoanswer: don't require 'Auto' to proceed checking the request 2022-02-28 10:01:32 +01:00
a0e4e9e8e3 Update 'black' version 2022-02-28 10:01:32 +01:00
c66df77d4a Merge branch 'master' of https://ae-dev.utbm.fr/ae/Sith 2022-02-18 16:35:10 +01:00
cfb6b34630 Updated roles to be more inclusive 2022-02-18 16:30:45 +01:00
d8fd0adf47 Merge branch 'skia/et_autoanswer' into 'master'
eboutic: change HTTP return code to avoid blaming the bank's service

See merge request ae/Sith!295
2022-02-10 12:32:43 +00:00
928ae13a8a Merge branch 'bugfix-113-error500' into 'master'
#113: bug fixed

See merge request ae/Sith!294
2022-02-10 12:30:55 +00:00
c2e0ea70e4 eboutic: change HTTP return code to avoid blaming the bank's service 2022-01-04 15:50:36 +01:00
782ce24895 Changed python3 to python 2021-12-02 12:22:34 +01:00
b630742fd4 #113: bug fixed 2021-11-30 17:54:51 +01:00
b20df930a2 Merge branch 'feature-111-fixture_documentation' into 'master'
add fixture documentation

See merge request ae/Sith!292
2021-11-25 22:06:41 +00:00
d60a96fc5c correct populate.rst 2021-11-23 23:44:34 +01:00
05b0a0ab2f Adapted WSL doc to follow recommendation :) 2021-11-23 19:19:24 +01:00
9eb137e503 add fixture documentation 2021-11-22 21:37:10 +01:00
7d797009bb Added WSL windows doc for project install 2021-11-19 13:10:18 +01:00
3c1818f229 Merge branch 'family_rework' into 'master'
Updated text and translations to be more inclusive

See merge request ae/Sith!290
2021-11-18 15:38:13 +00:00
d8b69e9b45 Updated text and translations to be more inclusive 2021-11-18 16:24:14 +01:00
9177c9d4c2 Merge branch 'bugfix-110-ClubSellings' into 'master'
Fix error 500 in club sellings

Closes #110

See merge request ae/Sith!289
2021-11-18 14:32:11 +00:00
5195352975 fixed black pipeline 2021-11-18 15:14:39 +01:00
deb8f865df fix #110 2021-11-18 15:04:25 +01:00
5b2c70e4fb Merge branch 'gender_options' into 'master'
Fix pronouns field being mandatory

See merge request ae/Sith!288
2021-11-18 09:07:21 +00:00
Cel
f66db0859e Fix pronouns field being mandatory 2021-11-18 09:07:19 +00:00
b6488d1d00 Merge branch 'poor_logo_quality' into 'master'
Updated somo logo size where they looked blurry (we love responsive)

See merge request ae/Sith!287
2021-11-10 11:33:05 +00:00
6a4ac336ad Updated somo logo size where they looked blurry (we love responsive) 2021-11-10 12:11:07 +01:00
7ac6dcf8a0 Merge branch 'family_rework' into 'master'
Edited the word "GodFather" to "Family"

See merge request ae/Sith!286
2021-11-10 10:35:40 +00:00
c6a3677cc5 Fixed duplicated translation 2021-11-05 21:11:52 +01:00
707459acd6 Changed word 'Godfather' to 'Family' 2021-11-05 21:01:19 +01:00
6390c3320e Applied black on migration 2021-11-05 20:40:20 +01:00
b8aabc466c Fixed locales
+Pronoun description on the user's profile

Signed-off-by: Ailé <03ht@theodurr.fr>
2021-11-05 20:28:37 +01:00
c66e4232b9 Merge branch 'master' into gender_options
Signed-off-by: Théo DURR <03ht@theodurr.fr>
2021-11-05 17:18:17 +01:00
336450d43f Merge branch 'add-promo-logos' into 'master'
Add missing promo logos

Closes #107

See merge request ae/Sith!285
2021-10-27 10:22:07 +00:00
7e66aadd6f Add missing promo logos 2021-10-27 08:37:58 +02:00
Sli
bf2b796936 Merge branch 'poetry' into 'master'
Using poetry as a dependency system for development

See merge request ae/Sith!281
2021-10-15 16:12:59 +00:00
Sli
85623f48a9 Using poetry as a dependency system for development 2021-10-15 16:12:56 +00:00
4fbee9c3de Make pronouns visible on profile and miniprofile 2021-10-13 08:59:40 +02:00
bfa3b45547 counter_click.jinja: fix error display with Vue 2021-10-11 22:09:45 +02:00
677a9da469 Merge branch 'master' into gender_options 2021-10-11 17:13:06 +02:00
1f7752d457 Add pronouns to profile ; Update gender settings
Add pronouns to option list in profile
Modify "Sex" translation to "Genre"
Added "Other" to sex option list (alongside Man and Woman)

update DB,add default value to Pronouns field

Update views.py
2021-10-06 14:12:34 +02:00
89979dbf61 com: news list: fix UI for admins 2021-10-03 19:08:14 +02:00
8d1abb8f33 Add .mailmap file for cleaner stats 2021-10-03 18:44:47 +02:00
2df3494c3b Merge branch 'skia/weekmail_fix' into 'master'
com: fix weekmail for the case of non-existing email addresses

See merge request ae/Sith!282
2021-10-03 16:35:41 +00:00
39bb490257 com: fix weekmail for the case of non-existing email addresses
If an email address is set as destination for the Weekmail, the SMTP may
refuse it, and `smtplib` will throw a `SMTPRecipientsRefused` error,
containing the list of refused addresses. This commit provides an
interface for the weekmail sender to quickly unsubscribe the faulty
users, so that the next try sending the weekmail can be performed
successfully.
2021-10-03 18:16:51 +02:00
7a7aad0503 style: fix header bar on medium size screens 2021-10-03 16:08:53 +02:00
b157a3fa90 Merge branch 'skia/mobile_ui' into 'master'
Add a first version of a mobile friendly UI

Although not perfect and with many flaws, this should still allow far
easier navigation on mobile devices.

See merge request ae/Sith!280
2021-10-01 17:05:11 +00:00
1b688a8aa5 Add a first version of a mobile friendly UI
Although not perfect and with many flaws, this should still allow far
easier navigation on mobile devices.
2021-10-01 18:44:14 +02:00
e8978cc065 sith/toolbar_debug: don't fail when there is no template 2021-10-01 14:08:57 +02:00
7fd68e4825 Merge branch 'skia/ci_speedup' into 'master'
CI speedup

* Put the Xapian search index in `/dev/shm`, which is an in-memory storage makes the tests go from about 1500s to about 600s.
* Keep the `pip` cache between jobs, to avoid re-downloading all the wheels all the time. This gains about 1min.

See merge request ae/Sith!279
2021-09-30 10:58:49 +00:00
4119eefe37 gitlab-ci: keep pip cache between jobs 2021-09-30 12:07:00 +02:00
aafc2e6e96 gitlab-ci: put search_indexes in shared memory 2021-09-30 12:07:00 +02:00
2cbe6fa11c Merge branch 'genderMatmatroncheV2' into 'master'
Remove gender option of matmatronche & update gender settings

Afin de se mettre à jour il est dorénavant possible de ne pas définir son genre sexué sur l'édit de son profil. D'ailleurs j'ai découvert que de base pour un profil random le sexe était défini sur "Homme" maintenant il est en "-------" !

![image](/uploads/43e9f32dc545b35cbe422a53602b2457/image.png)

De plus afin que personnes n'utilisent l'outil matmatronche à des fins de site de rencontres en cherchant uniquement les "Homme" ou les "Femme" d'une promo etc... Le choix du sexe dans la recherche a été supprimé.

![image](/uploads/e6e75d5661862178acfbe71f3f7efc35/image.png)

C'est la première fois que je fais une modification en solo alors n'hésitez pas à me casser en deux et m'expliquer si j'ai fauté :D

See merge request ae/Sith!264
2021-09-29 15:57:52 +00:00
eec7bcf296 Remove gender option of matmatronche & update gender settings 2021-09-29 17:29:01 +02:00
6c45de34a4 Merge branch 'poster' into 'master'
[com]: add helper_text for resolution and format of poster

See merge request ae/Sith!209
2021-09-29 14:56:30 +00:00
Cyl
61a40c47d2 [com]: add helper_text for resolution and format of poster 2021-09-29 16:09:05 +02:00
007157e2e8 Merge branch 'datetime-hell2' into 'master'
core: create TzAwareDateTimeField to replace forms.DateTimeField

Follow up of !267. I read about Gitlab's slash and merge just after I did my own kind by resetting back to the original commit and creating one commit manually. Sublime merge helps but I still need more practice. :)

What was the right way to group every commit under one?

See merge request ae/Sith!270
2021-09-29 13:53:12 +00:00
49a0ade315 core: create TzAwareDateTimeField to replace forms.DateTimeField 2021-09-29 15:24:06 +02:00
782cd9a45a Merge branch 'sexy-search' into 'master'
Sexy search

The goal of this MR is to solve the search issue #96. Let's assume we have a user with firstname `Jean-François`, lastname `Du Pont` and nickname `Ai'gnan`. Here is a list of search that did not include him previously but now includes him (was and still is case-insensitive):

* `jean françois` (missing -) ;
* `jean-francois` (missing ç) ;
* `jean francois` (both) ;
* `dupont` (space) ;
* `françois` (not the start of his name) ;
* `aignan` (missing ').

You get it, there are a lot of mistakes that humans can do. It also sorts results by `User.last_update` to avoid putting old accounts at the top of common requests (such as firstname-only or lastname-only requests).

### How it works

For those who don't know, the search is handled by Xapian (the search backend) through the haystack library which provides a Django-friendly interface to multiple search backends. Xapian maintains kind of a duplicate of the database (only for models against which we want to search something) which is optimised for search operations. Its "models" are called "indexes" (see `core.search_indexes.UserIndex` for the user model).

Every time a user is created or modified, it is indexed (through a signal handler) so that Xapian knows about it. For the user search, what is indexed is the string outputted by the `core/templates/search/indexes/core/user_auto.txt` template. For our example from above, it looks like this:

```
jean francois
du pont
aignan
jeanfrancois
dupont

jeanfrancoisdupont
```

As you can see, unicode is removed. There also are kind-of duplicates with different spacing as we are using an autocomplete algorithm: it searches from the beginning of words.

The one I am not sure about is the last one. Its goal is to allow searching without putting a space between the firstname and lastname. Is this useful?

The prod will have to do a `./manage.py update_index`, not sure it does it in the upgrade script.

See merge request ae/Sith!269
2021-09-28 00:14:38 +00:00
6382e631b6 search: reduce user index size 2021-09-28 01:44:15 +02:00
12493cffca search: make sure we don't have indexes that are too long 2021-09-28 01:44:15 +02:00
a38ab57ddf search: sort by User.last_update 2021-09-28 01:44:15 +02:00
30091ef69c search: ascii everywhere and unformalized whitespace 2021-09-28 01:44:15 +02:00
1a483bfa2c Merge branch 'och' into 'master'
Settings: Added new subscription for the new CA offer

This year we made a new deal with the CA: if a student open an account, they give us 50€ and the student 80€ with on year of subscription.

See merge request ae/Sith!276
2021-09-27 23:31:46 +00:00
1a091951e8 Added new subscription for the new CA offer 2021-09-28 01:11:23 +02:00
bfb66b352a Merge branch 'dep-hell2' into 'master'
core: add ./manage.py check_front command and call it on runserver

See #92 and !268.

This simplifies checking that front-end dependencies are up to date. It does not allow one to update an outdated dependency. That must be done manually (would otherwise require depending on a CDN or add npm as a dependency). A manual update will make sure changelogs are read and changes will be made appropriately.

We add a `check_front` command to `manage.py` and run it on calls to `runserver`.

This MR does not update any dependency as it is not its goal. MR incoming!

Should doc be added? It seems pretty simple and I don't see what should be documented: if it's red, update it.

~"Review TODO" @sli

See merge request ae/Sith!271
2021-09-27 20:23:35 +00:00
be26e3df7f core: add ./manage.py check_front command and call it on runserver 2021-09-27 22:00:36 +02:00
cb3307509d Merge branch 'skia/counter_rework' into 'master'
counter: make click page dynamic to avoid repetitive loading

See merge request ae/Sith!278
2021-09-27 19:21:28 +00:00
a3158253a7 Black update 2021-09-26 13:58:39 +02:00
406380e4f1 counter: make click page dynamic to avoid repetitive loading
This makes the whole click page load only once for a normal click
workflow. The current basket is now rendered client side with Vue.JS,
and the backend view is able to answer with JSON if asked to.

This should lighten the workflow a lot on the client side, especially
with poor connectivity, and the server should also feel lighter during
big events, due to far less complex Jinja pages to render.
2021-09-26 13:58:39 +02:00
efb70652af counter: redirect to counter main when barman login is timed out 2021-09-26 13:58:39 +02:00
05256bb99a counter: templates: click: JS clean up 2021-09-26 13:58:39 +02:00
64d0cc2fa8 counter: don't display info boxes and navigation menu
This will lighten the pages and make the functionality directly
accessible without ever scrolling the header garbage that is never
needed on those pages.
2021-09-26 13:58:39 +02:00
f5d7267ba7 Merge branch 'skia/fix_ci' into 'master'
Fix CI

See merge request ae/Sith!277
2021-07-21 13:16:02 +00:00
24c0a21cc1 locale: update with latest code version 2021-04-23 12:02:03 +02:00
6a352d642b accounting: fix tests with a computed date instead of hard-coded one 2021-04-23 12:02:03 +02:00
Sli
48ae1f7c1c Merge branch 'och' into 'master'
Edited subscriptions

See merge request ae/Sith!275
2020-09-01 00:21:48 +02:00
aaf1adaaa1 sith: Added a new subscription 2020-08-30 23:53:19 +02:00
f34f5fe693 Upgrade black and format accordingly 2020-08-27 15:59:42 +02:00
Sli
f485178422 Merge branch 'och' into 'master'
settings: Added a new subscription

See merge request ae/Sith!274
2020-06-18 00:23:51 +02:00
Och
797ca0f926 settings: Added a new subscription 2020-06-18 00:23:51 +02:00
Sli
390a4b0064 Merge branch 'bugfix' into 'master'
cache: fix error 500 with new django version

See merge request ae/Sith!273
2020-06-16 19:11:49 +02:00
94b029dc9c cache: fix error 500 with new django version 2020-06-12 20:44:37 +02:00
Sli
45d5728c3e Merge branch 'skia/lazy_load_user_pictures' into 'master'
core: add lazy loading in user pictures page

See merge request ae/Sith!272
2020-06-12 20:19:34 +02:00
6eabbaf209 core: add lazy loading in user pictures page 2020-05-15 12:14:14 +02:00
Sli
03fdd0b947 Merge branch 'trombi' into 'master'
trombi: raw tool for trombi admins to add a club membership to a trombi member

See merge request ae/Sith!266
2020-03-23 21:12:58 +01:00
fb8faacddc trombi: raw tool for trombi admins to add a club membership to a trombi member 2020-03-22 16:14:37 +01:00
Sli
7ee4557ab5 Merge branch 'fix-webcam-error' into 'master'
Front: turn Webcam.js error from an alert to a console log

See merge request ae/Sith!265
2020-03-05 19:20:22 +01:00
5accdbccbb Front: use Webcam.on() for error handling 2020-03-04 07:13:16 +01:00
7fb26f9e45 Front: turn Webcam.js error from an alert to a console log 2020-03-03 09:01:20 +01:00
26a07f722d Remove gender option of matmatronche & update gender settings 2020-02-16 17:51:51 +01:00
Sli
9176a03a8a Merge branch 'bugfix' into 'master'
Fix some SAS and forum errors

Closes #89

See merge request ae/Sith!263
2019-12-17 12:03:28 +01:00
4a1bfc366d sas: fix 500 error when tagging the same user twice or adding a non existing user 2019-12-17 11:25:17 +01:00
ebee8c34e1 forum: fix ForumTopicSubscribeView error 500 with anonymous user 2019-12-16 15:00:33 +01:00
4ecad1c73b Revert "PÈRE 200 !!!!!!!!!!! PÈRE 200 !!!!!!!! TRALALALALÈREEEEUUUU !!!!"
This reverts commit d1b3a4d3f6.
2019-12-10 15:31:37 +01:00
d1b3a4d3f6 PÈRE 200 !!!!!!!!!!! PÈRE 200 !!!!!!!! TRALALALALÈREEEEUUUU !!!! 2019-12-09 03:16:57 +01:00
Sli
40832bb3bf Merge branch 'clubs' into 'master'
Improve Sellings view for clubs

See merge request ae/Sith!262
2019-11-29 16:32:18 +01:00
4a78157f9a club: fix typo on ClubSellingView 2019-11-28 15:14:51 +01:00
bf5fc8750d club: steam CSV download for SellingView 2019-11-28 14:52:33 +01:00
274a7b7137 core/club: allow adding custom js action to pagination link, useful for FormDetailView with pagination 2019-11-28 01:46:41 +01:00
8dd2c02d3e club: add pagination for ClubSellingView 2019-11-28 00:30:51 +01:00
a73f5cb270 club: use sums in bdd for ClubSellingView 2019-11-27 21:37:59 +01:00
7d40e11144 club: ClubSellingView way faster and with multiple selections everywhere 2019-11-27 20:59:32 +01:00
af48553e35 club: separation between archived products and non archived ones 2019-11-27 16:23:14 +01:00
Sli
ad8bcc7282 Merge branch 'bugfix' into 'master'
com: fix 500 error when utbm mail server refuse weekmail

See merge request ae/Sith!260
2019-11-25 14:18:22 +01:00
Sli
22a44415e4 Merge branch 'sli' into 'master'
core: add UserIsRootMixin and an admin delete view for memberships

See merge request ae/Sith!261
2019-11-25 13:32:46 +01:00
6a153719f9 com: fix 500 error when utbm mail server refuse weekmail 2019-11-25 13:30:47 +01:00
5c8fa1b9e7 core: add UserIsRootMixin and an admin delete view for memberships 2019-11-24 19:23:43 +01:00
Sli
d82679e3d7 Merge branch 'documentation' into 'master'
add autoreload/build to documentation server and enhace documentation

See merge request ae/Sith!246
2019-11-21 15:06:13 +01:00
Sli
9cb432a082 doc: correct documentation for groups 2019-11-21 11:11:25 +01:00
Sli
869d29d4a4 doc: corrections for populate documentation 2019-11-21 11:10:31 +01:00
c3d2e64a43 doc: add infos on populate command with group and users available 2019-11-20 18:51:13 +01:00
e1770ec52c doc: add documentation for groups 2019-11-20 17:55:00 +01:00
1256744f1b documentation: add autoreload and build for documentation server 2019-11-20 17:03:18 +01:00
77dddbc581 documentation: add help ressources and update installation instructions 2019-11-20 17:03:18 +01:00
Sli
bfa4000365 Merge branch 'eboutic' into 'master'
eboutic: don't display future account balance if contains refilling item

See merge request ae/Sith!258
2019-11-14 19:31:19 +01:00
Sli
50c2f8164d Merge branch 'deletion_logs' into 'master'
Add generic operation logs and implements it for Sellings and Refilling deletions

See merge request ae/Sith!259
2019-11-14 19:29:55 +01:00
5c30de5f22 core: redesign request middleware with django latest design and better use of threading 2019-11-14 16:32:29 +01:00
1c03ce621f core: remove default value for OperationLog 2019-11-14 16:11:20 +01:00
e634cda318 core/counter: add generic operation logs and implements it for Sellings and Refilling deletions 2019-11-14 01:14:44 +01:00
3501703c15 eboutic: don't display future account balance if contains refilling item 2019-11-05 19:50:08 +01:00
Sli
129f2e53ee Merge branch 'galaRequests' into 'master'
eticketListView: product id instead of eticket id

See merge request ae/Sith!257
2019-11-05 11:23:52 +01:00
209867b3a8 black: makes new version happy 2019-11-04 13:46:09 +01:00
59511d255f eticketListView: product id instead of eticket id 2019-11-03 20:58:20 +01:00
Sli
f42daa01c5 Merge branch 'add-account-amount' into 'master'
Add the account amount to the eboutic

See merge request ae/Sith!254
2019-10-28 23:37:33 +01:00
29ee1b05af Merge branch 'master' into 'add-account-amount'
# Conflicts:
#   locale/fr/LC_MESSAGES/django.po
2019-10-28 15:48:54 +01:00
Sli
42055b9001 Merge branch 'auto-uv-pedagogy' into 'master'
Auto fill UVs in pedagogy

See merge request ae/Sith!253
2019-10-25 12:09:39 +02:00
00c96f5b71 eboutic: fix account amount 2019-10-24 14:40:26 +02:00
5cc7eff94f pedagogy: uv autofill finishing touches 2019-10-24 14:18:29 +02:00
Sli
28077ef0b0 Merge branch 'fix-create-club' into 'master'
club: fix 500 on club_new

See merge request ae/Sith!256
2019-10-22 08:34:08 +02:00
143b128891 club: fix 500 on club_new 2019-10-21 22:56:24 +02:00
6b06b647bc eboutic: add p tag in makecommand 2019-10-21 22:10:38 +02:00
413c613c9f Fix translation for basket account amount 2019-10-21 22:06:56 +02:00
1c0d15ba2a settings: fix black report 2019-10-21 21:17:11 +02:00
28bd6b8708 uv: make autofill available on edit page 2019-10-21 17:10:16 +02:00
419a48ac3a /pedagogy/uv/create put urls in settings 2019-10-21 17:01:21 +02:00
6fce27113a /pedagogy/uv/create use quick notif 2019-10-21 16:52:51 +02:00
53a7633700 uv: Add error handler to uv autofill 2019-10-21 10:34:46 +02:00
4094394cef api: typo in doc comment 2019-10-21 08:13:36 +02:00
f533c39e67 api: fix uv manager acquisition if uv is only available in spring 2019-10-21 02:10:09 +02:00
86bc491df4 Fix UV_endpoint auth 2019-10-20 18:26:11 +02:00
4759551c16 Autofull UV small changes 2019-10-20 17:09:36 +02:00
b057dbfd60 Initial add account amount in eboutic 2019-10-18 21:41:39 +02:00
bddb88d97f Comment UV API and fix little bugs 2019-10-18 18:13:53 +02:00
dbe44a9c1c Fix hour count and submit button 2019-10-18 01:45:49 +02:00
eeb791c460 Initial autofill on UV 2019-10-18 01:28:59 +02:00
Sli
6d0eba6bcf Merge branch 'rework-front' into 'master'
Markdown widget follows the required attribute

See merge request ae/Sith!249
2019-10-17 14:47:56 +02:00
Sli
4d04b21f04 Merge branch 'cleanup-forms' into 'master'
Remove unused multiple-select library

See merge request ae/Sith!250
2019-10-17 14:46:02 +02:00
Sli
2f1b26053b Merge branch 'fix-news-form' into 'master'
Fix 500 when a news needs a start_date and/or end_date but we don't provide

See merge request ae/Sith!252
2019-10-17 14:42:41 +02:00
Sli
1848945d64 Merge branch 'bugfix' into 'master'
Fix huge permission problem inducing server memory leaks for etickets

See merge request ae/Sith!251
2019-10-17 12:36:57 +02:00
9278419345 core: rename GenericContentPermission into GenericContentPermissionMixinBuilder 2019-10-17 11:56:02 +02:00
566dcc7aee counter: fix Selling view permission 2019-10-17 11:24:52 +02:00
a6088c0e4a core: refactor permissions mixins 2019-10-17 11:24:51 +02:00
60c9498a56 Fix 500 on news creation/edition 2019-10-17 10:25:29 +02:00
241650c171 counter: fix eticket server crash induced by old permission system and fix Selling permission 2019-10-16 21:21:51 +02:00
811809895e club: fix mailing list form that unexpectedly relied on try catch in permissions 2019-10-16 21:21:06 +02:00
fe9164bfef core: don't use try/except to catch type of view in permissions mixins 2019-10-16 19:28:32 +02:00
ad3f003fbb Remove unused multiple-select library 2019-10-16 14:28:53 +02:00
7ecb057b68 Isolate easymde instances so that they can be referenced 2019-10-16 12:18:23 +02:00
e932abfa74 Prevent pressing submit if the Markdown widget is empty 2019-10-15 10:41:10 +02:00
0011f4c7b0 Only register onchange once the submit button has been pressed 2019-10-15 10:23:15 +02:00
13312e9879 Highlight a markdown input in red if required and submit is pressed
Kind of copy the behaviour of a Firefox input
Once the submit button has been pressed, highlight in red the text
input if it's required but empty
2019-10-15 09:54:10 +02:00
ced90c23db More JS-like, callback as last argument 2019-10-15 09:53:44 +02:00
Sli
42f5773f51 Merge branch 'fix-guy-feature' into 'master'
Fix the guyguy "feature" on the profile page

See merge request ae/Sith!248
2019-10-15 00:26:04 +02:00
Sli
b270c76249 Merge branch 'galaRequests' into 'master'
Some gala requests: new minor features

See merge request ae/Sith!247
2019-10-14 22:55:55 +02:00
34df825718 Fix the guyguy "feature" on the profile page 2019-10-14 23:49:32 +03:00
Cyl
aac4e3b99c Minor fix for requestGala 2019-10-14 00:32:11 +02:00
5a55a6c642 E-ticket link is sent in the email 2019-10-13 19:02:25 +02:00
65c3483c1f core:login allow the user to create an account when not logged 2019-10-13 18:43:30 +02:00
Sli
660a3161f5 Merge branch 'new_django' into 'master'
upgrade to django 2.2

See merge request ae/Sith!243
2019-10-12 23:19:26 +02:00
Sli
9e6c4b32e3 Merge branch 'bugfix' into 'master'
Fix error when editing poster while being admin

See merge request ae/Sith!245
2019-10-09 18:11:05 +02:00
25225fc451 com: fix error when editing poster while being admin 2019-10-09 17:43:23 +02:00
c3f2d0a134 django2.2: unlock djangorestframework version limit 2019-10-08 22:46:38 +02:00
cd2d3ee6b4 django2.2: fix tests for accounting 2019-10-08 22:46:38 +02:00
81fcf411c1 django2.2: forms fixs for new API 2019-10-08 22:46:38 +02:00
d7075eb762 django2.2: fix breaking change for getting uploaded files size 2019-10-08 22:46:38 +02:00
cf3f5ea60c django2.2: fix django server crash because of breaking change in widget rendering method 2019-10-08 22:46:37 +02:00
59185ab2a8 django2.2: rewrite login and password stack because of removed API 2019-10-08 22:46:37 +02:00
a177fa8232 django2.2: replace deprecated base_name with basename in urls 2019-10-08 22:46:37 +02:00
308cf30a5a django2.2: replace deprecated login view 2019-10-08 22:46:37 +02:00
99c8d95443 django2.2: remove direct assignments to many-to-many fields 2019-10-08 22:46:37 +02:00
97c316b62e django2.2: replace removed is_anonymous() and is_authenticated to their now used counterparts 2019-10-08 22:46:03 +02:00
90921fd4cd django2.2: some migrations to make django happy 2019-10-08 22:46:03 +02:00
296cc4144c django2.2: remove SessionAuthenticationMiddleware
See https://docs.djangoproject.com/en/2.2/releases/2.0/ for more details
2019-10-08 22:46:03 +02:00
f7548ab8d1 django2.2: add on_delete on migrations for OneToOneField 2019-10-08 22:46:03 +02:00
3cb306bc91 django2.2: add on_delete on migrations for ForeignKey 2019-10-08 22:46:03 +02:00
c20d5855e4 django2.2: remove view_page permission as it clash with built-in permission
Need a bit of discussion as I'm not an expert in django built-in permissions
An issue on github says it's related to https://docs.djangoproject.com/en/2.1/releases/2.1/#considerations-for-the-new-model-view-permission
2019-10-08 22:46:02 +02:00
00bd60ef4f django2.2: add exception parameter in forbidden and not_found views 2019-10-08 22:46:02 +02:00
b8c7fb6f74 django2.2: adapt ChoiceField to the new API 2019-10-08 22:46:02 +02:00
df531198c9 django2.2: fix urls and adapt them to new format 2019-10-08 22:46:02 +02:00
12b6f0d488 django2.2: adapt DateTimeField in forms with the new API 2019-10-08 22:46:02 +02:00
6cc234e8d3 django2.2: add default mandatory on_delete on OneToOneField 2019-10-08 22:46:02 +02:00
4dadb1dbc0 django2.2: add default mandatory on_delete on all ForeignKey 2019-10-08 22:46:01 +02:00
2616e8b24c django2.2: relpace django.core.urlresolvers by django.urls 2019-10-08 22:46:01 +02:00
be855c6c90 django2.2: migrate url to re_path 2019-10-08 22:46:01 +02:00
Sli
7be9077fce Merge branch 'documentation' into 'master'
write a new shiny and comprehensive documentation

See merge request ae/Sith!224
2019-10-08 22:42:19 +02:00
d48c09a914 documentation: revise title levels on git documentation 2019-10-08 22:13:29 +02:00
d5c3dbf864 Add git introduction 2019-10-08 20:22:36 +02:00
2a9b89fd2a core: remove README wiki page that wasn't helpful and anyway broken with rst files 2019-10-08 11:12:22 +02:00
c73f4ca847 documentation: add CONTRIBUTING 2019-10-08 11:08:47 +02:00
d63b5335d4 documentation: apply corrections suggested in comments 2019-10-08 11:08:33 +02:00
a766f7137c documentation: add instructions for direnv 2019-10-08 00:59:19 +02:00
5c3c14ab37 core: wrap compilemessages to avoid compiling whole env and avoid crash at sphinx messages compilation 2019-10-08 00:59:00 +02:00
775413ac7e documentation: weekmail documentation 2019-10-07 23:58:04 +02:00
1f271c75f0 documentation: add instructions to add and edit subscriptions 2019-10-07 23:51:38 +02:00
4df152185e documentation: rewrite README and remove TODO 2019-10-07 23:51:38 +02:00
c83b30f27b documentation: add doc compile test in pipeline 2019-10-07 23:51:38 +02:00
db10f7b963 documentation: tutorial about rights management 2019-10-07 23:51:37 +02:00
ed68c2cb38 documentation: move documentation about usage in production 2019-10-07 23:51:37 +02:00
a6c8dea190 documentation: move markdown syntax documentation and move helper for counting lines 2019-10-07 23:51:37 +02:00
124eaf42cd documentation: add models manipulation in hello world tutorial 2019-10-07 23:51:37 +02:00
5489096bf5 documentation: add explanation on MVT and explain project structure 2019-10-07 23:51:37 +02:00
3a425c6792 documentation: add infos about fontawesome + tests instructions + more external doc 2019-10-07 23:51:37 +02:00
8809753108 documentation fix tests because of missing README.md 2019-10-07 23:51:37 +02:00
4428a2e89c documentation: add hello world app tutorial 2019-10-07 23:51:36 +02:00
0616597bf2 documentation: remove CONTRIBUTING and migrate its content in the doc 2019-10-07 23:51:36 +02:00
782a763046 documentation: update instructions, add down loadable files and add all pdf reports for download purpose 2019-10-07 23:51:36 +02:00
8dcade6890 documentation: remove Doxygen, include README into doc update tech and install 2019-10-07 23:51:36 +02:00
dd49d71cb7 documentation: installation instructions 2019-10-07 23:51:36 +02:00
b0b52fd714 documentation: technologies presentation 2019-10-07 23:51:36 +02:00
ef40baaa84 documentation: bump sphinx version 2019-10-07 23:51:35 +02:00
7c259bf26b documentation: write introduction 2019-10-07 23:51:35 +02:00
05e5008305 documentation: base structure for new documentation 2019-10-07 23:51:35 +02:00
Sli
448f5ff40f Merge branch 'com' into 'master'
com: fix error 500 when editing poster with com admin

See merge request ae/Sith!242
2019-10-06 17:55:30 +02:00
Sli
5482f1174d Merge branch 'trombi' into 'master'
trombi: permissions fixs

See merge request ae/Sith!241
2019-10-02 17:30:46 +02:00
2da0560ec8 com: fix error 500 when editing poster with com admin 2019-10-02 15:32:33 +02:00
5151fc3792 trombi: permissions fixs 2019-10-02 14:56:04 +02:00
Sli
fd5cd56f81 Merge branch 'trombi' into 'master'
Trombi fixs

See merge request ae/Sith!240
2019-09-30 00:00:38 +02:00
35d9c05abf trombi: fix trombi tools if user has a trombi profile but no trombi linked 2019-09-29 12:19:22 +02:00
fcb3035b67 trombi: fix some 500 errors when accessing page without being in a trombi 2019-09-29 12:09:03 +02:00
Sli
b7db969f08 Merge branch 'counter' into 'master'
[Counter] - Buying group is now required

See merge request ae/Sith!239
2019-09-20 14:03:07 +02:00
Cyl
14303fd46c [Counter] - Buying group is now required 2019-09-19 23:04:03 +02:00
Sli
5e6b17cd19 Merge branch 'eticket' into 'master'
[Counter] - add the product ID for every Eticket

See merge request ae/Sith!237
2019-09-18 14:42:16 +02:00
Cyl
8232ff59a0 [Counter] - add the product ID for every Eticket 2019-09-16 23:41:06 +02:00
Sli
411c117f0f Merge branch 'performances' into 'master'
Improve performances on forum and SAS

See merge request ae/Sith!235
2019-09-16 14:23:52 +02:00
298499c749 sas: add cache on Picture permissions to avoid hitting the bdd too much 2019-09-16 11:33:32 +02:00
b8ad2d4835 sas: add pagination on AlbumView 2019-09-16 01:26:20 +02:00
Sli
d37eb134e2 Merge branch 'bugfix' into 'master'
Fix error 500 when editing properties of user without linked customer

See merge request ae/Sith!236
2019-09-15 17:48:21 +02:00
63ec5d68f4 core: fix error 500 when editing properties of user without linked customer 2019-09-15 17:05:07 +02:00
8330e1eaf2 sas: simplify and optimize permissions for SAS images 2019-09-15 16:43:17 +02:00
1f86827e46 core: improve performances on not found images 2019-09-15 16:22:13 +02:00
321e5e3ff5 forum: enhance performances on toggle_favorite 2019-09-15 16:12:24 +02:00
Sli
3eb8292d15 Merge branch 'bugfix' into 'master'
Fix error 500 with expired reset password links

See merge request ae/Sith!234
2019-09-11 10:33:39 +02:00
5a3f90fd28 core: fix error 500 with expired reset password links 2019-09-10 16:56:58 +02:00
Sli
eb975f4de1 Merge branch 'performances' into 'master'
Speed up counter click interface and auto scroll

See merge request ae/Sith!233
2019-09-10 15:50:38 +02:00
405b938e08 counter: speed counter click interface and auto scroll 2019-09-10 14:22:13 +02:00
Sli
f899e32fb0 Merge branch 'performances' into 'master'
Improve overall performances on notifications, news pages and navbar

See merge request ae/Sith!232
2019-09-09 11:07:04 +02:00
9181e77d55 core: add some cache on birthdays and counters_activity 2019-09-09 01:20:15 +02:00
f1b3a174b6 core: improve performances on notification reads and display 2019-09-09 00:45:08 +02:00
Sli
eb9821ed36 Merge branch 'sas' into 'master'
core: add index on folder and sas properties of SithFile to speed up SAS

See merge request ae/Sith!231
2019-09-06 16:47:18 +02:00
Sli
defb7fb3a3 Merge branch 'pedagogy_v2' into 'master'
Disable having two comments from the same user for the same uv in uv guide

See merge request ae/Sith!229
2019-09-06 16:28:28 +02:00
83e225a744 core: add index on folder and sas properties of SithFile to speed up SAS 2019-09-06 16:16:03 +02:00
f30bea3dc9 pedagogy: add script to remove all previous doubled comments 2019-09-04 20:49:18 +02:00
a69f7b12b1 pedagogy: add script to remove all previous doubled comments 2019-09-04 20:49:17 +02:00
Sli
ca042fe75e Merge branch 'a19_subscriptions' into 'master'
SUBSCRIPTIONS: 5 new discounted subscriptions for integration

See merge request ae/Sith!226
2019-08-29 19:33:07 +02:00
Zar
dc9111dbcd SUBSCRIPTIONS: 5 new discounted subscriptions for integration 2019-08-29 19:06:58 +02:00
Sli
8a16a66299 Merge branch 'bugfix' into 'master'
com/core: remove links to index edition that caused error 500

See merge request ae/Sith!230
2019-08-29 18:03:34 +02:00
d7a7613807 com: add basics tests for ComAlert and ComInfo to detect regressions on tabs 2019-08-29 17:29:38 +02:00
3fc8688941 com/core: remove links to index edition that caused error 500 2019-08-29 17:23:27 +02:00
Sli
d7b351a1aa Merge branch 'cyl' into 'master'
[COM] Make the news visible for non-authenticated user and birthday visible for subriber only

See merge request ae/Sith!225
2019-08-29 15:07:08 +02:00
Cyl
9e0c4e70d4 [COM] Make the news visible for non-authenticated user and birthday visible for subriber only 2019-08-28 20:40:31 +02:00
Sli
2232c495be Merge branch 'bugfix' into 'master'
pedagogy: fix a bug when updating an UV from a different author

See merge request ae/Sith!228
2019-08-28 16:08:32 +02:00
Sli
18b1bea664 Merge branch 'weekmail' into 'master'
com: add weekmail banner and footer for A19

See merge request ae/Sith!227
2019-08-28 02:35:04 +02:00
a5d5c41dd6 pedagogy: fix a bug when updating an UV from a different author 2019-08-27 22:46:41 +02:00
66d5c71a92 com: add weekmail banner and footer for A19 2019-08-27 18:56:43 +02:00
Sli
824ea37f44 Merge branch 'pedagogy_v2' into 'master'
Better display on mobile for guide page

See merge request ae/Sith!221
2019-08-26 12:11:26 +02:00
Sli
eb29f98c37 Merge branch 'bugfix' into 'master'
pedagogy: correctly fill star widget when editing comment

Closes #88

See merge request ae/Sith!223
2019-08-20 22:57:02 +02:00
d903dc58cf pedagogy: correctly fill star widget when editing comment 2019-08-08 18:59:44 +02:00
f09de0ab7d pedagogy: remove stars on small devices for grade 2019-08-08 12:46:51 +02:00
d29603c584 pedagogy: fix display of guide on smaller devices 2019-08-07 20:03:21 +02:00
3380980c5c pedagogy: add generic font for .radio-guide 2019-08-07 17:50:50 +02:00
Sli
38ef13d9b6 Merge branch 'markdown-editor' into 'master'
core: upgrade easymde

See merge request ae/Sith!222
2019-07-29 15:28:35 +02:00
6c43b1c43d pedagogy: better display on mobile for guide page
Widen search bar and use a grid template
Reduce zooming when clicking on the search bar (firexfox)
Remove zooming when clicking on search bar (chrome)
2019-07-25 19:01:53 +02:00
2b34c46412 core: upgrade easymde 2019-07-19 23:36:56 +02:00
Sli
f9227fa29d Merge branch 'bugfix' into 'master'
Fix error 500 when accessing user tools with anonymous user and fix dependancies

See merge request ae/Sith!220
2019-07-15 15:03:27 +02:00
96a3eaff1c ci: fix django rest framework version 2019-07-15 14:40:03 +02:00
65cb85a887 ci: fix building of pygraphviz 2019-07-15 14:29:47 +02:00
640a72c52d core: add tests for UserToolsView 2019-07-15 12:36:05 +02:00
9b7b96a310 core: add UserIsLoggedMixin to check if an user is not anonymous 2019-07-15 12:27:19 +02:00
b18746e769 core: fix error 500 when accessing user tools with anonymous user 2019-07-13 04:58:23 +02:00
Sli
a2b431b1ab Merge branch 'pedagogy_v2' into 'master'
New version of the pedagogy

See merge request ae/Sith!212
2019-07-11 00:05:27 +02:00
d844bccb04 pedagogy: improve performances on json mode for UVListView 2019-07-10 12:26:37 +02:00
Sli
49f928e754 Apply suggestion to pedagogy/views.py 2019-07-10 12:12:56 +02:00
07fc1014be pedagogy: put methods after properties in models 2019-07-09 16:59:59 +02:00
Sli
facb6faf75 Merge branch 'pedagogy_v2_front' into 'pedagogy_v2'
Frontend for pedagogy

See merge request ae/Sith!218
2019-07-09 16:57:00 +02:00
e72338a7d9 pedagogy: enlarge shape around author 2019-07-09 16:33:06 +02:00
f37c022538 pedagogy: put report button at left and author at right 2019-07-09 16:03:47 +02:00
5229628d48 pedagogy: fix weird blank spacing in comment block 2019-07-09 15:08:48 +02:00
b4b7bf05b4 pedagogy: remove scrolling on desktop for comments 2019-07-09 14:43:46 +02:00
231415a772 pedagogy: hide scroll bar at bottom of comments 2019-07-08 23:43:27 +02:00
f052d307d7 pedagogy: make report button less visible and author more important 2019-07-08 23:16:53 +02:00
f15971cecf pedagogy: simplify moderation form for user 2019-07-08 17:34:23 +02:00
99cf59c7a4 pedagogy: remove fira font to reduce downloaded content for user 2019-07-08 15:51:31 +02:00
0d13014e8a pedagogy: simpler generation for department radio buttons 2019-07-08 15:36:30 +02:00
fd1f89de1d pedagogy: wrap all css inside a class named pedagogy to avoid name clashes 2019-07-08 15:25:28 +02:00
Sli
78b616427f Merge branch 'new_django' into 'master'
core: rename MIDDLEWARE_CLASSES into MIDDLEWARE

See merge request ae/Sith!219
2019-07-08 15:18:21 +02:00
c15ea345dd pedagogy: generate search form radios trough loop and remove semester_translated 2019-07-08 15:17:12 +02:00
1d319e90f0 pedagogy: don't make the anchor scroll with comment text 2019-07-08 09:47:54 +02:00
e6e500e2f9 pedagogy: fix margins on stars for mobile devices 2019-07-08 09:37:30 +02:00
cf1ec1dc86 pedagogy: add missing markdown treatment for key_concept 2019-07-08 09:26:22 +02:00
46a042cde2 pedagogy: fix mobile display on chrome 2019-07-08 09:19:23 +02:00
52129d7511 pedagogy: new mobile view and use of css grids for comment display 2019-07-08 02:54:49 +02:00
d03835d737 pedagogy: allow search on uv title 2019-07-08 00:54:53 +02:00
b4b7817baa pedagogy: auto send form when typing 2019-07-08 00:53:02 +02:00
d85152e58c pedagogy: quick access to comment from moderation 2019-07-08 00:01:54 +02:00
f118040432 pedagogy: add pedagogy in user tools 2019-07-08 00:01:38 +02:00
9f1aff8c07 pedagogy: add retries on search form and make uv table clickable 2019-07-07 23:52:54 +02:00
94bbdf372b pedagogy: fix css grids on chrome android 2019-07-07 22:14:46 +02:00
240d94bd57 pedagogy: enhance display on mobile and fix some bugs with webkit 2019-07-07 21:56:59 +02:00
3ee7ff2752 pedagogy: display hours of UVs 2019-07-07 21:38:00 +02:00
2c5385cf5c pedagogy: enhance mobile view 2019-07-07 21:03:27 +02:00
c8a691044f pedagogy: add translations 2019-07-07 19:36:47 +02:00
f93eaff876 pedagogy: small fix for leave comment on desktop 2019-07-07 19:08:30 +02:00
10faa14bef pedagogy: better display on mobile 2019-07-07 19:07:19 +02:00
30ccbdc32d pedagogy: fix search api when searching one letter (case sensitivity) 2019-07-07 18:55:23 +02:00
79243aece3 pedagogy: better display for uv_details 2019-07-07 18:51:36 +02:00
a61322b83f pedagogy: fix search form display rights 2019-07-07 18:43:43 +02:00
3df73f4d1f pedagogy: css class name consistency 2019-07-07 18:33:56 +02:00
7165a63e97 pedagogy: polish uv_details 2019-07-07 18:29:31 +02:00
2404edd289 pedagogy: clean up guide page 2019-07-07 16:52:28 +02:00
3bff09b04c pedagogy: correctly display uv infos 2019-07-07 16:33:03 +02:00
28748af5d3 pedagogy: smart back button in uv_detail 2019-07-07 14:44:25 +02:00
a56a4e2cb8 pedagogy: better comment display on mobile 2019-07-06 03:54:46 +02:00
339497b2c2 pedagogy: display semester in search view 2019-07-06 02:57:20 +02:00
c05168a2b5 pedagogy: display most recent comment first 2019-07-06 02:33:05 +02:00
782ee35779 pedagogy: incorpore all elements in comment block 2019-07-06 02:30:47 +02:00
43acee8f1b pedagogy: enhance comment look 2019-07-06 02:16:04 +02:00
4a19441a17 pedagogy: translations for semesters in details and handle markdown 2019-07-06 01:16:09 +02:00
11acf5897f pedagogy: correctly hide AP input with its label 2019-07-06 00:34:41 +02:00
4be99fe828 guide design not finished 2019-07-06 00:14:15 -05:00
601193ff3c small change comment 2019-07-05 22:42:13 -05:00
f500dec1f1 core: rename MIDDLEWARE_CLASSES into MIDDLEWARE
MIDDLEWARE_CLASSES is deprecated since django 1.11 and with last version of django-debug-toolbar it broke the dev server
See here for more details https://docs.djangoproject.com/fr/2.2/topics/http/middleware/#upgrading-pre-django-1-10-style-middleware
2019-07-05 22:01:30 +02:00
Cyl
bfb7380715 [Pedagogy] Front comment nearly ended 2019-07-05 20:54:14 +02:00
3e3c576ad7 pedagogy: live uv update on guide 2019-07-05 20:11:33 +02:00
2aa1314fac pedagogy: basic display of the guide 2019-07-05 18:16:28 +02:00
3063e4a24f pedagogy: auto fill search form from get arguments in URL 2019-07-05 16:40:14 +02:00
6f8ec4740c pedagogy: simpler user interface for autumn and spring search 2019-07-05 16:40:14 +02:00
cbcd84c931 pedagogy: add a search form 2019-07-05 16:40:14 +02:00
e475273cd3 pedagogy: enhance StarList widget 2019-07-05 16:40:14 +02:00
Cyl
851231869b [pedagogy] Star for comment grades 2019-07-05 16:40:13 +02:00
3376f4dfb4 pedagogy: fix typo for AUTUMN_AND_SPRING 2019-07-05 16:40:02 +02:00
205f93569a pedagogy: grade averages for UVs 2019-07-04 18:07:51 +02:00
Sli
4f7a8661ba Merge branch 'pedagogy_v2_moderation' into 'pedagogy_v2'
Pedagogy comments moderation

See merge request ae/Sith!215
2019-07-04 15:54:24 +02:00
6e7d351e8e pedagogy: send notification to pedagogy admins at comment report 2019-07-04 15:32:00 +02:00
Sli
73f1927ce4 Merge branch 'pedagogy_v2_old_base_import' into 'pedagogy_v2'
pedagogy: fix bdd id out of sync after old data base import

See merge request ae/Sith!216
2019-06-25 15:24:46 +02:00
56e3f39de1 pedagogy: fix bdd id out of sync after old data base import 2019-06-20 19:20:06 +02:00
75a2aefd69 pedagogy: display if comment is reported 2019-06-20 15:03:51 +02:00
55e822412a pedagogy: full test suite for pedagogy moderation 2019-06-20 14:57:58 +02:00
171d9a4381 pedagogy: tests and fixs for uv moderation form 2019-06-20 14:22:06 +02:00
806084e707 pedagogy: allow to deny removal request for comment in moderation 2019-06-20 13:19:35 +02:00
04009a6a5b pedagogy: moderation interface 2019-06-20 12:15:12 +02:00
3d0f5c0a15 pedagogy: base for uv comment moderation 2019-06-20 01:29:12 +02:00
Sli
437af4dd04 Merge branch 'pedagogy_v2_search_api' into 'pedagogy_v2'
Pedagogy v2 search api

See merge request ae/Sith!214
2019-06-19 10:36:27 +02:00
Sli
ba61455017 Merge branch 'pedagogy_v2_old_base_import' into 'pedagogy_v2'
Pedagogy v2 old base import

See merge request ae/Sith!213
2019-06-19 10:35:50 +02:00
624f1d653d pedagogy: tests for search API 2019-06-19 10:04:29 +02:00
e21821ace5 pedagogy: handle one letter search 2019-06-19 02:00:00 +02:00
22c028af11 pedagogy: rename query to search in search API 2019-06-19 01:53:02 +02:00
f0560f0d2a pedagogy: fix import for HUMA on old database 2019-06-19 01:49:34 +02:00
502ae09523 pedagogy: add filters to search api 2019-06-19 01:26:11 +02:00
2cbef2babc pedagogy: support json response from search API 2019-06-19 00:58:20 +02:00
e11d45b51e pedagogy: more details on uv_detail for tests purpose 2019-06-19 00:58:14 +02:00
061320a5df pedagogy: search index for uvs and search api 2019-06-19 00:57:55 +02:00
2aa465b138 pedagogy: don't update uv comment publish date at each save 2019-06-19 00:35:44 +02:00
d18f0aa829 pedagogy: import results from old uv guide 2019-06-18 17:46:46 +02:00
e7b8ddb631 pedagogy: importation from old uv guide 2019-06-18 17:20:10 +02:00
358a625cc4 pedagogy: simplify and implement department system according to old database model 2019-06-18 10:56:05 +02:00
d44fa73b2a pedagogy: Fix grade range on UVCOmment 2019-06-17 18:42:33 +02:00
505 changed files with 24868 additions and 17813 deletions

14
.envrc Normal file
View File

@ -0,0 +1,14 @@
if [[ ! -f pyproject.toml ]]; then
log_error 'No pyproject.toml found. Use `poetry new` or `poetry init` to create one first.'
exit 2
fi
local VENV=$(poetry env list --full-path | cut -d' ' -f1)
if [[ -z $VENV || ! -d $VENV/bin ]]; then
log_error 'No poetry virtual environment found. Use `poetry install` to create one first.'
exit 2
fi
export VIRTUAL_ENV=$VENV
export POETRY_ACTIVE=1
PATH_add "$VENV/bin"

View File

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

View File

@ -0,0 +1,53 @@
name: "Setup project"
description: "Setup Python and Poetry"
runs:
using: composite
steps:
- name: Install apt packages
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: gettext libxapian-dev libgraphviz-dev
version: 1.0 # increment to reset cache
- name: Install dependencies
run: |
sudo apt update
sudo apt install gettext libxapian-dev libgraphviz-dev
shell: bash
- name: Set up python
uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: Load cached Poetry installation
id: cached-poetry
uses: actions/cache@v3
with:
path: ~/.local
key: poetry-0 # increment to reset cache
- name: Install Poetry
if: steps.cached-poetry.outputs.cache-hit != 'true'
shell: bash
run: curl -sSL https://install.python-poetry.org | python3 -
- name: Check pyproject.toml syntax
shell: bash
run: poetry check
- name: Load cached dependencies
uses: actions/cache@v3
with:
path: ~/.cache/pypoetry
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
restore-keys: |
${{ runner.os }}-poetry-
- name: Install dependencies
run: poetry install -E testing -E docs
shell: bash
- name: Compile gettext messages
run: poetry run ./manage.py compilemessages
shell: bash

10
.github/actions/setup_xapian/action.yml vendored Normal file
View File

@ -0,0 +1,10 @@
name: "Setup xapian"
description: "Setup the xapian indexes"
runs:
using: composite
steps:
- name: Setup xapian index
run: |
mkdir -p /dev/shm/search_indexes
ln -s /dev/shm/search_indexes sith/search_indexes
shell: bash

13
.github/auto_assign.yml vendored Normal file
View File

@ -0,0 +1,13 @@
# Set to true to add reviewers to pull requests
addReviewers: true
# Set to true to add assignees to pull requests
addAssignees: author
# A list of team reviewers to be added to pull requests (GitHub team slug)
reviewers:
- ae-utbm/sith-3-developers
# Number of reviewers has no impact on GitHub teams
# Set 0 to add all the reviewers (default: 0)
numberOfReviewers: 0

18
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,18 @@
# 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] "

43
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,43 @@
name: Sith 3 CI
on:
push:
branches:
- master
- taiste
pull_request:
branches:
- master
- taiste
jobs:
black:
name: Black format
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v3
- name: Setup Project
uses: ./.github/actions/setup_project
- run: poetry run black --check .
tests:
name: Run tests and generate coverage report
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v3
- uses: ./.github/actions/setup_project
- uses: ./.github/actions/setup_xapian
- uses: ./.github/actions/compile_messages
- name: Run tests
run: poetry run coverage run ./manage.py test
- name: Generate coverage report
run: |
poetry run coverage report
poetry run coverage html
- name: Archive code coverage results
uses: actions/upload-artifact@v3
with:
name: coverage-report
path: coverage_report

64
.github/workflows/deploy.yml vendored Normal file
View File

@ -0,0 +1,64 @@
name: Deploy to production
concurrency: production
on:
push:
branches: [master]
workflow_dispatch:
jobs:
deployment:
runs-on: ubuntu-latest
environment: production
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/sith/.local/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: production
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: production

62
.github/workflows/taiste.yml vendored Normal file
View File

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

5
.gitignore vendored
View File

@ -4,12 +4,17 @@ db.sqlite3
*.mo
*__pycache__*
.DS_Store
pyrightconfig.json
dist/
.vscode/
.idea/
env/
doc/html
data/
galaxy/test_galaxy_state.json
/static/
sith/settings_custom.py
sith/search_indexes/
.coverage
coverage_report/
doc/_build

View File

@ -1,26 +0,0 @@
stages:
- test
test:
stage: test
script:
- apt-get update
- apt-get install -y gettext python3-xapian
- pushd /usr/lib/python3/dist-packages/xapian && ln -s _xapian* _xapian.so && popd
- export PYTHONPATH="/usr/lib/python3/dist-packages:$PYTHONPATH"
- python -c 'import xapian' # Fail immediately if there is a problem with xapian
- pip install -r requirements.txt
- pip install coverage
- ./manage.py compilemessages
- coverage run ./manage.py test
- coverage html
- coverage report
artifacts:
paths:
- coverage_report/
black:
stage: test
script:
- pip install black
- black --check .

19
.mailmap Normal file
View File

@ -0,0 +1,19 @@
Code <gregoire.duvauchelle@utbm.fr>
Cyl <labetowiez@aol.fr>
Juste <maaxleblanc@gmail.com>
Krophil <pierre.brunet@krophil.fr>
Lo-J <renaudg779@gmail.com>
Nabos <gnikwo@hotmail.com>
Och <francescowitz68@gmail.com>
Partoo <joqaste@gmail.com>
Skia <skia@hya.sk> <lordbanana25@mailoo.org>
Skia <skia@hya.sk> <skia@libskia.so>
Sli <klmp200@klmp200.net> <antoine@bartuccio.fr>
Soldat <ryan-68@live.fr>
Terre <jbaptiste.lenglet+git@gmail.com>
Vial <robin.trioux@utbm.fr>
Zar <antoine.charmeau@utbm.fr> <antoine.charmeau@laposte.net>
root <root@localhost.localdomain>
tleb <tleb@openmailbox.org> <theo.lebrun@live.fr>
tleb <tleb@openmailbox.org> <theo.lebrun@utbm.fr>
Maréchal <thgirod@hotmail.com>

26
.readthedocs.yml Normal file
View File

@ -0,0 +1,26 @@
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Allow installing xapian-bindings in pip
build:
apt_packages:
- libxapian-dev
# Build documentation in the doc/ directory with Sphinx
sphinx:
configuration: doc/conf.py
# Optionally build your docs in additional formats such as PDF and ePub
formats: all
# Optionally set the version of Python and requirements required to build your docs
python:
version: "3.8"
install:
- method: pip
path: .
extra_requirements:
- docs

View File

@ -1,106 +0,0 @@
*Contribuer c'est la vie*
=========================
Hey ! Tu veux devenir un mec bien et en plus devenir bon en python si tu l'es pas déjà ?
Il se trouve que le sith AE prévu pour l'été 2016 a besoin de toi !
Pour faire le sith, on utilise le framework Web [Django](https://docs.djangoproject.com/fr/1.11/intro/)
N'hésite pas à lire les tutos et à nous demander (ae.info@utbm.fr).
Bon, passons aux choses sérieuses, pour bidouiller le sith sans le casser :
Ben en fait, tu peux pas le casser, tu vas juste t'amuser comme un petit fou sur un clone du sith.
C'est pas compliqué, il suffit d'avoir [Git](http://www.git-scm.com/book/fr/v2), python et pip (pour faciliter la gestion des paquets python).
Tout d'abord, tu vas avoir besoin d'un compte Gitlab pour pouvoir te connecter.
Ensuite, tu fais :
`git clone https://ae-dev.utbm.fr/ae/Sith.git`
Avec cette commande, tu clones le sith AE dans le dossier courant.
```bash
cd Sith
virtualenv --system-site-packages --python=python3 env
source env/bin/activate
pip install -r requirements.txt
./manage runserver
```
Attention aux dépendances système, à voir dans le README.md
Maintenant, faut passer le sith en mode debug dans le fichier de settings personnalisé.
```bash
echo "DEBUG=True" > sith/settings_custom.py
echo 'SITH_URL = "localhost:8000"' >> sith/settings_custom.py
```
Enfin, il s'agit de créer la base de donnée de test lors de la première utilisation
```bash
./manage.py setup
```
Et pour lancer le sith, tu fais `python3 manage.py runserver`
Voilà, c'est le sith AE. Il y a des issues dans le gitlab qui sont à régler. Si tu as un domaine qui t'intéresse, une appli que tu voudrais développer, n'hésites pas et contacte-nous.
Va, et que l'AE soit avec toi.
# Black
Pour uniformiser le formattage du code nous utilisons [Black](https://github.com/ambv/black). Cela permet d'avoir le même codestyle et donc le codereview prend moins de temps. Tout étant dans le même format, il est plus facile pour chacun de comprendre le code de chacun ! Cela permet aussi d'éviter des erreurs (y parait 🤷‍♀️).
Installation de black:
```bash
pip install black
```
## Sous VsCode:
Attention, pour VsCode, Black doit être installé dans votre virtualenv !
Ajouter ces deux lignes dans les settings de VsCode
```json
{
"python.formatting.provider": "black",
"editor.formatOnSave": true
}
```
## Sous Sublime Text
Il faut installer le plugin [sublack](https://packagecontrol.io/packages/sublack) depuis Package Control.
Il suffit ensuite d'ajouter dans les settings du projet (ou en global)
```json
{
"sublack.black_on_save": true
}
```
Si vous utilisez le plugin [anaconda](http://damnwidget.github.io/anaconda/), pensez à modifier les paramètres du linter pep8 pour éviter de recevoir des warnings dans le formatage de black
```json
{
"pep8_ignore": [
"E203",
"E266",
"E501",
"W503"
]
}
```
Sites et doc cools
------------------
[Classy Class-Based Views](http://ccbv.co.uk/projects/Django/1.11/)
Helpers:
`./manage.py makemessages --ignore "env/*" -e py,jinja`
`for f in $(find . -name "*.py" ! -path "*migration*" ! -path "./env/*" ! -path "./doc/*"); do cat ./doc/header "$f" > /tmp/temp && mv /tmp/temp "$f"; done`

3
CONTRIBUTING.rst Normal file
View File

@ -0,0 +1,3 @@
Pour contribuer au projet, vous pouvez vous référer à la documentation disponible à https://sith-ae.readthedocs.io/.
Et n'oubliez pas, contribuer c'est la vie !

2320
Doxyfile

File diff suppressed because it is too large Load Diff

View File

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

134
README.md
View File

@ -1,104 +1,40 @@
[![pipeline status](https://ae-dev.utbm.fr/ae/Sith/badges/master/pipeline.svg)](https://ae-dev.utbm.fr/ae/Sith/commits/master)
[![coverage report](https://ae-dev.utbm.fr/ae/Sith/badges/master/coverage.svg)](https://ae-dev.utbm.fr/ae/Sith/commits/master)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)
[![project chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://ae-dev.zulipchat.com)
<p align="center">
<a href="#">
<img src="https://img.shields.io/badge/Code%20Style-Black-000000?style=for-the-badge">
</a>
<a href="#">
<img src="https://img.shields.io/github/checks-status/ae-utbm/sith3/master?logo=github&style=for-the-badge&label=BUILD">
</a>
<a href="https://sith-ae.readthedocs.io/">
<img src="https://img.shields.io/readthedocs/sith-ae?logo=readthedocs&style=for-the-badge">
</a>
<a href="https://discord.gg/XK9WfPsUFm">
<img src="https://img.shields.io/discord/971448179075731476?label=Discord&logo=discord&style=for-the-badge">
</a>
</p>
## Sith AE
<h3 align="center">This is the source code of the UTBM's student association available at https://ae.utbm.fr/.</h3>
### Dependencies:
See requirements.txt
<p align="justify">All documentation is in the <code>docs</code> directory and online at https://sith-ae.readthedocs.io/. This documentation is written in French because it targets a French audience and it's too much work to maintain two versions. The code and code comments are strictly written in English.</p>
You may need to install some dev libraries like `libmysqlclient-dev`, `libssl-dev`, `libjpeg-dev`, `python3-xapian`, or `zlib1g-dev` to install all the
requiered dependancies with pip. You may also need `mysql-client`. Don't also forget `python3-dev` if you don't have it
already.
You can check all of them with:
```bash
sudo apt install libmysqlclient-dev libssl-dev libjpeg-dev zlib1g-dev python3-dev libffi-dev python3-dev libgraphviz-dev pkg-config python3-xapian gettext
```
On macos, you will need homebrew
```bash
brew install xapian
```
If it doesn't work it's because it need [this pull request](https://github.com/Homebrew/homebrew-core/pull/34835) to be validated.
The development is done with sqlite, but it is advised to set a more robust DBMS for production (Postgresql for example)
### Get started
To start working on the project, just run the following commands:
```bash
git clone https://ae-dev.utbm.fr/ae/Sith.git
cd Sith
virtualenv --system-site-packages --python=python3 env
source env/bin/activate
pip install -r requirements.txt
./manage.py setup
```
To start the simple development server, just run `python3 manage.py runserver`
For more informations, check out the CONTRIBUTING.md file.
### Logging errors with sentry
To connect the app to sentry.io, you must set the variable SENTRY_DSN in your settings custom. It's composed of the full link given on your sentry project
### Generating documentation
There is a Doxyfile at the root of the project, meaning that if you have Doxygen, you can run `doxygen Doxyfile` to
generate a complete HTML documentation that will be available in the *./doc/html/* folder.
### Collecting statics for production:
We use scss in the project. In development environment (DEBUG=True), scss is compiled every time the file is needed. For production, it assumes you have already compiled every files and to do so, you need to use the following commands :
```bash
./manage.py collectstatic # To collect statics
./manage.py compilestatic # To compile scss in those statics
```
### Misc about development
#### Controlling the rights
When you need to protect an object, there are three levels:
* Editing the object properties
* Editing the object various values
* Viewing the object
Now you have many solutions in your model:
* You can define a `is_owned_by(self, user)`, a `can_be_edited_by(self, user)`, and/or a `can_be_viewed_by(self, user)`
method, each returning True is the user passed can edit/view the object, False otherwise.
This allows you to make complex request when the group solution is not powerful enough.
It's useful too when you want to define class-wide permissions, e.g. the club members, that are viewable only for
Subscribers.
* You can add an `owner_group` field, as a ForeignKey to Group. Second is an `edit_groups` field, as a ManyToMany to
Group, and third is a `view_groups`, same as for edit.
Finally, when building a class based view, which is highly advised, you just have to inherit it from CanEditPropMixin,
CanEditMixin, or CanViewMixin, which are located in core.views. Your view will then be protected using either the
appropriate group fields, or the right method to check user permissions.
#### Counting the number of line of code
```bash
sudo apt install cloc
cloc --exclude-dir=doc,env .
```
#### Updating doc/SYNTAX.md
If you make an update in the Markdown syntax parser, it's good to document
update the syntax reference page in `doc/SYNTAX.md`. But updating this file will
break the tests if you don't update the corresponding `doc/SYNTAX.html` file at
the same time.
To do that, simply run `./manage.py markdown > doc/SYNTAX.html`,
and the tests should pass again.
<h4>If you want to contribute, here's how we recommend to read the docs:</h4>
<ul>
<li>
<p align="justify">
First, it's advised to read the about part of the project to understand the goals and the mindset of the current and previous maintainers and know what to expect to learn.
</p>
</li>
<li>
<p align="justify">
If in the first part you realize that you need more background about what we use, we provide some links to tutorials and documentation at the end of our documentation. Feel free to use it and complete it with what you found helpful.
</p>
</li>
<li>
<p align="justify">
Keep in mind that this documentation is thought to be read in order.
</p>
</li>
</ul>
> This project is licensed under GNU GPL, see the LICENSE file at the top of the repository for more details.

View File

@ -1,9 +0,0 @@
# TODO
## Easter eggs
* 'A' 'L' 'L' 'O': Entendre le Allooo de Madame Coucoune
* idem avec cacafe
* Un meat spin quelque part
* Konami code

View File

@ -1,23 +1,15 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#

View File

@ -1,24 +1,16 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#

View File

@ -4,10 +4,10 @@ from __future__ import unicode_literals
from django.db import migrations, models
import django.core.validators
import accounting.models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = []
operations = [
@ -243,6 +243,7 @@ class Migration(migrations.Migration):
verbose_name="accounting type",
to="accounting.AccountingType",
blank=True,
on_delete=django.db.models.deletion.CASCADE,
),
),
],
@ -267,6 +268,7 @@ class Migration(migrations.Migration):
verbose_name="simplified accounting types",
to="accounting.AccountingType",
related_name="simplified_types",
on_delete=django.db.models.deletion.CASCADE,
),
),
],

View File

@ -2,10 +2,10 @@
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("club", "0001_initial"),
("accounting", "0001_initial"),
@ -22,6 +22,7 @@ class Migration(migrations.Migration):
verbose_name="invoice",
to="core.SithFile",
blank=True,
on_delete=django.db.models.deletion.CASCADE,
),
),
migrations.AddField(
@ -31,12 +32,14 @@ class Migration(migrations.Migration):
verbose_name="journal",
to="accounting.GeneralJournal",
related_name="operations",
on_delete=django.db.models.deletion.CASCADE,
),
),
migrations.AddField(
model_name="operation",
name="linked_operation",
field=models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
blank=True,
to="accounting.Operation",
null=True,
@ -54,6 +57,7 @@ class Migration(migrations.Migration):
verbose_name="simple type",
to="accounting.SimplifiedAccountingType",
blank=True,
on_delete=django.db.models.deletion.CASCADE,
),
),
migrations.AddField(
@ -63,6 +67,7 @@ class Migration(migrations.Migration):
verbose_name="club account",
to="accounting.ClubAccount",
related_name="journals",
on_delete=django.db.models.deletion.CASCADE,
),
),
migrations.AddField(
@ -72,20 +77,27 @@ class Migration(migrations.Migration):
verbose_name="bank account",
to="accounting.BankAccount",
related_name="club_accounts",
on_delete=django.db.models.deletion.CASCADE,
),
),
migrations.AddField(
model_name="clubaccount",
name="club",
field=models.ForeignKey(
verbose_name="club", to="club.Club", related_name="club_account"
verbose_name="club",
to="club.Club",
related_name="club_account",
on_delete=django.db.models.deletion.CASCADE,
),
),
migrations.AddField(
model_name="bankaccount",
name="club",
field=models.ForeignKey(
verbose_name="club", to="club.Club", related_name="bank_accounts"
verbose_name="club",
to="club.Club",
related_name="bank_accounts",
on_delete=django.db.models.deletion.CASCADE,
),
),
migrations.AlterUniqueTogether(

View File

@ -6,7 +6,6 @@ import phonenumber_field.modelfields
class Migration(migrations.Migration):
dependencies = [("accounting", "0002_auto_20160824_2152")]
operations = [

View File

@ -6,7 +6,6 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [("accounting", "0003_auto_20160824_2203")]
operations = [
@ -29,6 +28,7 @@ class Migration(migrations.Migration):
related_name="labels",
verbose_name="club account",
to="accounting.ClubAccount",
on_delete=django.db.models.deletion.CASCADE,
),
),
],

View File

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

View File

@ -1,33 +1,25 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
from django.core.urlresolvers import reverse
from django.urls import reverse
from django.core.exceptions import ValidationError
from django.core import validators
from django.db import models
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from django.template import defaultfilters
from phonenumber_field.modelfields import PhoneNumberField
@ -74,7 +66,7 @@ class Company(models.Model):
"""
Method to see if that object can be edited by the given user
"""
if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
return True
return False
@ -110,7 +102,12 @@ class BankAccount(models.Model):
name = models.CharField(_("name"), max_length=30)
iban = models.CharField(_("iban"), max_length=255, blank=True)
number = models.CharField(_("account number"), max_length=255, blank=True)
club = models.ForeignKey(Club, related_name="bank_accounts", verbose_name=_("club"))
club = models.ForeignKey(
Club,
related_name="bank_accounts",
verbose_name=_("club"),
on_delete=models.CASCADE,
)
class Meta:
verbose_name = _("Bank account")
@ -120,7 +117,9 @@ class BankAccount(models.Model):
"""
Method to see if that object can be edited by the given user
"""
if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
if user.is_anonymous:
return False
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
return True
m = self.club.get_membership_for(user)
if m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]:
@ -136,9 +135,17 @@ class BankAccount(models.Model):
class ClubAccount(models.Model):
name = models.CharField(_("name"), max_length=30)
club = models.ForeignKey(Club, related_name="club_account", verbose_name=_("club"))
club = models.ForeignKey(
Club,
related_name="club_account",
verbose_name=_("club"),
on_delete=models.CASCADE,
)
bank_account = models.ForeignKey(
BankAccount, related_name="club_accounts", verbose_name=_("bank account")
BankAccount,
related_name="club_accounts",
verbose_name=_("bank account"),
on_delete=models.CASCADE,
)
class Meta:
@ -149,7 +156,9 @@ class ClubAccount(models.Model):
"""
Method to see if that object can be edited by the given user
"""
if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
if user.is_anonymous:
return False
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
return True
return False
@ -203,7 +212,11 @@ class GeneralJournal(models.Model):
name = models.CharField(_("name"), max_length=40)
closed = models.BooleanField(_("is closed"), default=False)
club_account = models.ForeignKey(
ClubAccount, related_name="journals", null=False, verbose_name=_("club account")
ClubAccount,
related_name="journals",
null=False,
verbose_name=_("club account"),
on_delete=models.CASCADE,
)
amount = CurrencyField(_("amount"), default=0)
effective_amount = CurrencyField(_("effective_amount"), default=0)
@ -216,7 +229,9 @@ class GeneralJournal(models.Model):
"""
Method to see if that object can be edited by the given user
"""
if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
if user.is_anonymous:
return False
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
return True
if self.club_account.can_be_edited_by(user):
return True
@ -226,7 +241,7 @@ class GeneralJournal(models.Model):
"""
Method to see if that object can be edited by the given user
"""
if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
return True
if self.club_account.can_be_edited_by(user):
return True
@ -263,7 +278,11 @@ class Operation(models.Model):
number = models.IntegerField(_("number"))
journal = models.ForeignKey(
GeneralJournal, related_name="operations", null=False, verbose_name=_("journal")
GeneralJournal,
related_name="operations",
null=False,
verbose_name=_("journal"),
on_delete=models.CASCADE,
)
amount = CurrencyField(_("amount"))
date = models.DateField(_("date"))
@ -282,6 +301,7 @@ class Operation(models.Model):
verbose_name=_("invoice"),
null=True,
blank=True,
on_delete=models.CASCADE,
)
done = models.BooleanField(_("is done"), default=False)
simpleaccounting_type = models.ForeignKey(
@ -290,6 +310,7 @@ class Operation(models.Model):
verbose_name=_("simple type"),
null=True,
blank=True,
on_delete=models.CASCADE,
)
accounting_type = models.ForeignKey(
"AccountingType",
@ -297,6 +318,7 @@ class Operation(models.Model):
verbose_name=_("accounting type"),
null=True,
blank=True,
on_delete=models.CASCADE,
)
label = models.ForeignKey(
"Label",
@ -328,6 +350,7 @@ class Operation(models.Model):
null=True,
blank=True,
default=None,
on_delete=models.CASCADE,
)
class Meta:
@ -397,7 +420,9 @@ class Operation(models.Model):
"""
Method to see if that object can be edited by the given user
"""
if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
if user.is_anonymous:
return False
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
return True
if self.journal.closed:
return False
@ -410,7 +435,7 @@ class Operation(models.Model):
"""
Method to see if that object can be edited by the given user
"""
if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
return True
if self.journal.closed:
return False
@ -466,7 +491,9 @@ class AccountingType(models.Model):
"""
Method to see if that object can be edited by the given user
"""
if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
if user.is_anonymous:
return False
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
return True
return False
@ -487,6 +514,7 @@ class SimplifiedAccountingType(models.Model):
AccountingType,
related_name="simplified_types",
verbose_name=_("simplified accounting types"),
on_delete=models.CASCADE,
)
class Meta:
@ -518,7 +546,10 @@ class Label(models.Model):
name = models.CharField(_("label"), max_length=64)
club_account = models.ForeignKey(
ClubAccount, related_name="labels", verbose_name=_("club account")
ClubAccount,
related_name="labels",
verbose_name=_("club account"),
on_delete=models.CASCADE,
)
class Meta:
@ -533,6 +564,8 @@ class Label(models.Model):
)
def is_owned_by(self, user):
if user.is_anonymous:
return False
return self.club_account.is_owned_by(user)
def can_be_edited_by(self, user):

View File

@ -12,7 +12,7 @@
</p>
<hr>
<h2>{% trans %}Bank account: {% endtrans %}{{ object.name }}</h2>
{% if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) and not object.club_accounts.exists() %}
{% if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) and not object.club_accounts.exists() %}
<a href="{{ url('accounting:bank_delete', b_account_id=object.id) }}">{% trans %}Delete{% endtrans %}</a>
{% endif %}
<h4>{% trans %}Infos{% endtrans %}</h4>

View File

@ -9,7 +9,7 @@
<h4>
{% trans %}Accounting{% endtrans %}
</h4>
{% if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %}
{% if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %}
<p><a href="{{ url('accounting:simple_type_list') }}">{% trans %}Manage simplified types{% endtrans %}</a></p>
<p><a href="{{ url('accounting:type_list') }}">{% trans %}Manage accounting types{% 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() %}
<a href="{{ url('accounting:club_delete', c_account_id=object.id) }}">{% trans %}Delete{% endtrans %}</a>
{% endif %}
{% if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %}
{% if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %}
<p><a href="{{ url('accounting:label_new') }}?parent={{ object.id }}">{% trans %}New label{% endtrans %}</a></p>
{% endif %}
<p><a href="{{ url('accounting:label_list', clubaccount_id=object.id) }}">{% trans %}Label list{% endtrans %}</a></p>
@ -56,7 +56,7 @@
{% endif %}
<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>
{% if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) and j.operations.count() == 0 %}
{% if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) and j.operations.count() == 0 %}
<a href="{{ url('accounting:journal_delete', j_id=j.id) }}">{% trans %}Delete{% endtrans %}</a>
{% endif %}
</td>

View File

@ -6,11 +6,12 @@
{% block content %}
<div id="accounting">
{% if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) or user.is_root %}
{% if user.is_root
or user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
%}
<p><a href="{{ url('accounting:co_new') }}">{% trans %}Create new company{% endtrans %}</a></p>
{% endif %}
</br>
<br/>
<table>
<thead>
<tr>

View File

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

View File

@ -20,14 +20,14 @@
{% for k,v in statement.items() %}
<tr>
<td>{{ k }}</td>
<td>{{ v }}</td>
<td>{{ "%.2f" % v }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<p><strong>{% trans %}Amount: {% endtrans %}</strong>{{ object.amount }} €</p>
<p><strong>{% trans %}Effective amount: {% endtrans %}</strong>{{ object.effective_amount }} €</p>
<p><strong>{% trans %}Amount: {% endtrans %}</strong>{{ "%.2f" % object.amount }} €</p>
<p><strong>{% trans %}Effective amount: {% endtrans %}</strong>{{ "%.2f" %object.effective_amount }} €</p>
</div>
{% endblock %}

View File

@ -18,12 +18,12 @@
{% for k,v in dict['CREDIT'].items() %}
<tr>
<td>{{ k }}</td>
<td>{{ v }}</td>
<td>{{ "%.2f" % v }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% trans %}Total: {% endtrans %}{{ dict['CREDIT_sum'] }}
{% trans %}Total: {% endtrans %}{{ "%.2f" % dict['CREDIT_sum'] }}
<h6>{% trans %}Debit{% endtrans %}</h6>
<table>
@ -37,19 +37,19 @@
{% for k,v in dict['DEBIT'].items() %}
<tr>
<td>{{ k }}</td>
<td>{{ v }}</td>
<td>{{ "%.2f" % v }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% trans %}Total: {% endtrans %}{{ dict['DEBIT_sum'] }}
{% trans %}Total: {% endtrans %}{{ "%.2f" % dict['DEBIT_sum'] }}
{% endmacro %}
{% block content %}
<h3>{% trans %}Statement by nature: {% endtrans %} {{ object.name }}</h3>
{% for k,v in statement.items() %}
<h4 style="background: lightblue; padding: 4px;">{{ k }} : {{ v['CREDIT_sum'] - v['DEBIT_sum'] }}</h4>
<h4 style="background: lightblue; padding: 4px;">{{ k }} : {{ "%.2f" % (v['CREDIT_sum'] - v['DEBIT_sum']) }}</h4>
{{ display_tables(v) }}
<hr>
{% endfor %}

View File

@ -28,14 +28,14 @@
{% else %}
<td></td>
{% endif %}
<td>{{ credit_statement[key] }}</td>
<td>{{ "%.2f" % credit_statement[key] }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<p>Total : {{ total_credit }}</p>
<p>Total : {{ "%.2f" % total_credit }}</p>
<h4>{% trans %}Debit{% endtrans %}</h4>
@ -56,13 +56,13 @@
{% else %}
<td></td>
{% endif %}
<td>{{ debit_statement[key] }}</td>
<td>{{ "%.2f" % debit_statement[key] }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<p>Total : {{ total_debit }}</p>
<p>Total : {{ "%.2f" % total_debit }}</p>
</div>
{% endblock %}

View File

@ -13,7 +13,7 @@
</p>
<hr>
<p><a href="{{ url('accounting:club_details', c_account_id=object.id) }}">{% trans %}Back to club account{% endtrans %}</a></p>
{% if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %}
{% if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %}
<p><a href="{{ url('accounting:label_new') }}?parent={{ object.id }}">{% trans %}New label{% endtrans %}</a></p>
{% endif %}
{% if object.labels.all() %}
@ -21,7 +21,7 @@
<ul>
{% for l in object.labels.all() %}
<li><a href="{{ url('accounting:label_edit', label_id=l.id) }}">{{ l }}</a>
{% if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %}
{% if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %}
-
<a href="{{ url('accounting:label_delete', label_id=l.id) }}">{% trans %}Delete{% endtrans %}</a>
{% endif %}

View File

@ -1,31 +1,23 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
from django.test import TestCase
from django.core.urlresolvers import reverse
from django.urls import reverse
from django.core.management import call_command
from datetime import date
from datetime import date, timedelta
from core.models import User
from accounting.models import (
@ -39,7 +31,6 @@ from accounting.models import (
class RefoundAccountTest(TestCase):
def setUp(self):
call_command("populate")
self.skia = User.objects.filter(username="skia").first()
# reffil skia's account
self.skia.customer.amount = 800
@ -81,7 +72,6 @@ class RefoundAccountTest(TestCase):
class JournalTest(TestCase):
def setUp(self):
call_command("populate")
self.journal = GeneralJournal.objects.filter(id=1).first()
def test_permission_granted(self):
@ -109,7 +99,9 @@ class JournalTest(TestCase):
class OperationTest(TestCase):
def setUp(self):
call_command("populate")
self.tomorrow_formatted = (date.today() + timedelta(days=1)).strftime(
"%d/%m/%Y"
)
self.journal = GeneralJournal.objects.filter(id=1).first()
self.skia = User.objects.filter(username="skia").first()
at = AccountingType(
@ -158,7 +150,7 @@ class OperationTest(TestCase):
"target_type": "OTHER",
"target_id": "",
"target_label": "Le fantome de la nuit",
"date": "04/12/2020",
"date": self.tomorrow_formatted,
"mode": "CASH",
"cheque_number": "",
"invoice": "",
@ -191,7 +183,7 @@ class OperationTest(TestCase):
"target_type": "OTHER",
"target_id": "",
"target_label": "Le fantome de la nuit",
"date": "04/12/2020",
"date": self.tomorrow_formatted,
"mode": "CASH",
"cheque_number": "",
"invoice": "",
@ -218,7 +210,7 @@ class OperationTest(TestCase):
"target_type": "OTHER",
"target_id": "",
"target_label": "Le fantome du jour",
"date": "04/12/2020",
"date": self.tomorrow_formatted,
"mode": "CASH",
"cheque_number": "",
"invoice": "",
@ -245,7 +237,7 @@ class OperationTest(TestCase):
"target_type": "OTHER",
"target_id": "",
"target_label": "Le fantome de l'aurore",
"date": "04/12/2020",
"date": self.tomorrow_formatted,
"mode": "CASH",
"cheque_number": "",
"invoice": "",
@ -272,30 +264,50 @@ class OperationTest(TestCase):
def test_nature_statement(self):
self.client.login(username="comptable", password="plop")
response_get = self.client.get(
response = self.client.get(
reverse("accounting:journal_nature_statement", args=[self.journal.id])
)
self.assertTrue(
"bob (Troll Pench\\xc3\\xa9) : 3.00" in str(response_get.content)
)
self.assertContains(response, "bob (Troll Penché) : 3.00", status_code=200)
def test_person_statement(self):
self.client.login(username="comptable", password="plop")
response_get = self.client.get(
response = self.client.get(
reverse("accounting:journal_person_statement", args=[self.journal.id])
)
self.assertTrue(
"<td>3.00</td>" in str(response_get.content)
and '<td><a href="/user/1/">S&#39; Kia</a></td>'
in str(response_get.content)
self.assertContains(response, "Total : 5575.72", status_code=200)
self.assertContains(response, "Total : 71.42")
self.assertContains(
response,
"""
<td><a href="/user/1/">S&#39; Kia</a></td>
<td>3.00</td>""",
)
self.assertContains(
response,
"""
<td><a href="/user/1/">S&#39; Kia</a></td>
<td>823.00</td>""",
)
def test_accounting_statement(self):
self.client.login(username="comptable", password="plop")
response_get = self.client.get(
response = self.client.get(
reverse("accounting:journal_accounting_statement", args=[self.journal.id])
)
self.assertTrue(
"<td>443 - Cr\\xc3\\xa9dit - Ce code n&#39;existe pas</td>"
in str(response_get.content)
self.assertContains(
response,
"""
<tr>
<td>443 - Crédit - Ce code n&#39;existe pas</td>
<td>3.00</td>
</tr>""",
status_code=200,
)
self.assertContains(
response,
"""
<p><strong>Montant : </strong>-5504.30 €</p>
<p><strong>Montant effectif: </strong>-5504.30 €</p>""",
)

View File

@ -1,152 +1,140 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
from django.conf.urls import url
from django.urls import path
from accounting.views import *
urlpatterns = [
# Accounting types
url(
r"^simple_type$",
path(
"simple_type/",
SimplifiedAccountingTypeListView.as_view(),
name="simple_type_list",
),
url(
r"^simple_type/create$",
path(
"simple_type/create/",
SimplifiedAccountingTypeCreateView.as_view(),
name="simple_type_new",
),
url(
r"^simple_type/(?P<type_id>[0-9]+)/edit$",
path(
"simple_type/<int:type_id>/edit/",
SimplifiedAccountingTypeEditView.as_view(),
name="simple_type_edit",
),
# Accounting types
url(r"^type$", AccountingTypeListView.as_view(), name="type_list"),
url(r"^type/create$", AccountingTypeCreateView.as_view(), name="type_new"),
url(
r"^type/(?P<type_id>[0-9]+)/edit$",
path("type/", AccountingTypeListView.as_view(), name="type_list"),
path("type/create/", AccountingTypeCreateView.as_view(), name="type_new"),
path(
"type/<int:type_id>/edit/",
AccountingTypeEditView.as_view(),
name="type_edit",
),
# Bank accounts
url(r"^$", BankAccountListView.as_view(), name="bank_list"),
url(r"^bank/create$", BankAccountCreateView.as_view(), name="bank_new"),
url(
r"^bank/(?P<b_account_id>[0-9]+)$",
path("", BankAccountListView.as_view(), name="bank_list"),
path("bank/create", BankAccountCreateView.as_view(), name="bank_new"),
path(
"bank/<int:b_account_id>/",
BankAccountDetailView.as_view(),
name="bank_details",
),
url(
r"^bank/(?P<b_account_id>[0-9]+)/edit$",
path(
"bank/<int:b_account_id>/edit/",
BankAccountEditView.as_view(),
name="bank_edit",
),
url(
r"^bank/(?P<b_account_id>[0-9]+)/delete$",
path(
"bank/<int:b_account_id>/delete/",
BankAccountDeleteView.as_view(),
name="bank_delete",
),
# Club accounts
url(r"^club/create$", ClubAccountCreateView.as_view(), name="club_new"),
url(
r"^club/(?P<c_account_id>[0-9]+)$",
path("club/create/", ClubAccountCreateView.as_view(), name="club_new"),
path(
"club/<int:c_account_id>/",
ClubAccountDetailView.as_view(),
name="club_details",
),
url(
r"^club/(?P<c_account_id>[0-9]+)/edit$",
path(
"club/<int:c_account_id>/edit/",
ClubAccountEditView.as_view(),
name="club_edit",
),
url(
r"^club/(?P<c_account_id>[0-9]+)/delete$",
path(
"club/<int:c_account_id>/delete/",
ClubAccountDeleteView.as_view(),
name="club_delete",
),
# Journals
url(r"^journal/create$", JournalCreateView.as_view(), name="journal_new"),
url(
r"^journal/(?P<j_id>[0-9]+)$",
path("journal/create/", JournalCreateView.as_view(), name="journal_new"),
path(
"journal/<int:j_id>/",
JournalDetailView.as_view(),
name="journal_details",
),
url(
r"^journal/(?P<j_id>[0-9]+)/edit$",
path(
"journal/<int:j_id>/edit/",
JournalEditView.as_view(),
name="journal_edit",
),
url(
r"^journal/(?P<j_id>[0-9]+)/delete$",
path(
"journal/<int:j_id>/delete/",
JournalDeleteView.as_view(),
name="journal_delete",
),
url(
r"^journal/(?P<j_id>[0-9]+)/statement/nature$",
path(
"journal/<int:j_id>/statement/nature/",
JournalNatureStatementView.as_view(),
name="journal_nature_statement",
),
url(
r"^journal/(?P<j_id>[0-9]+)/statement/person$",
path(
"journal/<int:j_id>/statement/person/",
JournalPersonStatementView.as_view(),
name="journal_person_statement",
),
url(
r"^journal/(?P<j_id>[0-9]+)/statement/accounting$",
path(
"journal/<int:j_id>/statement/accounting/",
JournalAccountingStatementView.as_view(),
name="journal_accounting_statement",
),
# Operations
url(
r"^operation/create/(?P<j_id>[0-9]+)$",
path(
"operation/create/<int:j_id>/",
OperationCreateView.as_view(),
name="op_new",
),
url(r"^operation/(?P<op_id>[0-9]+)$", OperationEditView.as_view(), name="op_edit"),
url(
r"^operation/(?P<op_id>[0-9]+)/pdf$", OperationPDFView.as_view(), name="op_pdf"
),
path("operation/<int:op_id>/", OperationEditView.as_view(), name="op_edit"),
path("operation/<int:op_id>/pdf/", OperationPDFView.as_view(), name="op_pdf"),
# Companies
url(r"^company/list$", CompanyListView.as_view(), name="co_list"),
url(r"^company/create$", CompanyCreateView.as_view(), name="co_new"),
url(r"^company/(?P<co_id>[0-9]+)$", CompanyEditView.as_view(), name="co_edit"),
path("company/list/", CompanyListView.as_view(), name="co_list"),
path("company/create/", CompanyCreateView.as_view(), name="co_new"),
path("company/<int:co_id>/", CompanyEditView.as_view(), name="co_edit"),
# Labels
url(r"^label/new$", LabelCreateView.as_view(), name="label_new"),
url(
r"^label/(?P<clubaccount_id>[0-9]+)$",
path("label/new/", LabelCreateView.as_view(), name="label_new"),
path(
"label/<int:clubaccount_id>/",
LabelListView.as_view(),
name="label_list",
),
url(
r"^label/(?P<label_id>[0-9]+)/edit$", LabelEditView.as_view(), name="label_edit"
),
url(
r"^label/(?P<label_id>[0-9]+)/delete$",
path("label/<int:label_id>/edit/", LabelEditView.as_view(), name="label_edit"),
path(
"label/<int:label_id>/delete/",
LabelDeleteView.as_view(),
name="label_delete",
),
# User account
url(r"^refound/account$", RefoundAccountView.as_view(), name="refound_account"),
path("refound/account/", RefoundAccountView.as_view(), name="refound_account"),
]

View File

@ -1,31 +1,23 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
from django.views.generic import ListView, DetailView
from django.views.generic.edit import UpdateView, CreateView, DeleteView, FormView
from django.core.urlresolvers import reverse_lazy, reverse
from django.utils.translation import ugettext_lazy as _
from django.urls import reverse_lazy, reverse
from django.utils.translation import gettext_lazy as _
from django.forms.models import modelform_factory
from django.core.exceptions import PermissionDenied, ValidationError
from django.forms import HiddenInput
@ -496,7 +488,7 @@ class OperationCreateView(CanCreateMixin, CreateView):
return ret
def get_context_data(self, **kwargs):
""" Add journal to the context """
"""Add journal to the context"""
kwargs = super(OperationCreateView, self).get_context_data(**kwargs)
if self.journal:
kwargs["object"] = self.journal
@ -514,7 +506,7 @@ class OperationEditView(CanEditMixin, UpdateView):
template_name = "accounting/operation_edit.jinja"
def get_context_data(self, **kwargs):
""" Add journal to the context """
"""Add journal to the context"""
kwargs = super(OperationEditView, self).get_context_data(**kwargs)
kwargs["object"] = self.object.journal
return kwargs
@ -735,7 +727,7 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView):
return statement
def get_context_data(self, **kwargs):
""" Add infos to the context """
"""Add infos to the context"""
kwargs = super(JournalNatureStatementView, self).get_context_data(**kwargs)
kwargs["statement"] = self.big_statement()
return kwargs
@ -774,7 +766,7 @@ class JournalPersonStatementView(JournalTabsMixin, CanViewMixin, DetailView):
return sum(self.statement(movement_type).values())
def get_context_data(self, **kwargs):
""" Add journal to the context """
"""Add journal to the context"""
kwargs = super(JournalPersonStatementView, self).get_context_data(**kwargs)
kwargs["credit_statement"] = self.statement("CREDIT")
kwargs["debit_statement"] = self.statement("DEBIT")
@ -804,7 +796,7 @@ class JournalAccountingStatementView(JournalTabsMixin, CanViewMixin, DetailView)
return statement
def get_context_data(self, **kwargs):
""" Add journal to the context """
"""Add journal to the context"""
kwargs = super(JournalAccountingStatementView, self).get_context_data(**kwargs)
kwargs["statement"] = self.statement()
return kwargs
@ -899,7 +891,7 @@ class RefoundAccountView(FormView):
form_class = CloseCustomerAccountForm
def permission(self, user):
if user.is_root or user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
if user.is_root or user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
return True
else:
raise PermissionDenied

View File

@ -1,23 +1,15 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#

View File

@ -1,24 +1,16 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#

View File

@ -1,24 +1,16 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#

View File

@ -1,24 +1,16 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#

View File

@ -1,56 +1,50 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
from django.conf.urls import url, include
from django.urls import re_path, path, include
from api.views import *
from rest_framework import routers
# Router config
router = routers.DefaultRouter()
router.register(r"counter", CounterViewSet, base_name="api_counter")
router.register(r"user", UserViewSet, base_name="api_user")
router.register(r"club", ClubViewSet, base_name="api_club")
router.register(r"group", GroupViewSet, base_name="api_group")
router.register(r"counter", CounterViewSet, basename="api_counter")
router.register(r"user", UserViewSet, basename="api_user")
router.register(r"club", ClubViewSet, basename="api_club")
router.register(r"group", GroupViewSet, basename="api_group")
# Launderette
router.register(
r"launderette/place", LaunderettePlaceViewSet, base_name="api_launderette_place"
r"launderette/place", LaunderettePlaceViewSet, basename="api_launderette_place"
)
router.register(
r"launderette/machine",
LaunderetteMachineViewSet,
base_name="api_launderette_machine",
basename="api_launderette_machine",
)
router.register(
r"launderette/token", LaunderetteTokenViewSet, base_name="api_launderette_token"
r"launderette/token", LaunderetteTokenViewSet, basename="api_launderette_token"
)
urlpatterns = [
# API
url(r"^", include(router.urls)),
url(r"^login/", include("rest_framework.urls", namespace="rest_framework")),
url(r"^markdown$", RenderMarkdown, name="api_markdown"),
url(r"^mailings$", FetchMailingLists, name="mailings_fetch"),
re_path(r"^", include(router.urls)),
re_path(r"^login/", include("rest_framework.urls", namespace="rest_framework")),
re_path(r"^markdown$", RenderMarkdown, name="api_markdown"),
re_path(r"^mailings$", FetchMailingLists, name="mailings_fetch"),
re_path(r"^uv$", uv_endpoint, name="uv_endpoint"),
path("sas/<int:user>", all_pictures_of_user_endpoint, name="all_pictures_of_user"),
]

View File

@ -1,31 +1,23 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
from rest_framework.response import Response
from rest_framework import viewsets
from django.core.exceptions import PermissionDenied
from rest_framework.decorators import detail_route
from rest_framework.decorators import action
from django.db.models.query import QuerySet
from core.views import can_view, can_edit
@ -33,8 +25,8 @@ from core.views import can_view, can_edit
def check_if(obj, user, test):
"""
Detect if it's a single object or a queryset
aply a given test on individual object and return global permission
Detect if it's a single object or a queryset
aply a given test on individual object and return global permission
"""
if isinstance(obj, QuerySet):
for o in obj:
@ -46,10 +38,10 @@ def check_if(obj, user, test):
class ManageModelMixin:
@detail_route()
@action(detail=True)
def id(self, request, pk=None):
"""
Get by id (api/v1/router/{pk}/id/)
Get by id (api/v1/router/{pk}/id/)
"""
self.queryset = get_object_or_404(self.queryset.filter(id=pk))
serializer = self.get_serializer(self.queryset)
@ -77,3 +69,5 @@ from .user import *
from .club import *
from .group import *
from .launderette import *
from .uv import *
from .sas import *

View File

@ -1,31 +1,22 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
from rest_framework.response import Response
from rest_framework.decorators import api_view, renderer_classes
from rest_framework.renderers import StaticHTMLRenderer
from rest_framework.views import APIView
from core.templatetags.renderer import markdown
@ -34,7 +25,7 @@ from core.templatetags.renderer import markdown
@renderer_classes((StaticHTMLRenderer,))
def RenderMarkdown(request):
"""
Render Markdown
Render Markdown
"""
try:
data = markdown(request.POST["text"])

View File

@ -1,24 +1,16 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
@ -43,7 +35,7 @@ class ClubSerializer(serializers.ModelSerializer):
class ClubViewSet(RightModelViewSet):
"""
Manage Clubs (api/v1/club/)
Manage Clubs (api/v1/club/)
"""
serializer_class = ClubSerializer

View File

@ -1,30 +1,22 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
from rest_framework import serializers
from rest_framework.response import Response
from rest_framework.decorators import list_route
from rest_framework.decorators import action
from counter.models import Counter
@ -32,7 +24,6 @@ from api.views import RightModelViewSet
class CounterSerializer(serializers.ModelSerializer):
is_open = serializers.BooleanField(read_only=True)
barman_list = serializers.ListField(
child=serializers.IntegerField(), read_only=True
@ -45,16 +36,16 @@ class CounterSerializer(serializers.ModelSerializer):
class CounterViewSet(RightModelViewSet):
"""
Manage Counters (api/v1/counter/)
Manage Counters (api/v1/counter/)
"""
serializer_class = CounterSerializer
queryset = Counter.objects.all()
@list_route()
@action(detail=False)
def bar(self, request):
"""
Return all bars (api/v1/counter/bar/)
Return all bars (api/v1/counter/bar/)
"""
self.queryset = self.queryset.filter(type="BAR")
serializer = self.get_serializer(self.queryset, many=True)

View File

@ -1,24 +1,16 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
@ -36,7 +28,7 @@ class GroupSerializer(serializers.ModelSerializer):
class GroupViewSet(RightModelViewSet):
"""
Manage Groups (api/v1/group/)
Manage Groups (api/v1/group/)
"""
serializer_class = GroupSerializer

View File

@ -1,30 +1,22 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
from rest_framework import serializers
from rest_framework.response import Response
from rest_framework.decorators import list_route
from rest_framework.decorators import action
from launderette.models import Launderette, Machine, Token
@ -32,7 +24,6 @@ from api.views import RightModelViewSet
class LaunderettePlaceSerializer(serializers.ModelSerializer):
machine_list = serializers.ListField(
child=serializers.IntegerField(), read_only=True
)
@ -72,7 +63,7 @@ class LaunderetteTokenSerializer(serializers.ModelSerializer):
class LaunderettePlaceViewSet(RightModelViewSet):
"""
Manage Launderette (api/v1/launderette/place/)
Manage Launderette (api/v1/launderette/place/)
"""
serializer_class = LaunderettePlaceSerializer
@ -81,7 +72,7 @@ class LaunderettePlaceViewSet(RightModelViewSet):
class LaunderetteMachineViewSet(RightModelViewSet):
"""
Manage Washing Machines (api/v1/launderette/machine/)
Manage Washing Machines (api/v1/launderette/machine/)
"""
serializer_class = LaunderetteMachineSerializer
@ -90,34 +81,34 @@ class LaunderetteMachineViewSet(RightModelViewSet):
class LaunderetteTokenViewSet(RightModelViewSet):
"""
Manage Launderette's tokens (api/v1/launderette/token/)
Manage Launderette's tokens (api/v1/launderette/token/)
"""
serializer_class = LaunderetteTokenSerializer
queryset = Token.objects.all()
@list_route()
@action(detail=False)
def washing(self, request):
"""
Return all washing tokens (api/v1/launderette/token/washing)
Return all washing tokens (api/v1/launderette/token/washing)
"""
self.queryset = self.queryset.filter(type="WASHING")
serializer = self.get_serializer(self.queryset, many=True)
return Response(serializer.data)
@list_route()
@action(detail=False)
def drying(self, request):
"""
Return all drying tokens (api/v1/launderette/token/drying)
Return all drying tokens (api/v1/launderette/token/drying)
"""
self.queryset = self.queryset.filter(type="DRYING")
serializer = self.get_serializer(self.queryset, many=True)
return Response(serializer.data)
@list_route()
@action(detail=False)
def avaliable(self, request):
"""
Return all avaliable tokens (api/v1/launderette/token/avaliable)
Return all avaliable tokens (api/v1/launderette/token/avaliable)
"""
self.queryset = self.queryset.filter(
borrow_date__isnull=True, user__isnull=True
@ -125,10 +116,10 @@ class LaunderetteTokenViewSet(RightModelViewSet):
serializer = self.get_serializer(self.queryset, many=True)
return Response(serializer.data)
@list_route()
@action(detail=False)
def unavaliable(self, request):
"""
Return all unavaliable tokens (api/v1/launderette/token/unavaliable)
Return all unavaliable tokens (api/v1/launderette/token/unavaliable)
"""
self.queryset = self.queryset.filter(
borrow_date__isnull=False, user__isnull=False

42
api/views/sas.py Normal file
View File

@ -0,0 +1,42 @@
from typing import List
from rest_framework.decorators import api_view, renderer_classes
from rest_framework.exceptions import PermissionDenied
from rest_framework.generics import get_object_or_404
from rest_framework.renderers import JSONRenderer
from rest_framework.request import Request
from rest_framework.response import Response
from core.views import can_edit
from core.models import User
from sas.models import Picture
def all_pictures_of_user(user: User) -> List[Picture]:
return [
relation.picture
for relation in user.pictures.exclude(picture=None)
.order_by("-picture__parent__date", "id")
.select_related("picture__parent")
]
@api_view(["GET"])
@renderer_classes((JSONRenderer,))
def all_pictures_of_user_endpoint(request: Request, user: int):
requested_user: User = get_object_or_404(User, pk=user)
if not can_edit(requested_user, request.user):
raise PermissionDenied
return Response(
[
{
"name": f"{picture.parent.name} - {picture.name}",
"date": picture.date,
"author": str(picture.owner),
"full_size_url": picture.get_download_url(),
"compressed_url": picture.get_download_compressed_url(),
"thumb_url": picture.get_download_thumb_url(),
}
for picture in all_pictures_of_user(requested_user)
]
)

View File

@ -1,24 +1,16 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
@ -26,7 +18,7 @@ import datetime
from rest_framework import serializers
from rest_framework.response import Response
from rest_framework.decorators import list_route
from rest_framework.decorators import action
from core.models import User
@ -50,17 +42,17 @@ class UserSerializer(serializers.ModelSerializer):
class UserViewSet(RightModelViewSet):
"""
Manage Users (api/v1/user/)
Only show active users
Manage Users (api/v1/user/)
Only show active users
"""
serializer_class = UserSerializer
queryset = User.objects.filter(is_active=True)
@list_route()
@action(detail=False)
def birthday(self, request):
"""
Return all users born today (api/v1/user/birstdays)
Return all users born today (api/v1/user/birstdays)
"""
date = datetime.datetime.today()
self.queryset = self.queryset.filter(date_of_birth=date)

127
api/views/uv.py Normal file
View File

@ -0,0 +1,127 @@
from rest_framework.response import Response
from rest_framework.decorators import api_view, renderer_classes
from rest_framework.renderers import JSONRenderer
from django.core.exceptions import PermissionDenied
from django.conf import settings
from rest_framework import serializers
import urllib.request
import json
from pedagogy.views import CanCreateUVFunctionMixin
@api_view(["GET"])
@renderer_classes((JSONRenderer,))
def uv_endpoint(request):
if not CanCreateUVFunctionMixin.can_create_uv(request.user):
raise PermissionDenied
params = request.query_params
if "year" not in params or "code" not in params:
raise serializers.ValidationError("Missing query parameter")
short_uv, full_uv = find_uv("fr", params["year"], params["code"])
if short_uv is None or full_uv is None:
return Response(status=204)
return Response(make_clean_uv(short_uv, full_uv))
def find_uv(lang, year, code):
"""
Uses the UTBM API to find an UV.
short_uv is the UV entry in the UV list. It is returned as it contains
information which are not in full_uv.
full_uv is the detailed representation of an UV.
"""
# query the UV list
uvs_url = settings.SITH_PEDAGOGY_UTBM_API + "/uvs/{}/{}".format(lang, year)
response = urllib.request.urlopen(uvs_url)
uvs = json.loads(response.read().decode("utf-8"))
try:
# find the first UV which matches the code
short_uv = next(uv for uv in uvs if uv["code"] == code)
except StopIteration:
return (None, None)
# get detailed information about the UV
uv_url = settings.SITH_PEDAGOGY_UTBM_API + "/uv/{}/{}/{}/{}".format(
lang, year, code, short_uv["codeFormation"]
)
response = urllib.request.urlopen(uv_url)
full_uv = json.loads(response.read().decode("utf-8"))
return (short_uv, full_uv)
def make_clean_uv(short_uv, full_uv):
"""
Cleans the data up so that it corresponds to our data representation.
"""
res = {}
res["credit_type"] = short_uv["codeCategorie"]
# probably wrong on a few UVs as we pick the first UV we find but
# availability depends on the formation
semesters = {
(True, True): "AUTUMN_AND_SPRING",
(True, False): "AUTUMN",
(False, True): "SPRING",
}
res["semester"] = semesters.get(
(short_uv["ouvertAutomne"], short_uv["ouvertPrintemps"]), "CLOSED"
)
langs = {"es": "SP", "en": "EN", "de": "DE"}
res["language"] = langs.get(full_uv["codeLangue"], "FR")
if full_uv["departement"] == "Pôle Humanités":
res["department"] = "HUMA"
else:
departments = {
"AL": "IMSI",
"AE": "EE",
"GI": "GI",
"GC": "EE",
"GM": "MC",
"TC": "TC",
"GP": "IMSI",
"ED": "EDIM",
"AI": "GI",
"AM": "MC",
}
res["department"] = departments.get(full_uv["codeFormation"], "NA")
res["credits"] = full_uv["creditsEcts"]
activities = ("CM", "TD", "TP", "THE", "TE")
for activity in activities:
res["hours_{}".format(activity)] = 0
for activity in full_uv["activites"]:
if activity["code"] in activities:
res["hours_{}".format(activity["code"])] += activity["nbh"] // 60
# wrong if the manager changes depending on the semester
semester = full_uv.get("automne", None)
if not semester:
semester = full_uv.get("printemps", {})
res["manager"] = semester.get("responsable", "")
res["title"] = full_uv["libelle"]
descriptions = {
"objectives": "objectifs",
"program": "programme",
"skills": "acquisitionCompetences",
"key_concepts": "acquisitionNotions",
}
for res_key, full_uv_key in descriptions.items():
res[res_key] = full_uv[full_uv_key]
# if not found or the API did not return a string
if type(res[res_key]) != str:
res[res_key] = ""
return res

View File

@ -1,23 +1,15 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#

View File

@ -1,31 +1,36 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
from ajax_select import make_ajax_form
from django.contrib import admin
from club.models import Club, Membership
admin.site.register(Club)
admin.site.register(Membership)
@admin.register(Club)
class ClubAdmin(admin.ModelAdmin):
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

@ -25,7 +25,7 @@
from django.conf import settings
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField
@ -34,6 +34,7 @@ from club.models import Mailing, MailingSubscription, Club, Membership
from core.models import User
from core.views.forms import SelectDate, SelectDateTime
from counter.models import Counter
from core.views.forms import TzAwareDateTimeField
class ClubEditForm(forms.ModelForm):
@ -66,7 +67,7 @@ class MailingForm(forms.Form):
super(MailingForm, self).__init__(*args, **kwargs)
self.fields["action"] = forms.TypedChoiceField(
(
choices=(
(self.ACTION_NEW_MAILING, _("New Mailing")),
(self.ACTION_NEW_SUBSCRIPTION, _("Subscribe")),
(self.ACTION_REMOVE_SUBSCRIPTION, _("Remove")),
@ -157,23 +158,27 @@ class MailingForm(forms.Form):
return cleaned_data
class SellingsFormBase(forms.Form):
begin_date = forms.DateTimeField(
["%Y-%m-%d %H:%M:%S"],
label=_("Begin date"),
required=False,
widget=SelectDateTime,
)
end_date = forms.DateTimeField(
["%Y-%m-%d %H:%M:%S"],
label=_("End date"),
required=False,
widget=SelectDateTime,
)
counter = forms.ModelChoiceField(
class SellingsForm(forms.Form):
begin_date = TzAwareDateTimeField(label=_("Begin date"), required=False)
end_date = TzAwareDateTimeField(label=_("End date"), required=False)
counters = forms.ModelMultipleChoiceField(
Counter.objects.order_by("name").all(), label=_("Counter"), required=False
)
def __init__(self, club, *args, **kwargs):
super(SellingsForm, self).__init__(*args, **kwargs)
self.fields["products"] = forms.ModelMultipleChoiceField(
club.products.order_by("name").filter(archived=False).all(),
label=_("Products"),
required=False,
)
self.fields["archived_products"] = forms.ModelMultipleChoiceField(
club.products.order_by("name").filter(archived=True).all(),
label=_("Archived products"),
required=False,
)
class ClubMemberForm(forms.Form):
"""
@ -224,9 +229,7 @@ class ClubMemberForm(forms.Form):
id__in=[
ms.user.id
for ms in self.club_members
if ms.can_be_edited_by(
self.request_user, self.request_user_membership
)
if ms.can_be_edited_by(self.request_user)
]
).all(),
label=_("Mark as old"),
@ -238,8 +241,8 @@ class ClubMemberForm(forms.Form):
def clean_users(self):
"""
Check that the user is not trying to add an user already in the club
Also check that the user is valid and has a valid subscription
Check that the user is not trying to add an user already in the club
Also check that the user is valid and has a valid subscription
"""
cleaned_data = super(ClubMemberForm, self).clean()
users = []
@ -262,7 +265,7 @@ class ClubMemberForm(forms.Form):
def clean(self):
"""
Check user rights for adding an user
Check user rights for adding an user
"""
cleaned_data = super(ClubMemberForm, self).clean()

View File

@ -3,10 +3,10 @@ from __future__ import unicode_literals
from django.db import migrations, models
import django.core.validators
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = []
operations = [
@ -90,7 +90,10 @@ class Migration(migrations.Migration):
(
"club",
models.ForeignKey(
verbose_name="club", to="club.Club", related_name="members"
verbose_name="club",
to="club.Club",
related_name="members",
on_delete=django.db.models.deletion.CASCADE,
),
),
],

View File

@ -3,10 +3,10 @@ from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("club", "0001_initial"),
@ -18,6 +18,7 @@ class Migration(migrations.Migration):
model_name="membership",
name="user",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
verbose_name="user",
to=settings.AUTH_USER_MODEL,
related_name="membership",
@ -34,6 +35,7 @@ class Migration(migrations.Migration):
model_name="club",
name="home",
field=models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
blank=True,
null=True,
related_name="home_of_club",
@ -45,14 +47,21 @@ class Migration(migrations.Migration):
model_name="club",
name="owner_group",
field=models.ForeignKey(
default=1, to="core.Group", related_name="owned_club"
on_delete=django.db.models.deletion.CASCADE,
default=1,
to="core.Group",
related_name="owned_club",
),
),
migrations.AddField(
model_name="club",
name="parent",
field=models.ForeignKey(
null=True, to="club.Club", related_name="children", blank=True
on_delete=django.db.models.deletion.CASCADE,
null=True,
to="club.Club",
related_name="children",
blank=True,
),
),
migrations.AddField(

View File

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

View File

@ -3,10 +3,10 @@ from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [("club", "0003_auto_20160902_2042")]
operations = [
@ -14,6 +14,7 @@ class Migration(migrations.Migration):
model_name="membership",
name="user",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
verbose_name="user",
related_name="memberships",
to=settings.AUTH_USER_MODEL,

View File

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

View File

@ -6,7 +6,6 @@ import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [("club", "0005_auto_20161120_1149")]
operations = [

View File

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

View File

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

View File

@ -5,10 +5,10 @@ from django.db import migrations, models
from django.conf import settings
import re
import django.core.validators
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("club", "0008_auto_20170515_2214"),
@ -51,12 +51,16 @@ class Migration(migrations.Migration):
(
"club",
models.ForeignKey(
verbose_name="Club", related_name="mailings", to="club.Club"
on_delete=django.db.models.deletion.CASCADE,
verbose_name="Club",
related_name="mailings",
to="club.Club",
),
),
(
"moderator",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
null=True,
verbose_name="moderator",
related_name="moderated_mailings",
@ -84,6 +88,7 @@ class Migration(migrations.Migration):
(
"mailing",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
verbose_name="Mailing",
related_name="subscriptions",
to="club.Mailing",
@ -92,6 +97,7 @@ class Migration(migrations.Migration):
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
null=True,
verbose_name="User",
related_name="mailing_subscriptions",

View File

@ -5,6 +5,7 @@ from django.db import migrations, models
from club.models import Club
from core.operations import PsqlRunOnly
import django.db.models.deletion
def generate_club_pages(apps, schema_editor):
@ -18,7 +19,6 @@ def generate_club_pages(apps, schema_editor):
class Migration(migrations.Migration):
dependencies = [("core", "0024_auto_20170906_1317"), ("club", "0010_club_logo")]
operations = [
@ -31,7 +31,11 @@ class Migration(migrations.Migration):
model_name="club",
name="page",
field=models.OneToOneField(
related_name="club", blank=True, null=True, to="core.Page"
on_delete=django.db.models.deletion.CASCADE,
related_name="club",
blank=True,
null=True,
to="core.Page",
),
),
migrations.AddField(

View File

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

View File

@ -3,10 +3,10 @@ from __future__ import unicode_literals
from django.db import migrations, models
import club.models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [("club", "0010_auto_20170912_2028")]
operations = [
@ -14,6 +14,7 @@ class Migration(migrations.Migration):
model_name="club",
name="owner_group",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
default=club.models.Club.get_default_owner_group,
related_name="owned_club",
to="core.Group",

View File

@ -22,14 +22,18 @@
# 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.core import validators
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django.db.models import Q
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError, ObjectDoesNotExist
from django.db import transaction
from django.core.urlresolvers import reverse
from django.urls import reverse
from django.utils import timezone
from django.core.validators import RegexValidator, validate_email
from django.utils.functional import cached_property
@ -46,7 +50,9 @@ class Club(models.Model):
id = models.AutoField(primary_key=True, db_index=True)
name = models.CharField(_("name"), max_length=64)
parent = models.ForeignKey("Club", related_name="children", null=True, blank=True)
parent = models.ForeignKey(
"Club", related_name="children", null=True, blank=True, on_delete=models.CASCADE
)
unix_name = models.CharField(
_("unix name"),
max_length=30,
@ -70,12 +76,16 @@ class Club(models.Model):
_("short description"), max_length=1000, default="", blank=True, null=True
)
address = models.CharField(_("address"), max_length=254)
# This function prevents generating migration upon settings change
def get_default_owner_group():
return settings.SITH_GROUP_ROOT_ID
owner_group = models.ForeignKey(
Group, related_name="owned_club", default=get_default_owner_group
Group,
related_name="owned_club",
default=get_default_owner_group,
on_delete=models.CASCADE,
)
edit_groups = models.ManyToManyField(
Group, related_name="editable_club", blank=True
@ -91,7 +101,9 @@ class Club(models.Model):
blank=True,
on_delete=models.SET_NULL,
)
page = models.OneToOneField(Page, related_name="club", blank=True, null=True)
page = models.OneToOneField(
Page, related_name="club", blank=True, null=True, on_delete=models.CASCADE
)
class Meta:
ordering = ["name", "unix_name"]
@ -115,12 +127,22 @@ class Club(models.Model):
def clean(self):
self.check_loop()
def _change_unixname(self, new_name):
def _change_unixname(self, old_name, new_name):
c = Club.objects.filter(unix_name=new_name).first()
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:
self.home.name = new_name
self.home.save()
else:
raise ValidationError(_("A club with that unix_name already exists"))
@ -164,29 +186,34 @@ class Club(models.Model):
self.page.parent = self.parent.page
self.page.save(force_lock=True)
@transaction.atomic()
def save(self, *args, **kwargs):
with transaction.atomic():
creation = False
old = Club.objects.filter(id=self.id).first()
if not old:
creation = True
else:
if old.unix_name != self.unix_name:
self._change_unixname(self.unix_name)
super(Club, self).save(*args, **kwargs)
if creation:
board = MetaGroup(name=self.unix_name + settings.SITH_BOARD_SUFFIX)
board.save()
member = MetaGroup(name=self.unix_name + settings.SITH_MEMBER_SUFFIX)
member.save()
subscribers = Group.objects.filter(
name=settings.SITH_MAIN_MEMBERS_GROUP
).first()
self.make_home()
self.home.edit_groups = [board]
self.home.view_groups = [member, subscribers]
self.home.save()
self.make_page()
old = Club.objects.filter(id=self.id).first()
creation = old is None
if not creation and old.unix_name != self.unix_name:
self._change_unixname(self.unix_name)
super(Club, self).save(*args, **kwargs)
if creation:
board = MetaGroup(name=self.unix_name + settings.SITH_BOARD_SUFFIX)
board.save()
member = MetaGroup(name=self.unix_name + settings.SITH_MEMBER_SUFFIX)
member.save()
subscribers = Group.objects.filter(
name=settings.SITH_MAIN_MEMBERS_GROUP
).first()
self.make_home()
self.home.edit_groups.set([board])
self.home.view_groups.set([member, subscribers])
self.home.save()
self.make_page()
cache.set(f"sith_club_{self.unix_name}", self)
def delete(self, *args, **kwargs):
super().delete(*args, **kwargs)
# Invalidate the cache of this club and of its memberships
for membership in self.members.ongoing().select_related("user"):
cache.delete(f"membership_{self.id}_{membership.user.id}")
cache.delete(f"sith_club_{self.unix_name}")
def __str__(self):
return self.name
@ -201,7 +228,9 @@ class Club(models.Model):
"""
Method to see if that object can be super edited by the given user
"""
return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP)
if user.is_anonymous:
return False
return user.is_board_member
def get_full_logo_url(self):
return "https://%s%s" % (settings.SITH_URL, self.logo.url)
@ -221,28 +250,89 @@ class Club(models.Model):
return False
return sub.was_subscribed
_memberships = {}
def get_membership_for(self, user):
def get_membership_for(self, user: User) -> Optional["Membership"]:
"""
Returns the current membership the given user
Return the current membership the given user.
The result is cached.
"""
try:
return Club._memberships[self.id][user.id]
except:
m = self.members.filter(user=user.id).filter(end_date=None).first()
try:
Club._memberships[self.id][user.id] = m
except:
Club._memberships[self.id] = {}
Club._memberships[self.id][user.id] = m
return m
if user.is_anonymous:
return None
membership = cache.get(f"membership_{self.id}_{user.id}")
if membership == "not_member":
return None
if membership is None:
membership = self.members.filter(user=user, end_date=None).first()
if membership is None:
cache.set(f"membership_{self.id}_{user.id}", "not_member")
else:
cache.set(f"membership_{self.id}_{user.id}", membership)
return membership
def has_rights_in_club(self, user):
m = self.get_membership_for(user)
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):
"""
The Membership class makes the connection between User and Clubs
@ -261,9 +351,15 @@ class Membership(models.Model):
related_name="memberships",
null=False,
blank=False,
on_delete=models.CASCADE,
)
club = models.ForeignKey(
Club, verbose_name=_("club"), related_name="members", null=False, blank=False
Club,
verbose_name=_("club"),
related_name="members",
null=False,
blank=False,
on_delete=models.CASCADE,
)
start_date = models.DateField(_("start date"), default=timezone.now)
end_date = models.DateField(_("end date"), null=True, blank=True)
@ -276,6 +372,8 @@ class Membership(models.Model):
_("description"), max_length=128, null=False, blank=True
)
objects = MembershipQuerySet.as_manager()
def __str__(self):
return (
self.club.name
@ -290,24 +388,34 @@ class Membership(models.Model):
"""
Method to see if that object can be super edited by the given user
"""
return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP)
if user.is_anonymous:
return False
return user.is_board_member
def can_be_edited_by(self, user, membership=None):
def can_be_edited_by(self, user: User) -> bool:
"""
Method to see if that object can be edited by the given user
Check if that object can be edited by the given user
"""
if user.memberships:
if membership: # This is for optimisation purpose
ms = membership
else:
ms = user.memberships.filter(club=self.club, end_date=None).first()
return (ms and ms.role >= self.role) or user.is_in_group(
settings.SITH_MAIN_BOARD_GROUP
)
return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP)
if user.is_root or user.is_board_member:
return True
membership = self.club.get_membership_for(user)
if membership is not None and membership.role >= self.role:
return True
return False
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):
@ -317,7 +425,12 @@ class Mailing(models.Model):
"""
club = models.ForeignKey(
Club, verbose_name=_("Club"), related_name="mailings", null=False, blank=False
Club,
verbose_name=_("Club"),
related_name="mailings",
null=False,
blank=False,
on_delete=models.CASCADE,
)
email = models.CharField(
_("Email address"),
@ -334,7 +447,11 @@ class Mailing(models.Model):
)
is_moderated = models.BooleanField(_("is moderated"), default=False)
moderator = models.ForeignKey(
User, related_name="moderated_mailings", verbose_name=_("moderator"), null=True
User,
related_name="moderated_mailings",
verbose_name=_("moderator"),
null=True,
on_delete=models.CASCADE,
)
def clean(self):
@ -351,14 +468,12 @@ class Mailing(models.Model):
return self.email + "@" + settings.SITH_MAILING_DOMAIN
def can_moderate(self, user):
return user.is_root or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
return user.is_root or user.is_com_admin
def is_owned_by(self, user):
return (
user.is_in_group(self)
or user.is_root
or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
)
if user.is_anonymous:
return False
return user.is_root or user.is_com_admin
def can_view(self, user):
return self.club.has_rights_in_club(user)
@ -366,9 +481,8 @@ class Mailing(models.Model):
def can_be_edited_by(self, user):
return self.club.has_rights_in_club(user)
def delete(self):
for sub in self.subscriptions.all():
sub.delete()
def delete(self, *args, **kwargs):
self.subscriptions.all().delete()
super(Mailing, self).delete()
def fetch_format(self):
@ -409,6 +523,7 @@ class MailingSubscription(models.Model):
related_name="subscriptions",
null=False,
blank=False,
on_delete=models.CASCADE,
)
user = models.ForeignKey(
User,
@ -416,6 +531,7 @@ class MailingSubscription(models.Model):
related_name="mailing_subscriptions",
null=True,
blank=True,
on_delete=models.CASCADE,
)
email = models.EmailField(_("Email address"), blank=False, null=False)
@ -439,10 +555,12 @@ class MailingSubscription(models.Model):
super(MailingSubscription, self).clean()
def is_owned_by(self, user):
if user.is_anonymous:
return False
return (
self.mailing.club.has_rights_in_club(user)
or user.is_root
or self.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
or self.user.is_com_admin
)
def can_be_edited_by(self, user):

View File

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

View File

@ -1,9 +1,9 @@
{% extends "core/base.jinja" %}
{% from 'core/macros.jinja' import user_profile_link %}
{% from 'core/macros.jinja' import user_profile_link, paginate %}
{% block content %}
<h3>{% trans %}Sellings{% endtrans %}</h3>
<form action="" method="get">
<form id="form" action="?page=1" method="post">
{% csrf_token %}
{{ form }}
<p><input type="submit" value="{% trans %}Show{% endtrans %}" /></p>
@ -28,7 +28,7 @@
</tr>
</thead>
<tbody>
{% for s in result %}
{% for s in paginated_result %}
<tr>
<td>{{ s.date|localtime|date(DATETIME_FORMAT) }} {{ s.date|localtime|time(DATETIME_FORMAT) }}</td>
<td>{{ s.counter }}</td>
@ -53,6 +53,14 @@
{% endfor %}
</tbody>
</table>
<script type="text/javascript">
function formPagination(link){
$("form").attr("action", link.href);
link.href = "javascript:void(0)"; // block link action
$("form").submit();
}
</script>
{{ paginate(paginated_result, paginator, "formPagination(this)") }}
{% endblock %}

View File

@ -45,7 +45,7 @@
</thead>
<tbody>
{% for widget in form_mailing_removal.subwidgets %}
{% set user = ms[widget.data.value][0] %}
{% set user = ms[widget.data.value.value][0] %}
<tr>
<td>{{ user.get_username }}</td>
<td>{{ user.get_email }}</td>

View File

@ -1,395 +1,576 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
from datetime import timedelta
from django.conf import settings
from django.core.cache import cache
from django.test import TestCase
from django.utils import timezone, html
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
from django.utils.timezone import now, localtime
from django.utils.translation import gettext as _
from django.urls import reverse
from django.core.management import call_command
from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
from core.models import User
from core.models import User, AnonymousUser
from club.models import Club, Membership, Mailing
from club.forms import MailingForm
# Create your tests here.
from sith.settings import SITH_BAR_MANAGER, SITH_MAIN_CLUB_ID
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):
call_command("populate")
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()
# by default, Skia is in the AE, which creates side effect
self.skia.memberships.all().delete()
def test_create_add_user_to_club_from_root_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": 3},
# create a fake club
self.club = Club.objects.create(
name="Fake Club",
unix_name="fake-club",
address="5 rue de la République, 90000 Belfort",
)
response = self.client.get(
reverse("club:club_members", kwargs={"club_id": self.bdf.id})
self.members_url = reverse(
"club:club_members", kwargs={"club_id": self.club.id}
)
self.assertTrue(response.status_code == 200)
self.assertTrue(
"S&#39; Kia</a></td>\\n <td>Responsable info</td>"
in str(response.content)
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
)
def test_create_add_multiple_user_to_club_from_root_ok(self):
# sli was a member but isn't anymore
Membership.objects.create(
club=self.club,
user=self.sli,
start_date=a_month_ago,
end_date=yesterday,
role=2,
)
cache.clear()
class MembershipQuerySetTest(ClubTest):
def test_ongoing(self):
"""
Test that the ongoing queryset method returns the memberships that
are not ended.
"""
current_members = self.club.members.ongoing()
expected = [
self.skia.memberships.get(club=self.club),
self.comptable.memberships.get(club=self.club),
self.richard.memberships.get(club=self.club),
]
self.assertEqual(len(current_members), len(expected))
for member in current_members:
self.assertIn(member, expected)
def test_board(self):
"""
Test that the board queryset method returns the memberships
of user in the club board
"""
board_members = list(self.club.members.board())
expected = [
self.skia.memberships.get(club=self.club),
self.comptable.memberships.get(club=self.club),
# sli is no more member, but he was in the board
self.sli.memberships.get(club=self.club),
]
self.assertEqual(len(board_members), len(expected))
for member in board_members:
self.assertIn(member, expected)
def test_ongoing_board(self):
"""
Test that combining ongoing and board returns users
who are currently board members of the club
"""
members = list(self.club.members.ongoing().board())
expected = [
self.skia.memberships.get(club=self.club),
self.comptable.memberships.get(club=self.club),
]
self.assertEqual(len(members), len(expected))
for member in members:
self.assertIn(member, expected)
def test_update_invalidate_cache(self):
"""
Test that the `update` queryset method properly invalidate cache
"""
mem_skia = self.skia.memberships.get(club=self.club)
cache.set(f"membership_{mem_skia.club_id}_{mem_skia.user_id}", mem_skia)
self.skia.memberships.update(end_date=localtime(now()).date())
self.assertEqual(
cache.get(f"membership_{mem_skia.club_id}_{mem_skia.user_id}"), "not_member"
)
mem_richard = self.richard.memberships.get(club=self.club)
cache.set(
f"membership_{mem_richard.club_id}_{mem_richard.user_id}", mem_richard
)
self.richard.memberships.update(role=5)
new_mem = self.richard.memberships.get(club=self.club)
self.assertNotEqual(new_mem, "not_member")
self.assertEqual(new_mem.role, 5)
def test_delete_invalidate_cache(self):
"""
Test that the `delete` queryset properly invalidate cache
"""
mem_skia = self.skia.memberships.get(club=self.club)
mem_comptable = self.comptable.memberships.get(club=self.club)
cache.set(f"membership_{mem_skia.club_id}_{mem_skia.user_id}", mem_skia)
cache.set(
f"membership_{mem_comptable.club_id}_{mem_comptable.user_id}", mem_comptable
)
# should delete the subscriptions of skia and comptable
self.club.members.ongoing().board().delete()
self.assertEqual(
cache.get(f"membership_{mem_skia.club_id}_{mem_skia.user_id}"), "not_member"
)
self.assertEqual(
cache.get(f"membership_{mem_comptable.club_id}_{mem_comptable.user_id}"),
"not_member",
)
class ClubModelTest(ClubTest):
def assert_membership_just_started(self, user: User, role: int):
"""
Assert that the given membership is active and started today
"""
membership = user.memberships.ongoing().filter(club=self.club).first()
self.assertIsNotNone(membership)
self.assertEqual(localtime(now()).date(), membership.start_date)
self.assertIsNone(membership.end_date)
self.assertEqual(membership.role, role)
self.assertEqual(membership.club.get_membership_for(user), membership)
member_group = self.club.unix_name + settings.SITH_MEMBER_SUFFIX
board_group = self.club.unix_name + settings.SITH_BOARD_SUFFIX
self.assertTrue(user.is_in_group(name=member_group))
self.assertTrue(user.is_in_group(name=board_group))
def assert_membership_just_ended(self, user: User):
"""
Assert that the given user have a membership which ended today
"""
today = localtime(now()).date()
self.assertIsNotNone(
user.memberships.filter(club=self.club, end_date=today).first()
)
self.assertIsNone(self.club.get_membership_for(user))
def test_access_unauthorized(self):
"""
Test that users who never subscribed and anonymous users
cannot see the page
"""
response = self.client.post(self.members_url)
self.assertEqual(response.status_code, 403)
self.client.login(username="public", password="plop")
response = self.client.post(self.members_url)
self.assertEqual(response.status_code, 403)
def test_display(self):
"""
Test that a GET request return a page where the requested
information are displayed.
"""
self.client.login(username=self.skia.username, password="plop")
response = self.client.get(self.members_url)
self.assertEqual(response.status_code, 200)
expected_html = (
"<table><thead><tr>"
"<td>Utilisateur</td><td>Rôle</td><td>Description</td>"
"<td>Depuis</td><td>Marquer comme ancien</td>"
"</tr></thead><tbody>"
)
memberships = self.club.members.ongoing().order_by("-role")
input_id = 0
for membership in memberships.select_related("user"):
user = membership.user
expected_html += (
f"<tr><td><a href=\"{reverse('core:user_profile', args=[user.id])}\">"
f"{user.get_display_name()}</a></td>"
f"<td>{settings.SITH_CLUB_ROLES[membership.role]}</td>"
f"<td>{membership.description}</td>"
f"<td>{membership.start_date}</td><td>"
)
if membership.role <= 3: # 3 is the role of skia
expected_html += (
'<input type="checkbox" name="users_old" '
f'value="{user.id}" '
f'id="id_users_old_{input_id}">'
)
input_id += 1
expected_html += "</td></tr>"
expected_html += "</tbody></table>"
self.assertInHTML(expected_html, response.content.decode())
def test_root_add_one_club_member(self):
"""
Test that root users can add members to clubs, one at a time
"""
self.client.login(username="root", password="plop")
self.client.post(
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
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": "|%d|%d|" % (self.skia.id, self.rbatsbak.id),
"start_date": "12/06/2016",
"users": f"|{self.subscriber.id}|{self.krophil.id}|",
"role": 3,
},
)
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(
"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
)
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_create_add_user_to_club_from_root_fail_not_subscriber(self):
def test_add_unauthorized_members(self):
"""
Test that users who are not currently subscribed
cannot be members of clubs.
"""
self.client.login(username="root", password="plop")
response = self.client.post(
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
{"users": self.guy.id, "start_date": "12/06/2016", "role": 3},
self.members_url,
{"users": self.public.id, "role": 1},
)
self.assertTrue(response.status_code == 200)
self.assertIsNone(self.public.memberships.filter(club=self.club).first())
self.assertTrue('<ul class="errorlist"><li>' in str(response.content))
response = self.client.get(
reverse("club:club_members", kwargs={"club_id": self.bdf.id})
)
self.assertFalse(
"Guy Carlier</a></td>\\n <td>Responsable info</td>"
in str(response.content)
)
def test_create_add_user_to_club_from_root_fail_already_in_club(self):
response = self.client.post(
self.members_url,
{"users": self.old_subscriber.id, "role": 1},
)
self.assertIsNone(self.public.memberships.filter(club=self.club).first())
self.assertIsNone(self.club.get_membership_for(self.public))
self.assertTrue('<ul class="errorlist"><li>' in str(response.content))
def test_add_members_already_members(self):
"""
Test that users who are already members of a club
cannot be added again to this club
"""
self.client.login(username="root", password="plop")
current_membership = self.skia.memberships.ongoing().get(club=self.club)
nb_memberships = self.skia.memberships.count()
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)
self.members_url,
{"users": self.skia.id, "role": current_membership.role + 1},
)
self.skia.refresh_from_db()
self.assertEqual(nb_memberships, self.skia.memberships.count())
new_membership = self.skia.memberships.ongoing().get(club=self.club)
self.assertEqual(current_membership, new_membership)
self.assertEqual(self.club.get_membership_for(self.skia), new_membership)
def test_create_add_user_non_existent_to_club_from_root_fail(self):
def test_add_not_existing_users(self):
"""
Test that not existing users cannot be added in clubs.
If one user in the request is invalid, no membership creation at all
can take place.
"""
self.client.login(username="root", password="plop")
nb_memberships = self.club.members.count()
response = self.client.post(
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
{"users": [9999], "start_date": "12/06/2016", "role": 3},
self.members_url,
{"users": [9999], "role": 1},
)
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")
self.assertContains(response, '<ul class="errorlist"><li>')
self.club.refresh_from_db()
self.assertEqual(self.club.members.count(), nb_memberships)
response = self.client.post(
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
self.members_url,
{
"users": "|%d|%d|" % (self.skia.id, 9999),
"users": f"|{self.subscriber.id}|{9999}|",
"start_date": "12/06/2016",
"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.assertContains(response, '<ul class="errorlist"><li>')
self.club.refresh_from_db()
self.assertEqual(self.club.members.count(), nb_memberships)
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.assertTrue(response.status_code == 200)
self.assertTrue(
"""Richard Batsbak</a></td>\\n <td>Vice-Pr\\xc3\\xa9sident</td>"""
in str(response.content)
)
def test_create_add_user_to_club_from_richard_fail(self):
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")
def test_president_add_members(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(
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
{"users": self.skia.id, "start_date": "12/06/2016", "role": 10},
self.members_url,
{"users": self.subscriber.id, "role": 9},
)
self.assertTrue(response.status_code == 200)
self.assertTrue(
"<li>Vous n&#39;avez pas la permission de faire cela</li>"
in str(response.content)
self.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_role_required_if_users_specified(self):
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")
response = self.client.post(
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
{"users": self.rbatsbak.id, "start_date": "12/06/2016"},
self.members_url,
{"users": self.subscriber.id, "start_date": "12/06/2016"},
)
self.assertTrue(
'<ul class="errorlist"><li>Vous devez choisir un r' in str(response.content)
)
def test_mark_old_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": "|%d|%d|" % (self.skia.id, self.rbatsbak.id),
"start_date": "12/06/2016",
"role": 3,
},
)
def test_end_membership_self(self):
"""
Test that a member can end its own membership
"""
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}),
self.client.post(
self.members_url,
{"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.post(
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
{"users": self.rbatsbak.id, "start_date": "12/06/2016", "role": 3},
)
self.client.login(username="skia", password="plop")
response = self.client.post(
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)
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_mark_old_multiple_users_from_skia_ok(self):
self.client.login(username="root", password="plop")
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(
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.members_url,
{"users_old": [self.richard.id]},
)
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)
# 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)
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.assertFalse(
"S&#39; Kia</a></td>\\n <td>Responsable info</td>"
in content
)
def test_delete_remove_from_meta_group(self):
"""
Test that when a club is deleted, all its members are removed from the
associated metagroup
"""
memberships = self.club.members.select_related("user")
users = [membership.user for membership in memberships]
meta_group = self.club.unix_name + settings.SITH_MEMBER_SUFFIX
def test_mark_old_user_to_club_from_richard_ok(self):
self.client.login(username="root", password="plop")
self.client.post(
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.club.delete()
for user in users:
self.assertFalse(user.is_in_group(name=meta_group))
# Test with equal rights
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 == 302)
def test_add_to_meta_group(self):
"""
Test that when a membership begins, the user is added to the meta group
"""
group_members = self.club.unix_name + settings.SITH_MEMBER_SUFFIX
board_members = self.club.unix_name + settings.SITH_BOARD_SUFFIX
self.assertFalse(self.subscriber.is_in_group(name=group_members))
self.assertFalse(self.subscriber.is_in_group(name=board_members))
Membership.objects.create(club=self.club, user=self.subscriber, role=3)
self.assertTrue(self.subscriber.is_in_group(name=group_members))
self.assertTrue(self.subscriber.is_in_group(name=board_members))
response = self.client.get(
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>Responsable info</td>"
in content
)
def test_remove_from_meta_group(self):
"""
Test that when a membership ends, the user is removed from meta group
"""
group_members = self.club.unix_name + settings.SITH_MEMBER_SUFFIX
board_members = self.club.unix_name + settings.SITH_BOARD_SUFFIX
self.assertTrue(self.comptable.is_in_group(name=group_members))
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))
# Test with lower rights
self.client.post(
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
{"users": self.skia.id, "start_date": "12/06/2016", "role": 0},
)
def test_club_owner(self):
"""
Test that a club is owned only by board members of the main club
"""
anonymous = AnonymousUser()
self.assertFalse(self.club.is_owned_by(anonymous))
self.assertFalse(self.club.is_owned_by(self.subscriber))
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.assertTrue(response.status_code == 200)
content = str(response.content)
self.assertTrue(
"Richard Batsbak</a></td>\\n <td>Curieux</td>" in content
)
self.assertTrue(
"S&#39; Kia</a></td>\\n <td>Responsable info</td>"
in content
)
# make sli a board member
self.sli.memberships.all().delete()
Membership(club=self.ae, user=self.sli, role=3).save()
self.assertTrue(self.club.is_owned_by(self.sli))
class MailingFormTest(TestCase):
"""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):
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(
user=self.rbatsbak,
club=self.bdf,
@ -641,7 +822,7 @@ class MailingFormTest(TestCase):
{"action": MailingForm.ACTION_NEW_MAILING, "mailing_email": "mde"},
)
mde = Mailing.objects.get(email="mde")
response = self.client.post(
self.client.post(
reverse("club:mailing", kwargs={"club_id": self.bdf.id}),
{
"action": MailingForm.ACTION_NEW_SUBSCRIPTION,
@ -650,6 +831,11 @@ class MailingFormTest(TestCase):
"subscription_mailing": mde.id,
},
)
response = self.client.get(
reverse("club:mailing", kwargs={"club_id": self.bdf.id})
)
self.assertContains(response, "comunity@git.an")
self.assertContains(response, "richard@git.an")
self.assertContains(response, "krophil@git.an")
@ -699,7 +885,6 @@ class ClubSellingViewTest(TestCase):
"""
def setUp(self):
call_command("populate")
self.ae = Club.objects.filter(unix_name="ae").first()
def test_page_not_internal_error(self):

View File

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

View File

@ -23,6 +23,7 @@
#
#
import csv
from django.conf import settings
from django import forms
@ -30,19 +31,28 @@ from django.views.generic import ListView, DetailView, TemplateView, View
from django.views.generic.edit import DeleteView
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import UpdateView, CreateView
from django.http import HttpResponseRedirect, HttpResponse, Http404
from django.core.urlresolvers import reverse, reverse_lazy
from django.http import (
HttpResponseRedirect,
HttpResponse,
Http404,
StreamingHttpResponse,
)
from django.urls import reverse, reverse_lazy
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext as _t
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext as _t
from django.core.exceptions import PermissionDenied, ValidationError, NON_FIELD_ERRORS
from django.core.paginator import Paginator, InvalidPage
from django.shortcuts import get_object_or_404, redirect
from django.db.models import Sum
from core.views import (
CanCreateMixin,
CanViewMixin,
CanEditMixin,
CanEditPropMixin,
UserIsRootMixin,
TabedViewMixin,
PageEditViewBase,
DetailFormView,
@ -59,7 +69,7 @@ from com.views import (
)
from club.models import Club, Membership, Mailing, MailingSubscription
from club.forms import MailingForm, ClubEditForm, ClubMemberForm, SellingsFormBase
from club.forms import MailingForm, ClubEditForm, ClubMemberForm, SellingsForm
class ClubTabsMixin(TabedViewMixin):
@ -280,7 +290,7 @@ class ClubMembersView(ClubTabsMixin, CanViewMixin, DetailFormView):
def form_valid(self, form):
"""
Check user rights
Check user rights
"""
resp = super(ClubMembersView, self).form_valid(form)
@ -296,9 +306,7 @@ class ClubMembersView(ClubTabsMixin, CanViewMixin, DetailFormView):
return resp
def dispatch(self, request, *args, **kwargs):
self.members = (
self.get_object().members.filter(end_date=None).order_by("-role").all()
)
self.members = self.get_object().members.ongoing().order_by("-role")
return super(ClubMembersView, self).dispatch(request, *args, **kwargs)
def get_success_url(self, **kwargs):
@ -318,7 +326,7 @@ class ClubOldMembersView(ClubTabsMixin, CanViewMixin, DetailView):
current_tab = "elderlies"
class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailView):
class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailFormView):
"""
Sellings of a club
"""
@ -327,21 +335,35 @@ class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailView):
pk_url_kwarg = "club_id"
template_name = "club/club_sellings.jinja"
current_tab = "sellings"
form_class = SellingsForm
paginate_by = 70
def get_form_class(self):
kwargs = {
"product": forms.ModelChoiceField(
self.object.products.order_by("name").all(),
label=_("Product"),
required=False,
)
}
return type("SellingsForm", (SellingsFormBase,), kwargs)
def dispatch(self, request, *args, **kwargs):
try:
self.asked_page = int(request.GET.get("page", 1))
except ValueError:
raise Http404
return super(ClubSellingView, self).dispatch(request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(ClubSellingView, self).get_form_kwargs()
kwargs["club"] = self.object
return kwargs
def post(self, request, *args, **kwargs):
return self.get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
kwargs = super(ClubSellingView, self).get_context_data(**kwargs)
form = self.get_form_class()(self.request.GET)
qs = Selling.objects.filter(club=self.object)
kwargs["result"] = qs[:0]
kwargs["paginated_result"] = kwargs["result"]
kwargs["total"] = 0
kwargs["total_quantity"] = 0
kwargs["benefit"] = 0
form = self.get_form()
if form.is_valid():
if not len([v for v in form.cleaned_data.values() if v is not None]):
qs = Selling.objects.filter(id=-1)
@ -349,19 +371,36 @@ class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailView):
qs = qs.filter(date__gte=form.cleaned_data["begin_date"])
if form.cleaned_data["end_date"]:
qs = qs.filter(date__lte=form.cleaned_data["end_date"])
if form.cleaned_data["counter"]:
qs = qs.filter(counter=form.cleaned_data["counter"])
if form.cleaned_data["product"]:
qs = qs.filter(product__id=form.cleaned_data["product"].id)
if form.cleaned_data["counters"]:
qs = qs.filter(counter__in=form.cleaned_data["counters"])
selected_products = []
if form.cleaned_data["products"]:
selected_products.extend(form.cleaned_data["products"])
if form.cleaned_data["archived_products"]:
selected_products.extend(form.cleaned_data["archived_products"])
if len(selected_products) > 0:
qs = qs.filter(product__in=selected_products)
kwargs["result"] = qs.all().order_by("-id")
kwargs["total"] = sum([s.quantity * s.unit_price for s in qs.all()])
kwargs["total_quantity"] = sum([s.quantity for s in qs.all()])
kwargs["benefit"] = kwargs["total"] - sum(
[s.product.purchase_price for s in qs.exclude(product=None)]
kwargs["total"] = sum([s.quantity * s.unit_price for s in kwargs["result"]])
total_quantity = qs.all().aggregate(Sum("quantity"))
if total_quantity["quantity__sum"]:
kwargs["total_quantity"] = total_quantity["quantity__sum"]
benefit = (
qs.exclude(product=None).all().aggregate(Sum("product__purchase_price"))
)
else:
kwargs["result"] = qs[:0]
kwargs["form"] = form
if benefit["product__purchase_price__sum"]:
kwargs["benefit"] = benefit["product__purchase_price__sum"]
kwargs["paginator"] = Paginator(kwargs["result"], self.paginate_by)
try:
kwargs["paginated_result"] = kwargs["paginator"].page(self.asked_page)
except InvalidPage:
raise Http404
return kwargs
@ -370,16 +409,45 @@ class ClubSellingCSVView(ClubSellingView):
Generate sellings in csv for a given period
"""
def get(self, request, *args, **kwargs):
import csv
class StreamWriter:
"""Implements a file-like interface for streaming the CSV"""
response = HttpResponse(content_type="text/csv")
def write(self, value):
"""Write the value by returning it, instead of storing in a buffer."""
return value
def write_selling(self, selling):
row = [selling.date, selling.counter]
if selling.seller:
row.append(selling.seller.get_display_name())
else:
row.append("")
if selling.customer:
row.append(selling.customer.user.get_display_name())
else:
row.append("")
row = row + [
selling.label,
selling.quantity,
selling.quantity * selling.unit_price,
selling.get_payment_method_display(),
]
if selling.product:
row.append(selling.product.selling_price)
row.append(selling.product.purchase_price)
row.append(selling.product.selling_price - selling.product.purchase_price)
else:
row = row + ["", "", ""]
return row
def get(self, request, *args, **kwargs):
self.object = self.get_object()
name = _("Sellings") + "_" + self.object.name + ".csv"
response["Content-Disposition"] = "filename=" + name
kwargs = self.get_context_data(**kwargs)
# Use the StreamWriter class instead of request for streaming
pseudo_buffer = self.StreamWriter()
writer = csv.writer(
response, delimiter=";", lineterminator="\n", quoting=csv.QUOTE_ALL
pseudo_buffer, delimiter=";", lineterminator="\n", quoting=csv.QUOTE_ALL
)
writer.writerow([_t("Quantity"), kwargs["total_quantity"]])
@ -400,29 +468,17 @@ class ClubSellingCSVView(ClubSellingView):
_t("Benefit"),
]
)
for o in kwargs["result"]:
row = [o.date, o.counter]
if o.seller:
row.append(o.seller.get_display_name())
else:
row.append("")
if o.customer:
row.append(o.customer.user.get_display_name())
else:
row.append("")
row = row + [
o.label,
o.quantity,
o.quantity * o.unit_price,
o.get_payment_method_display(),
]
if o.product:
row.append(o.product.selling_price)
row.append(o.product.purchase_price)
row.append(o.product.selling_price - o.product.purchase_price)
else:
row = row + ["", "", ""]
writer.writerow(row)
# Stream response
response = StreamingHttpResponse(
(
writer.writerow(self.write_selling(selling))
for selling in kwargs["result"]
),
content_type="text/csv",
)
name = _("Sellings") + "_" + self.object.name + ".csv"
response["Content-Disposition"] = "filename=" + name
return response
@ -451,7 +507,7 @@ class ClubEditPropView(ClubTabsMixin, CanEditPropMixin, UpdateView):
current_tab = "props"
class ClubCreateView(CanEditPropMixin, CreateView):
class ClubCreateView(CanCreateMixin, CreateView):
"""
Create a club (for the Sith admin)
"""
@ -493,6 +549,19 @@ class MembershipSetOldView(CanEditMixin, DetailView):
)
class MembershipDeleteView(UserIsRootMixin, DeleteView):
"""
Delete a membership (for admins only)
"""
model = Membership
pk_url_kwarg = "membership_id"
template_name = "core/delete_confirm.jinja"
def get_success_url(self):
return reverse_lazy("core:user_clubs", kwargs={"user_id": self.object.user.id})
class ClubStatView(TemplateView):
template_name = "club/stats.jinja"
@ -574,7 +643,8 @@ class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView):
except ValidationError as validation_error:
return validation_error
users_to_save.append(sub.save())
sub.save()
users_to_save.append(sub)
if cleaned_data["subscription_email"]:
sub = MailingSubscription(
@ -633,7 +703,6 @@ class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView):
class MailingDeleteView(CanEditMixin, DeleteView):
model = Mailing
template_name = "core/delete_confirm.jinja"
pk_url_kwarg = "mailing_id"
@ -651,7 +720,6 @@ class MailingDeleteView(CanEditMixin, DeleteView):
class MailingSubscriptionDeleteView(CanEditMixin, DeleteView):
model = MailingSubscription
template_name = "core/delete_confirm.jinja"
pk_url_kwarg = "mailing_subscription_id"

View File

@ -1,23 +1,15 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#

View File

@ -1,43 +1,49 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
from ajax_select import make_ajax_form
from django.contrib import admin
from haystack.admin import SearchModelAdmin
from com.models import *
@admin.register(News)
class NewsAdmin(SearchModelAdmin):
search_fields = ["title", "summary", "content"]
list_display = ("title", "type", "club", "author")
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):
search_fields = ["title"]
list_display = ("title", "sent")
search_fields = ("title",)
admin.site.register(Sith)
admin.site.register(News, NewsAdmin)
admin.site.register(Weekmail, WeekmailAdmin)
admin.site.register(Screen)
admin.site.register(Poster)

View File

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

View File

@ -3,10 +3,10 @@ from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("club", "0005_auto_20161120_1149"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
@ -50,6 +50,7 @@ class Migration(migrations.Migration):
(
"author",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="owned_news",
to=settings.AUTH_USER_MODEL,
verbose_name="author",
@ -58,12 +59,16 @@ class Migration(migrations.Migration):
(
"club",
models.ForeignKey(
related_name="news", to="club.Club", verbose_name="club"
on_delete=django.db.models.deletion.CASCADE,
related_name="news",
to="club.Club",
verbose_name="club",
),
),
(
"moderator",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="moderated_news",
null=True,
to=settings.AUTH_USER_MODEL,
@ -99,7 +104,10 @@ class Migration(migrations.Migration):
(
"news",
models.ForeignKey(
related_name="dates", to="com.News", verbose_name="news_date"
on_delete=django.db.models.deletion.CASCADE,
related_name="dates",
to="com.News",
verbose_name="news_date",
),
),
],

View File

@ -3,10 +3,10 @@ from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("club", "0006_auto_20161229_0040"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
@ -56,6 +56,7 @@ class Migration(migrations.Migration):
(
"author",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name="author",
related_name="owned_weekmail_articles",
@ -64,6 +65,7 @@ class Migration(migrations.Migration):
(
"club",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="club.Club",
verbose_name="club",
related_name="weekmail_articles",
@ -72,6 +74,7 @@ class Migration(migrations.Migration):
(
"weekmail",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="com.Weekmail",
verbose_name="weekmail",
related_name="articles",

View File

@ -4,10 +4,10 @@ from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
from django.conf import settings
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("club", "0010_auto_20170912_2028"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
@ -48,12 +48,16 @@ class Migration(migrations.Migration):
(
"club",
models.ForeignKey(
verbose_name="club", related_name="posters", to="club.Club"
on_delete=django.db.models.deletion.CASCADE,
verbose_name="club",
related_name="posters",
to="club.Club",
),
),
(
"moderator",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
verbose_name="moderator",
blank=True,
null=True,

View File

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

View File

@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.23 on 2019-08-18 17:00
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [("com", "0005_auto_20180318_2227")]
operations = [migrations.RemoveField(model_name="sith", name="index_page")]

View File

@ -26,16 +26,17 @@
from django.shortcuts import render
from django.db import models, transaction
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from django.utils import timezone
from django.core.urlresolvers import reverse
from django.urls import reverse
from django.conf import settings
from django.contrib.staticfiles.templatetags.staticfiles import static
from django.templatetags.static import static
from django.core.mail import EmailMultiAlternatives
from django.core.exceptions import ValidationError
from django.utils import timezone
from core import utils
from core.models import User, Preferences, RealGroup, Notification, SithFile
from club.models import Club
@ -45,11 +46,13 @@ class Sith(models.Model):
alert_msg = models.TextField(_("alert message"), default="", blank=True)
info_msg = models.TextField(_("info message"), default="", blank=True)
index_page = models.TextField(_("index page"), default="", blank=True)
weekmail_destinations = models.TextField(_("weekmail destinations"), default="")
version = utils.get_git_revision_short_hash()
def is_owned_by(self, user):
return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
if user.is_anonymous:
return False
return user.is_com_admin
def __str__(self):
return "⛩ Sith ⛩"
@ -72,23 +75,34 @@ class News(models.Model):
type = models.CharField(
_("type"), max_length=16, choices=NEWS_TYPES, default="EVENT"
)
club = models.ForeignKey(Club, related_name="news", verbose_name=_("club"))
club = models.ForeignKey(
Club, related_name="news", verbose_name=_("club"), on_delete=models.CASCADE
)
author = models.ForeignKey(
User, related_name="owned_news", verbose_name=_("author")
User,
related_name="owned_news",
verbose_name=_("author"),
on_delete=models.CASCADE,
)
is_moderated = models.BooleanField(_("is moderated"), default=False)
moderator = models.ForeignKey(
User, related_name="moderated_news", verbose_name=_("moderator"), null=True
User,
related_name="moderated_news",
verbose_name=_("moderator"),
null=True,
on_delete=models.CASCADE,
)
def is_owned_by(self, user):
return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) or user == self.author
if user.is_anonymous:
return False
return user.is_com_admin or user == self.author
def can_be_edited_by(self, user):
return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
return user.is_com_admin
def can_be_viewed_by(self, user):
return self.is_moderated or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
return self.is_moderated or user.is_com_admin
def get_absolute_url(self):
return reverse("com:news_detail", kwargs={"news_id": self.id})
@ -139,7 +153,12 @@ class NewsDate(models.Model):
we don't have to make copies
"""
news = models.ForeignKey(News, related_name="dates", verbose_name=_("news_date"))
news = models.ForeignKey(
News,
related_name="dates",
verbose_name=_("news_date"),
on_delete=models.CASCADE,
)
start_date = models.DateTimeField(_("start_date"), null=True, blank=True)
end_date = models.DateTimeField(_("end_date"), null=True, blank=True)
@ -150,6 +169,13 @@ class NewsDate(models.Model):
class Weekmail(models.Model):
"""
The weekmail class
:ivar title: Title of the weekmail
:ivar intro: Introduction of the weekmail
:ivar joke: Joke of the week
:ivar protip: Tip of the week
:ivar conclusion: Conclusion of the weekmail
:ivar sent: Track if the weekmail has been sent
"""
title = models.CharField(_("title"), max_length=64, blank=True)
@ -163,6 +189,10 @@ class Weekmail(models.Model):
ordering = ["-id"]
def send(self):
"""
Send the weekmail to all users with the receive weekmail option opt-in.
Also send the weekmail to the mailing list in settings.SITH_COM_EMAIL.
"""
dest = [
i[0]
for i in Preferences.objects.filter(receive_weekmail=True).values_list(
@ -184,44 +214,72 @@ class Weekmail(models.Model):
Weekmail().save()
def render_text(self):
"""
Renders a pure text version of the mail for readers without HTML support.
"""
return render(
None, "com/weekmail_renderer_text.jinja", context={"weekmail": self}
).content.decode("utf-8")
def render_html(self):
"""
Renders an HTML version of the mail with images and fancy CSS.
"""
return render(
None, "com/weekmail_renderer_html.jinja", context={"weekmail": self}
).content.decode("utf-8")
def get_banner(self):
return "http://" + settings.SITH_URL + static("com/img/weekmail_bannerA18.jpg")
"""
Return an absolute link to the banner.
"""
return (
"http://" + settings.SITH_URL + static("com/img/weekmail_bannerV2P22.png")
)
def get_footer(self):
return "http://" + settings.SITH_URL + static("com/img/weekmail_footerA18.jpg")
"""
Return an absolute link to the footer.
"""
return "http://" + settings.SITH_URL + static("com/img/weekmail_footerP22.png")
def __str__(self):
return "Weekmail %s (sent: %s) - %s" % (self.id, self.sent, self.title)
def is_owned_by(self, user):
return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
if user.is_anonymous:
return False
return user.is_com_admin
class WeekmailArticle(models.Model):
weekmail = models.ForeignKey(
Weekmail, related_name="articles", verbose_name=_("weekmail"), null=True
Weekmail,
related_name="articles",
verbose_name=_("weekmail"),
null=True,
on_delete=models.CASCADE,
)
title = models.CharField(_("title"), max_length=64)
content = models.TextField(_("content"))
author = models.ForeignKey(
User, related_name="owned_weekmail_articles", verbose_name=_("author")
User,
related_name="owned_weekmail_articles",
verbose_name=_("author"),
on_delete=models.CASCADE,
)
club = models.ForeignKey(
Club, related_name="weekmail_articles", verbose_name=_("club")
Club,
related_name="weekmail_articles",
verbose_name=_("club"),
on_delete=models.CASCADE,
)
rank = models.IntegerField(_("rank"), default=-1)
def is_owned_by(self, user):
return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
if user.is_anonymous:
return False
return user.is_com_admin
def __str__(self):
return "%s - %s (%s)" % (self.title, self.author, self.club)
@ -237,7 +295,9 @@ class Screen(models.Model):
)
def is_owned_by(self, user):
return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
if user.is_anonymous:
return False
return user.is_com_admin
def __str__(self):
return "%s" % (self.name)
@ -249,7 +309,11 @@ class Poster(models.Model):
)
file = models.ImageField(_("file"), null=False, upload_to="com/posters")
club = models.ForeignKey(
Club, related_name="posters", verbose_name=_("club"), null=False
Club,
related_name="posters",
verbose_name=_("club"),
null=False,
on_delete=models.CASCADE,
)
screens = models.ManyToManyField(Screen, related_name="posters")
date_begin = models.DateTimeField(blank=False, null=False, default=timezone.now)
@ -264,6 +328,7 @@ class Poster(models.Model):
verbose_name=_("moderator"),
null=True,
blank=True,
on_delete=models.CASCADE,
)
def save(self, *args, **kwargs):
@ -285,12 +350,12 @@ class Poster(models.Model):
raise ValidationError(_("Begin date should be before end date"))
def is_owned_by(self, user):
return user.is_in_group(
settings.SITH_GROUP_COM_ADMIN_ID
) or Club.objects.filter(id__in=user.clubs_with_rights)
if user.is_anonymous:
return False
return user.is_com_admin or len(user.clubs_with_rights) > 0
def can_be_moderated_by(self, user):
return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
return user.is_com_admin
def get_display_name(self):
return self.club.get_display_name()

View File

@ -35,7 +35,7 @@
<p>{% trans %}Author: {% endtrans %}{{ user_profile_link(news.author) }}</p>
{% if news.moderator %}
<p>{% trans %}Moderator: {% endtrans %}{{ user_profile_link(news.moderator) }}</p>
{% elif user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) %}
{% elif user.is_com_admin %}
<p> <a href="{{ url('com:news_moderate', news_id=news.id) }}">{% trans %}Moderate{% endtrans %}</a></p>
{% endif %}
{% 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.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>
{% if user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) %}
{% if user.is_com_admin %}
<p>{{ form.automoderation.errors }}<label for="{{ form.automoderation.name }}">{{ form.automoderation.label }}</label>
{{ form.automoderation }}</p>
{% endif %}

View File

@ -6,145 +6,159 @@
{% endblock %}
{% block content %}
{% if user.is_com_admin %}
<div id="news_admin">
<a class="button" href="{{ url('com:news_admin_list') }}">{% trans %}Administrate news{% endtrans %}</a>
</div>
<br>
{% endif %}
<div id="news">
{% if user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) %}
<div id="news_admin">
<a href="{{ url('com:news_admin_list') }}">{% trans %}Administrate news{% endtrans %}</a>
<div id="left_column" class="news_column">
{% for news in object_list.filter(type="NOTICE") %}
<section class="news_notice">
<h4><a href="{{ url('com:news_detail', news_id=news.id) }}">{{ news.title }}</a></h4>
<div class="news_content">{{ news.summary|markdown }}</div>
</section>
{% endfor %}
{% for news in object_list.filter(dates__start_date__lte=timezone.now(), dates__end_date__gte=timezone.now(), type="CALL") %}
<section class="news_call">
<h4> <a href="{{ url('com:news_detail', news_id=news.id) }}">{{ news.title }}</a></h4>
<div class="news_date">
<span>{{ news.dates.first().start_date|localtime|date(DATETIME_FORMAT) }}
{{ news.dates.first().start_date|localtime|time(DATETIME_FORMAT) }}</span> -
<span>{{ news.dates.first().end_date|localtime|date(DATETIME_FORMAT) }}
{{ news.dates.first().end_date|localtime|time(DATETIME_FORMAT) }}</span>
</div>
<div class="news_content">{{ news.summary|markdown }}</div>
</section>
{% endfor %}
{% set events_dates = NewsDate.objects.filter(end_date__gte=timezone.now(), start_date__lte=timezone.now()+timedelta(days=5), news__type="EVENT", news__is_moderated=True).datetimes('start_date', 'day') %}
<h3>{% trans %}Events today and the next few days{% endtrans %}</h3>
{% if events_dates %}
{% for d in events_dates %}
<div class="news_events_group">
<div class="news_events_group_date">
<div>
<div>{{ d|localtime|date('D') }}</div>
<div class="day">{{ d|localtime|date('d') }}</div>
<div>{{ d|localtime|date('b') }}</div>
</div>
</div>
<div class="news_events_group_items">
{% for news in object_list.filter(dates__start_date__gte=d,
dates__start_date__lte=d+timedelta(days=1),
type="EVENT").exclude(dates__end_date__lt=timezone.now())
.order_by('dates__start_date') %}
<section class="news_event">
<div class="club_logo">
{% if news.club.logo %}
<img src="{{ news.club.logo.url }}" alt="{{ news.club }}" />
{% else %}
<img src="{{ static("com/img/news.png") }}" alt="{{ news.club }}" />
{% endif %}
</div>
<h4> <a href="{{ url('com:news_detail', news_id=news.id) }}">{{ news.title }}</a></h4>
<div><a href="{{ news.club.get_absolute_url() }}">{{ news.club }}</a></div>
<div class="news_date">
<span>{{ news.dates.first().start_date|localtime|time(DATETIME_FORMAT) }}</span> -
<span>{{ news.dates.first().end_date|localtime|time(DATETIME_FORMAT) }}</span>
</div>
<div class="news_content">{{ news.summary|markdown }}
<div class="button_bar">
{{ fb_quick(news) }}
{{ tweet_quick(news) }}
</div>
</div>
</section>
{% endfor %}
</div>
</div>
{% endfor %}
{% else %}
<div class="news_empty">
<em>{% trans %}Nothing to come...{% endtrans %}</em>
</div>
{% endif %}
{% set coming_soon = object_list.filter(dates__start_date__gte=timezone.now()+timedelta(days=5),
type="EVENT").order_by('dates__start_date') %}
{% if coming_soon %}
<h3>{% trans %}Coming soon... don't miss!{% endtrans %}</h3>
{% for news in coming_soon %}
<section class="news_coming_soon">
<a href="{{ url('com:news_detail', news_id=news.id) }}">{{ news.title }}</a>
<span class="news_date">{{ news.dates.first().start_date|localtime|date(DATETIME_FORMAT) }}
{{ news.dates.first().start_date|localtime|time(DATETIME_FORMAT) }} -
{{ news.dates.first().end_date|localtime|date(DATETIME_FORMAT) }}
{{ news.dates.first().end_date|localtime|time(DATETIME_FORMAT) }}</span>
</section>
{% endfor %}
{% 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>
{% endif %}
<div id="right_column" class="news_column">
<div id="agenda">
<div id="agenda_title">{% trans %}Agenda{% endtrans %}</div>
<div id="agenda_content">
{% for d in NewsDate.objects.filter(end_date__gte=timezone.now(),
news__is_moderated=True, news__type__in=["WEEKLY",
"EVENT"]).order_by('start_date', 'end_date') %}
<div class="agenda_item">
<div class="agenda_date">
<strong>{{ d.start_date|localtime|date('D d M Y') }}</strong>
</div>
<div class="agenda_time">
<span>{{ d.start_date|localtime|time(DATETIME_FORMAT) }}</span> -
<span>{{ d.end_date|localtime|time(DATETIME_FORMAT) }}</span>
</div>
<div>
<strong><a href="{{ url('com:news_detail', news_id=d.news.id) }}">{{ d.news.title }}</a></strong>
<a href="{{ d.news.club.get_absolute_url() }}">{{ d.news.club }}</a>
</div>
<div class="agenda_item_content">{{ d.news.summary|markdown }}</div>
</div>
{% endfor %}
</div>
</div>
<div id="birthdays">
<div id="birthdays_title">{% trans %}Birthdays{% endtrans %}</div>
<div id="birthdays_content">
<ul class="birthdays_year">
{% for d in birthdays.dates('date_of_birth', 'year', 'DESC') %}
<li>
{% trans age=timezone.now().year - d.year %}{{ age }} year old{% endtrans %}
<ul>
{% for u in birthdays.filter(date_of_birth__year=d.year) %}
<li><a href="{{ u.get_absolute_url() }}">{{ u.get_short_name() }}</a></li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
<div id="agenda">
<div id="agenda_title">{% trans %}Agenda{% endtrans %}</div>
<div id="agenda_content">
{% for d in NewsDate.objects.filter(end_date__gte=timezone.now(),
news__is_moderated=True, news__type__in=["WEEKLY",
"EVENT"]).order_by('start_date', 'end_date') %}
<div class="agenda_item">
<div class="agenda_date">
<strong>{{ d.start_date|localtime|date('D d M Y') }}</strong>
</div>
<div class="agenda_time">
<span>{{ d.start_date|localtime|time(DATETIME_FORMAT) }}</span> -
<span>{{ d.end_date|localtime|time(DATETIME_FORMAT) }}</span>
</div>
<div>
<strong><a href="{{ url('com:news_detail', news_id=d.news.id) }}">{{ d.news.title }}</a></strong>
<a href="{{ d.news.club.get_absolute_url() }}">{{ d.news.club }}</a>
</div>
<div class="agenda_item_content">{{ d.news.summary|markdown }}</div>
</div>
{% endfor %}
</div>
</div>
<div id="birthdays">
<div id="birthdays_title">{% trans %}Birthdays{% endtrans %}</div>
<div id="birthdays_content">
{% if user.is_subscribed %}
{# Cache request for 1 hour #}
{% cache 3600 "birthdays" %}
<ul class="birthdays_year">
{% for d in birthdays.dates('date_of_birth', 'year', 'DESC') %}
<li>
{% trans age=timezone.now().year - d.year %}{{ age }} year old{% endtrans %}
<ul>
{% for u in birthdays.filter(date_of_birth__year=d.year) %}
<li><a href="{{ u.get_absolute_url() }}">{{ u.get_short_name() }}</a></li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
{% endcache %}
{% else %}
<p>{% trans %}You need an up to date subscription to access this content{% endtrans %}</p>
{% endif %}
</div>
</div>
</div>
</div>
<div id="left_column" class="news_column">
{% for news in object_list.filter(type="NOTICE") %}
<section class="news_notice">
<h4><a href="{{ url('com:news_detail', news_id=news.id) }}">{{ news.title }}</a></h4>
<div class="news_content">{{ news.summary|markdown }}</div>
</section>
{% endfor %}
{% for news in object_list.filter(dates__start_date__lte=timezone.now(),
dates__end_date__gte=timezone.now(), type="CALL") %}
<section class="news_call">
<h4> <a href="{{ url('com:news_detail', news_id=news.id) }}">{{ news.title }}</a></h4>
<div class="news_date">
<span>{{ news.dates.first().start_date|localtime|date(DATETIME_FORMAT) }}
{{ news.dates.first().start_date|localtime|time(DATETIME_FORMAT) }}</span> -
<span>{{ news.dates.first().end_date|localtime|date(DATETIME_FORMAT) }}
{{ news.dates.first().end_date|localtime|time(DATETIME_FORMAT) }}</span>
</div>
<div class="news_content">{{ news.summary|markdown }}</div>
</section>
{% endfor %}
{% set events_dates = NewsDate.objects.filter(end_date__gte=timezone.now(), start_date__lte=timezone.now()+timedelta(days=5),
news__type="EVENT", news__is_moderated=True).datetimes('start_date', 'day') %}
<h3>{% trans %}Events today and the next few days{% endtrans %}</h3>
{% if events_dates %}
{% for d in events_dates %}
<div class="news_events_group">
<div class="news_events_group_date">
<div>
<div>{{ d|localtime|date('D') }}</div>
<div class="day">{{ d|localtime|date('d') }}</div>
<div>{{ d|localtime|date('b') }}</div>
</div>
</div>
<div class="news_events_group_items">
{% for news in object_list.filter(dates__start_date__gte=d,
dates__start_date__lte=d+timedelta(days=1),
type="EVENT").exclude(dates__end_date__lt=timezone.now())
.order_by('dates__start_date') %}
<section class="news_event">
<div class="club_logo">
{% if news.club.logo %}
<img src="{{ news.club.logo.url }}" alt="{{ news.club }}" />
{% else %}
<img src="{{ static("com/img/news.png") }}" alt="{{ news.club }}" />
{% endif %}
</div>
<h4> <a href="{{ url('com:news_detail', news_id=news.id) }}">{{ news.title }}</a></h4>
<div><a href="{{ news.club.get_absolute_url() }}">{{ news.club }}</a></div>
<div class="news_date">
<span>{{ news.dates.first().start_date|localtime|time(DATETIME_FORMAT) }}</span> -
<span>{{ news.dates.first().end_date|localtime|time(DATETIME_FORMAT) }}</span>
</div>
<div class="news_content">{{ news.summary|markdown }}
<div class="button_bar">
{{ fb_quick(news) }}
{{ tweet_quick(news) }}
</div>
</div>
</section>
{% endfor %}
</div>
</div>
{% endfor %}
{% else %}
<div class="news_empty">
<em>{% trans %}Nothing to come...{% endtrans %}</em>
</div>
{% endif %}
{% set coming_soon = object_list.filter(dates__start_date__gte=timezone.now()+timedelta(days=5),
type="EVENT").order_by('dates__start_date') %}
{% if coming_soon %}
<h3>{% trans %}Coming soon... don't miss!{% endtrans %}</h3>
{% for news in coming_soon %}
<section class="news_coming_soon">
<a href="{{ url('com:news_detail', news_id=news.id) }}">{{ news.title }}</a>
<span class="news_date">{{ news.dates.first().start_date|localtime|date(DATETIME_FORMAT) }}
{{ news.dates.first().start_date|localtime|time(DATETIME_FORMAT) }} -
{{ news.dates.first().end_date|localtime|date(DATETIME_FORMAT) }}
{{ news.dates.first().end_date|localtime|time(DATETIME_FORMAT) }}</span>
</section>
{% endfor %}
{% endif %}
</div>
</div>
{% endblock %}

View File

@ -36,8 +36,8 @@
<div class="name">{{ poster.name }}</div>
<div class="image"><img src="{{ poster.file.url }}"></img></div>
<div class="dates">
<div class="begin">{{ poster.date_begin | date("d/M/Y H:m") }}</div>
<div class="end">{{ poster.date_end | date("d/M/Y H:m") }}</div>
<div class="begin">{{ poster.date_begin | localtime | date("d/M/Y H:m") }}</div>
<div class="end">{{ poster.date_end | localtime | date("d/M/Y H:m") }}</div>
</div>
{% if app == "com" %}
<a class="edit" href="{{ url(app + ":poster_edit", poster.id) }}">{% trans %}Edit{% endtrans %}</a>

View File

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

View File

@ -7,15 +7,33 @@
{% block content %}
<a href="{{ url('com:weekmail') }}">{% trans %}Back{% endtrans %}</a>
{% if request.GET['send'] %}
<p>{% trans %}Are you sure you want to send this weekmail?{% endtrans %}</p>
{% if request.LANGUAGE_CODE != settings.LANGUAGE_CODE[:2] %}
<p><strong>{% trans %}Warning: you are sending the weekmail in another language than the default one!{% endtrans %}</strong></p>
{% endif %}
<form method="post" action="">
{% csrf_token %}
<button type="submit" name="send" value="validate">{% trans %}Send{% endtrans %}</button>
</form>
{% if bad_recipients %}
<p>
<span class="important">
{% trans %}The following recipients were refused by the SMTP:{% endtrans %}
</span>
<ul>
{% for r in bad_recipients.keys() %}
<li>{{ r }}</li>
{% endfor %}
</ul>
</p>
<form method="post" action="">
{% csrf_token %}
<button type="submit" name="send" value="clean">{% trans %}Clean subscribers{% endtrans %}</button>
</form>
{% else %}
{% if request.GET['send'] %}
<p>{% trans %}Are you sure you want to send this weekmail?{% endtrans %}</p>
{% if request.LANGUAGE_CODE != settings.LANGUAGE_CODE[:2] %}
<p><strong>{% trans %}Warning: you are sending the weekmail in another language than the default one!{% endtrans %}</strong></p>
{% endif %}
<form method="post" action="">
{% csrf_token %}
<button type="submit" name="send" value="validate">{% trans %}Send{% endtrans %}</button>
</form>
{% endif %}
{% endif %}
<hr>
{{ weekmail_rendered|safe }}

View File

@ -1,44 +1,59 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase
from django.conf import settings
from django.core.urlresolvers import reverse
from django.urls import reverse
from django.core.management import call_command
from django.utils import html
from django.utils.timezone import localtime, now
from django.utils.translation import gettext as _
from core.models import User, RealGroup
from club.models import Club, Membership
from com.models import Sith, News, Weekmail, WeekmailArticle, Poster
from core.models import User, RealGroup, AnonymousUser
class ComAlertTest(TestCase):
def test_page_is_working(self):
self.client.login(username="comunity", password="plop")
response = self.client.get(reverse("com:alert_edit"))
self.assertNotEqual(response.status_code, 500)
self.assertEqual(response.status_code, 200)
class ComInfoTest(TestCase):
def test_page_is_working(self):
self.client.login(username="comunity", password="plop")
response = self.client.get(reverse("com:info_edit"))
self.assertNotEqual(response.status_code, 500)
self.assertEqual(response.status_code, 200)
class ComTest(TestCase):
def setUp(self):
call_command("populate")
self.skia = User.objects.filter(username="skia").first()
self.com_group = RealGroup.objects.filter(
@classmethod
def setUpTestData(cls):
cls.skia = User.objects.filter(username="skia").first()
cls.com_group = RealGroup.objects.filter(
id=settings.SITH_GROUP_COM_ADMIN_ID
).first()
self.skia.groups = [self.com_group]
self.skia.save()
cls.skia.groups.set([cls.com_group])
cls.skia.save()
def setUp(self):
self.client.login(username=self.skia.username, password="plop")
def test_alert_msg(self):
@ -54,9 +69,11 @@ class ComTest(TestCase):
)
r = self.client.get(reverse("core:index"))
self.assertTrue(r.status_code == 200)
self.assertTrue(
"""<div id="alert_box">\\n <div class="markdown"><h3>ALERTE!</h3>\\n<p><strong>Caaaataaaapuuuulte!!!!</strong></p>"""
in str(r.content)
self.assertContains(
r,
"""<div id="alert_box">
<div class="markdown"><h3>ALERTE!</h3>
<p><strong>Caaaataaaapuuuulte!!!!</strong></p>""",
)
def test_info_msg(self):
@ -70,7 +87,127 @@ class ComTest(TestCase):
)
r = self.client.get(reverse("core:index"))
self.assertTrue(r.status_code == 200)
self.assertTrue(
"""<div id="info_box">\\n <div class="markdown"><h3>INFO: <strong>Caaaataaaapuuuulte!!!!</strong></h3>"""
in str(r.content)
self.assertContains(
r,
"""<div id="info_box">
<div class="markdown"><h3>INFO: <strong>Caaaataaaapuuuulte!!!!</strong></h3>""",
)
def test_birthday_non_subscribed_user(self):
self.client.login(username="guy", password="plop")
response = self.client.get(reverse("core:index"))
self.assertContains(
response,
text=html.escape(
_("You need an up to date subscription to access this content")
),
)
def test_birthday_subscibed_user(self):
response = self.client.get(reverse("core:index"))
self.assertNotContains(
response,
text=html.escape(
_("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,120 +1,111 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
from django.conf.urls import url
from django.urls import path
from com.views import *
from club.views import MailingDeleteView
from com.views import *
urlpatterns = [
url(r"^sith/edit/alert$", AlertMsgEditView.as_view(), name="alert_edit"),
url(r"^sith/edit/info$", InfoMsgEditView.as_view(), name="info_edit"),
url(r"^sith/edit/index$", IndexEditView.as_view(), name="index_edit"),
url(
r"^sith/edit/weekmail_destinations$",
path("sith/edit/alert/", AlertMsgEditView.as_view(), name="alert_edit"),
path("sith/edit/info/", InfoMsgEditView.as_view(), name="info_edit"),
path(
"sith/edit/weekmail_destinations/",
WeekmailDestinationEditView.as_view(),
name="weekmail_destinations",
),
url(r"^weekmail$", WeekmailEditView.as_view(), name="weekmail"),
url(r"^weekmail/preview$", WeekmailPreviewView.as_view(), name="weekmail_preview"),
url(
r"^weekmail/new_article$",
path("weekmail/", WeekmailEditView.as_view(), name="weekmail"),
path("weekmail/preview/", WeekmailPreviewView.as_view(), name="weekmail_preview"),
path(
"weekmail/new_article/",
WeekmailArticleCreateView.as_view(),
name="weekmail_article",
),
url(
r"^weekmail/article/(?P<article_id>[0-9]+)/delete$",
path(
"weekmail/article/<int:article_id>/delete/",
WeekmailArticleDeleteView.as_view(),
name="weekmail_article_delete",
),
url(
r"^weekmail/article/(?P<article_id>[0-9]+)/edit$",
path(
"weekmail/article/<int:article_id>/edit/",
WeekmailArticleEditView.as_view(),
name="weekmail_article_edit",
),
url(r"^news$", NewsListView.as_view(), name="news_list"),
url(r"^news/admin$", NewsAdminListView.as_view(), name="news_admin_list"),
url(r"^news/create$", NewsCreateView.as_view(), name="news_new"),
url(
r"^news/(?P<news_id>[0-9]+)/delete$",
path("news/", NewsListView.as_view(), name="news_list"),
path("news/admin/", NewsAdminListView.as_view(), name="news_admin_list"),
path("news/create/", NewsCreateView.as_view(), name="news_new"),
path(
"news/<int:news_id>/delete/",
NewsDeleteView.as_view(),
name="news_delete",
),
url(
r"^news/(?P<news_id>[0-9]+)/moderate$",
path(
"news/<int:news_id>/moderate/",
NewsModerateView.as_view(),
name="news_moderate",
),
url(r"^news/(?P<news_id>[0-9]+)/edit$", NewsEditView.as_view(), name="news_edit"),
url(r"^news/(?P<news_id>[0-9]+)$", NewsDetailView.as_view(), name="news_detail"),
url(r"^mailings$", MailingListAdminView.as_view(), name="mailing_admin"),
url(
r"^mailings/(?P<mailing_id>[0-9]+)/moderate$",
path("news/<int:news_id>/edit/", NewsEditView.as_view(), name="news_edit"),
path("news/<int:news_id>/", NewsDetailView.as_view(), name="news_detail"),
path("mailings/", MailingListAdminView.as_view(), name="mailing_admin"),
path(
"mailings/<int:mailing_id>/moderate/",
MailingModerateView.as_view(),
name="mailing_moderate",
),
url(
r"^mailings/(?P<mailing_id>[0-9]+)/delete$",
path(
"mailings/<int:mailing_id>/delete/",
MailingDeleteView.as_view(redirect_page="com:mailing_admin"),
name="mailing_delete",
),
url(r"^poster$", PosterListView.as_view(), name="poster_list"),
url(r"^poster/create$", PosterCreateView.as_view(), name="poster_create"),
url(
r"^poster/(?P<poster_id>[0-9]+)/edit$",
path("poster/", PosterListView.as_view(), name="poster_list"),
path("poster/create/", PosterCreateView.as_view(), name="poster_create"),
path(
"poster/<int:poster_id>/edit/",
PosterEditView.as_view(),
name="poster_edit",
),
url(
r"^poster/(?P<poster_id>[0-9]+)/delete$",
path(
"poster/<int:poster_id>/delete/",
PosterDeleteView.as_view(),
name="poster_delete",
),
url(
r"^poster/moderate$",
path(
"poster/moderate/",
PosterModerateListView.as_view(),
name="poster_moderate_list",
),
url(
r"^poster/(?P<object_id>[0-9]+)/moderate$",
path(
"poster/<int:object_id>/moderate/",
PosterModerateView.as_view(),
name="poster_moderate",
),
url(r"^screen$", ScreenListView.as_view(), name="screen_list"),
url(r"^screen/create$", ScreenCreateView.as_view(), name="screen_create"),
url(
r"^screen/(?P<screen_id>[0-9]+)/slideshow$",
path("screen/", ScreenListView.as_view(), name="screen_list"),
path("screen/create/", ScreenCreateView.as_view(), name="screen_create"),
path(
"screen/<int:screen_id>/slideshow/",
ScreenSlideshowView.as_view(),
name="screen_slideshow",
),
url(
r"^screen/(?P<screen_id>[0-9]+)/edit$",
path(
"screen/<int:screen_id>/edit/",
ScreenEditView.as_view(),
name="screen_edit",
),
url(
r"^screen/(?P<screen_id>[0-9]+)/delete$",
path(
"screen/<int:screen_id>/delete/",
ScreenDeleteView.as_view(),
name="screen_delete",
),

View File

@ -28,8 +28,8 @@ from django.http import HttpResponseRedirect
from django.views.generic import ListView, DetailView, View
from django.views.generic.edit import UpdateView, CreateView, DeleteView
from django.views.generic.detail import SingleObjectMixin
from django.utils.translation import ugettext_lazy as _
from django.core.urlresolvers import reverse, reverse_lazy
from django.utils.translation import gettext_lazy as _
from django.urls import reverse, reverse_lazy
from django.core.exceptions import ValidationError
from django.utils import timezone
from django.conf import settings
@ -39,6 +39,7 @@ from django.core.exceptions import PermissionDenied
from django import forms
from datetime import timedelta
from smtplib import SMTPRecipientsRefused
from com.models import Sith, News, NewsDate, Weekmail, WeekmailArticle, Screen, Poster
from core.views import (
@ -52,6 +53,7 @@ from core.views import (
from core.views.forms import SelectDateTime, MarkdownInput
from core.models import Notification, RealGroup, User
from club.models import Club, Mailing
from core.views.forms import TzAwareDateTimeField
# Sith object
@ -72,20 +74,14 @@ class PosterForm(forms.ModelForm):
"display_time",
]
widgets = {"screens": forms.CheckboxSelectMultiple}
help_texts = {"file": _("Format: 16:9 | Resolution: 1920x1080")}
date_begin = forms.DateTimeField(
["%Y-%m-%d %H:%M:%S"],
date_begin = TzAwareDateTimeField(
label=_("Start date"),
widget=SelectDateTime,
required=True,
initial=timezone.now().strftime("%Y-%m-%d %H:%M:%S"),
)
date_end = forms.DateTimeField(
["%Y-%m-%d %H:%M:%S"],
label=_("End date"),
widget=SelectDateTime,
required=False,
)
date_end = TzAwareDateTimeField(label=_("End date"), required=False)
def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user", None)
@ -114,9 +110,6 @@ class ComTabsMixin(TabedViewMixin):
"name": _("Weekmail destinations"),
}
)
tab_list.append(
{"url": reverse("com:index_edit"), "slug": "index", "name": _("Index page")}
)
tab_list.append(
{"url": reverse("com:info_edit"), "slug": "info", "name": _("Info message")}
)
@ -153,7 +146,7 @@ class ComTabsMixin(TabedViewMixin):
class IsComAdminMixin(View):
def dispatch(self, request, *args, **kwargs):
if not (request.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)):
if not request.user.is_com_admin:
raise PermissionDenied
return super(IsComAdminMixin, self).dispatch(request, *args, **kwargs)
@ -182,14 +175,6 @@ class InfoMsgEditView(ComEditView):
success_url = reverse_lazy("com:info_edit")
class IndexEditView(ComEditView):
form_class = modelform_factory(
Sith, fields=["index_page"], widgets={"index_page": MarkdownInput}
)
current_tab = "index"
success_url = reverse_lazy("com:index_edit")
class WeekmailDestinationEditView(ComEditView):
fields = ["weekmail_destinations"]
current_tab = "weekmail_destinations"
@ -210,21 +195,10 @@ class NewsForm(forms.ModelForm):
"content": MarkdownInput,
}
start_date = forms.DateTimeField(
["%Y-%m-%d %H:%M:%S"],
label=_("Start date"),
widget=SelectDateTime,
required=False,
)
end_date = forms.DateTimeField(
["%Y-%m-%d %H:%M:%S"],
label=_("End date"),
widget=SelectDateTime,
required=False,
)
until = forms.DateTimeField(
["%Y-%m-%d %H:%M:%S"], label=_("Until"), widget=SelectDateTime, required=False
)
start_date = TzAwareDateTimeField(label=_("Start date"), required=False)
end_date = TzAwareDateTimeField(label=_("End date"), required=False)
until = TzAwareDateTimeField(label=_("Until"), required=False)
automoderation = forms.BooleanField(label=_("Automoderation"), required=False)
def clean(self):
@ -238,7 +212,11 @@ class NewsForm(forms.ModelForm):
self.add_error(
"end_date", ValidationError(_("This field is required."))
)
if self.cleaned_data["start_date"] > self.cleaned_data["end_date"]:
if (
not self.has_error("start_date")
and not self.has_error("end_date")
and self.cleaned_data["start_date"] > self.cleaned_data["end_date"]
):
self.add_error(
"end_date",
ValidationError(
@ -305,9 +283,7 @@ class NewsEditView(CanEditMixin, UpdateView):
def form_valid(self, form):
self.object = form.save()
if form.cleaned_data["automoderation"] and self.request.user.is_in_group(
settings.SITH_GROUP_COM_ADMIN_ID
):
if form.cleaned_data["automoderation"] and self.request.user.is_com_admin:
self.object.moderator = self.request.user
self.object.is_moderated = True
self.object.save()
@ -355,9 +331,7 @@ class NewsCreateView(CanCreateMixin, CreateView):
def form_valid(self, form):
self.object = form.save()
if form.cleaned_data["automoderation"] and self.request.user.is_in_group(
settings.SITH_GROUP_COM_ADMIN_ID
):
if form.cleaned_data["automoderation"] and self.request.user.is_com_admin:
self.object.moderator = self.request.user
self.object.is_moderated = True
self.object.save()
@ -437,23 +411,36 @@ class NewsDetailView(CanViewMixin, DetailView):
# Weekmail
class WeekmailPreviewView(ComTabsMixin, CanEditPropMixin, DetailView):
class WeekmailPreviewView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, DetailView):
model = Weekmail
template_name = "com/weekmail_preview.jinja"
success_url = reverse_lazy("com:weekmail")
current_tab = "weekmail"
def dispatch(self, request, *args, **kwargs):
self.bad_recipients = []
return super(WeekmailPreviewView, self).dispatch(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
try:
if request.POST["send"] == "validate":
if request.POST["send"] == "validate":
try:
self.object.send()
return HttpResponseRedirect(
reverse("com:weekmail") + "?qn_weekmail_send_success"
)
except:
pass
return super(WeekmailEditView, self).get(request, *args, **kwargs)
except SMTPRecipientsRefused as e:
self.bad_recipients = e.recipients
elif request.POST["send"] == "clean":
try:
self.object.send() # This should fail
except SMTPRecipientsRefused as e:
users = User.objects.filter(email__in=e.recipients.keys())
for u in users:
u.preferences.receive_weekmail = False
u.preferences.save()
self.quick_notif_list += ["qn_success"]
return super(WeekmailPreviewView, self).get(request, *args, **kwargs)
def get_object(self, queryset=None):
return self.model.objects.filter(sent=False).order_by("-id").first()
@ -462,6 +449,7 @@ class WeekmailPreviewView(ComTabsMixin, CanEditPropMixin, DetailView):
"""Add rendered weekmail"""
kwargs = super(WeekmailPreviewView, self).get_context_data(**kwargs)
kwargs["weekmail_rendered"] = self.object.render_html()
kwargs["bad_recipients"] = self.bad_recipients
return kwargs
@ -538,7 +526,7 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi
return super(WeekmailEditView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
"""Add orphan articles """
"""Add orphan articles"""
kwargs = super(WeekmailEditView, self).get_context_data(**kwargs)
kwargs["orphans"] = WeekmailArticle.objects.filter(weekmail=None)
return kwargs
@ -625,10 +613,7 @@ class MailingListAdminView(ComTabsMixin, ListView):
current_tab = "mailings"
def dispatch(self, request, *args, **kwargs):
if not (
request.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
or request.user.is_root
):
if not (request.user.is_com_admin or request.user.is_root):
raise PermissionDenied
return super(MailingListAdminView, self).dispatch(request, *args, **kwargs)
@ -750,7 +735,7 @@ class PosterEditBaseView(UpdateView):
def get_context_data(self, **kwargs):
kwargs = super(PosterEditBaseView, self).get_context_data(**kwargs)
if not self.request.user.is_com_admin:
if hasattr(self, "club"):
kwargs["club"] = self.club
return kwargs

View File

@ -1,25 +1,15 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
default_app_config = "core.apps.SithConfig"

View File

@ -1,40 +1,34 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# 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.
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# 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.
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
from django.contrib import admin
from ajax_select import make_ajax_form
from core.models import User, Page, RealGroup, SithFile
from core.models import User, Page, RealGroup, MetaGroup, SithFile
from django.contrib.auth.models import Group as AuthGroup
from haystack.admin import SearchModelAdmin
admin.site.unregister(AuthGroup)
admin.site.register(MetaGroup)
admin.site.register(RealGroup)
@admin.register(User)
class UserAdmin(SearchModelAdmin):
list_display = ["first_name", "last_name", "username", "email", "nick_name"]
list_display = ("first_name", "last_name", "username", "email", "nick_name")
form = make_ajax_form(
User,
{
@ -48,11 +42,9 @@ class UserAdmin(SearchModelAdmin):
search_fields = ["first_name", "last_name", "username"]
admin.site.register(User, UserAdmin)
@admin.register(Page)
class PageAdmin(admin.ModelAdmin):
list_display = ("name", "_full_name", "owner_group")
form = make_ajax_form(
Page,
{
@ -66,4 +58,12 @@ class PageAdmin(admin.ModelAdmin):
@admin.register(SithFile)
class SithFileAdmin(admin.ModelAdmin):
form = make_ajax_form(SithFile, {"parent": "files"}) # ManyToManyField
list_display = ("name", "owner", "size", "date", "is_in_sas")
form = make_ajax_form(
SithFile,
{
"parent": "files",
"owner": "users",
"moderator": "users",
},
) # ManyToManyField

View File

@ -25,6 +25,7 @@
import sys
from django.apps import AppConfig
from django.core.cache import cache
from django.core.signals import request_started
@ -33,26 +34,17 @@ class SithConfig(AppConfig):
verbose_name = "Core app of the Sith"
def ready(self):
from core.models import User
from club.models import Club
from forum.models import Forum
import core.signals
def clear_cached_groups(**kwargs):
User._group_ids = {}
User._group_name = {}
cache.clear()
def clear_cached_memberships(**kwargs):
User._club_memberships = {}
Club._memberships = {}
Forum._club_memberships = {}
print("Connecting signals!", file=sys.stderr)
request_started.connect(
clear_cached_groups, weak=False, dispatch_uid="clear_cached_groups"
)
request_started.connect(
clear_cached_memberships,
weak=False,
dispatch_uid="clear_cached_memberships",
)
# TODO: there may be a need to add more cache clearing

35
core/converters.py Normal file
View File

@ -0,0 +1,35 @@
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: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

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