cof: Staging is finished #852

Closed
lbailly wants to merge 10 commits from cof-staging into master
11 changed files with 125 additions and 10 deletions

View file

@ -1,6 +1,7 @@
from django.urls import path from django.urls import path
from bds import views from bds import views
from shared.views import SympaListView
app_name = "bds" app_name = "bds"
urlpatterns = [ urlpatterns = [
@ -21,4 +22,10 @@ urlpatterns = [
name="members.expired", name="members.expired",
), ),
path("members/reset", views.ResetMembershipView.as_view(), name="members.reset"), path("members/reset", views.ResetMembershipView.as_view(), name="members.reset"),
# Sympa export view
path(
"sympa/members/",
SympaListView.as_view(filters={"bds__is_member": True}),
name="export.sympa",
),
] ]

View file

@ -27,6 +27,9 @@ ALLOWED_HOSTS = []
DEBUG = True DEBUG = True
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
SYMPA_PASSWORD = b"sympa"
SYMPA_USERNAME = b"sympa"
if TESTING: if TESTING:
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"] PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]

View file

@ -26,6 +26,9 @@ EMAIL_HOST = credentials.get("EMAIL_HOST")
DEFAULT_AUTO_FIELD = "django.db.models.AutoField" DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
SYMPA_PASSWORD = credentials["SYMPA_PASSWORD"].encode()
SYMPA_USERNAME = credentials["SYMPA_USERNAME"].encode()
## ##
# Installed Apps configuration # Installed Apps configuration

View file

@ -30,6 +30,9 @@ LDAP_SERVER_URL = credentials.get("LDAP_SERVER_URL")
DEFAULT_AUTO_FIELD = "django.db.models.AutoField" DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
SYMPA_PASSWORD = credentials["SYMPA_PASSWORD"].encode()
SYMPA_USERNAME = credentials["SYMPA_USERNAME"].encode()
## ##
# Installed Apps configuration # Installed Apps configuration
@ -201,7 +204,10 @@ CHANNEL_LAYERS = credentials.get_json(
}, },
) )
CORS_ORIGIN_WHITELIST = credentials.get("CORS_ORIGIN_WHITELIST", []) ASGI_APPLICATION = "gestioasso.routing.application"
CORS_ALLOWED_ORIGINS = credentials.get("CORS_ALLOWED_ORIGINS", [])
CSRF_TRUSTED_ORIGINS = [f"https://{host}" for host in ALLOWED_HOSTS]
SITE_ID = 1 SITE_ID = 1

View file

@ -22,10 +22,10 @@
<section class="actulist"> <section class="actulist">
{% if actus.has_previous %} {% if actus.has_previous %}
<a class="block prev-actus" href="?page={{ actus.previous_page_number }}{% for key,value in request.GET.items %}{% ifnotequal key 'page' %}&amp;{{ key }}={{ value }}{% endifnotequal %}{% endfor %}">{% trans "Actualités plus récentes" %}</a> <a class="block prev-actus" href="?page={{ actus.previous_page_number }}{% for key,value in request.GET.items %}{% if key != 'page' %}&amp;{{ key }}={{ value }}{% endif %}{% endfor %}">{% trans "Actualités plus récentes" %}</a>
{% endif %} {% endif %}
{% if actus.has_next %} {% if actus.has_next %}
<a class="block next-actus" href="?page={{ actus.next_page_number }}{% for key,value in request.GET.items %}{% ifnotequal key 'page' %}&amp;{{ key }}={{ value }}{% endifnotequal %}{% endfor %}">{% trans "Actualités plus anciennes" %}</a> <a class="block next-actus" href="?page={{ actus.next_page_number }}{% for key,value in request.GET.items %}{% if key != 'page' %}&amp;{{ key }}={{ value }}{% endif %}{% endfor %}">{% trans "Actualités plus anciennes" %}</a>
{% endif %} {% endif %}
{% for actu in page.actus %} {% for actu in page.actus %}
@ -44,10 +44,10 @@
{% endfor %} {% endfor %}
{% if actus.has_previous %} {% if actus.has_previous %}
<a class="block prev-actus" href="?page={{ actus.previous_page_number }}{% for key,value in request.GET.items %}{% ifnotequal key 'page' %}&amp;{{ key }}={{ value }}{% endifnotequal %}{% endfor %}">{% trans "Actualités plus récentes" %}</a> <a class="block prev-actus" href="?page={{ actus.previous_page_number }}{% for key,value in request.GET.items %}{% if key != 'page' %}&amp;{{ key }}={{ value }}{% endif %}{% endfor %}">{% trans "Actualités plus récentes" %}</a>
{% endif %} {% endif %}
{% if actus.has_next %} {% if actus.has_next %}
<a class="block next-actus" href="?page={{ actus.next_page_number }}{% for key,value in request.GET.items %}{% ifnotequal key 'page' %}&amp;{{ key }}={{ value }}{% endifnotequal %}{% endfor %}">{% trans "Actualités plus anciennes" %}</a> <a class="block next-actus" href="?page={{ actus.next_page_number }}{% for key,value in request.GET.items %}{% if key != 'page' %}&amp;{{ key }}={{ value }}{% endif %}{% endfor %}">{% trans "Actualités plus anciennes" %}</a>
{% endif %} {% endif %}
</section> </section>
{% endblock %} {% endblock %}

View file

@ -4,6 +4,16 @@ from django.views.generic.base import TemplateView
from django_cas_ng import views as django_cas_views from django_cas_ng import views as django_cas_views
from gestioncof import csv_views, views from gestioncof import csv_views, views
from shared.views import SympaListView
sympa_patterns = [
path(
f"{mailing}/",
SympaListView.as_view(filters={f"profile__mailing_{mailing}": True}),
name=f"sympa.{mailing}",
)
for mailing in ["bda", "bda_revente", "cof", "unernestaparis"]
]
export_patterns = [ export_patterns = [
path("members", views.export_members, name="export.members"), path("members", views.export_members, name="export.members"),
@ -162,4 +172,8 @@ urlpatterns = [
# Clubs # Clubs
# ----- # -----
path("clubs/", include(clubs_patterns)), path("clubs/", include(clubs_patterns)),
# -----
# Sympa export
# -----
path("sympa/", include(sympa_patterns)),
] ]

View file

@ -1,3 +1,5 @@
from asgiref.sync import sync_to_async
from ..decorators import kfet_is_team from ..decorators import kfet_is_team
from ..utils import DjangoJsonWebsocketConsumer, PermConsumerMixin from ..utils import DjangoJsonWebsocketConsumer, PermConsumerMixin
from .open import kfet_open from .open import kfet_open
@ -19,7 +21,7 @@ class OpenKfetConsumer(PermConsumerMixin, DjangoJsonWebsocketConsumer):
"""Send current status on connect.""" """Send current status on connect."""
await super().connect() await super().connect()
group = "team" if kfet_is_team(self.user) else "base" group = "team" if await sync_to_async(kfet_is_team)(self.user) else "base"
await self.channel_layer.group_add(f"kfet.open.{group}", self.channel_name) await self.channel_layer.group_add(f"kfet.open.{group}", self.channel_name)

View file

@ -31,6 +31,28 @@
{% endif %} {% endif %}
</div> </div>
<aside>
<div class="heading">
{{ positive_count }}
<span class="sub">compte{{ positive_count|pluralize }} en positif</span>
</div>
<div class="heading">
{{ positives_sum|floatformat:2 }}€
<span class="sub">de positif total</span>
</div>
</aside>
<aside>
<div class="heading">
{{ negative_count }}
<span class="sub">compte{{ negative_count|pluralize }} en négatif</span>
</div>
<div class="heading">
{{ negatives_sum|floatformat:2 }}€
<span class="sub">de négatif total</span>
</div>
</aside>
{% endblock %} {% endblock %}
{% block main %} {% block main %}

View file

@ -1,6 +1,7 @@
import json import json
import math import math
from asgiref.sync import sync_to_async
from channels.generic.websocket import AsyncJsonWebsocketConsumer from channels.generic.websocket import AsyncJsonWebsocketConsumer
from django.core.cache import cache from django.core.cache import cache
from django.core.serializers.json import DjangoJSONEncoder from django.core.serializers.json import DjangoJSONEncoder
@ -95,7 +96,7 @@ class PermConsumerMixin:
"""Check permissions on connection.""" """Check permissions on connection."""
self.user = self.scope["user"] self.user = self.scope["user"]
if self.user.has_perms(self.perms_connect): if await sync_to_async(self.user.has_perms)(self.perms_connect):
await super().connect() await super().connect()
else: else:
await self.close() await self.close()

View file

@ -184,7 +184,16 @@ class DemandeSoireeView(FormView):
@teamkfet_required @teamkfet_required
def account(request): def account(request):
accounts = Account.objects.select_related("cofprofile__user").order_by("trigramme") accounts = Account.objects.select_related("cofprofile__user").order_by("trigramme")
return render(request, "kfet/account.html", {"accounts": accounts}) positive_accounts = Account.objects.filter(balance__gte=0).exclude(trigramme="#13")
negative_accounts = Account.objects.filter(balance__lt=0).exclude(trigramme="#13")
return render(request, "kfet/account.html", {
"accounts": accounts,
"positive_count": positive_accounts.count(),
"positives_sum": sum(acc.balance for acc in positive_accounts),
"negative_count": negative_accounts.count(),
"negatives_sum": sum(acc.balance for acc in negative_accounts),
})
@login_required @login_required
@ -1171,6 +1180,13 @@ def kpsul_perform_operations(request):
operationgroup.amount += operation.amount operationgroup.amount += operation.amount
if operation.type == Operation.DEPOSIT: if operation.type == Operation.DEPOSIT:
required_perms.add("kfet.perform_deposit") required_perms.add("kfet.perform_deposit")
if request.user.profile.account_kfet == on_acc:
data["errors"].append(
{
"code": "auto_deposit",
"message": ("Impossible de charger son propre trigramme"),
}
)
if operation.type == Operation.EDIT: if operation.type == Operation.EDIT:
required_perms.add("kfet.edit_balance_account") required_perms.add("kfet.edit_balance_account")
need_comment = True need_comment = True

View file

@ -1,12 +1,53 @@
import base64
from collections import namedtuple from collections import namedtuple
from typing import Any
from dal import autocomplete from dal import autocomplete
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.http import Http404 from django.http import Http404, HttpResponse
from django.views.generic import TemplateView from django.views.generic import TemplateView, View
from shared.autocomplete import ModelSearch from shared.autocomplete import ModelSearch
User = get_user_model()
class SympaListView(View):
realm = "sympa"
username = settings.SYMPA_USERNAME
password = settings.SYMPA_PASSWORD
filters: dict[str, Any] = {}
def dispatch(self, request, *args, **kwargs):
if "HTTP_AUTHORIZATION" in request.META:
auth = request.META["HTTP_AUTHORIZATION"].split()
if len(auth) == 2 and auth[0].lower() == "basic":
name, passwd = base64.b64decode(auth[1]).split(b":")
if name == self.username and passwd == self.password:
return self.render_to_response(request, *args, **kwargs)
return HttpResponse(
status=401, headers={"WWW-Authenticate": f'Basic realm="{self.realm}"'}
)
def render_to_response(self, request, *args, **kwargs):
"""
Renders a list of emails in a text response.
"""
users = User.objects.filter(**self.filters)
return HttpResponse(
b"\n".join(u.email.encode("utf-8") for u in users if u.email),
content_type="text/plain",
)
class Select2QuerySetView(ModelSearch, autocomplete.Select2QuerySetView): class Select2QuerySetView(ModelSearch, autocomplete.Select2QuerySetView):
"""Compatibility layer between ModelSearch and Select2QuerySetView.""" """Compatibility layer between ModelSearch and Select2QuerySetView."""