feat(cof_clubs): init app

fix(clubs): move to cof-clubs

fix(bds): restore clubs app

fix(bulmaforms): installed apps

fix(bulma): load correct module

fix(urls): cof-log{in,out} instead of authens:

fix: duplicate import

fix: remove club members

fix: migrate completely to cof_clubs

chore(cof_clubs): remove unused bulma vendoring
This commit is contained in:
sinavir 2025-03-10 21:31:53 +01:00 committed by catvayor
parent 342ab6f141
commit 4f24570a0e
Signed by: lbailly
GPG key ID: CE3E645251AC63F3
52 changed files with 101566 additions and 299 deletions

0
cof_clubs/__init__.py Normal file
View file

6
cof_clubs/admin.py Normal file
View file

@ -0,0 +1,6 @@
from cof_clubs.models import Club, ClubBudgetAccountingPeriod, ClubBudgetLine
from django.contrib import admin
admin.site.register(Club)
admin.site.register(ClubBudgetAccountingPeriod)
admin.site.register(ClubBudgetLine)

5
cof_clubs/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class ClubsConfig(AppConfig):
name = "cof_clubs"

31
cof_clubs/forms.py Normal file
View file

@ -0,0 +1,31 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from .models import Club, ClubBudgetLine
class ClubBudgetLineForm(forms.ModelForm):
class Meta:
model = ClubBudgetLine
fields = ["label", "amount", "date"]
widgets = {"date": forms.DateInput(attrs={"type": "date"}, format="%Y-%m-%d")}
class ClubsForm(forms.Form):
"""
Formulaire d'inscription d'un membre à plusieurs clubs du COF.
"""
respo = forms.ModelMultipleChoiceField(
label=_("Respotude de club"),
queryset=Club.objects.all(),
widget=forms.CheckboxSelectMultiple,
required=False,
)
budget_manager = forms.ModelMultipleChoiceField(
label=_("Gestionnaire de budget de club"),
queryset=Club.objects.all(),
widget=forms.CheckboxSelectMultiple,
required=False,
)

View file

@ -0,0 +1,150 @@
# Generated by Django 4.2.16 on 2025-03-14 18:25
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="Club",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"name",
models.CharField(
max_length=1000, unique=True, verbose_name="nom du club"
),
),
(
"description",
models.TextField(blank=True, verbose_name="description"),
),
(
"budget_managers",
models.ManyToManyField(
blank=True,
related_name="managed_budgets_set",
to=settings.AUTH_USER_MODEL,
verbose_name="gestionnaires du budget su club",
),
),
(
"members",
models.ManyToManyField(
blank=True,
related_name="clubs",
to=settings.AUTH_USER_MODEL,
verbose_name="membres du club",
),
),
(
"respos",
models.ManyToManyField(
blank=True,
related_name="respo_set",
to=settings.AUTH_USER_MODEL,
verbose_name="responsables du club",
),
),
],
),
migrations.CreateModel(
name="ClubBudgetAccountingPeriod",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"name",
models.CharField(
max_length=1000,
unique=True,
verbose_name="exercice budgétaire des clubs",
),
),
(
"is_archived",
models.BooleanField(default=False, verbose_name="est archivé"),
),
],
),
migrations.CreateModel(
name="ClubBudgetLine",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("label", models.CharField(max_length=1000, verbose_name="libellé")),
(
"amount",
models.DecimalField(
decimal_places=2,
max_digits=12,
verbose_name="Montant (dépense en négatif)",
),
),
(
"created",
models.DateTimeField(
auto_now_add=True, verbose_name="date de création"
),
),
(
"modified",
models.DateTimeField(
auto_now=True, verbose_name="date de modification"
),
),
("date", models.DateTimeField(verbose_name="date")),
(
"posted",
models.BooleanField(default=False, verbose_name="est validée"),
),
(
"accounting_period",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="cof_clubs.clubbudgetaccountingperiod",
verbose_name="exercice financier",
),
),
(
"club",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="cof_clubs.club",
verbose_name="club",
),
),
],
),
]

View file

@ -0,0 +1,17 @@
# Generated by Django 4.2.16 on 2025-03-17 08:47
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("cof_clubs", "0001_initial"),
]
operations = [
migrations.RemoveField(
model_name="club",
name="members",
),
]

View file

69
cof_clubs/models.py Normal file
View file

@ -0,0 +1,69 @@
from django.contrib.auth import get_user_model
from django.db import models
from django.utils.translation import gettext_lazy as _
User = get_user_model()
class Club(models.Model):
name = models.CharField(_("nom du club"), max_length=1000, unique=True)
description = models.TextField(_("description"), blank=True)
respos = models.ManyToManyField(
User,
verbose_name=_("responsables du club"),
blank=True,
related_name="respo_set",
)
budget_managers = models.ManyToManyField(
User,
verbose_name=_("gestionnaires du budget su club"),
blank=True,
related_name="managed_budgets_set",
)
def __str__(self):
return self.name
class ClubBudgetAccountingPeriod(models.Model):
name = models.CharField(
_("exercice budgétaire des clubs"),
max_length=1000,
unique=True,
)
is_archived = models.BooleanField(verbose_name=_("est archivé"), default=False)
def __str__(self):
return self.name
class ClubBudgetLine(models.Model):
label = models.CharField(_("libellé"), max_length=1000)
amount = models.DecimalField(
max_digits=12, decimal_places=2, verbose_name="Montant (dépense en négatif)"
)
club = models.ForeignKey(
Club,
verbose_name=_("club"),
on_delete=models.CASCADE,
)
accounting_period = models.ForeignKey(
ClubBudgetAccountingPeriod,
verbose_name=_("exercice financier"),
on_delete=models.CASCADE,
)
created = models.DateTimeField(
auto_now_add=True,
verbose_name=_("date de création"),
)
modified = models.DateTimeField(
auto_now=True,
verbose_name=_("date de modification"),
)
date = models.DateTimeField(verbose_name=_("date"))
posted = models.BooleanField(verbose_name=_("est validée"), default=False)
def __str__(self):
return self.label

View file

@ -0,0 +1,38 @@
document.addEventListener('DOMContentLoaded', () => {
// Get all "navbar-burger" elements
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
// Add a click event on each of them
$navbarBurgers.forEach( el => {
el.addEventListener('click', () => {
// Get the target from the "data-target" attribute
const target = el.dataset.target;
const $target = document.getElementById(target);
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
el.classList.toggle('is-active');
$target.classList.toggle('is-active');
});
});
});
document.addEventListener('DOMContentLoaded', () => {
const $deleteElems = Array.prototype.slice.call(document.querySelectorAll('.delete'), 0);
// Add a click event on each of them
$deleteElems.forEach( el => {
el.addEventListener('click', () => {
// Get the target from the "data-target" attribute
if ('target' in el.dataset) {
const target = el.dataset.target;
const $target = document.getElementById(target);
$target.remove();
}
});
});
});

View file

@ -0,0 +1,5 @@
{% load static %}
<!-- CSS -->
<link href="{% static 'vendor/bulma/css/bulma.min.css' %}" rel="stylesheet" type="text/css" />
<link type="text/css" rel="stylesheet" href="{% static 'vendor/font-awesome/css/font-awesome.min.css' %}">

View file

@ -0,0 +1,55 @@
{% load i18n %}
{% load static %}
<nav class="navbar is-primary" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item py-0" href="{% url "cof_clubs:club-list" %}" >
<i class="fa fa-money"></i>
<i class="fa fa-euro"></i>
<i class="fa fa-money"></i>
</a>
<a href="{% url "home" %}" class="navbar-item">
{% trans "Retour vers GestioCOF" %}
</a>
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="mainNavbar">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="mainNavbar" class="navbar-menu">
<div class="navbar-end">
{% if user.is_authenticated %}
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">
<span class="icon"> <i class="fa fa-user"></i> </span>
<span>{{ user.username }}</span>
</a>
<div class="navbar-dropdown is-right">
<div class="dropdown-content">
<form class="navbar-item" action="{% url "cof-logout" %}">
{% csrf_token %}
<button class="button is-link is-size-6" type="submit">
<span>{% trans "Se déconnecter" %}</span>
</button>
</form>
</div>
</div>
</div>
{%else%}
<div class="navbar-item">
<div class="buttons">
<a href="{% url "cof-login" %}" class="button is-light">
<span>{% trans "Se connecter" %}</span>
<span class="icon">
<i class="fa fa-door-open"></i>
</span>
</a>
</div>
</div>
{% endif %}
</div>
</div>
</nav>

View file

@ -0,0 +1,39 @@
{% load i18n %}
{% load static %}
{% load bulma %}
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="{% blocktranslate %}Site web de gestion des clubs{% endblocktranslate %}" />
<title>{% block title %}{% translate "Budget clubs" %}{% endblock %}</title>
{% include "cof_clubs/_links.html" %}
</head>
<body>
{% include "cof_clubs/_nav.html" %}
<section id="notifications" class="section py-0">
{% for message in messages %}
<div class="notification {{ message.level_tag }} my-2" id="message-{{ forloop.counter0 }}">
<button class="delete" data-target="message-{{ forloop.counter0 }}"></button>
{% if 'safe' in message.tags %}
{{ message|safe }}
{% else %}
{{ message }}
{% endif %}
</div>
{% endfor %}
</section>
{% block content %}
{% endblock content %}
<script src="{% static "cof_clubs/common.js" %}" defer></script>
{% block extra_scripts %}
{% endblock extra_scripts %}
{# TODO: Add analytics #}
</body>
</html>

View file

@ -0,0 +1,93 @@
{% extends "cof_clubs/base.html" %}
{% load bulma %}
{% block content %}
{% regroup lines by accounting_period__name as lines_list %}
<div class="section">
<div class="content container is-max-desktop"/>
<h1>{{ object.name }}</h1>
<h5>Respos:</h5>
<div class="grid">
{% for r in object.respos.all %}
<div class="tag cell">{{r.username}}</div>
{% empty %}
<div class="tag cell is-warning">Ce club n'a pas de respo déclaré</div>
{% endfor%}
</div>
{% if object.budget_managers.all|length > 0 %}
<h5>Gestionnaires additionels du budget:</h5>
<div class="grid">
{% for r in object.budget_managers.all %}
<div class="tag cell">{{r.username}}</div>
{% endfor%}
</div>
{% endif %}
{% for period in lines_list %}
<h2 class="buttons">
{{ period.grouper }}
<a class="button" href="{% url "cof_clubs:budget-line-create" object.id period.list.0.accounting_period__id %}"><span class="icon"><i class="fa fa-plus"></i></span></a></h2>
<table class="table is-bordered is-striped is-fullwidth">
<thead>
<tr>
<th>Date</th>
<th>Libellé</th>
<th>Montant</th>
<th>Budget restant</th>
<th></th>
</tr>
</thead>
<tbody>
{% for line in period.list %}
<tr>
<td class="is-vcentered">{{ line.date|date:"SHORT_DATE_FORMAT" }}</td>
<td class="is-vcentered">{{ line.label }}</td>
<td class="is-vcentered has-text-right">{{ line.amount }}&nbsp;</td>
<td class="is-vcentered">{{ line.remaining }}</td>
<td class="is-vcentered">
<div class=buttons>
{% if line.posted %}
<div class="button is-static is-small">
<span class="icon has-text-success">
<i class="fa fa-check"></i>
</span>
</div>
{% else %}
<div class="button is-static is-small">
<span class="icon has-text-danger">
<i class="fa fa-question"></i>
</span>
</div>
{% endif %}
{% if line.can_edit %}
<a href="{% if user.profile.is_buro %}{% url "cof_clubs:budget-line-full-update" line.id %}{% else %}{% url "cof_clubs:budget-line-update" line.id %}{% endif %}" class="button is-small">
<span class="icon has-text-warning">
<i class="fa fa-pencil"></i>
</span>
</a>
<form action="{% url "cof_clubs:budget-line-delete" line.id %}" method="post">
{% csrf_token %}
<button type="submit" class="button is-small">
<span class="icon">
<i class="fa fa-trash"></i>
</span>
</button>
</form>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endfor %}
</div>
</div>
{% endblock content %}

View file

@ -0,0 +1,44 @@
{% extends "cof_clubs/base.html" %}
{% load bulma %}
{% block content %}
<div class="section">
<div class="content">
<h1>Liste des clubs gérés</h1>
<table class="table is-bordered is-striped is-fullwidth">
<thead>
<tr>
<th rowspan="2" >Nom du club</th>
<th colspan="{{ accounting_periods.count }}" >Budget restant</th>
</tr>
<tr>
{% for period in accounting_periods %}
<th>{{ period.name }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for club, data in clubs.items %}
<tr>
<td><a href="{% url "cof_clubs:club-detail" club %}">{{ data.name }}</a></td>
{%for amount in data.amounts %}
<td class="has-text-right">
{% if amount < 0 %}
<span class="icon">
<i class="fa fa-exclamation-triangle has-text-danger"></i>
</span>
{% endif %}
{{ amount }}&nbsp;
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock content %}

View file

@ -0,0 +1,31 @@
{% extends "cof_clubs/base.html" %}
{% load i18n %}
{% block content %}
<section class="section">
<h1 class="title">{% trans "Création/Modification de ligne de budget" %}</h1>
<form action="" method="post">
{% csrf_token %}
{% include "bulma/form.html" with errors=True form=form %}
<div class="field is-grouped">
<div class="control">
<button class="button is-primary" type="submit">
<span>{% trans "Enregister" %}</span>
</button>
</div>
{% if not object %}
<div class="control">
<button class="button is-secondary" type="submit" name="_addanother">
<span>{% trans "Enregister et ajouter un nouveau" %}</span>
</button>
</div>
{% endif %}
</div>
</form>
</section>
{% endblock %}

30
cof_clubs/urls.py Normal file
View file

@ -0,0 +1,30 @@
from django.urls import path
from . import views
app_name = "cof_clubs"
urlpatterns = [
path("club/", views.ClubListView.as_view(), name="club-list"),
path("club/<int:pk>", views.ClubDetailView.as_view(), name="club-detail"),
path("club/add", views.ClubCreateView.as_view(), name="club-create"),
path(
"line/<int:club>/<int:acct_period>/add",
views.BudgetLineCreate.as_view(),
name="budget-line-create",
),
path(
"line/<int:pk>/delete",
views.BudgetLineDelete.as_view(),
name="budget-line-delete",
),
path(
"line/<int:pk>/update",
views.BudgetLineUpdate.as_view(),
name="budget-line-update",
),
path(
"line/<int:pk>/full-update",
views.BudgetLineFullUpdate.as_view(),
name="budget-line-full-update",
),
]

204
cof_clubs/views.py Normal file
View file

@ -0,0 +1,204 @@
from django.contrib.auth.mixins import AccessMixin, LoginRequiredMixin
from django.db.models import Sum
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.views.generic import DetailView, TemplateView
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from gestioncof.decorators import BuroRequiredMixin
from .forms import ClubBudgetLineForm
from .models import Club, ClubBudgetAccountingPeriod, ClubBudgetLine
class ClubListView(LoginRequiredMixin, TemplateView):
template_name = "cof_clubs/club_list.html"
def get_context_data(self, *args, **kwargs):
ctx = super().get_context_data(*args, **kwargs)
accounting_periods = ClubBudgetAccountingPeriod.objects.filter(
is_archived=False
).values("name", "id")
if self.request.user.profile.is_buro:
managed_clubs = Club.objects.all().order_by("name") # TODO useless
else:
managed_clubs = self.request.user.managed_budgets_set.order_by(
"name"
) # TODO: useless
data = (
ClubBudgetLine.objects.filter(
accounting_period__is_archived=False, club__in=managed_clubs
)
.values(
"club__name",
"club",
"accounting_period",
)
.annotate(Sum("amount"))
)
structured_data = {
club.id: {
"name": club.name,
"amounts": [0 for _ in accounting_periods],
}
for club in managed_clubs
}
period_to_index = {a["id"]: i for i, a in enumerate(accounting_periods)}
for d in data:
structured_data[d["club"]]["amounts"][
period_to_index[d["accounting_period"]]
] = d["amount__sum"]
ctx["accounting_periods"] = accounting_periods
ctx["clubs"] = structured_data
return ctx
class ClubDetailView(AccessMixin, DetailView):
template_name = "cof_clubs/club_detail.html"
model = Club
def get(self, request, *args, **kwargs):
self.object = self.get_object()
if (
not self.request.user.profile.is_buro
and self.request.user not in self.object.respos.all()
and self.request.user not in self.object.budget_managers.all()
):
return self.handle_no_permission()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
def get_context_data(self, *args, **kwargs):
ctx = super().get_context_data(*args, **kwargs)
lines_qs = (
ctx["object"]
.clubbudgetline_set.filter(
accounting_period__is_archived=False,
)
.prefetch_related("club__respos", "club__budget_managers")
.order_by(
"accounting_period__is_archived", "accounting_period__name", "date"
)
)
lines = lines_qs.values(
"accounting_period__name",
"accounting_period__id",
"date",
"amount",
"label",
"id",
"posted",
"accounting_period__is_archived",
)
# Compute rights and partial sums
s = 0
current_period = None
for i, line in enumerate(lines):
if line["accounting_period__id"] != current_period:
current_period = line["accounting_period__id"]
s = 0
s += line["amount"]
line["can_edit"] = self.request.user.profile.is_buro or (
not line["posted"]
and not line["accounting_period__is_archived"]
and (
self.request.user in lines_qs[i].club.respos.all()
or self.request.user in lines_qs[i].club.budget_managers.all()
)
)
line["remaining"] = s
ctx["lines"] = lines
return ctx
class BudgetLineAccessMixin(AccessMixin):
def get_club(self):
return get_object_or_404(Club, pk=self.kwargs["club"])
def get_accounting_period(self):
return get_object_or_404(
ClubBudgetAccountingPeriod, pk=self.kwargs["acct_period"]
)
def dispatch(self, request, *args, **kwargs):
self.club = self.get_club()
if (
not self.request.user.profile.is_buro
and self.request.user not in self.club.respos.all()
and self.request.user not in self.club.budget_managers.all()
):
return self.handle_no_permission()
self.accounting_period = self.get_accounting_period()
if self.accounting_period.is_archived:
return self.handle_no_permission()
if (
not self.request.user.profile.is_buro
and hasattr(self, "object")
and self.object
and self.object.posted
):
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
class BudgetLineCreate(BudgetLineAccessMixin, CreateView):
model = ClubBudgetLine
form_class = ClubBudgetLineForm
def form_valid(self, form):
form.instance.accounting_period = self.accounting_period
form.instance.club = self.club
return super().form_valid(form)
def get_success_url(self):
if "_addanother" in self.request.POST:
return reverse("cof_clubs:budget-line-create", kwargs=self.kwargs)
return reverse("cof_clubs:club-detail", kwargs={"pk": self.kwargs["club"]})
class BudgetLineUpdate(BudgetLineAccessMixin, UpdateView):
model = ClubBudgetLine
form_class = ClubBudgetLineForm
def get_club(self):
if not hasattr(self, "object") or not self.object:
self.object = self.get_object()
return self.object.club
def get_accounting_period(self):
if not hasattr(self, "object") or not self.object:
self.object = self.get_object()
return self.object.accounting_period
def get_success_url(self):
return reverse("cof_clubs:club-detail", kwargs={"pk": self.club.id})
class BudgetLineFullUpdate(BuroRequiredMixin, UpdateView):
fields = ["label", "amount", "date", "posted", "club", "accounting_period"]
model = ClubBudgetLine
def get_success_url(self):
return reverse("cof_clubs:club-detail", kwargs={"pk": self.object.club.id})
class BudgetLineDelete(BudgetLineAccessMixin, DeleteView):
model = ClubBudgetLine
def get_club(self):
if not hasattr(self, "object") or not self.object:
self.object = self.get_object()
return self.object.club
def get_accounting_period(self):
if not hasattr(self, "object") or not self.object:
self.object = self.get_object()
return self.object.accounting_period
def get_success_url(self):
return reverse("cof_clubs:club-detail", kwargs={"pk": self.club.id})

View file

@ -50,6 +50,7 @@ INSTALLED_APPS = (
]
+ INSTALLED_APPS
+ [
"bulma",
"bda",
"petitscours",
"hcaptcha",
@ -67,6 +68,7 @@ INSTALLED_APPS = (
"wagtail.images",
"wagtail.search",
"wagtail.admin",
"cof_clubs",
"wagtail",
# "wagtail.contrib.modeladmin",
"wagtail.contrib.routable_page",

View file

@ -57,12 +57,14 @@ INSTALLED_APPS = [
"django.contrib.messages",
"django.contrib.admin",
"django.contrib.admindocs",
"bulma",
"gestioasso.apps.IgnoreSrcStaticFilesConfig",
"django_cas_ng",
"bootstrapform",
"widget_tweaks",
"bda",
"petitscours",
"cof_clubs",
"hcaptcha",
"kfet",
"kfet.open",

View file

@ -34,6 +34,7 @@ app_dict = {
"bda": "gestion/bda/",
"petitscours": "gestion/petitcours/",
"events": "gestion/event_v2/", # the events module is still experimental !
"cof_clubs": "gestion/budget/",
"authens": "gestion/authens/",
}
for app_name, url_prefix in app_dict.items():

View file

@ -9,7 +9,6 @@ from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
from gestioncof.models import (
Club,
CofProfile,
Event,
EventCommentField,
@ -295,25 +294,6 @@ class PetitCoursDemandeAdmin(admin.ModelAdmin):
readonly_fields = ("created",)
class ClubAdminForm(forms.ModelForm):
def clean(self):
cleaned_data = super().clean()
respos = cleaned_data.get("respos")
members = cleaned_data.get("membres")
for respo in respos.all():
if respo not in members:
raise forms.ValidationError(
"Erreur : le respo %s n'est pas membre du club."
% respo.get_full_name()
)
return cleaned_data
class ClubAdmin(admin.ModelAdmin):
list_display = ["name"]
form = ClubAdminForm
admin.site.register(Survey, SurveyAdmin)
admin.site.register(SurveyQuestion, SurveyQuestionAdmin)
admin.site.register(Event, EventAdmin)
@ -321,7 +301,6 @@ admin.site.register(EventOption, EventOptionAdmin)
admin.site.unregister(User)
admin.site.register(User, UserProfileAdmin)
admin.site.register(CofProfile)
admin.site.register(Club, ClubAdmin)
admin.site.register(PetitCoursSubject)
admin.site.register(PetitCoursAbility, PetitCoursAbilityAdmin)
admin.site.register(PetitCoursAttribution, PetitCoursAttributionAdmin)

View file

@ -7,7 +7,7 @@ from django.utils.translation import gettext_lazy as _
from djconfig.forms import ConfigForm
from bda.models import Spectacle
from gestioncof.models import CalendarSubscription, Club, CofProfile, EventCommentValue
from gestioncof.models import CalendarSubscription, CofProfile, EventCommentValue
from gestioncof.widgets import TriStateCheckbox
User = get_user_model()
@ -432,19 +432,6 @@ class CalendarForm(forms.ModelForm):
fields = ["subscribe_to_events", "subscribe_to_my_shows", "other_shows"]
class ClubsForm(forms.Form):
"""
Formulaire d'inscription d'un membre à plusieurs clubs du COF.
"""
clubs = forms.ModelMultipleChoiceField(
label="Inscriptions aux clubs du COF",
queryset=Club.objects.all(),
widget=forms.CheckboxSelectMultiple,
required=False,
)
# ---
# Announcements banner
# TODO: move this to the `gestion` app once the supportBDS branch is merged

View file

@ -0,0 +1,16 @@
# Generated by Django 4.2.16 on 2025-03-14 15:26
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("gestioncof", "0020_merge_20241218_2240"),
]
operations = [
migrations.DeleteModel(
name="Club",
),
]

View file

@ -151,17 +151,6 @@ def create_user_profile(sender, instance, created, **kwargs):
def post_delete_user(sender, instance, *args, **kwargs):
instance.user.delete()
class Club(models.Model):
name = models.CharField("Nom", max_length=200, unique=True)
description = models.TextField("Description", blank=True)
respos = models.ManyToManyField(User, related_name="clubs_geres", blank=True)
membres = models.ManyToManyField(User, related_name="clubs", blank=True)
def __str__(self):
return self.name
class Event(models.Model):
title = models.CharField("Titre", max_length=200)
location = models.CharField("Lieu", max_length=200)

View file

@ -67,6 +67,7 @@
{% if user.profile.is_cof %}
<li><a href="{% url "calendar" %}">Calendrier dynamique</a></li>
<li><a href="{% url "petits-cours-inscription" %}">Inscription pour donner des petits cours</a></li>
<li><a href="{% url "cof_clubs:club-list" %}">Budget des clubs</a></li>
{% endif %}
{% if not user.profile.login_clipper %}
<li><a href="{% url "password_change" %}">Changer mon mot de passe</a></li>
@ -97,7 +98,7 @@
<li><a href="{% url "admin:index" %}">Administration générale</a></li>
<li><a href="{% url "petits-cours-demandes-list" %}">Demandes de petits cours</a></li>
<li><a href="{% url "registration" %}">Inscription d'un nouveau membre</a></li>
<li><a href="{% url "liste-clubs" %}">Gestion des clubs</a></li>
<li><a href="{% url "cof_clubs:club-list" %}">Gestion des clubs</a></li>
</ul>
<ul>
<h4>Évènements & Sondages</h4>

View file

@ -1,25 +0,0 @@
{% extends "base_title.html" %}
{% block page_size %}col-sm-8{% endblock %}
{% block realcontent %}
<h2>Clubs enregistrés sur GestioCOF</h2>
<ul>
{% for club in owned_clubs %}
<li>
<a href="{% url "membres-club" club.name %}">
{{ club }} ({% for respo in club.respos.all %}{{ respo.get_full_name }}, {% empty %}pas de respo{% endfor %})
</a>
</li>
{% endfor %}
{% if other_clubs %}
{% for club in other_clubs %}
<li>
<p>
{{ club }} ({% for respo in club.respos.all %}{{ respo.get_full_name }}, {% empty %}pas de respo{% endfor %})
</p>
</li>
{% endfor %}
{% endif %}
</ul>
{% endblock %}

View file

@ -1,41 +0,0 @@
{% extends "base_title.html" %}
{% block realcontent %}
<h2>{{ club }}</h2>
{% if club.respos.exists %}
<h3>Respo{{ club.respos.all|pluralize }}</h3>
<table class="table table-striped">
{% for member in club.respos.all %}
<tr>
<td>{{ member }}</td>
<td>{{ member.email }}</td>
<td><a class="glyphicon glyphicon-arrow-down"
href="{% url "change-respo" club.name member.id %}"></a></td>
</tr>
{% endfor %}
</table>
{% else %}
<h3>Pas de respo</h3>
{% endif %}
{% if club.membres.exists %}
<h3>Liste des membres</h3>
<table class="table table-striped">
{% for member in members_no_respo %}
<tr>
<td>{{ member }}</td>
<td>{{ member.email }}</td>
<td><a class="glyphicon glyphicon-arrow-up"
href="{% url "change-respo" club.name member.id %}"></a></td>
</tr>
{% endfor %}
</table>
{% else %}
Ce club ne comporte actuellement aucun membre.
{% endif %}
{% endblock %}

View file

@ -8,11 +8,11 @@ from django.contrib.messages.storage.base import Message
from django.core import mail
from django.core.mail import EmailMessage
from django.template import loader
from django.test import Client, TestCase, override_settings
from django.test import TestCase, override_settings
from django.urls import reverse
from bda.models import Salle, Tirage
from gestioncof.models import CalendarSubscription, Club, Event, Survey, SurveyAnswer
from gestioncof.models import CalendarSubscription, Event, Survey, SurveyAnswer
from gestioncof.tests.mixins import MegaHelperMixin, ViewTestCaseMixin
from shared.autocomplete import Clipper, LDAPSearch
from shared.tests.mixins import CSVResponseMixin, ICalMixin, MockLDAPMixin
@ -617,121 +617,6 @@ class ExportMegaRemarksViewTests(MegaHelperMixin, ViewTestCaseMixin, TestCase):
)
class ClubListViewTests(ViewTestCaseMixin, TestCase):
url_name = "liste-clubs"
url_expected = "/gestion/clubs/liste"
auth_user = "member"
auth_forbidden = [None, "user"]
def setUp(self):
super().setUp()
self.c1 = Club.objects.create(name="Club1")
self.c2 = Club.objects.create(name="Club2")
m = self.users["member"]
self.c1.membres.add(m)
self.c1.respos.add(m)
def test_as_member(self):
r = self.client.get(self.url)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.context["owned_clubs"].get(), self.c1)
self.assertEqual(r.context["other_clubs"].get(), self.c2)
def test_as_staff(self):
u = self.users["staff"]
c = Client()
c.force_login(u, backend="django.contrib.auth.backends.ModelBackend")
r = c.get(self.url)
self.assertEqual(r.status_code, 200)
self.assertQuerysetEqual(
r.context["owned_clubs"],
map(repr, [self.c1, self.c2]),
transform=repr,
ordered=False,
)
class ClubMembersViewTests(ViewTestCaseMixin, TestCase):
url_name = "membres-club"
auth_user = "staff"
auth_forbidden = [None, "user", "member"]
@property
def url_kwargs(self):
return {"name": self.c.name}
@property
def url_expected(self):
return "/gestion/clubs/membres/{}".format(self.c.name)
def setUp(self):
super().setUp()
self.u1 = create_user("u1")
self.u2 = create_user("u2")
self.c = Club.objects.create(name="Club")
self.c.membres.add(self.u1, self.u2)
self.c.respos.add(self.u1)
def test_as_staff(self):
r = self.client.get(self.url)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.context["members_no_respo"].get(), self.u2)
def test_as_respo(self):
u = self.users["user"]
self.c.respos.add(u)
c = Client()
c.force_login(u, backend="django.contrib.auth.backends.ModelBackend")
r = c.get(self.url)
self.assertEqual(r.status_code, 200)
class ClubChangeRespoViewTests(ViewTestCaseMixin, TestCase):
url_name = "change-respo"
auth_user = "staff"
auth_forbidden = [None, "user", "member"]
@property
def url_kwargs(self):
return {"club_name": self.c.name, "user_id": self.users["user"].pk}
@property
def url_expected(self):
return "/gestion/clubs/change_respo/{}/{}".format(
self.c.name, self.users["user"].pk
)
def setUp(self):
super().setUp()
self.c = Club.objects.create(name="Club")
def test(self):
u = self.users["user"]
expected_redirect = reverse("membres-club", kwargs={"name": self.c.name})
self.c.membres.add(u)
r = self.client.get(self.url)
self.assertRedirects(r, expected_redirect)
self.assertIn(u, self.c.respos.all())
self.client.get(self.url)
self.assertNotIn(u, self.c.respos.all())
class CalendarViewTests(ViewTestCaseMixin, TestCase):
url_name = "calendar"
url_expected = "/gestion/calendar/subscription"

View file

@ -46,16 +46,6 @@ calendar_patterns = [
path("<slug:token>/calendar.ics", views.calendar_ics, name="calendar.ics"),
]
clubs_patterns = [
path("membres/<slug:name>", views.membres_club, name="membres-club"),
path("liste", views.liste_clubs, name="liste-clubs"),
path(
"change_respo/<slug:club_name>/<int:user_id>",
views.change_respo,
name="change-respo",
),
]
registration_patterns = [
# Inscription d'un nouveau membre
path("", views.registration, name="registration"),
@ -171,10 +161,6 @@ urlpatterns = [
# -----
path("calendar/", include(calendar_patterns)),
# -----
# Clubs
# -----
path("clubs/", include(clubs_patterns)),
# -----
# Sympa export
# -----
path("sympa/", include(sympa_patterns)),

View file

@ -3,6 +3,7 @@ import uuid
from datetime import timedelta
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
from cof_clubs.forms import ClubsForm
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
@ -35,7 +36,6 @@ from gestioncof.decorators import (
)
from gestioncof.forms import (
CalendarForm,
ClubsForm,
EventForm,
EventFormset,
EventStatusFilterForm,
@ -54,7 +54,6 @@ from gestioncof.forms import (
)
from gestioncof.models import (
CalendarSubscription,
Club,
CofProfile,
Event,
EventCommentField,
@ -426,7 +425,10 @@ def profile(request):
user_form.save()
profile_form.save()
messages.success(request, _("Votre profil a été mis à jour avec succès !"))
context = {"user_form": user_form, "profile_form": profile_form}
context = {
"user_form": user_form,
"profile_form": profile_form,
}
return render(request, "gestioncof/profile.html", context)
@ -501,7 +503,12 @@ def registration_form2(request, login_clipper=None, username=None, fullname=None
current_registrations=current_registrations,
)
# Clubs
clubs_form = ClubsForm(initial={"clubs": member.clubs.all()})
clubs_form = ClubsForm(
initial={
"respo": member.respo_set.all(),
"budget_manager": member.managed_budgets_set.all(),
}
)
else:
profile_form = RegistrationKFProfileForm(instance=profile)
registration_set_ro_fields(user_form, profile_form)
@ -624,13 +631,9 @@ def registration(request):
) = EventRegistration.objects.get_or_create(
user=member, event=form.event
)
update_event_form_comments(
form.event, form, current_registration
)
update_event_form_comments(form.event, form, current_registration)
current_registration.options.set(all_choices)
current_registration.paid = (
form.cleaned_data["status"] == "paid"
)
current_registration.paid = form.cleaned_data["status"] == "paid"
current_registration.save()
# if form.event.title == "Mega 15" and created_reg:
# field = EventCommentField.objects.get(
@ -647,9 +650,13 @@ def registration(request):
# possibilité d'associer un mail aux événements
# send_custom_mail(...)
# Enregistrement des inscriptions aux clubs
member.clubs.clear()
for club in clubs_form.cleaned_data["clubs"]:
club.membres.add(member)
member.respo_set.clear()
member.managed_budgets_set.clear()
for club in clubs_form.cleaned_data["respo"]:
club.respos.add(member)
club.save()
for club in clubs_form.cleaned_data["budget_manager"]:
club.budget_managers.add(member)
club.save()
# ---
@ -752,48 +759,6 @@ def self_kf_registration(request):
# -----
@login_required
def membres_club(request, name):
# Vérification des permissions : l'utilisateur doit être membre du burô
# ou respo du club.
user = request.user
club = get_object_or_404(Club, name=name)
if not request.user.profile.is_buro and club not in user.clubs_geres.all():
return HttpResponseForbidden("<h1>Permission denied</h1>")
members_no_respo = club.membres.exclude(clubs_geres=club).all()
return render(
request,
"membres_clubs.html",
{"club": club, "members_no_respo": members_no_respo},
)
@buro_required
def change_respo(request, club_name, user_id):
club = get_object_or_404(Club, name=club_name)
user = get_object_or_404(User, id=user_id)
if user in club.respos.all():
club.respos.remove(user)
elif user in club.membres.all():
club.respos.add(user)
else:
raise Http404
return redirect("membres-club", name=club_name)
@cof_required
def liste_clubs(request):
clubs = Club.objects
if request.user.profile.is_buro:
data = {"owned_clubs": clubs.all()}
else:
data = {
"owned_clubs": request.user.clubs_geres.all(),
"other_clubs": clubs.exclude(respos=request.user),
}
return render(request, "liste_clubs.html", data)
@buro_required
def export_members(request):
response = HttpResponse(content_type="text/csv")

9780
shared/static/bulma.css vendored Normal file

File diff suppressed because it is too large Load diff

1
shared/static/css.map Normal file

File diff suppressed because one or more lines are too long

21
shared/static/vendor/bulma/LICENSE vendored Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2023 Jeremy Thomas
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.

145
shared/static/vendor/bulma/README.md vendored Normal file
View file

@ -0,0 +1,145 @@
# [Bulma](https://bulma.io)
Bulma is a **modern CSS framework** based on [Flexbox](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Using_CSS_flexible_boxes).
![Github](https://img.shields.io/github/v/release/jgthms/bulma?logo=Bulma)
[![npm](https://img.shields.io/npm/v/bulma.svg)][npm-link]
[![npm](https://img.shields.io/npm/dm/bulma.svg)][npm-link]
[![](https://data.jsdelivr.com/v1/package/npm/bulma/badge)](https://www.jsdelivr.com/package/npm/bulma)
[![Awesome][awesome-badge]][awesome-link]
[![Join the chat at https://gitter.im/jgthms/bulma](https://badges.gitter.im/jgthms/bulma.svg)](https://gitter.im/jgthms/bulma)
[![Build Status](https://travis-ci.org/jgthms/bulma.svg?branch=master)](https://travis-ci.org/jgthms/bulma)
<a href="https://bulma.io"><img src="https://raw.githubusercontent.com/jgthms/bulma/master/docs/images/bulma-banner.png" alt="Bulma: a Flexbox CSS framework" style="max-width:100%;" width="600"></a>
## Quick install
Bulma is constantly in development! Try it out now:
### NPM
```sh
npm install bulma
```
**or**
### Yarn
```sh
yarn add bulma
```
### Bower
```sh
bower install bulma
```
### Import
After installation, you can import the CSS file into your project using this snippet:
```sh
@import 'bulma/css/bulma.css'
```
### CDN
[https://www.jsdelivr.com/package/npm/bulma](https://www.jsdelivr.com/package/npm/bulma)
Feel free to raise an issue or submit a pull request.
## CSS only
Bulma is a **CSS** framework. As such, the sole output is a single CSS file: [bulma.css](https://github.com/jgthms/bulma/blob/main/css/bulma.css)
You can either use that file, "out of the box", or download the Sass source files to customize the [variables](https://bulma.io/documentation/customize/#docsNav).
There is **no** JavaScript included. People generally want to use their own JS implementation (and usually already have one). Bulma can be considered "environment agnostic": it's just the style layer on top of the logic.
## Browser Support
Bulma uses [autoprefixer](https://github.com/postcss/autoprefixer) to make (most) Flexbox features compatible with earlier browser versions. According to [Can I use](https://caniuse.com/#feat=flexbox), Bulma is compatible with **recent** versions of:
- Chrome
- Edge
- Firefox
- Opera
- Safari
Internet Explorer (10+) is only partially supported.
## Documentation
The documentation resides in the [docs](docs) directory, and is built with the Ruby-based [Jekyll](https://jekyllrb.com/) tool.
Browse the [online documentation here.](https://bulma.io/documentation/start/overview/)
## Related projects
| Project | Description |
| ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- |
| [Bulma with Attribute Modules](https://github.com/j5bot/bulma-attribute-selectors) | Adds support for attribute-based selectors |
| [Bulma with Rails](https://github.com/joshuajansen/bulma-rails) | Integrates Bulma with the rails asset pipeline |
| [BulmaRazor](https://github.com/loogn/bulmarazor) | A lightweight component library based on Bulma and Blazor. |
| [Vue Admin (dead)](https://github.com/vue-bulma/vue-admin) | Vue Admin framework powered by Bulma |
| [Bulmaswatch](https://github.com/jenil/bulmaswatch) | Free themes for Bulma |
| [Goldfish (read-only)](https://github.com/Caiyeon/goldfish) | Vault UI with Bulma, Golang, and Vue Admin |
| [ember-bulma](https://github.com/open-tux/ember-bulma) | Ember addon providing a collection of UI components for Bulma |
| [Bloomer](https://bloomer.js.org) | A set of React components for Bulma |
| [React-bulma](https://github.com/kulakowka/react-bulma) | React.js components for Bulma |
| [Buefy](https://buefy.org/) | Lightweight UI components for Vue.js based on Bulma |
| [vue-bulma-components](https://github.com/vouill/vue-bulma-components) | Bulma components for Vue.js with straightforward syntax |
| [BulmaJS](https://github.com/VizuaaLOG/BulmaJS) | Javascript integration for Bulma. Written in ES6 with a data-\* API |
| [Bulma-modal-fx](https://github.com/postare/bulma-modal-fx) | A set of modal window effects with CSS transitions and animations for Bulma |
| [Bulma Stylus](https://github.com/groenroos/bulma-stylus) | Up-to-date 1:1 translation to Stylus |
| [Bulma.styl (read-only)](https://github.com/log1x/bulma.styl) | 1:1 Stylus translation of Bulma 0.6.11 |
| [elm-bulma](https://github.com/surprisetalk/elm-bulma) | Bulma + Elm |
| [elm-bulma-classes](https://github.com/ahstro/elm-bulma-classes) | Bulma classes prepared for usage with Elm |
| [Bulma Customizer](https://bulma-customizer.bstash.io/) | Bulma Customizer &#8211; Create your own **bespoke** Bulma build |
| [Fulma](https://fulma.github.io/Fulma/) | Wrapper around Bulma for [fable-react](https://github.com/fable-compiler/fable-react) |
| [Laravel Enso](https://github.com/laravel-enso/enso) | SPA Admin Panel built with Bulma, VueJS and Laravel |
| [Django Bulma](https://github.com/timonweb/django-bulma) | Integrates Bulma with Django |
| [Bulma Templates](https://github.com/dansup/bulma-templates) | Free Templates for Bulma |
| [React Bulma Components](https://github.com/couds/react-bulma-components) | Another React wrap on React for Bulma.io |
| [purescript-bulma](https://github.com/sectore/purescript-bulma) | PureScript bindings for Bulma |
| [Vue Datatable](https://github.com/laravel-enso/vuedatatable) | Bulma themed datatable based on Vue, Laravel & JSON templates |
| [bulma-fluent](https://mubaidr.github.io/bulma-fluent/) | Fluent Design Theme for Bulma inspired by Microsofts Fluent Design System |
| [csskrt-csskrt](https://github.com/4d11/csskrt-csskrt) | Automatically add Bulma classes to HTML files |
| [bulma-pagination-react](https://github.com/hipstersmoothie/bulma-pagination-react) | Bulma pagination as a react component |
| [bulma-helpers](https://github.com/jmaczan/bulma-helpers) | Functional / Atomic CSS classes for Bulma |
| [bulma-swatch-hook](https://github.com/hipstersmoothie/bulma-swatch-hook) | Bulma swatches as a react hook and a component |
| [BulmaWP (read-only)](https://github.com/tomhrtly/BulmaWP) | Starter WordPress theme for Bulma |
| [Ralma](https://github.com/aldi/ralma) | Stateless Ractive.js Components for Bulma |
| [Django Simple Bulma](https://github.com/python-discord/django-simple-bulma) | Lightweight integration of Bulma and Bulma-Extensions for your Django app |
| [rbx](https://dfee.github.io/rbx) | Comprehensive React UI Framework written in TypeScript |
| [Awesome Bulma Templates](https://github.com/aldi/awesome-bulma-templates) | Free real-world Templates built with Bulma |
| [Trunx](https://github.com/fibo/trunx) | Super Saiyan React components, son of awesome Bulma |
| [@aybolit/bulma](https://github.com/web-padawan/aybolit/tree/master/packages/bulma) | Web Components library inspired by Bulma and Bulma-extensions |
| [Drulma](https://www.drupal.org/project/drulma) | Drupal theme for Bulma. |
| [Bulrush](https://github.com/textbook/bulrush) | A Bulma-based Python Pelican blog theme |
| [Bulma Variable Export](https://github.com/service-paradis/bulma-variables-export) | Access Bulma Variables in Javascript/Typescript in project using Webpack |
| [Bulmil](https://github.com/gomah/bulmil) | An agnostic UI components library based on Web Components, made with Bulma & Stencil. |
| [Svelte Bulma Components](https://github.com/elcobvg/svelte-bulma-components) | Library of UI components to be used in [Svelte.js](https://svelte.technology/) or standalone. |
| [Bulma Nunjucks Starterkit](https://github.com/benninkcorien/nunjucks-starter-kit) | Starterkit for Nunjucks with Bulma. |
| [Bulma-Social](https://github.com/aldi/bulma-social) | Social Buttons and Colors for Bulma |
| [Divjoy](https://divjoy.com/?kit=bulma) | React codebase generator with Bulma templates |
| [Blazorise](https://github.com/Megabit/Blazorise) | Blazor component library with the support for Bulma CSS framework |
| [Oruga-Bulma](https://github.com/oruga-ui/theme-bulma) | Bulma theme for [Oruga UI](https://oruga.io) |
| [@bulvar/bulma](https://github.com/daniil4udo/bulvar/tree/master/packages/bulma) | Bulma with [CSS Variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) support |
| [@angular-bulma](https://quinnjr.github.io/angular-bulma) | [Angular](https://angular.io/) directives and components to use in your Bulma projects |
| [Bulma CSS Class Completion](https://github.com/eliutdev/bulma-css-class-completion) | CSS class name completion for the HTML class attribute based on Bulma CSS classes. |
| [Crispy-Bulma](https://github.com/ckrybus/crispy-bulma) | Bulma template pack for django-crispy-forms |
| [Manifest](https://manifest.build) | Manifest is a lightweight Backend-as-a-Service with essential features: DB, Admin panel, API, JS SDK |
| [Reactive Bulma](https://github.com/NicolasOmar/reactive-bulma) | A component library based on React, Bulma, Typescript and Rollup |
<p>Browser testing via<br /><a href="https://www.lambdatest.com/" target="_blank"><img src="https://bulma.io/assets/images/amis/lambdatest-logo.png" width="168" height="40" /></a></p>
## Copyright and license ![Github](https://img.shields.io/github/license/jgthms/bulma?logo=Github)
Code copyright 2023 Jeremy Thomas. Code released under [the MIT license](https://github.com/jgthms/bulma/blob/main/LICENSE).
[npm-link]: https://www.npmjs.com/package/bulma
[awesome-link]: https://github.com/awesome-css-group/awesome-css
[awesome-badge]: https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg

4
shared/static/vendor/bulma/bulma.scss vendored Normal file
View file

@ -0,0 +1,4 @@
@charset "utf-8";
/*! bulma.io v1.0.3 | MIT License | github.com/jgthms/bulma */
@use "sass";

21559
shared/static/vendor/bulma/css/bulma.css vendored Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

62
shared/static/vendor/bulma/package.json vendored Normal file
View file

@ -0,0 +1,62 @@
{
"name": "bulma",
"version": "1.0.3",
"homepage": "https://bulma.io",
"author": {
"name": "Jeremy Thomas",
"email": "bbxdesign@gmail.com",
"url": "https://jgthms.com"
},
"description": "Modern CSS framework based on Flexbox",
"main": "bulma.scss",
"unpkg": "css/bulma.css",
"style": "css/bulma.min.css",
"repository": {
"type": "git",
"url": "git+https://github.com/jgthms/bulma.git"
},
"license": "MIT",
"keywords": [
"css",
"sass",
"scss",
"flexbox",
"grid",
"responsive",
"framework"
],
"bugs": {
"url": "https://github.com/jgthms/bulma/issues"
},
"devDependencies": {
"cssnano": "^7.0.6",
"postcss-cli": "^11.0.0",
"prettier": "^3.4.2",
"rimraf": "^6.0.1",
"sass": "^1.83.0"
},
"scripts": {
"build-bulma": "sass --style=expanded --source-map bulma.scss css/bulma.css",
"minify-bulma": "postcss css/bulma.css --no-map --use cssnano --output css/bulma.min.css",
"version-no-dark-mode": "sass --style=expanded --source-map versions/bulma-no-dark-mode.scss css/versions/bulma-no-dark-mode.css",
"version-no-helpers": "sass --style=expanded --source-map versions/bulma-no-helpers.scss css/versions/bulma-no-helpers.css",
"version-no-helpers-prefixed": "sass --style=expanded --source-map versions/bulma-no-helpers-prefixed.scss css/versions/bulma-no-helpers-prefixed.css",
"version-prefixed": "sass --style=expanded --source-map versions/bulma-prefixed.scss css/versions/bulma-prefixed.css",
"build-versions": "npm run version-no-dark-mode && npm run version-no-helpers && npm run version-no-helpers-prefixed && npm run version-prefixed",
"minify-versions": "postcss css/versions/*.css --dir css/versions --ext min.css --no-map --use cssnano",
"build-all": "npm run build-bulma && npm run build-versions",
"minify-all": "npm run minify-bulma && npm run minify-versions",
"clean": "rimraf css",
"deploy": "npm run clean && npm run build-all && npm run minify-all",
"test": "sass --style=expanded --source-map --watch test.scss test.css",
"start": "npm run build-bulma -- --watch"
},
"files": [
"css",
"sass",
"versions",
"bulma.scss",
"LICENSE",
"README.md"
]
}

View file

@ -12,6 +12,7 @@ let
inherit (nix-pkgs)
authens
django-bootstrap-form
django-bulma-forms
django-cas-ng
loadcredential
;
@ -52,6 +53,7 @@ pkgs.mkShell {
django
django-autocomplete-light
django-bootstrap-form
django-bulma-forms
django-cas-ng
django-cors-headers
django-djconfig