311 Commits

Author SHA1 Message Date
f234f94171 Removed unused xapian action 2023-11-05 23:49:45 +01:00
fa758867cc Migrated from xapian to elastic 2023-11-05 23:42:52 +01:00
f41ff281fb Remove eurocks tickets from eboutic (event is finished) 2023-10-10 15:50:35 +02:00
321cb72ca8 October 2023 update (#672)
* integration of 3D secure v2 for eboutic bank payment

* edit yml to avoid git conflict when deploying on test

* escape html characters on xml (#505)

* Change country id to ISO 3166 1 numeric for 3DSV2 (#510)

* remove useless tests

* 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>

* update some dependencies (#523)

* [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

* enhance admin pages

* update documentation

* Update doc/about/tech.rst

Co-authored-by: Julien Constant <49886317+Juknum@users.noreply.github.com>

* remove csrf_token

* Fix 3DSv2 implementation (#542)

* Fixed wrong HMAC signature generation

* Fix xml du panier

Co-authored-by: Julien Constant <julienconstant190@gmail.com>

* [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>

* Update doc/about/tech.rst

* Update doc/start/install.rst

* Updated lock file according to pyproject

* unify account_id creation

* upgrade re_path to path (#533)

* redirect directly on counter if user is barman

* 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).

* resolved importError (#565)

* Add galaxy (#562)

* style.scss: lint

* style.scss: add 'th' padding

* core: populate: add much more data for development

* Add galaxy

* repair user merging tool (#498)

* Disabled galaxy feature (only visually)

* Disabled Galaxy button & Removed 404 exception display

* Update 404.jinja

* Fixed broken test

* Added eurocks links to eboutic

* fix typo

* fix wording

Co-authored-by: Théo DURR <git@theodurr.fr>

* Edited unit tests

This test caused a breach in security due to the alert block displaying sensitive data.

* Repair NaN bug for autocomplete on counter click

* remove-useless-queries-counter-stats (#519)

* 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

* Added GA/Clubs Google Calendar to main page (#585)

* Added GA/Clubs google calendar to main page

* Made tables full width

* Create dependabot.yml (#587)

* Bump django from 3.2.16 to 3.2.18 (#574)

* [CSS] Follow up of #578 (#589)

* [FIX] Broken link in readme and license fix (& update) (#591)

* Fixes pour la mise à jour de mars (#598)

* Fix problème de cache dans le SAS & améliore le CSS du SAS

Co-authored-by: Bartuccio Antoine <klmp200@users.noreply.github.com>

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

* [UPDATE] Bump sentry-sdk from 1.12.1 to 1.19.1 (#620)

* [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

* [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
...

* [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>

* [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>

* Mise à jour de Black vers la version 23.3 (#629)

* update link for poetry install

* [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>

* [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>

* Speed up tests (#638)

* 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

* [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>

* Remove duplicated css

* 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>

* [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>

* [FIX] Fix cached groups (#647)

* Bump sqlparse from 0.4.3 to 0.4.4 (#645)

Bumps [sqlparse](https://github.com/andialbrecht/sqlparse) from 0.4.3 to 0.4.4.
- [Release notes](https://github.com/andialbrecht/sqlparse/releases)
- [Changelog](https://github.com/andialbrecht/sqlparse/blob/master/CHANGELOG)
- [Commits](https://github.com/andialbrecht/sqlparse/compare/0.4.3...0.4.4)

---
updated-dependencies:
- dependency-name: sqlparse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* [UPDATE] Bump django-ordered-model from 3.6 to 3.7.4 (#625)

Bumps [django-ordered-model](https://github.com/django-ordered-model/django-ordered-model) from 3.6 to 3.7.4.
- [Release notes](https://github.com/django-ordered-model/django-ordered-model/releases)
- [Changelog](https://github.com/django-ordered-model/django-ordered-model/blob/master/CHANGES.md)
- [Commits](https://github.com/django-ordered-model/django-ordered-model/compare/3.6...3.7.4)

---
updated-dependencies:
- dependency-name: django-ordered-model
  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>

* Fix immutable default variable in `get_start_of_semester` (#656)

Le serveur ne percevait pas le changement de semestre, parce
que la valeur par défaut passée à la fonction `get_start_of_semester()` était une fonction appelée une seule fois, lors du lancement du serveur. Bref, c'était ça : https://beta.ruff.rs/docs/rules/function-call-in-default-argument/

---------

Co-authored-by: imperosol <thgirod@hotmail.com>

* Add missing method on AnonymousUser (#649)

* Add eurocks partnership in the eboutic (#661)

* Add eurocks partnership in the eboutic (#661)

Revert "Add eurocks partnership in the eboutic (#661)"

This reverts commit 193c820757.

Add eurocks partnership in the eboutic (#661)

* Update workflow

Following this update : https://github.blog/changelog/2023-09-13-github-actions-updates-to-github_ref-and-github-ref/

* Update workflow

* Remove eurocks tickets from eboutic (event is finished)

* Links update & translations typos fixes (#671)

* Remove BDF link (as BDF is now part of AE)

* Remove unused pages

* Fix typos

* Fix typo again

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Thomas Girod <thgirod@hotmail.com>
Co-authored-by: thomas girod <56346771+imperosol@users.noreply.github.com>
Co-authored-by: Théo DURR <git@theodurr.fr>
Co-authored-by: Skia <skia@hya.sk>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bartuccio Antoine <klmp200@users.noreply.github.com>
2023-10-10 15:41:19 +02:00
c436d39014 [PARTENARIAT] Partenariat Eurockéennes (#663) 2023-09-20 17:57:26 +02:00
b9298792ae Mise à jour de septembre 2023 (#659)
* integration of 3D secure v2 for eboutic bank payment

* edit yml to avoid git conflict when deploying on test

* escape html characters on xml (#505)

* Change country id to ISO 3166 1 numeric for 3DSV2 (#510)

* remove useless tests

* 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>

* update some dependencies (#523)

* [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

* enhance admin pages

* update documentation

* Update doc/about/tech.rst

Co-authored-by: Julien Constant <49886317+Juknum@users.noreply.github.com>

* remove csrf_token

* Fix 3DSv2 implementation (#542)

* Fixed wrong HMAC signature generation

* Fix xml du panier

Co-authored-by: Julien Constant <julienconstant190@gmail.com>

* [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>

* Update doc/about/tech.rst

* Update doc/start/install.rst

* Updated lock file according to pyproject

* unify account_id creation

* upgrade re_path to path (#533)

* redirect directly on counter if user is barman

* 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).

* resolved importError (#565)

* Add galaxy (#562)

* style.scss: lint

* style.scss: add 'th' padding

* core: populate: add much more data for development

* Add galaxy

* repair user merging tool (#498)

* Disabled galaxy feature (only visually)

* Disabled Galaxy button & Removed 404 exception display

* Update 404.jinja

* Fixed broken test

* Added eurocks links to eboutic

* fix typo

* fix wording

Co-authored-by: Théo DURR <git@theodurr.fr>

* Edited unit tests

This test caused a breach in security due to the alert block displaying sensitive data.

* Repair NaN bug for autocomplete on counter click

* remove-useless-queries-counter-stats (#519)

* 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

* Added GA/Clubs Google Calendar to main page (#585)

* Added GA/Clubs google calendar to main page

* Made tables full width

* Create dependabot.yml (#587)

* Bump django from 3.2.16 to 3.2.18 (#574)

* [CSS] Follow up of #578 (#589)

* [FIX] Broken link in readme and license fix (& update) (#591)

* Fixes pour la mise à jour de mars (#598)

* Fix problème de cache dans le SAS & améliore le CSS du SAS

Co-authored-by: Bartuccio Antoine <klmp200@users.noreply.github.com>

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

* [UPDATE] Bump sentry-sdk from 1.12.1 to 1.19.1 (#620)

* [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

* [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
...

* [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>

* [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>

* Mise à jour de Black vers la version 23.3 (#629)

* update link for poetry install

* [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>

* [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>

* Speed up tests (#638)

* 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

* [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>

* Remove duplicated css

* 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>

* [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>

* [FIX] Fix cached groups (#647)

* Bump sqlparse from 0.4.3 to 0.4.4 (#645)

Bumps [sqlparse](https://github.com/andialbrecht/sqlparse) from 0.4.3 to 0.4.4.
- [Release notes](https://github.com/andialbrecht/sqlparse/releases)
- [Changelog](https://github.com/andialbrecht/sqlparse/blob/master/CHANGELOG)
- [Commits](https://github.com/andialbrecht/sqlparse/compare/0.4.3...0.4.4)

---
updated-dependencies:
- dependency-name: sqlparse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* [UPDATE] Bump django-ordered-model from 3.6 to 3.7.4 (#625)

Bumps [django-ordered-model](https://github.com/django-ordered-model/django-ordered-model) from 3.6 to 3.7.4.
- [Release notes](https://github.com/django-ordered-model/django-ordered-model/releases)
- [Changelog](https://github.com/django-ordered-model/django-ordered-model/blob/master/CHANGES.md)
- [Commits](https://github.com/django-ordered-model/django-ordered-model/compare/3.6...3.7.4)

---
updated-dependencies:
- dependency-name: django-ordered-model
  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>

* Fix immutable default variable in `get_start_of_semester` (#656)

Le serveur ne percevait pas le changement de semestre, parce
que la valeur par défaut passée à la fonction `get_start_of_semester()` était une fonction appelée une seule fois, lors du lancement du serveur. Bref, c'était ça : https://beta.ruff.rs/docs/rules/function-call-in-default-argument/

---------

Co-authored-by: imperosol <thgirod@hotmail.com>

* Add missing method on AnonymousUser (#649)

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Thomas Girod <thgirod@hotmail.com>
Co-authored-by: thomas girod <56346771+imperosol@users.noreply.github.com>
Co-authored-by: Théo DURR <git@theodurr.fr>
Co-authored-by: Skia <skia@hya.sk>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bartuccio Antoine <klmp200@users.noreply.github.com>
2023-09-09 13:09:13 +02:00
4f9d5ae7b1 Revert "[PARTENARIAT] Ajout vitrine d'achat billets eurockéennes 2023 (#582)"
This reverts commit b12e8dc147.
2023-07-02 18:22:14 +02:00
259337dff1 [FIX] Fix cached groups (#647) 2023-05-12 13:29:16 +02:00
288764b551 Mise à jour d'avril (#643) 2023-05-10 11:56:33 +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
fa6527b24f [FIX] Deuxième vague de fixes pour la mise à jour de mars (#619) 2023-04-06 16:09:29 +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
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
b12e8dc147 [PARTENARIAT] Ajout vitrine d'achat billets eurockéennes 2023 (#582)
* Added eurocks links to eboutic
2023-03-09 17:13:45 +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
b7f20fed6c Galaxy (#575)
Co-authored-by: Skia <florent.jacquet@eshard.com>
2023-03-02 15:11:23 +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
310f1a2283 [FEATURE] Ajout du logo de la promo 23 & Amélioration des anciens logos (#541) 2023-01-05 18:37:13 +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
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
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
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
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
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
437 changed files with 18837 additions and 10064 deletions

15
.envrc
View File

@ -1 +1,14 @@
source ./env/bin/activate
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 libgraphviz-dev
version: 1.0 # increment to reset cache
- name: Install dependencies
run: |
sudo apt update
sudo apt install gettext 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

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] "

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

@ -0,0 +1,45 @@
name: Sith 3 CI
on:
push:
branches: [master, taiste, features/**]
pull_request:
branches: [master, taiste]
workflow_dispatch:
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
container:
image: docker.elastic.co/elasticsearch/elasticsearch:7.17.14
env:
discovery.type: single-node
ports:
- 9200
steps:
- name: Check out repository
uses: actions/checkout@v3
- uses: ./.github/actions/setup_project
- 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

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

@ -0,0 +1,63 @@
name: Sith3 taiste
on:
push:
branches: [taiste]
workflow_dispatch:
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

4
.gitignore vendored
View File

@ -4,10 +4,14 @@ 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/

View File

@ -1,28 +0,0 @@
stages:
- test
test:
stage: test
script:
- apt-get update
- apt-get install -y gettext python3-xapian libgraphviz-dev
- 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
- cd doc
- make html # Make documentation
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>

View File

@ -4,6 +4,11 @@
# 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
@ -13,6 +18,9 @@ formats: all
# Optionally set the version of Python and requirements required to build your docs
python:
version: 3.6
version: "3.8"
install:
- requirements: requirements.txt
- method: pip
path: .
extra_requirements:
- docs

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.

40
README.md Normal file
View File

@ -0,0 +1,40 @@
<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>
<h3 align="center">This is the source code of the UTBM's student association available at https://ae.utbm.fr/.</h3>
<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>
<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,37 +0,0 @@
.. image:: https://ae-dev.utbm.fr/ae/Sith/badges/master/pipeline.svg
:target: https://ae-dev.utbm.fr/ae/Sith/commits/master
:alt: pipeline status
.. image:: https://readthedocs.org/projects/sith-ae/badge/?version=latest
:target: https://sith-ae.readthedocs.io/?badge=latest
:alt: documentation Status
.. image:: https://ae-dev.utbm.fr/ae/Sith/badges/master/coverage.svg
:target: https://ae-dev.utbm.fr/ae/Sith/commits/master
:alt: coverage report
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/ambv/black
:alt: code style: black
.. image:: https://img.shields.io/badge/zulip-join_chat-brightgreen.svg
:target: https://ae-dev.zulipchat.com
:alt: project chat
This is the source code of the UTBM's student association available at https://ae.utbm.fr/.
All documentation is in the ``docs`` 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.
If you want to contribute, here's how we recommend to read the docs:
* 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.
* If in the first part you find 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.
* Keep in mind that this documentation is thought to be read in order.
To join our team :
* Send a mail at mailto:ae.utbm.fr
* Join our group chat at https://ae-dev.zulipchat.com
* See and join our Trello at https://trello.com/b/YQOaF33m/site-ae.
This project is licenced under GNU GPL, see the LICENSE file at the top of the repository for more details.

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

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

View File

@ -6,7 +6,6 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("club", "0001_initial"),
("accounting", "0001_initial"),

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 = [

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,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"
#
#
@ -27,7 +19,7 @@ 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
@ -125,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"]:
@ -162,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
@ -233,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
@ -243,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
@ -422,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
@ -435,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
@ -491,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
@ -562,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,7 +84,10 @@
<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 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 %}

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.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": "",

View File

@ -1,154 +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.urls import re_path
from django.urls import path
from accounting.views import *
urlpatterns = [
# Accounting types
re_path(
r"^simple_type$",
path(
"simple_type/",
SimplifiedAccountingTypeListView.as_view(),
name="simple_type_list",
),
re_path(
r"^simple_type/create$",
path(
"simple_type/create/",
SimplifiedAccountingTypeCreateView.as_view(),
name="simple_type_new",
),
re_path(
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
re_path(r"^type$", AccountingTypeListView.as_view(), name="type_list"),
re_path(r"^type/create$", AccountingTypeCreateView.as_view(), name="type_new"),
re_path(
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
re_path(r"^$", BankAccountListView.as_view(), name="bank_list"),
re_path(r"^bank/create$", BankAccountCreateView.as_view(), name="bank_new"),
re_path(
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",
),
re_path(
r"^bank/(?P<b_account_id>[0-9]+)/edit$",
path(
"bank/<int:b_account_id>/edit/",
BankAccountEditView.as_view(),
name="bank_edit",
),
re_path(
r"^bank/(?P<b_account_id>[0-9]+)/delete$",
path(
"bank/<int:b_account_id>/delete/",
BankAccountDeleteView.as_view(),
name="bank_delete",
),
# Club accounts
re_path(r"^club/create$", ClubAccountCreateView.as_view(), name="club_new"),
re_path(
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",
),
re_path(
r"^club/(?P<c_account_id>[0-9]+)/edit$",
path(
"club/<int:c_account_id>/edit/",
ClubAccountEditView.as_view(),
name="club_edit",
),
re_path(
r"^club/(?P<c_account_id>[0-9]+)/delete$",
path(
"club/<int:c_account_id>/delete/",
ClubAccountDeleteView.as_view(),
name="club_delete",
),
# Journals
re_path(r"^journal/create$", JournalCreateView.as_view(), name="journal_new"),
re_path(
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",
),
re_path(
r"^journal/(?P<j_id>[0-9]+)/edit$",
path(
"journal/<int:j_id>/edit/",
JournalEditView.as_view(),
name="journal_edit",
),
re_path(
r"^journal/(?P<j_id>[0-9]+)/delete$",
path(
"journal/<int:j_id>/delete/",
JournalDeleteView.as_view(),
name="journal_delete",
),
re_path(
r"^journal/(?P<j_id>[0-9]+)/statement/nature$",
path(
"journal/<int:j_id>/statement/nature/",
JournalNatureStatementView.as_view(),
name="journal_nature_statement",
),
re_path(
r"^journal/(?P<j_id>[0-9]+)/statement/person$",
path(
"journal/<int:j_id>/statement/person/",
JournalPersonStatementView.as_view(),
name="journal_person_statement",
),
re_path(
r"^journal/(?P<j_id>[0-9]+)/statement/accounting$",
path(
"journal/<int:j_id>/statement/accounting/",
JournalAccountingStatementView.as_view(),
name="journal_accounting_statement",
),
# Operations
re_path(
r"^operation/create/(?P<j_id>[0-9]+)$",
path(
"operation/create/<int:j_id>/",
OperationCreateView.as_view(),
name="op_new",
),
re_path(
r"^operation/(?P<op_id>[0-9]+)$", OperationEditView.as_view(), name="op_edit"
),
re_path(
r"^operation/(?P<op_id>[0-9]+)/pdf$", OperationPDFView.as_view(), name="op_pdf"
),
path("operation/<int:op_id>/", OperationEditView.as_view(), name="op_edit"),
path("operation/<int:op_id>/pdf/", OperationPDFView.as_view(), name="op_pdf"),
# Companies
re_path(r"^company/list$", CompanyListView.as_view(), name="co_list"),
re_path(r"^company/create$", CompanyCreateView.as_view(), name="co_new"),
re_path(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
re_path(r"^label/new$", LabelCreateView.as_view(), name="label_new"),
re_path(
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",
),
re_path(
r"^label/(?P<label_id>[0-9]+)/edit$", LabelEditView.as_view(), name="label_edit"
),
re_path(
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
re_path(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.urls import reverse_lazy, reverse
from django.utils.translation import ugettext_lazy as _
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,28 +1,20 @@
# -*- 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.urls import re_path, include
from django.urls import re_path, path, include
from api.views import *
from rest_framework import routers
@ -54,4 +46,5 @@ urlpatterns = [
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,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"
#
#
@ -78,3 +70,4 @@ from .club import *
from .group import *
from .launderette import *
from .uv import *
from .sas import *

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"
#
#
@ -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

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"
#
#
@ -32,7 +24,6 @@ from api.views import RightModelViewSet
class LaunderettePlaceSerializer(serializers.ModelSerializer):
machine_list = serializers.ListField(
child=serializers.IntegerField(), read_only=True
)

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"
#
#

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):
@ -157,23 +158,27 @@ class MailingForm(forms.Form):
return cleaned_data
class SellingsFormBase(forms.Form):
begin_date = forms.DateTimeField(
input_formats=["%Y-%m-%d %H:%M:%S"],
label=_("Begin date"),
required=False,
widget=SelectDateTime,
)
end_date = forms.DateTimeField(
input_formats=["%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"),

View File

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

View File

@ -7,7 +7,6 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("club", "0001_initial"),

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

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

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

@ -9,7 +9,6 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("club", "0008_auto_20170515_2214"),

View File

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

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

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

View File

@ -22,11 +22,15 @@
# 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.urls import reverse
@ -72,6 +76,7 @@ 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
@ -122,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"))
@ -171,14 +186,11 @@ 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:
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:
@ -194,6 +206,14 @@ class Club(models.Model):
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
@ -208,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)
@ -228,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
@ -289,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
@ -303,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):
@ -373,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)
@ -388,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):
@ -463,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,6 +13,7 @@
{% endif %}
<table>
<thead>
<tr>
<td>{% trans %}User{% endtrans %}</td>
<td>{% trans %}Role{% endtrans %}</td>
<td>{% trans %}Description{% endtrans %}</td>
@ -20,6 +21,7 @@
{% 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">
<h3>{% trans %}Sales{% endtrans %}</h3>
<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

@ -30,7 +30,7 @@
{% endif %}
</ul>
{% if object.club_account.exists() %}
<h4>{% trans %}Accouting: {% endtrans %}</h4>
<h4>{% trans %}Accounting: {% endtrans %}</h4>
<ul>
{% for ca in object.club_account.all() %}
<li><a href="{{ url('accounting:club_details', c_account_id=ca.id) }}">{{ ca.get_display_name() }}</a></li>

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.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,
@ -704,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,89 +23,84 @@
#
#
from django.urls import re_path
from django.urls import path
from club.views import *
urlpatterns = [
re_path(r"^$", ClubListView.as_view(), name="club_list"),
re_path(r"^new$", ClubCreateView.as_view(), name="club_new"),
re_path(r"^stats$", ClubStatView.as_view(), name="club_stats"),
re_path(r"^(?P<club_id>[0-9]+)/$", ClubView.as_view(), name="club_view"),
re_path(
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",
),
re_path(
r"^(?P<club_id>[0-9]+)/hist$", ClubPageHistView.as_view(), name="club_hist"
),
re_path(r"^(?P<club_id>[0-9]+)/edit$", ClubEditView.as_view(), name="club_edit"),
re_path(
r"^(?P<club_id>[0-9]+)/edit/page$",
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",
),
re_path(
r"^(?P<club_id>[0-9]+)/members$", ClubMembersView.as_view(), name="club_members"
),
re_path(
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",
),
re_path(
r"^(?P<club_id>[0-9]+)/sellings$",
path(
"<int:club_id>/sellings/",
ClubSellingView.as_view(),
name="club_sellings",
),
re_path(
r"^(?P<club_id>[0-9]+)/sellings/csv$",
path(
"<int:club_id>/sellings/csv/",
ClubSellingCSVView.as_view(),
name="sellings_csv",
),
re_path(
r"^(?P<club_id>[0-9]+)/prop$", ClubEditPropView.as_view(), name="club_prop"
),
re_path(r"^(?P<club_id>[0-9]+)/tools$", ClubToolsView.as_view(), name="tools"),
re_path(
r"^(?P<club_id>[0-9]+)/mailing$", ClubMailingView.as_view(), name="mailing"
),
re_path(
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",
),
re_path(
r"^(?P<mailing_id>[0-9]+)/mailing/delete$",
path(
"<int:mailing_id>/mailing/delete/",
MailingDeleteView.as_view(),
name="mailing_delete",
),
re_path(
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",
),
re_path(
r"^membership/(?P<membership_id>[0-9]+)/set_old$",
path(
"membership/<int:membership_id>/set_old/",
MembershipSetOldView.as_view(),
name="membership_set_old",
),
re_path(
r"^(?P<club_id>[0-9]+)/poster$", PosterListView.as_view(), name="poster_list"
path(
"membership/<int:membership_id>/delete/",
MembershipDeleteView.as_view(),
name="membership_delete",
),
re_path(
r"^(?P<club_id>[0-9]+)/poster/create$",
path("<int:club_id>/poster/", PosterListView.as_view(), name="poster_list"),
path(
"<int:club_id>/poster/create/",
PosterCreateView.as_view(),
name="poster_create",
),
re_path(
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",
),
re_path(
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.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):
@ -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
@ -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"
@ -634,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"
@ -652,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

@ -7,7 +7,6 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("club", "0005_auto_20161120_1149"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),

View File

@ -7,7 +7,6 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("club", "0006_auto_20161229_0040"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),

View File

@ -8,7 +8,6 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("club", "0010_auto_20170912_2028"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),

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

@ -6,7 +6,6 @@ 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.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
@ -46,9 +47,12 @@ class Sith(models.Model):
alert_msg = models.TextField(_("alert message"), default="", blank=True)
info_msg = models.TextField(_("info message"), 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 ⛩"
@ -90,13 +94,15 @@ class News(models.Model):
)
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})
@ -227,19 +233,23 @@ class Weekmail(models.Model):
"""
Return an absolute link to the banner.
"""
return "http://" + settings.SITH_URL + static("com/img/weekmail_bannerA19.jpg")
return (
"http://" + settings.SITH_URL + static("com/img/weekmail_bannerV2P22.png")
)
def get_footer(self):
"""
Return an absolute link to the footer.
"""
return "http://" + settings.SITH_URL + static("com/img/weekmail_footerA19.jpg")
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):
@ -267,7 +277,9 @@ class WeekmailArticle(models.Model):
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)
@ -283,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)
@ -336,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,11 +35,11 @@
<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) %}
<p> <a href="{{ url('com:news_edit', news_id=news.id) }}">{% trans %}Edit (will be remoderated){% endtrans %}</a></p>
<p> <a href="{{ url('com:news_edit', news_id=news.id) }}">{% trans %}Edit (will be moderated again){% endtrans %}</a></p>
{% endif %}
</div>
</div>

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,67 +6,15 @@
{% 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>
{% 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">
{% 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 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>
@ -74,8 +22,7 @@
</section>
{% endfor %}
{% for news in object_list.filter(dates__start_date__lte=timezone.now(),
dates__end_date__gte=timezone.now(), type="CALL") %}
{% 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">
@ -88,8 +35,7 @@
</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') %}
{% 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 %}
@ -151,7 +97,68 @@
</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>
<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">
{% 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>
{% 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="">
{% 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>
</form>
{% endif %}
{% endif %}
<hr>
{{ weekmail_rendered|safe }}

View File

@ -1,69 +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.urls import reverse
from django.core.management import call_command
from django.utils import html
from django.utils.translation import ugettext as _
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 setUp(self):
call_command("populate")
def test_page_is_working(self):
self.client.login(username="comunity", password="plop")
response = self.client.get(reverse("com:alert_edit"))
self.assertNotEquals(response.status_code, 500)
self.assertEquals(response.status_code, 200)
self.assertNotEqual(response.status_code, 500)
self.assertEqual(response.status_code, 200)
class ComInfoTest(TestCase):
def setUp(self):
call_command("populate")
def test_page_is_working(self):
self.client.login(username="comunity", password="plop")
response = self.client.get(reverse("com:info_edit"))
self.assertNotEquals(response.status_code, 500)
self.assertEquals(response.status_code, 200)
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.set([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):
@ -79,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):
@ -95,9 +87,10 @@ 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):
@ -119,3 +112,129 @@ class ComTest(TestCase):
_("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))
def test_news_viewer(self):
"""
Test that moderated news can be viewed by anyone
and not moderated news only by com admins
"""
# by default a news isn't moderated
self.assertTrue(self.new.can_be_viewed_by(self.com_admin))
self.assertFalse(self.new.can_be_viewed_by(self.sli))
self.assertFalse(self.new.can_be_viewed_by(self.anonymous))
self.assertFalse(self.new.can_be_viewed_by(self.author))
self.new.is_moderated = True
self.new.save()
self.assertTrue(self.new.can_be_viewed_by(self.com_admin))
self.assertTrue(self.new.can_be_viewed_by(self.sli))
self.assertTrue(self.new.can_be_viewed_by(self.anonymous))
self.assertTrue(self.new.can_be_viewed_by(self.author))
def test_news_editor(self):
"""
Test that only com admins can edit news
"""
self.assertTrue(self.new.can_be_edited_by(self.com_admin))
self.assertFalse(self.new.can_be_edited_by(self.sli))
self.assertFalse(self.new.can_be_edited_by(self.anonymous))
self.assertFalse(self.new.can_be_edited_by(self.author))
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,125 +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.urls import re_path
from django.urls import path
from com.views import *
from club.views import MailingDeleteView
from com.views import *
urlpatterns = [
re_path(r"^sith/edit/alert$", AlertMsgEditView.as_view(), name="alert_edit"),
re_path(r"^sith/edit/info$", InfoMsgEditView.as_view(), name="info_edit"),
re_path(
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",
),
re_path(r"^weekmail$", WeekmailEditView.as_view(), name="weekmail"),
re_path(
r"^weekmail/preview$", WeekmailPreviewView.as_view(), name="weekmail_preview"
),
re_path(
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",
),
re_path(
r"^weekmail/article/(?P<article_id>[0-9]+)/delete$",
path(
"weekmail/article/<int:article_id>/delete/",
WeekmailArticleDeleteView.as_view(),
name="weekmail_article_delete",
),
re_path(
r"^weekmail/article/(?P<article_id>[0-9]+)/edit$",
path(
"weekmail/article/<int:article_id>/edit/",
WeekmailArticleEditView.as_view(),
name="weekmail_article_edit",
),
re_path(r"^news$", NewsListView.as_view(), name="news_list"),
re_path(r"^news/admin$", NewsAdminListView.as_view(), name="news_admin_list"),
re_path(r"^news/create$", NewsCreateView.as_view(), name="news_new"),
re_path(
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",
),
re_path(
r"^news/(?P<news_id>[0-9]+)/moderate$",
path(
"news/<int:news_id>/moderate/",
NewsModerateView.as_view(),
name="news_moderate",
),
re_path(
r"^news/(?P<news_id>[0-9]+)/edit$", NewsEditView.as_view(), name="news_edit"
),
re_path(
r"^news/(?P<news_id>[0-9]+)$", NewsDetailView.as_view(), name="news_detail"
),
re_path(r"^mailings$", MailingListAdminView.as_view(), name="mailing_admin"),
re_path(
r"^mailings/(?P<mailing_id>[0-9]+)/moderate$",
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",
),
re_path(
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",
),
re_path(r"^poster$", PosterListView.as_view(), name="poster_list"),
re_path(r"^poster/create$", PosterCreateView.as_view(), name="poster_create"),
re_path(
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",
),
re_path(
r"^poster/(?P<poster_id>[0-9]+)/delete$",
path(
"poster/<int:poster_id>/delete/",
PosterDeleteView.as_view(),
name="poster_delete",
),
re_path(
r"^poster/moderate$",
path(
"poster/moderate/",
PosterModerateListView.as_view(),
name="poster_moderate_list",
),
re_path(
r"^poster/(?P<object_id>[0-9]+)/moderate$",
path(
"poster/<int:object_id>/moderate/",
PosterModerateView.as_view(),
name="poster_moderate",
),
re_path(r"^screen$", ScreenListView.as_view(), name="screen_list"),
re_path(r"^screen/create$", ScreenCreateView.as_view(), name="screen_create"),
re_path(
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",
),
re_path(
r"^screen/(?P<screen_id>[0-9]+)/edit$",
path(
"screen/<int:screen_id>/edit/",
ScreenEditView.as_view(),
name="screen_edit",
),
re_path(
r"^screen/(?P<screen_id>[0-9]+)/delete$",
path(
"screen/<int:screen_id>/delete/",
ScreenDeleteView.as_view(),
name="screen_delete",
),

View File

@ -28,7 +28,7 @@ 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.utils.translation import gettext_lazy as _
from django.urls import reverse, reverse_lazy
from django.core.exceptions import ValidationError
from django.utils import timezone
@ -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(
input_formats=["%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(
input_formats=["%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)
@ -150,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)
@ -199,24 +195,10 @@ class NewsForm(forms.ModelForm):
"content": MarkdownInput,
}
start_date = forms.DateTimeField(
input_formats=["%Y-%m-%d %H:%M:%S"],
label=_("Start date"),
widget=SelectDateTime,
required=False,
)
end_date = forms.DateTimeField(
input_formats=["%Y-%m-%d %H:%M:%S"],
label=_("End date"),
widget=SelectDateTime,
required=False,
)
until = forms.DateTimeField(
input_formats=["%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):
@ -301,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()
@ -351,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()
@ -433,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":
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()
@ -458,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
@ -534,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
@ -621,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)

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

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