diff --git a/.github/actions/setup_project/action.yml b/.github/actions/setup_project/action.yml index 951aba32..599fa11e 100644 --- a/.github/actions/setup_project/action.yml +++ b/.github/actions/setup_project/action.yml @@ -6,7 +6,7 @@ runs: - name: Install apt packages uses: awalsh128/cache-apt-pkgs-action@latest with: - packages: gettext pipx + packages: gettext version: 1.0 # increment to reset cache - name: Set up python @@ -19,12 +19,12 @@ runs: uses: actions/cache@v3 with: path: ~/.local - key: poetry-1 # increment to reset cache + key: poetry-3 # increment to reset cache - name: Install Poetry if: steps.cached-poetry.outputs.cache-hit != 'true' shell: bash - run: pipx install poetry + run: curl -sSL https://install.python-poetry.org | python3 - - name: Check pyproject.toml syntax shell: bash diff --git a/accounting/static/webpack/accounting/components/ajax-select-index.ts b/accounting/static/bundled/accounting/components/ajax-select-index.ts similarity index 100% rename from accounting/static/webpack/accounting/components/ajax-select-index.ts rename to accounting/static/bundled/accounting/components/ajax-select-index.ts diff --git a/accounting/widgets/select.py b/accounting/widgets/select.py index 27714440..6b3145b7 100644 --- a/accounting/widgets/select.py +++ b/accounting/widgets/select.py @@ -4,7 +4,7 @@ from accounting.models import ClubAccount, Company from accounting.schemas import ClubAccountSchema, CompanySchema from core.views.widgets.select import AutoCompleteSelect, AutoCompleteSelectMultiple -_js = ["webpack/accounting/components/ajax-select-index.ts"] +_js = ["bundled/accounting/components/ajax-select-index.ts"] class AutoCompleteSelectClubAccount(AutoCompleteSelect): diff --git a/babel.config.json b/babel.config.json deleted file mode 100644 index 623ee01c..00000000 --- a/babel.config.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "presets": [ - [ - "@babel/preset-env", - { - "targets": { - "edge": "17", - "firefox": "60", - "chrome": "67", - "safari": "11.1" - } - } - ] - ] -} diff --git a/club/static/webpack/club/components/ajax-select-index.ts b/club/static/bundled/club/components/ajax-select-index.ts similarity index 100% rename from club/static/webpack/club/components/ajax-select-index.ts rename to club/static/bundled/club/components/ajax-select-index.ts diff --git a/club/widgets/select.py b/club/widgets/select.py index c7063328..d46bb344 100644 --- a/club/widgets/select.py +++ b/club/widgets/select.py @@ -4,7 +4,7 @@ from club.models import Club from club.schemas import ClubSchema from core.views.widgets.select import AutoCompleteSelect, AutoCompleteSelectMultiple -_js = ["webpack/club/components/ajax-select-index.ts"] +_js = ["bundled/club/components/ajax-select-index.ts"] class AutoCompleteSelectClub(AutoCompleteSelect): diff --git a/com/templates/com/screen_slideshow.jinja b/com/templates/com/screen_slideshow.jinja index 509c0640..448f8dfc 100644 --- a/com/templates/com/screen_slideshow.jinja +++ b/com/templates/com/screen_slideshow.jinja @@ -3,7 +3,7 @@ {% trans %}Slideshow{% endtrans %} - + diff --git a/core/fixtures/images/sas/Family/richard.jpg b/core/fixtures/images/sas/Family/rbatsbak.jpg similarity index 100% rename from core/fixtures/images/sas/Family/richard.jpg rename to core/fixtures/images/sas/Family/rbatsbak.jpg diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py index 18a524b2..9e261bba 100644 --- a/core/management/commands/populate.py +++ b/core/management/commands/populate.py @@ -20,11 +20,10 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - -import os -from datetime import date, datetime, timedelta -from io import BytesIO, StringIO +from datetime import date, timedelta +from io import StringIO from pathlib import Path +from typing import ClassVar from django.conf import settings from django.contrib.auth.models import Permission @@ -47,1256 +46,856 @@ from accounting.models import ( ) from club.models import Club, Membership from com.models import News, NewsDate, Sith, Weekmail -from core.models import Group, Page, PageRev, SithFile, User +from core.models import Group, Page, PageRev, RealGroup, SithFile, User from core.utils import resize_image -from counter.models import Counter, Customer, Product, ProductType, Selling, StudentCard +from counter.models import Counter, Product, ProductType, StudentCard from election.models import Candidature, Election, ElectionList, Role -from forum.models import Forum, ForumTopic +from forum.models import Forum from pedagogy.models import UV from sas.models import Album, PeoplePictureRelation, Picture from subscription.models import Subscription class Command(BaseCommand): + ROOT_PATH: ClassVar[Path] = Path(__file__).parent.parent.parent.parent + SAS_FIXTURE_PATH: ClassVar[Path] = ( + ROOT_PATH / "core" / "fixtures" / "images" / "sas" + ) + help = "Populate a new instance of the Sith AE" - def add_arguments(self, parser): - parser.add_argument("--prod", action="store_true") - def reset_index(self, *args): + if connection.vendor == "sqlite": + # sqlite doesn't support this operation + return sqlcmd = StringIO() call_command("sqlsequencereset", *args, stdout=sqlcmd) cursor = connection.cursor() cursor.execute(sqlcmd.getvalue()) def handle(self, *args, **options): - os.environ["DJANGO_COLORS"] = "nocolor" - Site(id=4000, domain=settings.SITH_URL, name=settings.SITH_NAME).save() - root_path = Path(__file__).parent.parent.parent.parent - root_group, _ = Group.objects.get_or_create(name="Root") - Group(name="Public").save() - Group(name="Subscribers").save() - Group(name="Old subscribers").save() - Group(name="Accounting admin").save() - Group(name="Communication admin").save() - Group(name="Counter admin").save() - Group(name="Banned from buying alcohol").save() - Group(name="Banned from counters").save() - Group(name="Banned to subscribe").save() - sas_admin, _ = Group.objects.get_or_create(name="SAS admin") - Group(name="Forum admin").save() - Group(name="Pedagogy admin").save() + if not settings.DEBUG and not settings.TESTING: + raise Exception("Never call this command in prod. Never.") + + Sith.objects.create(weekmail_destinations="etudiants@git.an personnel@git.an") + Site.objects.create(domain=settings.SITH_URL, name=settings.SITH_NAME) + + root_group = Group.objects.create(name="Root") + public_group = Group.objects.create(name="Public") + subscribers = Group.objects.create(name="Subscribers") + old_subscribers = Group.objects.create(name="Old subscribers") + Group.objects.create(name="Accounting admin") + Group.objects.create(name="Communication admin") + Group.objects.create(name="Counter admin") + Group.objects.create(name="Banned from buying alcohol") + Group.objects.create(name="Banned from counters") + Group.objects.create(name="Banned to subscribe") + Group.objects.create(name="SAS admin") + Group.objects.create(name="Forum admin") + Group.objects.create(name="Pedagogy admin") self.reset_index("core", "auth") change_billing = Permission.objects.get(codename="change_billinginfo") add_billing = Permission.objects.get(codename="add_billinginfo") root_group.permissions.add(change_billing, add_billing) - root = User( + root = User.objects.create_superuser( id=0, username="root", last_name="", first_name="Bibou", email="ae.info@utbm.fr", date_of_birth="1942-06-12", - is_superuser=True, - is_staff=True, + password="plop", ) - root.set_password("plop") - root.save() - profiles_root = SithFile( - parent=None, name="profiles", is_folder=True, owner=root - ) - profiles_root.save() - home_root = SithFile(parent=None, name="users", is_folder=True, owner=root) - home_root.save() + self.profiles_root = SithFile.objects.create(name="profiles", owner=root) + home_root = SithFile.objects.create(name="users", owner=root) # Page needed for club creation p = Page(name=settings.SITH_CLUB_ROOT_PAGE) - p.set_lock(root) - p.save() + p.save(force_lock=True) - club_root = SithFile(parent=None, name="clubs", is_folder=True, owner=root) - club_root.save() - sas = SithFile(parent=None, name="SAS", is_folder=True, owner=root) - sas.save() - main_club = Club( + club_root = SithFile.objects.create(name="clubs", owner=root) + sas = SithFile.objects.create(name="SAS", owner=root) + main_club = Club.objects.create( id=1, name=settings.SITH_MAIN_CLUB["name"], unix_name=settings.SITH_MAIN_CLUB["unix_name"], address=settings.SITH_MAIN_CLUB["address"], ) - main_club.save() - bar_club = Club( + bar_club = Club.objects.create( id=2, name=settings.SITH_BAR_MANAGER["name"], unix_name=settings.SITH_BAR_MANAGER["unix_name"], address=settings.SITH_BAR_MANAGER["address"], ) - bar_club.save() - launderette_club = Club( + Club.objects.create( id=84, name=settings.SITH_LAUNDERETTE_MANAGER["name"], unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"], address=settings.SITH_LAUNDERETTE_MANAGER["address"], ) - launderette_club.save() self.reset_index("club") - for b in settings.SITH_COUNTER_BARS: - g = Group(name=b[1] + " admin") - g.save() - c = Counter(id=b[0], name=b[1], club=bar_club, type="BAR") - c.save() - g.editable_counters.add(c) - g.save() + counters = [ + *[ + Counter(id=bar_id, name=bar_name, club=bar_club, type="BAR") + for bar_id, bar_name in settings.SITH_COUNTER_BARS + ], + Counter(name="Eboutic", club=main_club, type="EBOUTIC"), + Counter(name="AE", club=main_club, type="OFFICE"), + Counter(name="Vidage comptes AE", club=main_club, type="OFFICE"), + ] + Counter.objects.bulk_create(counters) + bar_groups = [] + for bar_id, bar_name in settings.SITH_COUNTER_BARS: + group = RealGroup.objects.create(name=f"{bar_name} admin") + bar_groups.append( + Counter.edit_groups.through(counter_id=bar_id, group=group) + ) + Counter.edit_groups.through.objects.bulk_create(bar_groups) self.reset_index("counter") - Counter(name="Eboutic", club=main_club, type="EBOUTIC").save() - Counter(name="AE", club=main_club, type="OFFICE").save() - ae_members = Group.objects.get(name=settings.SITH_MAIN_MEMBERS_GROUP) + subscribers.viewable_files.add(home_root, club_root) - home_root.view_groups.set([ae_members]) - club_root.view_groups.set([ae_members]) - home_root.save() - club_root.save() - - Sith(weekmail_destinations="etudiants@git.an personnel@git.an").save() Weekmail().save() - p = Page(name="Index") - p.set_lock(root) - p.save() - p.view_groups.set([settings.SITH_GROUP_PUBLIC_ID]) - p.set_lock(root) - p.save() - PageRev( - page=p, - title="Wiki index", - author=root, - content=""" -Welcome to the wiki page! -""", - ).save() - - p = Page(name="services") - p.set_lock(root) - p.save() - p.view_groups.set([settings.SITH_GROUP_PUBLIC_ID]) - p.set_lock(root) - PageRev( - page=p, - title="Services", - author=root, - content=""" -| | | | -| :---: | :---: | :---: | :---: | -| [Eboutic](/eboutic) | [Laverie](/launderette) | Matmat | [Fichiers](/file) | -| SAS | Weekmail | Forum | | - -""", - ).save() - - p = Page(name="launderette") - p.set_lock(root) - p.save() - p.set_lock(root) - PageRev( - page=p, title="Laverie", author=root, content="Fonctionnement de la laverie" - ).save() - # Here we add a lot of test datas, that are not necessary for the Sith, but that provide a basic development environment - if not options["prod"]: - self.now = timezone.now().replace(hour=12) + self.now = timezone.now().replace(hour=12) - # Adding user Skia - skia = User( - username="skia", - last_name="Kia", - first_name="S'", - email="skia@git.an", - date_of_birth="1942-06-12", - ) - skia.set_password("plop") - skia.save() - skia.view_groups = [ae_members.id] - skia.save() - skia_profile_path = ( - root_path - / "core" - / "fixtures" - / "images" - / "sas" - / "Family" - / "skia.jpg" - ) - with open(skia_profile_path, "rb") as f: - name = str(skia.id) + "_profile.jpg" - skia_profile = SithFile( - parent=profiles_root, - name=name, - file=resize_image(Image.open(BytesIO(f.read())), 400, "WEBP"), - owner=skia, - is_folder=False, - mime_type="image/webp", - size=skia_profile_path.stat().st_size, - ) - skia_profile.file.name = name - skia_profile.save() - skia.profile_pict = skia_profile - skia.save() + skia = User.objects.create_user( + username="skia", + last_name="Kia", + first_name="S'", + email="skia@git.an", + date_of_birth="1942-06-12", + password="plop", + ) + public = User.objects.create_user( + username="public", + last_name="Not subscribed", + first_name="Public", + email="public@git.an", + date_of_birth="1942-06-12", + password="plop", + ) + subscriber = User.objects.create_user( + username="subscriber", + last_name="User", + first_name="Subscribed", + email="Subscribed@git.an", + date_of_birth="1942-06-12", + password="plop", + ) + old_subscriber = User.objects.create_user( + username="old_subscriber", + last_name="Subscriber", + first_name="Old", + email="old_subscriber@git.an", + date_of_birth="1942-06-12", + password="plop", + ) + counter = User.objects.create_user( + username="counter", + last_name="Ter", + first_name="Coun", + email="counter@git.an", + date_of_birth="1942-06-12", + password="plop", + ) + comptable = User.objects.create_user( + username="comptable", + last_name="Able", + first_name="Compte", + email="compta@git.an", + date_of_birth="1942-06-12", + password="plop", + ) + User.objects.create_user( + username="guy", + last_name="Carlier", + first_name="Guy", + email="guy@git.an", + date_of_birth="1942-06-12", + password="plop", + ) + richard = User.objects.create_user( + username="rbatsbak", + last_name="Batsbak", + first_name="Richard", + email="richard@git.an", + date_of_birth="1982-06-12", + password="plop", + ) + sli = User.objects.create_user( + username="sli", + last_name="Li", + first_name="S", + email="sli@git.an", + date_of_birth="1942-06-12", + password="plop", + ) + krophil = User.objects.create_user( + username="krophil", + last_name="Phil'", + first_name="Kro", + email="krophil@git.an", + date_of_birth="1942-06-12", + password="plop", + ) + comunity = User.objects.create_user( + username="comunity", + last_name="Unity", + first_name="Com", + email="comunity@git.an", + date_of_birth="1942-06-12", + password="plop", + ) + tutu = User.objects.create_user( + username="tutu", + last_name="Tu", + first_name="Tu", + email="tutu@git.an", + date_of_birth="1942-06-12", + password="plop", + ) + User.groups.through.objects.bulk_create( + [ + User.groups.through( + realgroup_id=settings.SITH_GROUP_COUNTER_ADMIN_ID, user=counter + ), + User.groups.through( + realgroup_id=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID, user=comptable + ), + User.groups.through( + realgroup_id=settings.SITH_GROUP_COM_ADMIN_ID, user=comunity + ), + User.groups.through( + realgroup_id=settings.SITH_GROUP_PEDAGOGY_ADMIN_ID, user=tutu + ), + User.groups.through( + realgroup_id=settings.SITH_GROUP_SAS_ADMIN_ID, user=skia + ), + ] + ) + for user in richard, sli, krophil, skia: + self._create_profile_pict(user) - # Adding user public - public = User( - username="public", - last_name="Not subscribed", - first_name="Public", - email="public@git.an", - date_of_birth="1942-06-12", - is_superuser=False, - is_staff=False, - ) - public.set_password("plop") - public.save() - public.view_groups = [ae_members.id] - public.save() - # Adding user Subscriber - subscriber = User( - username="subscriber", - last_name="User", - first_name="Subscribed", - email="Subscribed@git.an", - date_of_birth="1942-06-12", - is_superuser=False, - is_staff=False, - ) - subscriber.set_password("plop") - subscriber.save() - subscriber.view_groups = [ae_members.id] - subscriber.save() - # Adding user old Subscriber - old_subscriber = User( - username="old_subscriber", - last_name="Subscriber", - first_name="Old", - email="old_subscriber@git.an", - date_of_birth="1942-06-12", - is_superuser=False, - is_staff=False, - ) - old_subscriber.set_password("plop") - old_subscriber.save() - old_subscriber.view_groups = [ae_members.id] - old_subscriber.save() - # Adding user Counter admin - counter = User( - username="counter", - last_name="Ter", - first_name="Coun", - email="counter@git.an", - date_of_birth="1942-06-12", - is_superuser=False, - is_staff=False, - ) - counter.set_password("plop") - counter.save() - counter.view_groups = [ae_members.id] - counter.groups.set( - [ - Group.objects.filter(id=settings.SITH_GROUP_COUNTER_ADMIN_ID) - .first() - .id - ] - ) - counter.save() - # Adding user Comptable - comptable = User( - username="comptable", - last_name="Able", - first_name="Compte", - email="compta@git.an", - date_of_birth="1942-06-12", - is_superuser=False, - is_staff=False, - ) - comptable.set_password("plop") - comptable.save() - comptable.view_groups = [ae_members.id] - comptable.groups.set( - [ - Group.objects.filter(id=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) - .first() - .id - ] - ) - comptable.save() - # Adding user Guy - u = User( - username="guy", - last_name="Carlier", - first_name="Guy", - email="guy@git.an", - date_of_birth="1942-06-12", - is_superuser=False, - is_staff=False, - ) - u.set_password("plop") - u.save() - u.view_groups = [ae_members.id] - u.save() - # Adding user Richard Batsbak - richard = User( - username="rbatsbak", - last_name="Batsbak", - first_name="Richard", - email="richard@git.an", - date_of_birth="1982-06-12", - ) - richard.set_password("plop") - richard.save() - richard.godfathers.add(comptable) - richard_profile_path = ( - root_path - / "core" - / "fixtures" - / "images" - / "sas" - / "Family" - / "richard.jpg" - ) - with open(richard_profile_path, "rb") as f: - name = f"{richard.id}_profile.jpg" - richard_profile = SithFile( - parent=profiles_root, - name=name, - file=resize_image(Image.open(BytesIO(f.read())), 400, "WEBP"), - owner=richard, - is_folder=False, - mime_type="image/webp", - size=richard_profile_path.stat().st_size, - ) - richard_profile.file.name = name - richard_profile.save() - richard.profile_pict = richard_profile - richard.save() - richard.view_groups = [ae_members.id] - richard.save() - # Adding syntax help page - p = Page(name="Aide_sur_la_syntaxe") - p.save(force_lock=True) - with open(root_path / "core" / "fixtures" / "SYNTAX.md", "r") as rm: - PageRev( - page=p, title="Aide sur la syntaxe", author=skia, content=rm.read() - ).save() - p.view_groups.set([settings.SITH_GROUP_PUBLIC_ID]) - p.save(force_lock=True) - p = Page(name="Services") - p.save(force_lock=True) - p.view_groups.set([settings.SITH_GROUP_PUBLIC_ID]) - p.save(force_lock=True) - PageRev( - page=p, - title="Services", - author=skia, - content=""" + User.godfathers.through.objects.bulk_create( + [ + User.godfathers.through(from_user=richard, to_user=comptable), + User.godfathers.through(from_user=root, to_user=skia), + User.godfathers.through(from_user=skia, to_user=root), + User.godfathers.through(from_user=sli, to_user=skia), + User.godfathers.through(from_user=public, to_user=richard), + User.godfathers.through(from_user=subscriber, to_user=richard), + ] + ) + + # Adding syntax help page + syntax_page = Page(name="Aide_sur_la_syntaxe") + syntax_page.save(force_lock=True) + PageRev.objects.create( + page=syntax_page, + title="Aide sur la syntaxe", + author=skia, + content=(self.ROOT_PATH / "core" / "fixtures" / "SYNTAX.md").read_text(), + ) + services_page = Page(name="Services") + services_page.save(force_lock=True) + PageRev.objects.create( + page=services_page, + title="Services", + author=skia, + content=""" | | | | | :---: | :---: | :---: | | [Eboutic](/eboutic) | [Laverie](/launderette) | Matmat | | SAS | Weekmail | Forum| """, - ).save() + ) - # Subscription - default_subscription = "un-semestre" - # Root - s = Subscription( - member=root, - subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], - ) - s.subscription_start = s.compute_start() - s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], - start=s.subscription_start, - ) - s.save() - # Skia - s = Subscription( - member=User.objects.filter(pk=skia.pk).first(), - subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], - ) - s.subscription_start = s.compute_start() - s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], - start=s.subscription_start, - ) - s.save() - # Counter admin - s = Subscription( - member=User.objects.filter(pk=counter.pk).first(), - subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], - ) - s.subscription_start = s.compute_start() - s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], - start=s.subscription_start, - ) - s.save() - # Comptable - s = Subscription( - member=User.objects.filter(pk=comptable.pk).first(), - subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], - ) - s.subscription_start = s.compute_start() - s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], - start=s.subscription_start, - ) - s.save() - # Richard - s = Subscription( - member=User.objects.filter(pk=richard.pk).first(), - subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], - ) - s.subscription_start = s.compute_start() - s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], - start=s.subscription_start, - ) - s.save() - # User - s = Subscription( - member=User.objects.filter(pk=subscriber.pk).first(), - subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], - ) - s.subscription_start = s.compute_start() - s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], - start=s.subscription_start, - ) - s.save() - # Old subscriber - s = Subscription( - member=User.objects.filter(pk=old_subscriber.pk).first(), - subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], - ) - s.subscription_start = s.compute_start(datetime(year=2012, month=9, day=4)) - s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], - start=s.subscription_start, - ) - s.save() + index_page = Page(name="Index") + index_page.save(force_lock=True) + PageRev.objects.create( + page=index_page, + title="Wiki index", + author=root, + content=""" +Welcome to the wiki page! +""", + ) - # Clubs - Club( - name="Bibo'UT", - unix_name="bibout", - address="46 de la Boustifaille", - parent=main_club, - ).save() - guyut = Club( - name="Guy'UT", - unix_name="guyut", - address="42 de la Boustifaille", - parent=main_club, - ) - guyut.save() - Club( - name="Woenzel'UT", unix_name="woenzel", address="Woenzel", parent=guyut - ).save() - Membership(user=skia, club=main_club, role=3).save() - troll = Club( - name="Troll Penché", - unix_name="troll", - address="Terre Du Milieu", - parent=main_club, - ) - troll.save() - refound = Club( - name="Carte AE", - unix_name="carte_ae", - address="Jamais imprimée", - parent=main_club, - ) - refound.save() + laundry_page = Page(name="launderette") + laundry_page.save(force_lock=True) + PageRev.objects.create( + page=laundry_page, + title="Laverie", + author=root, + content="Fonctionnement de la laverie", + ) - # Counters - subscribers = Group.objects.get(name="Subscribers") - old_subscribers = Group.objects.get(name="Old subscribers") - Customer(user=skia, account_id="6568j", amount=0).save() - Customer(user=richard, account_id="4000k", amount=0).save() - p = ProductType(name="Bières bouteilles") - p.save() - c = ProductType(name="Cotisations") - c.save() - r = ProductType(name="Rechargements") - r.save() - verre = ProductType(name="Verre") - verre.save() - cotis = Product( - name="Cotis 1 semestre", - code="1SCOTIZ", - product_type=c, - purchase_price="15", - selling_price="15", - special_selling_price="15", - club=main_club, - ) - cotis.save() - cotis.buying_groups.add(subscribers) - cotis.buying_groups.add(old_subscribers) - cotis.save() - cotis2 = Product( - name="Cotis 2 semestres", - code="2SCOTIZ", - product_type=c, - purchase_price="28", - selling_price="28", - special_selling_price="28", - club=main_club, - ) - cotis2.save() - cotis2.buying_groups.add(subscribers) - cotis2.buying_groups.add(old_subscribers) - cotis2.save() - refill = Product( - name="Rechargement 15 €", - code="15REFILL", - product_type=r, - purchase_price="15", - selling_price="15", - special_selling_price="15", - club=main_club, - ) - refill.save() - refill.buying_groups.add(subscribers) - refill.save() - barb = Product( - name="Barbar", - code="BARB", - product_type=p, - purchase_price="1.50", - selling_price="1.7", - special_selling_price="1.6", - club=main_club, - limit_age=18, - ) - barb.save() - barb.buying_groups.add(subscribers) - barb.save() - cble = Product( - name="Chimay Bleue", - code="CBLE", - product_type=p, - purchase_price="1.50", - selling_price="1.7", - special_selling_price="1.6", - club=main_club, - limit_age=18, - ) - cble.save() - cble.buying_groups.add(subscribers) - cble.save() - cons = Product( - name="Consigne Eco-cup", - code="CONS", - product_type=verre, - purchase_price="1", - selling_price="1", - special_selling_price="1", - club=main_club, - ) - cons.save() - dcons = Product( - name="Déconsigne Eco-cup", - code="DECO", - product_type=verre, - purchase_price="-1", - selling_price="-1", - special_selling_price="-1", - club=main_club, - ) - dcons.save() - cors = Product( - name="Corsendonk", - code="CORS", - product_type=p, - purchase_price="1.50", - selling_price="1.7", - special_selling_price="1.6", - club=main_club, - limit_age=18, - ) - cors.save() - cors.buying_groups.add(subscribers) - cors.save() - carolus = Product( - name="Carolus", - code="CARO", - product_type=p, - purchase_price="1.50", - selling_price="1.7", - special_selling_price="1.6", - club=main_club, - limit_age=18, - ) - carolus.save() - carolus.buying_groups.add(subscribers) - carolus.save() - mde = Counter.objects.filter(name="MDE").first() - mde.products.add(barb) - mde.products.add(cble) - mde.products.add(cons) - mde.products.add(dcons) - mde.sellers.add(skia) + public_group.viewable_page.set( + [syntax_page, services_page, index_page, laundry_page] + ) - mde.save() + self._create_subscription(root) + self._create_subscription(skia) + self._create_subscription(counter) + self._create_subscription(comptable) + self._create_subscription(richard) + self._create_subscription(subscriber) + self._create_subscription(old_subscriber, start=date(year=2012, month=9, day=4)) + self._create_subscription(sli) + self._create_subscription(krophil) + self._create_subscription(comunity) + self._create_subscription(tutu) + StudentCard(uid="9A89B82018B0A0", customer=sli.customer).save() - eboutic = Counter.objects.filter(name="Eboutic").first() - eboutic.products.add(barb) - eboutic.products.add(cotis) - eboutic.products.add(cotis2) - eboutic.products.add(refill) - eboutic.save() + # Clubs + Club.objects.create( + name="Bibo'UT", + unix_name="bibout", + address="46 de la Boustifaille", + parent=main_club, + ) + guyut = Club.objects.create( + name="Guy'UT", + unix_name="guyut", + address="42 de la Boustifaille", + parent=main_club, + ) + Club.objects.create( + name="Woenzel'UT", unix_name="woenzel", address="Woenzel", parent=guyut + ) + troll = Club.objects.create( + name="Troll Penché", + unix_name="troll", + address="Terre Du Milieu", + parent=main_club, + ) + refound = Club.objects.create( + name="Carte AE", + unix_name="carte_ae", + address="Jamais imprimée", + parent=main_club, + ) - refound_counter = Counter(name="Carte AE", club=refound, type="OFFICE") - refound_counter.save() - refound_product = Product( - name="remboursement", - code="REMBOURS", - purchase_price="0", - selling_price="0", - special_selling_price="0", - club=refound, - ) - refound_product.save() - - # Accounting test values: - BankAccount(name="AE TG", club=main_club).save() - BankAccount(name="Carte AE", club=main_club).save() - ba = BankAccount(name="AE TI", club=main_club) - ba.save() - ca = ClubAccount(name="Troll Penché", bank_account=ba, club=troll) - ca.save() - gj = GeneralJournal(name="A16", start_date=date.today(), club_account=ca) - gj.save() - credit = AccountingType( - code="74", label="Subventions d'exploitation", movement_type="CREDIT" - ) - credit.save() - debit = AccountingType( - code="606", - label="Achats non stockés de matières et fournitures(*1)", - movement_type="DEBIT", - ) - debit.save() - debit2 = AccountingType( - code="604", - label="Achats d'études et prestations de services(*2)", - movement_type="DEBIT", - ) - debit2.save() - buying = AccountingType( - code="60", label="Achats (sauf 603)", movement_type="DEBIT" - ) - buying.save() - comptes = AccountingType( - code="6", label="Comptes de charge", movement_type="DEBIT" - ) - comptes.save() - simple = SimplifiedAccountingType( - label="Je fais du simple 6", accounting_type=comptes - ) - simple.save() - woenzco = Company(name="Woenzel & co") - woenzco.save() - - operation_list = [ - ( - 27, - "J'avais trop de bière", - "CASH", - None, - buying, - "USER", - skia.id, - "", - None, + Membership.objects.bulk_create( + [ + Membership(user=skia, club=main_club, role=3), + Membership( + user=comunity, + club=bar_club, + start_date=localdate(), + role=settings.SITH_CLUB_ROLES_ID["Board member"], ), - ( - 4000, - "Ceci n'est pas une opération... en fait si mais non", - "CHECK", - None, - debit, - "COMPANY", - woenzco.id, - "", - 23, + Membership( + user=sli, + club=troll, + role=9, + description="Padawan Troll", + start_date=localdate() - timedelta(days=17), ), - ( - 22, - "C'est de l'argent ?", - "CARD", - None, - credit, - "CLUB", - troll.id, - "", - None, + Membership( + user=krophil, + club=troll, + role=10, + description="Maitre Troll", + start_date=localdate() - timedelta(days=200), ), - ( - 37, - "Je paye CASH", - "CASH", - None, - debit2, - "OTHER", - None, - "tous les étudiants <3", - None, + Membership( + user=skia, + club=troll, + role=2, + description="Grand Ancien Troll", + start_date=localdate() - timedelta(days=400), + end_date=localdate() - timedelta(days=86), ), - (300, "Paiement Guy", "CASH", None, buying, "USER", skia.id, "", None), - (32.3, "Essence", "CASH", None, buying, "OTHER", None, "station", None), - ( - 46.42, - "Allumette", - "CHECK", - None, - credit, - "CLUB", - main_club.id, - "", - 57, - ), - ( - 666.42, - "Subvention de far far away", - "CASH", - None, - comptes, - "CLUB", - main_club.id, - "", - None, - ), - ( - 496, - "Ça, c'est un 6", - "CARD", - simple, - None, - "USER", - skia.id, - "", - None, - ), - ( - 17, - "La Gargotte du Korrigan", - "CASH", - None, - debit2, - "CLUB", - bar_club.id, - "", - None, + Membership( + user=richard, + club=troll, + role=2, + description="", + start_date=localdate() - timedelta(days=200), + end_date=localdate() - timedelta(days=100), ), ] - for op in operation_list: - operation = Operation( - journal=gj, - date=date.today(), - amount=op[0], - remark=op[1], - mode=op[2], - done=True, - simpleaccounting_type=op[3], - accounting_type=op[4], - target_type=op[5], - target_id=op[6], - target_label=op[7], - cheque_number=op[8], - ) - operation.clean() - operation.save() + ) - # Adding user sli - sli = User( - username="sli", - last_name="Li", - first_name="S", - email="sli@git.an", - date_of_birth="1942-06-12", - ) - sli.set_password("plop") - sli.save() - sli.view_groups = [ae_members.id] - sli.save() - sli_profile_path = ( - root_path - / "core" - / "fixtures" - / "images" - / "sas" - / "Family" - / "sli.jpg" - ) - with open(sli_profile_path, "rb") as f: - name = str(sli.id) + "_profile.jpg" - sli_profile = SithFile( - parent=profiles_root, - name=name, - file=resize_image(Image.open(BytesIO(f.read())), 400, "WEBP"), - owner=sli, - is_folder=False, - mime_type="image/webp", - size=sli_profile_path.stat().st_size, - ) - sli_profile.file.name = name - sli_profile.save() - sli.profile_pict = sli_profile - sli.save() - # Adding user Krophil - krophil = User( - username="krophil", - last_name="Phil'", - first_name="Kro", - email="krophil@git.an", - date_of_birth="1942-06-12", - ) - krophil.set_password("plop") - krophil.save() - krophil_profile_path = ( - root_path - / "core" - / "fixtures" - / "images" - / "sas" - / "Family" - / "krophil.jpg" - ) - with open(krophil_profile_path, "rb") as f: - name = str(krophil.id) + "_profile.jpg" - krophil_profile = SithFile( - parent=profiles_root, - name=name, - file=resize_image(Image.open(BytesIO(f.read())), 400, "WEBP"), - owner=krophil, - is_folder=False, - mime_type="image/webp", - size=krophil_profile_path.stat().st_size, - ) - krophil_profile.file.name = name - krophil_profile.save() - krophil.profile_pict = krophil_profile - krophil.save() - # Adding user Com Unity - comunity = User( - username="comunity", - last_name="Unity", - first_name="Com", - email="comunity@git.an", - date_of_birth="1942-06-12", - ) - comunity.set_password("plop") - comunity.save() - comunity.groups.set( - [Group.objects.filter(name="Communication admin").first().id] - ) - comunity.save() - Membership( - user=comunity, - club=bar_club, - start_date=localdate(), - role=settings.SITH_CLUB_ROLES_ID["Board member"], - ).save() - # Adding user tutu - tutu = User( - username="tutu", - last_name="Tu", - first_name="Tu", - email="tutu@git.an", - date_of_birth="1942-06-12", - ) - tutu.set_password("plop") - tutu.save() - tutu.groups.set([settings.SITH_GROUP_PEDAGOGY_ADMIN_ID]) - tutu.save() + p = ProductType.objects.create(name="Bières bouteilles") + c = ProductType.objects.create(name="Cotisations") + r = ProductType.objects.create(name="Rechargements") + verre = ProductType.objects.create(name="Verre") + cotis = Product.objects.create( + name="Cotis 1 semestre", + code="1SCOTIZ", + product_type=c, + purchase_price="15", + selling_price="15", + special_selling_price="15", + club=main_club, + ) + cotis2 = Product.objects.create( + name="Cotis 2 semestres", + code="2SCOTIZ", + product_type=c, + purchase_price="28", + selling_price="28", + special_selling_price="28", + club=main_club, + ) + refill = Product.objects.create( + name="Rechargement 15 €", + code="15REFILL", + product_type=r, + purchase_price="15", + selling_price="15", + special_selling_price="15", + club=main_club, + ) + barb = Product.objects.create( + name="Barbar", + code="BARB", + product_type=p, + purchase_price="1.50", + selling_price="1.7", + special_selling_price="1.6", + club=main_club, + limit_age=18, + ) + cble = Product.objects.create( + name="Chimay Bleue", + code="CBLE", + product_type=p, + purchase_price="1.50", + selling_price="1.7", + special_selling_price="1.6", + club=main_club, + limit_age=18, + ) + cons = Product.objects.create( + name="Consigne Eco-cup", + code="CONS", + product_type=verre, + purchase_price="1", + selling_price="1", + special_selling_price="1", + club=main_club, + ) + dcons = Product.objects.create( + name="Déconsigne Eco-cup", + code="DECO", + product_type=verre, + purchase_price="-1", + selling_price="-1", + special_selling_price="-1", + club=main_club, + ) + cors = Product.objects.create( + name="Corsendonk", + code="CORS", + product_type=p, + purchase_price="1.50", + selling_price="1.7", + special_selling_price="1.6", + club=main_club, + limit_age=18, + ) + carolus = Product.objects.create( + name="Carolus", + code="CARO", + product_type=p, + purchase_price="1.50", + selling_price="1.7", + special_selling_price="1.6", + club=main_club, + limit_age=18, + ) + subscribers.products.add(cotis, cotis2, refill, barb, cble, cors, carolus) + old_subscribers.products.add(cotis, cotis2) - # Adding subscription for sli - s = Subscription( - member=User.objects.filter(pk=sli.pk).first(), - subscription_type=next(iter(settings.SITH_SUBSCRIPTIONS.keys())), - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], - ) - s.subscription_start = s.compute_start() - s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], - start=s.subscription_start, - ) - s.save() - StudentCard(uid="9A89B82018B0A0", customer=sli.customer).save() - # Adding subscription for Krophil - s = Subscription( - member=User.objects.filter(pk=krophil.pk).first(), - subscription_type=next(iter(settings.SITH_SUBSCRIPTIONS.keys())), - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], - ) - s.subscription_start = s.compute_start() - s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], - start=s.subscription_start, - ) - s.save() - # Com Unity - s = Subscription( - member=comunity, - subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], - ) - s.subscription_start = s.compute_start() - s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], - start=s.subscription_start, - ) - s.save() - # Tutu - s = Subscription( - member=tutu, - subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], - ) - s.subscription_start = s.compute_start() - s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], - start=s.subscription_start, - ) - s.save() + mde = Counter.objects.get(name="MDE") + mde.products.add(barb, cble, cons, dcons) - Selling( - label=dcons.name, - product=dcons, - counter=mde, - unit_price=dcons.selling_price, - club=main_club, - quantity=settings.SITH_ECOCUP_LIMIT + 3, - seller=skia, - customer=krophil.customer, - ).save() + eboutic = Counter.objects.get(name="Eboutic") + eboutic.products.add(barb, cotis, cotis2, refill) - # Add barman to counter - c = Counter.objects.get(id=2) - c.sellers.add(User.objects.get(pk=krophil.pk)) - mde.sellers.add(sli) - c.save() + Counter.objects.create(name="Carte AE", club=refound, type="OFFICE") + Product.objects.create( + name="remboursement", + code="REMBOURS", + purchase_price="0", + selling_price="0", + special_selling_price="0", + club=refound, + ) - # Create an election - public_group = Group.objects.get(id=settings.SITH_GROUP_PUBLIC_ID) - subscriber_group = Group.objects.get(name=settings.SITH_MAIN_MEMBERS_GROUP) - ae_board_group = Group.objects.get(name=settings.SITH_MAIN_BOARD_GROUP) - el = Election( - title="Élection 2017", - description="La roue tourne", - start_candidature="1942-06-12 10:28:45+01", - end_candidature="2042-06-12 10:28:45+01", - start_date="1942-06-12 10:28:45+01", - end_date="7942-06-12 10:28:45+01", - ) - el.save() - el.view_groups.add(public_group) - el.edit_groups.add(ae_board_group) - el.candidature_groups.add(subscriber_group) - el.vote_groups.add(subscriber_group) - el.save() - liste = ElectionList(title="Candidature Libre", election=el) - liste.save() - listeT = ElectionList(title="Troll", election=el) - listeT.save() - pres = Role(election=el, title="Président AE", description="Roi de l'AE") - pres.save() - resp = Role( - election=el, title="Co Respo Info", max_choice=2, description="Ghetto++" - ) - resp.save() - cand = Candidature( - role=resp, user=skia, election_list=liste, program="Refesons le site AE" - ) - cand.save() - cand = Candidature( - role=resp, - user=sli, - election_list=liste, - program="Vasy je deviens mon propre adjoint", - ) - cand.save() - cand = Candidature( - role=resp, user=krophil, election_list=listeT, program="Le Pôle Troll !" - ) - cand.save() - cand = Candidature( - role=pres, - user=sli, - election_list=listeT, - program="En fait j'aime pas l'info, je voulais faire GMC", - ) - cand.save() + # Accounting test values: + BankAccount.objects.create(name="AE TG", club=main_club) + BankAccount.objects.create(name="Carte AE", club=main_club) + ba = BankAccount.objects.create(name="AE TI", club=main_club) + ca = ClubAccount.objects.create( + name="Troll Penché", bank_account=ba, club=troll + ) + gj = GeneralJournal.objects.create( + name="A16", start_date=date.today(), club_account=ca + ) + credit = AccountingType.objects.create( + code="74", label="Subventions d'exploitation", movement_type="CREDIT" + ) + debit = AccountingType.objects.create( + code="606", + label="Achats non stockés de matières et fournitures(*1)", + movement_type="DEBIT", + ) + debit2 = AccountingType.objects.create( + code="604", + label="Achats d'études et prestations de services(*2)", + movement_type="DEBIT", + ) + buying = AccountingType.objects.create( + code="60", label="Achats (sauf 603)", movement_type="DEBIT" + ) + comptes = AccountingType.objects.create( + code="6", label="Comptes de charge", movement_type="DEBIT" + ) + SimplifiedAccountingType.objects.create( + label="Je fais du simple 6", accounting_type=comptes + ) + woenzco = Company.objects.create(name="Woenzel & co") - # Forum - room = Forum( - name="Salon de discussions", - description="Pour causer de tout", - is_category=True, + operation_list = [ + (27, "J'avais trop de bière", "CASH", buying, "USER", skia.id, None), + (4000, "Pas une opération", "CHECK", debit, "COMPANY", woenzco.id, 23), + (22, "C'est de l'argent ?", "CARD", credit, "CLUB", troll.id, None), + (37, "Je paye CASH", "CASH", debit2, "OTHER", None, None), + (300, "Paiement Guy", "CASH", buying, "USER", skia.id, None), + (32.3, "Essence", "CASH", buying, "OTHER", None, None), + (46.42, "Allumette", "CHECK", credit, "CLUB", main_club.id, 57), + (666.42, "Subvention club", "CASH", comptes, "CLUB", main_club.id, None), + (496, "Ça, c'est un 6", "CARD", comptes, "USER", skia.id, None), + (17, "La Gargotte du Korrigan", "CASH", debit2, "CLUB", bar_club.id, None), + ] + operations = [ + Operation( + number=index, + journal=gj, + date=localdate(), + amount=op[0], + remark=op[1], + mode=op[2], + done=True, + accounting_type=op[3], + target_type=op[4], + target_id=op[5], + target_label="" if op[4] != "OTHER" else "Autre source", + cheque_number=op[6], ) - room.save() - Forum(name="AE", description="Réservé au bureau AE", parent=room).save() - Forum(name="BdF", description="Réservé au bureau BdF", parent=room).save() - hall = Forum( - name="Hall de discussions", - description="Pour toutes les discussions", - parent=room, - ) - hall.save() - various = Forum( - name="Divers", description="Pour causer de rien", is_category=True - ) - various.save() - Forum( - name="Promos", description="Réservé aux Promos", parent=various - ).save() - ForumTopic(forum=hall) + for index, op in enumerate(operation_list, start=1) + ] + for operation in operations: + operation.clean() + Operation.objects.bulk_create(operations) - # News - friday = self.now - while friday.weekday() != 4: - friday += timedelta(hours=6) - friday.replace(hour=20, minute=0, second=0) - # Event - n = News( - title="Apero barman", - summary="Viens boire un coup avec les barmans", - content="Glou glou glou glou glou glou glou", - type="EVENT", - club=bar_club, - author=subscriber, - is_moderated=True, - moderator=skia, - ) - n.save() + # Add barman to counter + Counter.sellers.through.objects.bulk_create( + [ + Counter.sellers.through(counter_id=2, user=krophil), + Counter.sellers.through(counter=mde, user=skia), + ] + ) + + # Create an election + ae_board_group = Group.objects.get(name=settings.SITH_MAIN_BOARD_GROUP) + el = Election.objects.create( + title="Élection 2017", + description="La roue tourne", + start_candidature="1942-06-12 10:28:45+01", + end_candidature="2042-06-12 10:28:45+01", + start_date="1942-06-12 10:28:45+01", + end_date="7942-06-12 10:28:45+01", + ) + el.view_groups.add(public_group) + el.edit_groups.add(ae_board_group) + el.candidature_groups.add(subscribers) + el.vote_groups.add(subscribers) + liste = ElectionList.objects.create(title="Candidature Libre", election=el) + listeT = ElectionList.objects.create(title="Troll", election=el) + pres = Role.objects.create( + election=el, title="Président AE", description="Roi de l'AE" + ) + resp = Role.objects.create( + election=el, title="Co Respo Info", max_choice=2, description="Ghetto++" + ) + Candidature.objects.bulk_create( + [ + Candidature( + role=resp, + user=skia, + election_list=liste, + program="Refesons le site AE", + ), + Candidature( + role=resp, + user=sli, + election_list=liste, + program="Vasy je deviens mon propre adjoint", + ), + Candidature( + role=resp, + user=krophil, + election_list=listeT, + program="Le Pôle Troll !", + ), + Candidature( + role=pres, + user=sli, + election_list=listeT, + program="En fait j'aime pas l'info, je voulais faire GMC", + ), + ] + ) + + # Forum + room = Forum.objects.create( + name="Salon de discussions", + description="Pour causer de tout", + is_category=True, + ) + various = Forum.objects.create( + name="Divers", description="Pour causer de rien", is_category=True + ) + Forum.objects.bulk_create( + [ + Forum(name="AE", description="Réservé au bureau AE", parent=room), + Forum(name="BdF", description="Réservé au bureau BdF", parent=room), + Forum(name="Promos", description="Réservé aux Promos", parent=various), + Forum( + name="Hall de discussions", + description="Pour toutes les discussions", + parent=room, + ), + ] + ) + + # News + friday = self.now + while friday.weekday() != 4: + friday += timedelta(hours=6) + friday.replace(hour=20, minute=0, second=0) + # Event + news_dates = [] + n = News.objects.create( + title="Apero barman", + summary="Viens boire un coup avec les barmans", + content="Glou glou glou glou glou glou glou", + type="EVENT", + club=bar_club, + author=subscriber, + is_moderated=True, + moderator=skia, + ) + news_dates.append( NewsDate( news=n, start_date=self.now + timedelta(hours=70), end_date=self.now + timedelta(hours=72), - ).save() - n = News( - title="Repas barman", - summary="Enjoy la fin du semestre!", - content=( - "Viens donc t'enjailler avec les autres barmans aux " - "frais du BdF! \\o/" - ), - type="EVENT", - club=bar_club, - author=subscriber, - is_moderated=True, - moderator=skia, ) - n.save() + ) + n = News.objects.create( + title="Repas barman", + summary="Enjoy la fin du semestre!", + content=( + "Viens donc t'enjailler avec les autres barmans aux " + "frais du BdF! \\o/" + ), + type="EVENT", + club=bar_club, + author=subscriber, + is_moderated=True, + moderator=skia, + ) + news_dates.append( NewsDate( news=n, start_date=self.now + timedelta(hours=72), end_date=self.now + timedelta(hours=84), - ).save() - n = News( - title="Repas fromager", - summary="Wien manger du l'bon fromeug'", - content="Fô viendre mangey d'la bonne fondue!", - type="EVENT", - club=bar_club, - author=subscriber, - is_moderated=True, - moderator=skia, ) - n.save() + ) + News.objects.create( + title="Repas fromager", + summary="Wien manger du l'bon fromeug'", + content="Fô viendre mangey d'la bonne fondue!", + type="EVENT", + club=bar_club, + author=subscriber, + is_moderated=True, + moderator=skia, + ) + news_dates.append( NewsDate( news=n, start_date=self.now + timedelta(hours=96), end_date=self.now + timedelta(hours=100), - ).save() - n = News( - title="SdF", - summary="Enjoy la fin des finaux!", - content="Viens faire la fête avec tout plein de gens!", - type="EVENT", - club=bar_club, - author=subscriber, - is_moderated=True, - moderator=skia, ) - n.save() + ) + n = News.objects.create( + title="SdF", + summary="Enjoy la fin des finaux!", + content="Viens faire la fête avec tout plein de gens!", + type="EVENT", + club=bar_club, + author=subscriber, + is_moderated=True, + moderator=skia, + ) + news_dates.append( NewsDate( news=n, start_date=friday + timedelta(hours=24 * 7 + 1), end_date=self.now + timedelta(hours=24 * 7 + 9), - ).save() - # Weekly - n = News( - title="Jeux sans faim", - summary="Viens jouer!", - content="Rejoins la fine équipe du Troll Penché et viens " - "d'amuser le Vendredi soir!", - type="WEEKLY", - club=troll, - author=subscriber, - is_moderated=True, - moderator=skia, ) - n.save() - for i in range(10): + ) + # Weekly + n = News.objects.create( + title="Jeux sans faim", + summary="Viens jouer!", + content="Rejoins la fine équipe du Troll Penché et viens " + "t'amuser le Vendredi soir!", + type="WEEKLY", + club=troll, + author=subscriber, + is_moderated=True, + moderator=skia, + ) + news_dates.extend( + [ NewsDate( news=n, - start_date=friday + timedelta(hours=24 * 7 * i), - end_date=friday + timedelta(hours=24 * 7 * i + 8), - ).save() + start_date=friday + timedelta(days=7 * i), + end_date=friday + timedelta(days=7 * i, hours=8), + ) + for i in range(10) + ] + ) + NewsDate.objects.bulk_create(news_dates) - # Create som data for pedagogy + # Create som data for pedagogy - UV( - code="PA00", - author=User.objects.get(id=0), - credit_type=settings.SITH_PEDAGOGY_UV_TYPE[3][0], - manager="Laurent HEYBERGER", - semester=settings.SITH_PEDAGOGY_UV_SEMESTER[3][0], - language=settings.SITH_PEDAGOGY_UV_LANGUAGE[0][0], - department=settings.SITH_PROFILE_DEPARTMENTS[-2][0], - credits=5, - title="Participation dans une association étudiante", - objectives="* Permettre aux étudiants de réaliser, pendant un semestre, un projet culturel ou associatif et de le valoriser.", - program="""* Semestre précédent proposition d'un projet et d'un cahier des charges + UV( + code="PA00", + author=User.objects.get(id=0), + credit_type=settings.SITH_PEDAGOGY_UV_TYPE[3][0], + manager="Laurent HEYBERGER", + semester=settings.SITH_PEDAGOGY_UV_SEMESTER[3][0], + language=settings.SITH_PEDAGOGY_UV_LANGUAGE[0][0], + department=settings.SITH_PROFILE_DEPARTMENTS[-2][0], + credits=5, + title="Participation dans une association étudiante", + objectives="* Permettre aux étudiants de réaliser, pendant un semestre, un projet culturel ou associatif et de le valoriser.", + program="""* Semestre précédent proposition d'un projet et d'un cahier des charges * Evaluation par un jury de six membres * Si accord réalisation dans le cadre de l'UV * Compte-rendu de l'expérience * Présentation""", - skills="""* Gérer un projet associatif ou une action éducative en autonomie: + skills="""* Gérer un projet associatif ou une action éducative en autonomie: * en produisant un cahier des charges qui -définit clairement le contexte du projet personnel -pose les jalons de ce projet -estime de manière réaliste les moyens et objectifs du projet -définit exactement les livrables attendus * en étant capable de respecter ce cahier des charges ou, le cas échéant, de réviser le cahier des charges de manière argumentée. * Relater son expérience dans un rapport: * qui permettra à d'autres étudiants de poursuivre les actions engagées * qui montre la capacité à s'auto-évaluer et à adopter une distance critique sur son action.""", - key_concepts="""* Autonomie + key_concepts="""* Autonomie * Responsabilité * Cahier des charges * Gestion de projet""", - hours_THE=121, - hours_TE=4, - ).save() + hours_THE=121, + hours_TE=4, + ).save() - # SAS - skia.groups.add(sas_admin.id) - sas_fixtures_path = root_path / "core" / "fixtures" / "images" / "sas" - for f in sas_fixtures_path.glob("*"): - if f.is_dir(): - album = Album( - parent=sas, - name=f.name, + # SAS + for f in self.SAS_FIXTURE_PATH.glob("*"): + if f.is_dir(): + album = Album( + parent=sas, + name=f.name, + owner=root, + is_folder=True, + is_in_sas=True, + is_moderated=True, + ) + album.clean() + album.save() + for p in f.iterdir(): + file = resize_image(Image.open(p), 1000, "WEBP") + pict = Picture( + parent=album, + name=p.name, + file=file, owner=root, - is_folder=True, + is_folder=False, is_in_sas=True, is_moderated=True, + mime_type="image/webp", + size=file.size, ) - album.clean() - album.save() - for p in f.iterdir(): - pict = Picture( - parent=album, - name=p.name, - file=resize_image( - Image.open(BytesIO(p.read_bytes())), 1000, "WEBP" - ), - owner=root, - is_folder=False, - is_in_sas=True, - is_moderated=True, - mime_type="image/webp", - size=p.stat().st_size, - ) - pict.file.name = p.name - pict.clean() - pict.generate_thumbnails() - pict.save() + pict.file.name = p.name + pict.clean() + pict.generate_thumbnails() - p = Picture.objects.get(name="skia.jpg") - PeoplePictureRelation(user=skia, picture=p).save() - p = Picture.objects.get(name="sli.jpg") - PeoplePictureRelation(user=sli, picture=p).save() - p = Picture.objects.get(name="krophil.jpg") - PeoplePictureRelation(user=krophil, picture=p).save() - p = Picture.objects.get(name="skia_sli.jpg") - PeoplePictureRelation(user=skia, picture=p).save() - PeoplePictureRelation(user=sli, picture=p).save() - p = Picture.objects.get(name="skia_sli_krophil.jpg") - PeoplePictureRelation(user=skia, picture=p).save() - PeoplePictureRelation(user=sli, picture=p).save() - PeoplePictureRelation(user=krophil, picture=p).save() - p = Picture.objects.get(name="richard.jpg") - PeoplePictureRelation(user=richard, picture=p).save() + img_skia = Picture.objects.get(name="skia.jpg") + img_sli = Picture.objects.get(name="sli.jpg") + img_krophil = Picture.objects.get(name="krophil.jpg") + img_skia_sli = Picture.objects.get(name="skia_sli.jpg") + img_skia_sli_krophil = Picture.objects.get(name="skia_sli_krophil.jpg") + img_richard = Picture.objects.get(name="rbatsbak.jpg") + PeoplePictureRelation.objects.bulk_create( + [ + PeoplePictureRelation(user=skia, picture=img_skia), + PeoplePictureRelation(user=sli, picture=img_sli), + PeoplePictureRelation(user=krophil, picture=img_krophil), + PeoplePictureRelation(user=skia, picture=img_skia_sli), + PeoplePictureRelation(user=sli, picture=img_skia_sli), + PeoplePictureRelation(user=skia, picture=img_skia_sli_krophil), + PeoplePictureRelation(user=sli, picture=img_skia_sli_krophil), + PeoplePictureRelation(user=krophil, picture=img_skia_sli_krophil), + PeoplePictureRelation(user=richard, picture=img_richard), + ] + ) - with open(skia_profile_path, "rb") as f: - name = str(skia.id) + "_profile.jpg" - skia_profile = SithFile( - parent=profiles_root, - name=name, - file=resize_image(Image.open(BytesIO(f.read())), 400, "WEBP"), - owner=skia, - is_folder=False, - mime_type="image/webp", - size=skia_profile_path.stat().st_size, - ) - skia_profile.file.name = name - skia_profile.save() - skia.profile_pict = skia_profile - skia.save() + def _create_profile_pict(self, user: User): + path = self.SAS_FIXTURE_PATH / "Family" / f"{user.username}.jpg" + file = resize_image(Image.open(path), 400, "WEBP") + name = f"{user.id}_profile.webp" + profile = SithFile( + parent=self.profiles_root, + name=name, + file=file, + owner=user, + is_folder=False, + mime_type="image/webp", + size=file.size, + ) + profile.file.name = name + profile.save() + user.profile_pict = profile + user.save() - # Create some additional data for galaxy to work with - root.godfathers.add(skia) - skia.godfathers.add(root) - sli.godfathers.add(skia) - richard.godchildren.add(subscriber) - richard.godchildren.add(public) - Membership( - user=sli, - club=troll, - role=9, - description="Padawan Troll", - start_date=localdate() - timedelta(days=17), - ).save() - Membership( - user=krophil, - club=troll, - role=10, - description="Maitre Troll", - start_date=localdate() - timedelta(days=200), - ).save() - Membership( - user=skia, - club=troll, - role=2, - description="Grand Ancien Troll", - start_date=localdate() - timedelta(days=400), - end_date=localdate() - timedelta(days=86), - ).save() - Membership( - user=richard, - club=troll, - role=2, - description="", - start_date=localdate() - timedelta(days=200), - end_date=localdate() - timedelta(days=100), - ).save() + def _create_subscription( + self, + user: User, + subscription_type: str = "un-semestre", + start: date | None = None, + ): + s = Subscription( + member=user, + subscription_type=subscription_type, + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], + ) + s.subscription_start = s.compute_start(start) + s.subscription_end = s.compute_end( + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) + s.save() diff --git a/core/management/commands/populate_more.py b/core/management/commands/populate_more.py index 50a0052f..eaac58c0 100644 --- a/core/management/commands/populate_more.py +++ b/core/management/commands/populate_more.py @@ -1,14 +1,12 @@ import random from datetime import date, timedelta from datetime import timezone as tz -from decimal import Decimal from typing import Iterator from dateutil.relativedelta import relativedelta from django.conf import settings from django.core.management.base import BaseCommand -from django.db.models import Count, Exists, F, Min, OuterRef, Subquery, Sum -from django.db.models.functions import Coalesce +from django.db.models import Count, Exists, Min, OuterRef, Subquery from django.utils.timezone import localdate, make_aware, now from faker import Faker @@ -268,24 +266,6 @@ class Command(BaseCommand): Product.buying_groups.through.objects.bulk_create(buying_groups) Counter.products.through.objects.bulk_create(selling_places) - @staticmethod - def _update_balances(): - customers = Customer.objects.annotate( - money_in=Sum(F("refillings__amount"), default=0), - money_out=Coalesce( - Subquery( - Selling.objects.filter(customer=OuterRef("pk")) - .values("customer_id") # group by customer - .annotate(res=Sum(F("unit_price") * F("quantity"), default=0)) - .values("res") - ), - Decimal("0"), - ), - ).annotate(real_balance=F("money_in") - F("money_out")) - for c in customers: - c.amount = c.real_balance - Customer.objects.bulk_update(customers, fields=["amount"]) - def create_sales(self, sellers: list[User]): customers = list( Customer.objects.annotate( @@ -355,7 +335,7 @@ class Command(BaseCommand): sales.extend(this_customer_sales) Refilling.objects.bulk_create(reloads) Selling.objects.bulk_create(sales) - self._update_balances() + Customer.objects.update_amount() def create_permanences(self, sellers: list[User]): counters = list( diff --git a/core/models.py b/core/models.py index adbe56c4..7a9d3a46 100644 --- a/core/models.py +++ b/core/models.py @@ -26,6 +26,7 @@ from __future__ import annotations import importlib import logging import os +import string import unicodedata from datetime import timedelta from pathlib import Path @@ -528,13 +529,15 @@ class User(AbstractBaseUser): return False @cached_property - def can_create_subscription(self): - from club.models import Club + def can_create_subscription(self) -> bool: + from club.models import Membership - for club in Club.objects.filter(id__in=settings.SITH_CAN_CREATE_SUBSCRIPTIONS): - if club in self.clubs_with_rights: - return True - return False + return ( + Membership.objects.board() + .ongoing() + .filter(club_id__in=settings.SITH_CAN_CREATE_SUBSCRIPTIONS) + .exists() + ) @cached_property def is_launderette_manager(self): @@ -688,12 +691,20 @@ class User(AbstractBaseUser): .encode("ascii", "ignore") .decode("utf-8") ) - un_set = [u.username for u in User.objects.all()] - if user_name in un_set: - i = 1 - while user_name + str(i) in un_set: - i += 1 - user_name += str(i) + # load all usernames which could conflict with the new one. + # we need to actually load them, instead of performing a count, + # because we cannot be sure that two usernames refer to the + # actual same word (eg. tmore and tmoreau) + possible_conflicts: list[str] = list( + User.objects.filter(username__startswith=user_name).values_list( + "username", flat=True + ) + ) + nb_conflicts = sum( + 1 for name in possible_conflicts if name.rstrip(string.digits) == user_name + ) + if nb_conflicts > 0: + user_name += str(nb_conflicts) # exemple => exemple1 self.username = user_name return user_name diff --git a/core/static/webpack/alpine-index.js b/core/static/bundled/alpine-index.js similarity index 100% rename from core/static/webpack/alpine-index.js rename to core/static/bundled/alpine-index.js diff --git a/core/static/webpack/core/components/ajax-select-base.ts b/core/static/bundled/core/components/ajax-select-base.ts similarity index 100% rename from core/static/webpack/core/components/ajax-select-base.ts rename to core/static/bundled/core/components/ajax-select-base.ts diff --git a/core/static/webpack/core/components/ajax-select-index.ts b/core/static/bundled/core/components/ajax-select-index.ts similarity index 100% rename from core/static/webpack/core/components/ajax-select-index.ts rename to core/static/bundled/core/components/ajax-select-index.ts diff --git a/core/static/webpack/core/components/easymde-index.ts b/core/static/bundled/core/components/easymde-index.ts similarity index 100% rename from core/static/webpack/core/components/easymde-index.ts rename to core/static/bundled/core/components/easymde-index.ts diff --git a/core/static/webpack/core/components/include-index.ts b/core/static/bundled/core/components/include-index.ts similarity index 100% rename from core/static/webpack/core/components/include-index.ts rename to core/static/bundled/core/components/include-index.ts diff --git a/core/static/bundled/core/components/nfc-input-index.ts b/core/static/bundled/core/components/nfc-input-index.ts new file mode 100644 index 00000000..0b57931b --- /dev/null +++ b/core/static/bundled/core/components/nfc-input-index.ts @@ -0,0 +1,42 @@ +import { inheritHtmlElement, registerComponent } from "#core:utils/web-components"; + +@registerComponent("nfc-input") +export class NfcInput extends inheritHtmlElement("input") { + connectedCallback() { + super.connectedCallback(); + + /* Disable feature if browser is not supported or if not HTTPS */ + // biome-ignore lint/correctness/noUndeclaredVariables: browser API + if (typeof NDEFReader === "undefined") { + return; + } + + const button = document.createElement("button"); + const logo = document.createElement("i"); + logo.classList.add("fa-brands", "fa-nfc-symbol"); + button.setAttribute("type", "button"); // Prevent form submission on click + button.appendChild(logo); + button.addEventListener("click", async () => { + // biome-ignore lint/correctness/noUndeclaredVariables: browser API + const ndef = new NDEFReader(); + this.setAttribute("scan", "active"); + await ndef.scan(); + ndef.addEventListener("readingerror", () => { + this.removeAttribute("scan"); + window.alert(gettext("Unsupported NFC card")); + }); + + // biome-ignore lint/correctness/noUndeclaredVariables: browser API + ndef.addEventListener("reading", (event: NDEFReadingEvent) => { + this.removeAttribute("scan"); + this.node.value = event.serialNumber.replace(/:/g, "").toUpperCase(); + /* Auto submit form, we need another button to not trigger our previously defined click event */ + const submit = document.createElement("button"); + this.node.appendChild(submit); + submit.click(); + submit.remove(); + }); + }); + this.appendChild(button); + } +} diff --git a/core/static/bundled/fontawesome-index.js b/core/static/bundled/fontawesome-index.js new file mode 100644 index 00000000..20b8b800 --- /dev/null +++ b/core/static/bundled/fontawesome-index.js @@ -0,0 +1 @@ +import "@fortawesome/fontawesome-free/css/all.css"; diff --git a/core/static/bundled/htmx-index.js b/core/static/bundled/htmx-index.js new file mode 100644 index 00000000..56edea4a --- /dev/null +++ b/core/static/bundled/htmx-index.js @@ -0,0 +1,3 @@ +import htmx from "htmx.org"; + +Object.assign(window, { htmx }); diff --git a/core/static/bundled/jquery-ui-index.js b/core/static/bundled/jquery-ui-index.js new file mode 100644 index 00000000..92da2271 --- /dev/null +++ b/core/static/bundled/jquery-ui-index.js @@ -0,0 +1,2 @@ +// This is only used to import jquery-ui css files +import "jquery-ui/themes/base/all.css"; diff --git a/core/static/webpack/sentry-popup-index.ts b/core/static/bundled/sentry-popup-index.ts similarity index 100% rename from core/static/webpack/sentry-popup-index.ts rename to core/static/bundled/sentry-popup-index.ts diff --git a/core/static/bundled/types/web-nfc.d.ts b/core/static/bundled/types/web-nfc.d.ts new file mode 100644 index 00000000..c647eb55 --- /dev/null +++ b/core/static/bundled/types/web-nfc.d.ts @@ -0,0 +1,106 @@ +// Type definitions for Web NFC +// Project: https://github.com/w3c/web-nfc +// Definitions by: Takefumi Yoshii +// TypeScript Version: 3.9 + +// This type definitions referenced to WebIDL. +// https://w3c.github.io/web-nfc/#actual-idl-index + +// This has been modified to not trigger biome linting + +// biome-ignore lint/correctness/noUnusedVariables: this is the official definition +interface Window { + // biome-ignore lint/style/useNamingConvention: this is the official API name + NDEFMessage: NDEFMessage; +} + +// biome-ignore lint/style/useNamingConvention: this is the official API name +declare class NDEFMessage { + constructor(messageInit: NDEFMessageInit); + records: readonly NDEFRecord[]; +} + +// biome-ignore lint/style/useNamingConvention: this is the official API name +declare interface NDEFMessageInit { + records: NDEFRecordInit[]; +} + +// biome-ignore lint/style/useNamingConvention: this is the official API name +declare type NDEFRecordDataSource = string | BufferSource | NDEFMessageInit; + +// biome-ignore lint/correctness/noUnusedVariables: this is the official definition +interface Window { + // biome-ignore lint/style/useNamingConvention: this is the official API name + NDEFRecord: NDEFRecord; +} +// biome-ignore lint/style/useNamingConvention: this is the official API name +declare class NDEFRecord { + constructor(recordInit: NDEFRecordInit); + readonly recordType: string; + readonly mediaType?: string; + readonly id?: string; + readonly data?: DataView; + readonly encoding?: string; + readonly lang?: string; + toRecords?: () => NDEFRecord[]; +} +// biome-ignore lint/style/useNamingConvention: this is the official API name +declare interface NDEFRecordInit { + recordType: string; + mediaType?: string; + id?: string; + encoding?: string; + lang?: string; + data?: NDEFRecordDataSource; +} + +// biome-ignore lint/style/useNamingConvention: this is the official API name +declare type NDEFMessageSource = string | BufferSource | NDEFMessageInit; + +// biome-ignore lint/correctness/noUnusedVariables: this is the official definition +interface Window { + // biome-ignore lint/style/useNamingConvention: this is the official API name + NDEFReader: NDEFReader; +} +// biome-ignore lint/style/useNamingConvention: this is the official API name +declare class NDEFReader extends EventTarget { + constructor(); + // biome-ignore lint/suspicious/noExplicitAny: who am I to doubt the w3c definitions ? + onreading: (this: this, event: NDEFReadingEvent) => any; + // biome-ignore lint/suspicious/noExplicitAny: who am I to doubt the w3c definitions ? + onreadingerror: (this: this, error: Event) => any; + scan: (options?: NDEFScanOptions) => Promise; + write: (message: NDEFMessageSource, options?: NDEFWriteOptions) => Promise; + makeReadOnly: (options?: NDEFMakeReadOnlyOptions) => Promise; +} + +// biome-ignore lint/correctness/noUnusedVariables: this is the official definition +interface Window { + // biome-ignore lint/style/useNamingConvention: this is the official API name + NDEFReadingEvent: NDEFReadingEvent; +} +// biome-ignore lint/style/useNamingConvention: this is the official API name +declare class NDEFReadingEvent extends Event { + constructor(type: string, readingEventInitDict: NDEFReadingEventInit); + serialNumber: string; + message: NDEFMessage; +} +// biome-ignore lint/style/useNamingConvention: this is the official API name +interface NDEFReadingEventInit extends EventInit { + serialNumber?: string; + message: NDEFMessageInit; +} + +// biome-ignore lint/style/useNamingConvention: this is the official API name +interface NDEFWriteOptions { + overwrite?: boolean; + signal?: AbortSignal; +} +// biome-ignore lint/style/useNamingConvention: this is the official API name +interface NDEFMakeReadOnlyOptions { + signal?: AbortSignal; +} +// biome-ignore lint/style/useNamingConvention: this is the official API name +interface NDEFScanOptions { + signal: AbortSignal; +} diff --git a/core/static/webpack/user/family-graph-index.js b/core/static/bundled/user/family-graph-index.js similarity index 100% rename from core/static/webpack/user/family-graph-index.js rename to core/static/bundled/user/family-graph-index.js diff --git a/core/static/webpack/user/pictures-index.js b/core/static/bundled/user/pictures-index.js similarity index 100% rename from core/static/webpack/user/pictures-index.js rename to core/static/bundled/user/pictures-index.js diff --git a/core/static/webpack/utils/api.ts b/core/static/bundled/utils/api.ts similarity index 100% rename from core/static/webpack/utils/api.ts rename to core/static/bundled/utils/api.ts diff --git a/core/static/webpack/utils/globals.ts b/core/static/bundled/utils/globals.ts similarity index 100% rename from core/static/webpack/utils/globals.ts rename to core/static/bundled/utils/globals.ts diff --git a/core/static/webpack/utils/history.ts b/core/static/bundled/utils/history.ts similarity index 100% rename from core/static/webpack/utils/history.ts rename to core/static/bundled/utils/history.ts diff --git a/core/static/webpack/utils/web-components.ts b/core/static/bundled/utils/web-components.ts similarity index 100% rename from core/static/webpack/utils/web-components.ts rename to core/static/bundled/utils/web-components.ts diff --git a/core/static/core/components/nfc-input.scss b/core/static/core/components/nfc-input.scss new file mode 100644 index 00000000..924504db --- /dev/null +++ b/core/static/core/components/nfc-input.scss @@ -0,0 +1,34 @@ +nfc-input[scan="active"]::before { + content: ""; + position: relative; + top: 0; + bottom: 0; + left: 0; + background: #18c89b; + box-shadow: 0 0 70px 20px #18c89b; + clip-path: inset(0); + animation: + x 1s ease-in-out infinite alternate, + y 2s ease-in-out infinite; +} + +@keyframes x { + to { + transform: translateX(-100%); + left: 100%; + } +} + +@keyframes y { + 33% { + clip-path: inset(0 0 0 -100px); + } + + 50% { + clip-path: inset(0 0 0 0); + } + + 83% { + clip-path: inset(0 -100px 0 0); + } +} \ No newline at end of file diff --git a/core/static/core/forms.scss b/core/static/core/forms.scss new file mode 100644 index 00000000..7dab0484 --- /dev/null +++ b/core/static/core/forms.scss @@ -0,0 +1,89 @@ +@import "colors"; + +/** + * Style related to forms + */ + +a.button, +button, +input[type="button"], +input[type="submit"], +input[type="reset"], +input[type="file"] { + border: none; + text-decoration: none; + background-color: $background-button-color; + padding: 0.4em; + margin: 0.1em; + font-size: 1.2em; + border-radius: 5px; + color: black; + + &:hover { + background: hsl(0, 0%, 83%); + } +} + +a.button, +input[type="button"], +input[type="submit"], +input[type="reset"], +input[type="file"] { + font-weight: bold; +} + +a.button:not(:disabled), +button:not(:disabled), +input[type="button"]:not(:disabled), +input[type="submit"]:not(:disabled), +input[type="reset"]:not(:disabled), +input[type="checkbox"]:not(:disabled), +input[type="file"]:not(:disabled) { + cursor: pointer; +} + +input, +textarea[type="text"], +[type="number"] { + border: none; + text-decoration: none; + background-color: $background-button-color; + padding: 0.4em; + margin: 0.1em; + font-size: 1.2em; + border-radius: 5px; + max-width: 95%; +} + +textarea { + border: none; + text-decoration: none; + background-color: $background-button-color; + padding: 7px; + font-size: 1.2em; + border-radius: 5px; + font-family: sans-serif; +} + +select { + border: none; + text-decoration: none; + font-size: 1.2em; + background-color: $background-button-color; + padding: 10px; + border-radius: 5px; + cursor: pointer; +} + +a:not(.button) { + text-decoration: none; + color: $primary-dark-color; + + &:hover { + color: $primary-light-color; + } + + &:active { + color: $primary-color; + } +} diff --git a/core/static/core/style.scss b/core/static/core/style.scss index 21481454..50892df3 100644 --- a/core/static/core/style.scss +++ b/core/static/core/style.scss @@ -1,4 +1,5 @@ @import "colors"; +@import "forms"; /*--------------------------MEDIA QUERY HELPERS------------------------*/ $small-devices: 576px; @@ -13,91 +14,6 @@ body { font-family: sans-serif; } - -a.button, -button, -input[type="button"], -input[type="submit"], -input[type="reset"], -input[type="file"] { - border: none; - text-decoration: none; - background-color: $background-button-color; - padding: 0.4em; - margin: 0.1em; - font-size: 1.2em; - border-radius: 5px; - color: black; - - &:hover { - background: hsl(0, 0%, 83%); - } -} - -a.button, -input[type="button"], -input[type="submit"], -input[type="reset"], -input[type="file"] { - font-weight: bold; -} - -a.button:not(:disabled), -button:not(:disabled), -input[type="button"]:not(:disabled), -input[type="submit"]:not(:disabled), -input[type="reset"]:not(:disabled), -input[type="checkbox"]:not(:disabled), -input[type="file"]:not(:disabled) { - cursor: pointer; -} - -input, -textarea[type="text"], -[type="number"] { - border: none; - text-decoration: none; - background-color: $background-button-color; - padding: 0.4em; - margin: 0.1em; - font-size: 1.2em; - border-radius: 5px; - max-width: 95%; -} - -textarea { - border: none; - text-decoration: none; - background-color: $background-button-color; - padding: 7px; - font-size: 1.2em; - border-radius: 5px; - font-family: sans-serif; -} - -select { - border: none; - text-decoration: none; - font-size: 1.2em; - background-color: $background-button-color; - padding: 10px; - border-radius: 5px; - cursor: pointer; -} - -a:not(.button) { - text-decoration: none; - color: $primary-dark-color; - - &:hover { - color: $primary-light-color; - } - - &:active { - color: $primary-color; - } -} - [aria-busy] { --loading-size: 50px; --loading-stroke: 5px; @@ -262,8 +178,10 @@ a:not(.button) { font-weight: normal; color: white; padding: 9px 13px; + margin: 3px; border: none; text-decoration: none; + text-align: center; border-radius: 5px; &.btn-blue { @@ -367,6 +285,49 @@ a:not(.button) { .alert-aside { display: flex; flex-direction: column; + gap: 5px; + } + } + + .tabs { + border-radius: 5px; + + .tab-headers { + display: flex; + flex-flow: row wrap; + background-color: $primary-neutral-light-color; + padding: 3px 12px 12px; + column-gap: 20px; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + + .tab-header { + border: none; + padding-right: 0; + padding-left: 0; + font-size: 120%; + background-color: unset; + position: relative; + &:after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + border-bottom: 4px solid darken($primary-neutral-light-color, 10%); + border-radius: 2px; + transition: all 0.2s ease-in-out; + } + &:hover:after { + border-bottom-color: darken($primary-neutral-light-color, 20%); + } + &.active:after { + border-bottom-color: $primary-dark-color; + } + } + } + section { + padding: 20px; } } @@ -1246,26 +1207,26 @@ u, /*-----------------------------USER PROFILE----------------------------*/ .user_mini_profile { - height: 100%; - width: 100%; + --gap-size: 1em; + max-height: 100%; + max-width: 100%; + display: flex; + flex-direction: column; + gap: var(--gap-size); img { - max-width: 100%; max-height: 100%; + max-width: 100%; } .user_mini_profile_infos { padding: 0.2em; - height: 20%; + max-height: 20%; display: flex; flex-wrap: nowrap; justify-content: space-around; font-size: 0.9em; - div { - max-height: 100%; - } - .user_mini_profile_infos_text { text-align: center; @@ -1276,10 +1237,10 @@ u, } .user_mini_profile_picture { - height: 80%; - display: flex; - justify-content: center; - align-items: center; + max-height: calc(80% - var(--gap-size)); + max-width: 100%; + display: block; + margin: auto; } } diff --git a/core/static/user/user_detail.scss b/core/static/user/user_detail.scss index 3b32a327..87a3d199 100644 --- a/core/static/user/user_detail.scss +++ b/core/static/user/user_detail.scss @@ -130,7 +130,7 @@ main { width: 50%; display: flex; flex-direction: row; - justify-content: flex-end; + justify-content: space-evenly; @media (max-width: 960px) { width: 100%; @@ -143,21 +143,14 @@ main { } > .user_profile_pictures_bigone { - flex-grow: 9; - flex-basis: 20em; display: flex; justify-content: center; - align-items: center; > img { max-height: 100%; - max-width: 100%; - object-fit: contain; @media (max-width: 960px) { - max-width: 300px; - width: 100%; - object-fit: contain; + width: 300px; } } } @@ -169,7 +162,6 @@ main { justify-content: center; align-items: center; gap: 20px; - flex-grow: 1; @media (max-width: 960px) { flex-direction: row; diff --git a/core/static/webpack/fontawesome-index.js b/core/static/webpack/fontawesome-index.js deleted file mode 100644 index 90ce7c25..00000000 --- a/core/static/webpack/fontawesome-index.js +++ /dev/null @@ -1 +0,0 @@ -require("@fortawesome/fontawesome-free/css/all.css"); diff --git a/core/static/webpack/jquery-index.js b/core/static/webpack/jquery-index.js deleted file mode 100644 index 569d26e8..00000000 --- a/core/static/webpack/jquery-index.js +++ /dev/null @@ -1,25 +0,0 @@ -import $ from "jquery"; -import "jquery.shorten/src/jquery.shorten.min.js"; - -// We ship jquery-ui with jquery because when standalone with webpack -// JQuery is also included in the jquery-ui package. We do gain space by doing this -// We require jquery-ui components manually and not in a loop -// Otherwise it increases the output files by a x2 factor ! -require("jquery-ui/ui/widgets/accordion.js"); -require("jquery-ui/ui/widgets/autocomplete.js"); -require("jquery-ui/ui/widgets/button.js"); -require("jquery-ui/ui/widgets/dialog.js"); -require("jquery-ui/ui/widgets/tabs.js"); - -require("jquery-ui/themes/base/all.css"); - -/** - * Simple wrapper to solve shorten not being able on legacy pages - * @param {string} selector to be passed to jQuery - * @param {Object} options object to pass to the shorten function - **/ -function shorten(selector, options) { - $(selector).shorten(options); -} - -window.shorten = shorten; diff --git a/core/templates/core/500.jinja b/core/templates/core/500.jinja index c743cca0..8ca195c5 100644 --- a/core/templates/core/500.jinja +++ b/core/templates/core/500.jinja @@ -1,7 +1,7 @@ {% extends "core/base.jinja" %} {% block additional_js %} {% if settings.SENTRY_DSN %} - + {% endif %} {% endblock additional_js %} diff --git a/core/templates/core/base.jinja b/core/templates/core/base.jinja index 3849bb87..d089ec9f 100644 --- a/core/templates/core/base.jinja +++ b/core/templates/core/base.jinja @@ -5,7 +5,6 @@ {% block title %}{% trans %}Welcome!{% endtrans %}{% endblock %} - Association des Étudiants UTBM - @@ -14,18 +13,20 @@ {% block jquery_css %} - {# Thile file is quite heavy (around 250kb), so declaring it in a block allows easy removal #} - + {# Thile file is quite heavy (around 250kb), so declaring it in a block allows easy removal #} + {% endblock %} - - + + - - - - - + + + + + + + @@ -37,148 +38,13 @@ - - + + {% csrf_token %} - + {% block header %} {% if not popup %} -
- - {% if not user.is_authenticated %} - - {% else %} -
-
- -
    - {% cache 100 "counters_activity" %} - {# The sith has no periodic tasks manager - and using cron jobs would be way too overkill here. - Thus the barmen timeout is handled in the only place that - is loaded on every page : the header bar. - However, let's be clear : this has nothing to do here. - It's' merely a contrived workaround that should - replaced by a proper task manager as soon as possible. #} - {% set _ = Counter.objects.filter(type="BAR").handle_timeout() %} - {% endcache %} - {% for bar in Counter.objects.annotate_has_barman(user).annotate_is_open().filter(type="BAR") %} -
  • - {# If the user is a barman, we redirect him directly to the barman page - else we redirect him to the activity page #} - {% if bar.has_annotated_barman %} - - {% else %} - - {% endif %} - {% if bar.is_open %} - - {% else %} - - {% endif %} - {{ bar }} - -
  • - {% endfor %} -
-
- -
- {% endif %} -
- {% for language in LANGUAGES %} -
- {% csrf_token %} - - - -
- {% endfor %} -
-
+ {% include "core/base/header.jinja" %} {% block info_boxes %}
@@ -201,58 +67,10 @@ {% endif %} {% endblock %} - {% block nav %} {% if not popup %} - + {% include "core/base/navbar.jinja" %} {% endif %} {% endblock %} @@ -265,19 +83,16 @@
- {% if list_of_tabs %} -
-
- {% for t in list_of_tabs -%} - {{ t.name }} - {%- endfor %} -
-
- {% endif %} + {% block tabs %} + {% include "core/base/tabs.jinja" %} + {% endblock %} + + {% block errors%} + {% if error %} + {{ error }} + {% endif %} + {% endblock %} - {% if error %} - {{ error }} - {% endif %} {% block content %} {% endblock %}
diff --git a/core/templates/core/base/header.jinja b/core/templates/core/base/header.jinja new file mode 100644 index 00000000..99cd4c4f --- /dev/null +++ b/core/templates/core/base/header.jinja @@ -0,0 +1,136 @@ +
+ + {% if not user.is_authenticated %} + + {% else %} +
+
+ +
    + {% cache 100 "counters_activity" %} + {# The sith has no periodic tasks manager + and using cron jobs would be way too overkill here. + Thus the barmen timeout is handled in the only place that + is loaded on every page : the header bar. + However, let's be clear : this has nothing to do here. + It's' merely a contrived workaround that should + replaced by a proper task manager as soon as possible. #} + {% set _ = Counter.objects.filter(type="BAR").handle_timeout() %} + {% endcache %} + {% for bar in Counter.objects.annotate_has_barman(user).annotate_is_open().filter(type="BAR") %} +
  • + {# If the user is a barman, we redirect him directly to the barman page + else we redirect him to the activity page #} + {% if bar.has_annotated_barman %} + + {% else %} + + {% endif %} + {% if bar.is_open %} + + {% else %} + + {% endif %} + {{ bar }} + +
  • + {% endfor %} +
+
+ +
+ {% endif %} +
+ {% for language in LANGUAGES %} +
+ {% csrf_token %} + + + +
+ {% endfor %} +
+
diff --git a/core/templates/core/base/navbar.jinja b/core/templates/core/base/navbar.jinja new file mode 100644 index 00000000..fcdd4b1b --- /dev/null +++ b/core/templates/core/base/navbar.jinja @@ -0,0 +1,48 @@ + diff --git a/core/templates/core/base/tabs.jinja b/core/templates/core/base/tabs.jinja new file mode 100644 index 00000000..5a45ef09 --- /dev/null +++ b/core/templates/core/base/tabs.jinja @@ -0,0 +1,9 @@ +{% if list_of_tabs %} +
+
+ {% for t in list_of_tabs -%} + {{ t.name }} + {%- endfor %} +
+
+{% endif %} diff --git a/core/templates/core/base_fragment.jinja b/core/templates/core/base_fragment.jinja new file mode 100644 index 00000000..37fc17ce --- /dev/null +++ b/core/templates/core/base_fragment.jinja @@ -0,0 +1,20 @@ +{% block additional_css %}{% endblock %} +{% block additional_js %}{% endblock %} + +
+ {% block tabs %} + {% include "core/base/tabs.jinja" %} + {% endblock %} + + {% block errors %} + {% if error %} + {{ error }} + {% endif %} + {% endblock %} + + {% block content %} + {% endblock %} +
+ +{% block script %} +{% endblock %} diff --git a/core/templates/core/file.jinja b/core/templates/core/file.jinja index f8f42990..95c29bdc 100644 --- a/core/templates/core/file.jinja +++ b/core/templates/core/file.jinja @@ -1,4 +1,8 @@ -{% extends "core/base.jinja" %} +{% if is_fragment %} + {% extends "core/base_fragment.jinja" %} +{% else %} + {% extends "core/base.jinja" %} +{% endif %} {% block title %} {% if file %} @@ -21,7 +25,7 @@ {% endif %} {% endmacro %} -{% block content %} +{% block tabs %} {{ print_file_name(file) }}
@@ -44,6 +48,9 @@

+{% endblock %} + +{% block content %} {% if file %} {% block file %} diff --git a/core/templates/core/file_delete_confirm.jinja b/core/templates/core/file_delete_confirm.jinja index 521413e2..155ac62a 100644 --- a/core/templates/core/file_delete_confirm.jinja +++ b/core/templates/core/file_delete_confirm.jinja @@ -4,15 +4,49 @@ {% trans %}Delete confirmation{% endtrans %} {% endblock %} +{% if is_fragment %} + + {# Don't display tabs and errors #} + {% block tabs %} + {% endblock %} + {% block errors %} + {% endblock %} + +{% endif %} + {% block file %}

{% trans %}Delete confirmation{% endtrans %}

-
{% csrf_token %} + + {% if next %} + {% set action = current + "?next=" + next %} + {% else %} + {% set action = current %} + {% endif %} + + + {% csrf_token %} +

{% trans obj=object %}Are you sure you want to delete "{{ obj }}"?{% endtrans %}

- -
-
- + + + +
+ {% endblock %} diff --git a/core/templates/core/file_moderation.jinja b/core/templates/core/file_moderation.jinja index f8fd255e..fc9c0f43 100644 --- a/core/templates/core/file_moderation.jinja +++ b/core/templates/core/file_moderation.jinja @@ -1,4 +1,16 @@ -{% extends "core/base.jinja" %} +{% if is_fragment %} + {% extends "core/base_fragment.jinja" %} + + {# Don't display tabs and errors #} + {% block tabs %} + {% endblock %} + {% block errors %} + {% endblock %} +{% else %} + {% extends "core/base.jinja" %} +{% endif %} + +{% from "core/macros.jinja" import paginate_htmx %} {% block title %} {% trans %}File moderation{% endtrans %} @@ -7,8 +19,11 @@ {% block content %}

{% trans %}File moderation{% endtrans %}

- {% for f in files %} -
+ {% for f in object_list %} +
{% if f.is_folder %} Folder {% else %} @@ -20,9 +35,19 @@ {% trans %}Owner: {% endtrans %}{{ f.owner.get_display_name() }}
{% trans %}Date: {% endtrans %}{{ f.date|date(DATE_FORMAT) }} {{ f.date|time(TIME_FORMAT) }}

-

{% trans %}Moderate{% endtrans %} - - {% trans %}Delete{% endtrans %}

+

- + {% set current_page = url('core:file_moderation') + "?page=" + page_obj.number | string %} +

{% endfor %} + {{ paginate_htmx(page_obj, paginator) }}
{% endblock %} diff --git a/core/templates/core/macros.jinja b/core/templates/core/macros.jinja index c08053dd..6ab52cad 100644 --- a/core/templates/core/macros.jinja +++ b/core/templates/core/macros.jinja @@ -66,7 +66,12 @@
{% if user.promo and user.promo_has_logo() %}
- Promo {{ user.promo }} + Promo {{ user.promo }}
{% endif %} @@ -74,8 +79,11 @@ {% if user.profile_pict %} {% trans %}Profile{% endtrans %} {% else %} - {% trans %}Profile{% endtrans %} + {% trans %}Profile{% endtrans %} {% endif %} @@ -166,9 +174,37 @@ current_page (django.core.paginator.Page): the current page object paginator (django.core.paginator.Paginator): the paginator object #} + {{ paginate_server_side(current_page, paginator, False) }} +{% endmacro %} + +{% macro paginate_htmx(current_page, paginator) %} + {# Add pagination buttons for pages without Alpine but supporting fragments. + + This must be coupled with a view that handles pagination + with the Django Paginator object and supports fragments. + + The replaced fragment will be #content so make sure you are calling this macro inside your content block. + + Parameters: + current_page (django.core.paginator.Page): the current page object + paginator (django.core.paginator.Paginator): the paginator object + #} + {{ paginate_server_side(current_page, paginator, True) }} +{% endmacro %} + +{% macro paginate_server_side(current_page, paginator, use_htmx) %}