remove laundry

This commit is contained in:
Thomas Girod 2025-04-16 10:47:03 +02:00
parent 4fa83d0667
commit b9ee44ca9c
25 changed files with 24 additions and 1139 deletions

View File

@ -16,22 +16,13 @@
</ul> </ul>
<h4>{% trans %}Counters:{% endtrans %}</h4> <h4>{% trans %}Counters:{% endtrans %}</h4>
<ul> <ul>
{% if object.id == settings.SITH_LAUNDERETTE_CLUB_ID %} {% for c in object.counters.filter(type="OFFICE") %}
{% for l in Launderette.objects.all() %} <li>{{ c }}:
<li><a href="{{ url('launderette:main_click', launderette_id=l.id) }}">{{ l }}</a></li> <a href="{{ url('counter:details', counter_id=c.id) }}">View</a>
{% endfor %} <a href="{{ url('counter:admin', counter_id=c.id) }}">Edit</a>
{% elif object.counters.filter(type="OFFICE")|count > 0 %} </li>
{% for c in object.counters.filter(type="OFFICE") %} {% endfor %}
<li>{{ c }}:
<a href="{{ url('counter:details', counter_id=c.id) }}">View</a>
<a href="{{ url('counter:admin', counter_id=c.id) }}">Edit</a>
</li>
{% endfor %}
{% endif %}
</ul> </ul>
{% if object.id == settings.SITH_LAUNDERETTE_CLUB_ID %}
<li><a href="{{ url('launderette:launderette_list') }}">{% trans %}Manage launderettes{% endtrans %}</a></li>
{% endif %}
</div> </div>
{% endblock %} {% endblock %}

View File

@ -123,11 +123,6 @@ class Command(BaseCommand):
name="PdF", name="PdF",
address="6 Boulevard Anatole France, 90000 Belfort", address="6 Boulevard Anatole France, 90000 Belfort",
) )
Club.objects.create(
id=settings.SITH_LAUNDERETTE_CLUB_ID,
name="Laverie",
address="6 Boulevard Anatole France, 90000 Belfort",
)
self.reset_index("club") self.reset_index("club")
for bar_id, bar_name in settings.SITH_COUNTER_BARS: for bar_id, bar_name in settings.SITH_COUNTER_BARS:
@ -291,13 +286,7 @@ class Command(BaseCommand):
page=services_page, page=services_page,
title="Services", title="Services",
author=skia, author=skia,
content=""" content="- [Eboutic](/eboutic)\n- Matmat\n- SAS\n- Weekmail\n- Forum",
| | | |
| :---: | :---: | :---: |
| [Eboutic](/eboutic) | [Laverie](/launderette) | Matmat |
| SAS | Weekmail | Forum|
""",
) )
index_page = Page(name="Index") index_page = Page(name="Index")
@ -306,23 +295,10 @@ class Command(BaseCommand):
page=index_page, page=index_page,
title="Wiki index", title="Wiki index",
author=root, author=root,
content=""" content="Welcome to the wiki page!",
Welcome to the wiki page!
""",
) )
laundry_page = Page(name="launderette") groups.public.viewable_page.set([syntax_page, services_page, index_page])
laundry_page.save(force_lock=True)
PageRev.objects.create(
page=laundry_page,
title="Laverie",
author=root,
content="Fonctionnement de la laverie",
)
groups.public.viewable_page.set(
[syntax_page, services_page, index_page, laundry_page]
)
self._create_subscription(root) self._create_subscription(root)
self._create_subscription(skia) self._create_subscription(skia)
@ -868,7 +844,7 @@ Welcome to the wiki page!
counter_admin.permissions.add( counter_admin.permissions.add(
*list( *list(
perms.filter( perms.filter(
Q(content_type__app_label__in=["counter", "launderette"]) Q(content_type__app_label__in=["counter"])
& ~Q(codename__in=["delete_product", "delete_producttype"]) & ~Q(codename__in=["delete_product", "delete_producttype"])
) )
) )

View File

@ -421,14 +421,6 @@ class User(AbstractUser):
def is_board_member(self) -> bool: def is_board_member(self) -> bool:
return self.groups.filter(club_board=settings.SITH_MAIN_CLUB_ID).exists() return self.groups.filter(club_board=settings.SITH_MAIN_CLUB_ID).exists()
@cached_property
def is_launderette_manager(self):
from club.models import Club
return Club.objects.get(
id=settings.SITH_LAUNDERETTE_CLUB_ID
).get_membership_for(self)
@cached_property @cached_property
def is_banned_alcohol(self) -> bool: def is_banned_alcohol(self) -> bool:
return self.ban_groups.filter(id=settings.SITH_GROUP_BANNED_ALCOHOL_ID).exists() return self.ban_groups.filter(id=settings.SITH_GROUP_BANNED_ALCOHOL_ID).exists()
@ -676,10 +668,6 @@ class AnonymousUser(AuthAnonymousUser):
def is_board_member(self): def is_board_member(self):
return False return False
@property
def is_launderette_manager(self):
return False
@property @property
def is_banned_alcohol(self): def is_banned_alcohol(self):
return False return False

View File

@ -822,12 +822,6 @@ textarea {
margin-top: 10px; margin-top: 10px;
} }
/*---------------------------LAUNDERETTE-------------------------------*/
#token_form label {
display: inline;
}
/*--------------------------------FOOTER-------------------------------*/ /*--------------------------------FOOTER-------------------------------*/
footer { footer {

View File

@ -24,7 +24,6 @@
<span class="head">{% trans %}Services{% endtrans %}</span> <span class="head">{% trans %}Services{% endtrans %}</span>
<ul class="content"> <ul class="content">
<li><a href="{{ url('matmat:search_clear') }}">{% trans %}Matmatronch{% endtrans %}</a></li> <li><a href="{{ url('matmat:search_clear') }}">{% trans %}Matmatronch{% endtrans %}</a></li>
<li><a href="/launderette">{% trans %}Launderette{% endtrans %}</a></li>
<li><a href="{{ url('core:file_list') }}">{% trans %}Files{% endtrans %}</a></li> <li><a href="{{ url('core:file_list') }}">{% trans %}Files{% endtrans %}</a></li>
<li><a href="{{ url('pedagogy:guide') }}">{% trans %}Pedagogy{% endtrans %}</a></li> <li><a href="{{ url('pedagogy:guide') }}">{% trans %}Pedagogy{% endtrans %}</a></li>
</ul> </ul>

View File

@ -86,19 +86,6 @@
{% trans %}Account number: {% endtrans %}{{ user.customer.account_id }}<br/> {% trans %}Account number: {% endtrans %}{{ user.customer.account_id }}<br/>
{%- endmacro %} {%- endmacro %}
{% macro show_slots(user) %}
{% if user.slots.filter(start_date__gte=timezone.now()).exists() %}
<h5>{% trans %}Slot{% endtrans %}</h5>
<ul>
{% for i in user.slots.filter(start_date__gte=timezone.now().replace(tzinfo=None)).all() %}
<li>{{ i.get_type_display() }} - {{i.machine.launderette }}, {{ i.start_date|date("l j") }} :
{{ i.start_date|time(DATETIME_FORMAT) }} |
<a href="{{ url('launderette:delete_slot', slot_id=i.id) }}">{% trans %}Delete{% endtrans %}</a></li>
{% endfor %}
</ul>
{% endif %}
{% endmacro %}
{% macro show_tokens(user) %} {% macro show_tokens(user) %}
{% if user.tokens.exists() %} {% if user.tokens.exists() %}
<h5>{% trans %}Tokens{% endtrans %}</h5> <h5>{% trans %}Tokens{% endtrans %}</h5>

View File

@ -141,12 +141,6 @@
{{ user_subscription(profile) }} {{ user_subscription(profile) }}
</div> </div>
{% endif %} {% endif %}
{% if user == profile or user.is_root or user.is_board_member or user.is_launderette_manager %}
<div>
{{ show_tokens(profile) }}
{{ show_slots(profile) }}
</div>
{% endif %}
{% else %} {% else %}
<div> <div>
{% trans %}Not subscribed{% endtrans %} {% trans %}Not subscribed{% endtrans %}

View File

@ -564,10 +564,8 @@ class UserToolsView(LoginRequiredMixin, QuickNotifMixin, UserTabsMixin, Template
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
self.object = self.request.user self.object = self.request.user
from launderette.models import Launderette
kwargs = super().get_context_data(**kwargs) kwargs = super().get_context_data(**kwargs)
kwargs["launderettes"] = Launderette.objects.all()
kwargs["profile"] = self.request.user kwargs["profile"] = self.request.user
kwargs["object"] = self.request.user kwargs["object"] = self.request.user
return kwargs return kwargs

View File

@ -1 +0,0 @@
::: launderette.models

View File

@ -1 +0,0 @@
::: launderette.views

View File

@ -1,39 +0,0 @@
#
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# You can find the source code of the website at https://github.com/ae-utbm/sith
#
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
from django.contrib import admin
from launderette.models import Launderette, Machine, Slot, Token
@admin.register(Launderette)
class LaunderetteAdmin(admin.ModelAdmin):
list_display = ("name", "counter")
@admin.register(Machine)
class MachineAdmin(admin.ModelAdmin):
list_display = ("name", "launderette", "type", "is_working")
@admin.register(Token)
class TokenAdmin(admin.ModelAdmin):
list_display = ("name", "launderette", "type", "user")
autocomplete_fields = ("user",)
@admin.register(Slot)
class SlotAdmin(admin.ModelAdmin):
list_display = ("machine", "user", "start_date")
autocomplete_fields = ("user",)

View File

@ -0,0 +1,14 @@
# Generated by Django 5.2 on 2025-04-15 19:37
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [("launderette", "0001_initial")]
operations = [
migrations.DeleteModel(name="Launderette"),
migrations.DeleteModel(name="Machine"),
migrations.DeleteModel(name="Slot"),
migrations.DeleteModel(name="Token"),
]

View File

@ -1,193 +0,0 @@
#
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# You can find the source code of the website at https://github.com/ae-utbm/sith
#
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
from django.conf import settings
from django.db import DataError, models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from club.models import Club
from core.models import User
from counter.models import Counter
# Create your models here.
class Launderette(models.Model):
name = models.CharField(_("name"), max_length=30)
counter = models.OneToOneField(
Counter,
verbose_name=_("counter"),
related_name="launderette",
on_delete=models.CASCADE,
)
class Meta:
verbose_name = _("Launderette")
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse("launderette:launderette_list")
def is_owned_by(self, user):
"""Method to see if that object can be edited by the given user."""
if user.is_anonymous:
return False
launderette_club = Club.objects.get(id=settings.SITH_LAUNDERETTE_CLUB_ID)
m = launderette_club.get_membership_for(user)
return bool(m and m.role >= 9)
def can_be_edited_by(self, user):
launderette_club = Club.objects.get(id=settings.SITH_LAUNDERETTE_CLUB_ID)
m = launderette_club.get_membership_for(user)
return bool(m and m.role >= 2)
def can_be_viewed_by(self, user):
return user.is_subscribed
def get_machine_list(self):
return Machine.objects.filter(launderette_id=self.id)
def machine_list(self):
return [m.id for m in self.get_machine_list()]
def get_token_list(self):
return Token.objects.filter(launderette_id=self.id)
def token_list(self):
return [t.id for t in self.get_token_list()]
class Machine(models.Model):
name = models.CharField(_("name"), max_length=30)
launderette = models.ForeignKey(
Launderette,
related_name="machines",
verbose_name=_("launderette"),
on_delete=models.CASCADE,
)
type = models.CharField(
_("type"), max_length=10, choices=settings.SITH_LAUNDERETTE_MACHINE_TYPES
)
is_working = models.BooleanField(_("is working"), default=True)
class Meta:
verbose_name = _("Machine")
def __str__(self):
return "%s %s" % (self._meta.verbose_name, self.name)
def get_absolute_url(self):
return reverse(
"launderette:launderette_admin",
kwargs={"launderette_id": self.launderette.id},
)
def is_owned_by(self, user):
"""Method to see if that object can be edited by the given user."""
if user.is_anonymous:
return False
launderette_club = Club.objects.get(id=settings.SITH_LAUNDERETTE_CLUB_ID)
m = launderette_club.get_membership_for(user)
return bool(m and m.role >= 9)
class Token(models.Model):
name = models.CharField(_("name"), max_length=5)
launderette = models.ForeignKey(
Launderette,
related_name="tokens",
verbose_name=_("launderette"),
on_delete=models.CASCADE,
)
type = models.CharField(
_("type"), max_length=10, choices=settings.SITH_LAUNDERETTE_MACHINE_TYPES
)
borrow_date = models.DateTimeField(_("borrow date"), null=True, blank=True)
user = models.ForeignKey(
User,
related_name="tokens",
verbose_name=_("user"),
null=True,
blank=True,
on_delete=models.CASCADE,
)
class Meta:
verbose_name = _("Token")
unique_together = ("name", "launderette", "type")
ordering = ["type", "name"]
def __str__(self):
return (
f"{self.__class__._meta.verbose_name} {self.get_type_display()} "
f"#{self.name} ({self.launderette.name})"
)
def save(self, *args, **kwargs):
if self.name == "":
raise DataError(_("Token name can not be blank"))
else:
super().save(*args, **kwargs)
def is_owned_by(self, user):
"""Method to see if that object can be edited by the given user."""
if user.is_anonymous:
return False
launderette_club = Club.objects.get(id=settings.SITH_LAUNDERETTE_CLUB_ID)
m = launderette_club.get_membership_for(user)
return bool(m and m.role >= 9)
class Slot(models.Model):
start_date = models.DateTimeField(_("start date"))
type = models.CharField(
_("type"), max_length=10, choices=settings.SITH_LAUNDERETTE_MACHINE_TYPES
)
machine = models.ForeignKey(
Machine,
related_name="slots",
verbose_name=_("machine"),
on_delete=models.CASCADE,
)
token = models.ForeignKey(
Token,
related_name="slots",
verbose_name=_("token"),
blank=True,
null=True,
on_delete=models.CASCADE,
)
user = models.ForeignKey(
User, related_name="slots", verbose_name=_("user"), on_delete=models.CASCADE
)
class Meta:
verbose_name = _("Slot")
ordering = ["start_date"]
def __str__(self):
return "User: %s - Date: %s - Type: %s - Machine: %s - Token: %s" % (
self.user,
self.start_date,
self.get_type_display(),
self.machine.name,
self.token,
)
def is_owned_by(self, user):
return user == self.user

View File

@ -1,69 +0,0 @@
{% extends "core/base.jinja" %}
{% block title %}
{% trans %}Launderette admin{% endtrans %}
{% endblock %}
{% block content %}
<h3>{% trans %}Selling{% endtrans %}</h3>
<p><a href="{{ url('launderette:main_click', launderette_id=launderette.id) }}">{% trans %}Sell{% endtrans %}</a></p>
<hr>
<h3>{% trans %}Machines{% endtrans %}</h3>
<p><a href="{{ url('launderette:machine_new') }}?launderette={{ launderette.id }}">{% trans %}New machine{% endtrans %}</a></p>
<ul>
{% for m in launderette.machines.all() %}
<li><a href="{{ url('launderette:machine_edit', machine_id=m.id) }}">{{ m }}</a> -
<a href="{{ url('launderette:machine_delete', machine_id=m.id) }}">{% trans %}Delete{% endtrans %}</a></li>
{% endfor %}
</ul>
<hr>
<h3>{% trans %}Tokens{% endtrans %}</h3>
<p>
<form method="post" action="" id="token_form">
{% csrf_token %}
<p>{{ form.action.errors }}<label for="{{ form.action.name }}">{{ form.action.label }}</label>
{% for c in form.action %}
{{ c }}
{% endfor %}
</p>
<p>{{ form.token_type.errors }}<label for="{{ form.token_type.name }}">{{ form.token_type.label }}</label>
{% for c in form.token_type %}
{{ c }}
{% endfor %}
</p>
{{ form.tokens }}
<p><input type="submit" value="{% trans %}Go{% endtrans %}" /></p>
</form>
</p>
<p>
<table>
<thead>
<tr>
<td>{% trans %}Type{% endtrans %}</td>
<td>{% trans %}Name{% endtrans %}</td>
<td>{% trans %}User{% endtrans %}</td>
<td>{% trans %}Since{% endtrans %}</td>
</tr>
</thead>
<tbody>
{% for t in launderette.tokens.all() %}
<tr>
<td>{{ t.get_type_display() }}</td>
<td>{{ t.name }}</td>
{% if t.user %}
<td>{{ t.user.get_display_name() }}</td>
<td>{{ t.borrow_date|date(DATETIME_FORMAT) }} - {{ t.borrow_date|time(DATETIME_FORMAT) }}</td>
{% else %}
<td></td>
<td></td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</p>
{% endblock %}

View File

@ -1,58 +0,0 @@
{% extends "core/base.jinja" %}
{% from "core/macros.jinja" import show_slots, show_tokens %}
{% block title %}
{% trans %}Launderette{% endtrans %}
{% endblock %}
{% macro choose(date) %}
<form method="post" action="{{ url('launderette:book_slot', launderette_id=launderette.id) }}" class="inline" style="display:inline">
{% csrf_token %}
<input type="hidden" name="slot_type" value="{{ slot_type }}">
<button type="submit" name="slot" value="{{ date.isoformat() }}">{% trans %}Choose{% endtrans %}</button>
</form>
{% endmacro %}
{% block content %}
<h3>{{ launderette }}</h3>
<p>
<form method="post" action="{{ url('launderette:book_slot', launderette_id=launderette.id) }}"
class="inline" style="display:inline">
{% csrf_token %}
<button type="submit" name="slot_type" value="BOTH" {% if slot_type == "BOTH" -%}style="background: #FF0"{% endif %}>{% trans %}Washing and drying{% endtrans %}</button>
</form>
<form method="post" action="{{ url('launderette:book_slot', launderette_id=launderette.id) }}" class="inline" style="display:inline">
{% csrf_token %}
<button type="submit" name="slot_type" value="WASHING" {% if slot_type == "WASHING" -%}style="background: #FF0"{% endif %}>{% trans %}Washing{% endtrans %}</button>
</form>
<form method="post" action="{{ url('launderette:book_slot', launderette_id=launderette.id) }}" class="inline" style="display:inline">
{% csrf_token %}
<button type="submit" name="slot_type" value="DRYING" {% if slot_type == "DRYING" -%}style="background: #FF0"{% endif %}>{% trans %}Drying{% endtrans %}</button>
</form>
</p>
<table>
<thead>
<tr>
{% for day in planning.keys() %}
<th>{{ day|date('l') }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for i in range(0, 24) %}
<tr>
{% for hours in planning.values() %}
<td>
{% if hours[i] %}
{{ hours[i]|time(TIME_FORMAT) }} {{ choose(hours[i]) }}
{% endif %}
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{{ show_slots(user) }}
{{ show_tokens(user) }}
{% endblock %}

View File

@ -1,21 +0,0 @@
{% extends "core/base.jinja" %}
{% block title %}
{% trans %}Launderette{% endtrans %}
{% endblock %}
{% block content %}
{% if request.user.is_subscribed %}
<ul>
{% for l in launderette_list %}
<li><a href="{{ url('launderette:book_slot', launderette_id=l.id) }}">{{ l }}</a></li>
{% endfor %}
</ul>
{% endif %}
{% endblock %}

View File

@ -1,16 +0,0 @@
{% extends "core/base.jinja" %}
{% block title %}
{{ counter }}
{% endblock %}
{% block content %}
<h3>{% trans counter_name=counter %}{{ counter_name }} counter{% endtrans %}</h3>
<div>
<form method="post" action="" class="inline" style="display:inline">
{% csrf_token %}
{{ form.as_p() }}
<p><input type="submit" value="{% trans %}Go{% endtrans %}" /></p>
</form>
</div>
{% endblock %}

View File

@ -1,25 +0,0 @@
{% extends "core/base.jinja" %}
{% block title %}
{% trans %}Launderette admin list{% endtrans %}
{% endblock %}
{% block content %}
{% if user.is_root %}
<p><a href="{{ url('launderette:launderette_new') }}">{% trans %}New launderette{% endtrans %}</a></p>
{% endif %}
{% if launderette_list %}
<h3>{% trans %}Launderette admin list{% endtrans %}</h3>
<ul>
{% for l in launderette_list %}
<li><a href="{{ url('launderette:launderette_admin', launderette_id=l.id) }}">{{ l }}</a> -
<a href="{{ url('launderette:launderette_edit', launderette_id=l.id) }}">{% trans %}Edit{% endtrans %}</a></li>
{% endfor %}
</ul>
{% else %}
{% trans %}There is no launderette in this website.{% endtrans %}
{% endif %}
{% endblock %}

View File

@ -1,21 +0,0 @@
{% extends "core/base.jinja" %}
{% block title %}
{% trans %}Launderette{% endtrans %}
{% endblock %}
{% block content %}
{% if request.user.can_edit(page) %}
<p><a href="{{ url('core:page_edit', page_name=page.get_full_name()) }}">{% trans %}Edit presentation page{% endtrans %}</a></p>
{% endif %}
{% if request.user.is_subscribed %}
<p><a href="{{ url('launderette:book_main') }}">{% trans %}Book launderette slot{% endtrans %}</a></p>
{% endif %}
{{ page.revisions.last().content|markdown }}
{% endblock %}

View File

@ -1,16 +0,0 @@
#
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# You can find the source code of the website at https://github.com/ae-utbm/sith
#
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
# Create your tests here.

View File

@ -1,73 +0,0 @@
#
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# You can find the source code of the website at https://github.com/ae-utbm/sith
#
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
from django.urls import path
from launderette.views import (
LaunderetteAdminView,
LaunderetteBookMainView,
LaunderetteBookView,
LaunderetteClickView,
LaunderetteCreateView,
LaunderetteEditView,
LaunderetteListView,
LaunderetteMainClickView,
LaunderetteMainView,
MachineCreateView,
MachineDeleteView,
MachineEditView,
SlotDeleteView,
)
urlpatterns = [
# views
path("", LaunderetteMainView.as_view(), name="launderette_main"),
path("slot/<int:slot_id>/delete/", SlotDeleteView.as_view(), name="delete_slot"),
path("book/", LaunderetteBookMainView.as_view(), name="book_main"),
path("book/<int:launderette_id>/", LaunderetteBookView.as_view(), name="book_slot"),
path(
"<int:launderette_id>/click/",
LaunderetteMainClickView.as_view(),
name="main_click",
),
path(
"<int:launderette_id>/click/<int:user_id>/",
LaunderetteClickView.as_view(),
name="click",
),
path("admin/", LaunderetteListView.as_view(), name="launderette_list"),
path(
"admin/<int:launderette_id>/",
LaunderetteAdminView.as_view(),
name="launderette_admin",
),
path(
"admin/<int:launderette_id>/edit/",
LaunderetteEditView.as_view(),
name="launderette_edit",
),
path("admin/new/", LaunderetteCreateView.as_view(), name="launderette_new"),
path("admin/machine/new/", MachineCreateView.as_view(), name="machine_new"),
path(
"admin/machine/<int:machine_id>/edit/",
MachineEditView.as_view(),
name="machine_edit",
),
path(
"admin/machine/<int:machine_id>/delete/",
MachineDeleteView.as_view(),
name="machine_delete",
),
]

View File

@ -1,511 +0,0 @@
#
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# You can find the source code of the website at https://github.com/ae-utbm/sith
#
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
from collections import OrderedDict
from datetime import datetime, timedelta
from datetime import timezone as tz
from django import forms
from django.conf import settings
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.db import transaction
from django.template import defaultfilters
from django.urls import reverse_lazy
from django.utils import dateparse, timezone
from django.utils.translation import gettext as _
from django.views.generic import DetailView, ListView, TemplateView
from django.views.generic.edit import BaseFormView, CreateView, DeleteView, UpdateView
from club.models import Club
from core.auth.mixins import CanEditMixin, CanEditPropMixin, CanViewMixin
from core.models import Page, User
from counter.forms import GetUserForm
from counter.models import Counter, Customer, Selling
from launderette.models import Launderette, Machine, Slot, Token
# For users
class LaunderetteMainView(TemplateView):
"""Main presentation view."""
template_name = "launderette/launderette_main.jinja"
def get_context_data(self, **kwargs):
"""Add page to the context."""
kwargs = super().get_context_data(**kwargs)
kwargs["page"] = Page.objects.filter(name="launderette").first()
return kwargs
class LaunderetteBookMainView(CanViewMixin, ListView):
"""Choose which launderette to book."""
model = Launderette
template_name = "launderette/launderette_book_choose.jinja"
class LaunderetteBookView(CanViewMixin, DetailView):
"""Display the launderette schedule."""
model = Launderette
pk_url_kwarg = "launderette_id"
template_name = "launderette/launderette_book.jinja"
def get(self, request, *args, **kwargs):
self.slot_type = "BOTH"
self.machines = {}
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.slot_type = "BOTH"
self.machines = {}
with transaction.atomic():
self.object = self.get_object()
if "slot_type" in request.POST:
self.slot_type = request.POST["slot_type"]
if "slot" in request.POST and request.user.is_authenticated:
self.subscriber = request.user
if self.subscriber.is_subscribed:
self.date = dateparse.parse_datetime(request.POST["slot"]).replace(
tzinfo=tz.utc
)
if self.slot_type in ["WASHING", "DRYING"]:
if self.check_slot(self.slot_type):
Slot(
user=self.subscriber,
start_date=self.date,
machine=self.machines[self.slot_type],
type=self.slot_type,
).save()
elif self.check_slot("WASHING") and self.check_slot(
"DRYING", self.date + timedelta(hours=1)
):
Slot(
user=self.subscriber,
start_date=self.date,
machine=self.machines["WASHING"],
type="WASHING",
).save()
Slot(
user=self.subscriber,
start_date=self.date + timedelta(hours=1),
machine=self.machines["DRYING"],
type="DRYING",
).save()
return super().get(request, *args, **kwargs)
def check_slot(self, machine_type, date=None):
if date is None:
date = self.date
for m in self.object.machines.filter(is_working=True, type=machine_type):
slot = Slot.objects.filter(start_date=date, machine=m).first()
if slot is None:
self.machines[machine_type] = m
return True
return False
@staticmethod
def date_iterator(startDate, endDate, delta=timedelta(days=1)):
currentDate = startDate
while currentDate < endDate:
yield currentDate
currentDate += delta
def get_context_data(self, **kwargs):
"""Add page to the context."""
kwargs = super().get_context_data(**kwargs)
kwargs["planning"] = OrderedDict()
kwargs["slot_type"] = self.slot_type
start_date = datetime.now().replace(
hour=0, minute=0, second=0, microsecond=0, tzinfo=tz.utc
)
for date in LaunderetteBookView.date_iterator(
start_date, start_date + timedelta(days=6), timedelta(days=1)
):
kwargs["planning"][date] = []
for h in LaunderetteBookView.date_iterator(
date, date + timedelta(days=1), timedelta(hours=1)
):
free = False
if (
(
self.slot_type == "BOTH"
and self.check_slot("WASHING", h)
and self.check_slot("DRYING", h + timedelta(hours=1))
)
or (self.slot_type == "WASHING" and self.check_slot("WASHING", h))
or (self.slot_type == "DRYING" and self.check_slot("DRYING", h))
):
free = True
if free and datetime.now().replace(tzinfo=tz.utc) < h:
kwargs["planning"][date].append(h)
else:
kwargs["planning"][date].append(None)
return kwargs
class SlotDeleteView(CanEditPropMixin, DeleteView):
"""Delete a slot."""
model = Slot
pk_url_kwarg = "slot_id"
template_name = "core/delete_confirm.jinja"
def get_success_url(self):
return self.request.user.get_absolute_url()
# For admins
class LaunderetteListView(CanEditPropMixin, ListView):
"""Choose which launderette to administer."""
model = Launderette
template_name = "launderette/launderette_list.jinja"
class LaunderetteEditView(CanEditPropMixin, UpdateView):
"""Edit a launderette."""
model = Launderette
pk_url_kwarg = "launderette_id"
fields = ["name"]
template_name = "core/edit.jinja"
class LaunderetteCreateView(PermissionRequiredMixin, CreateView):
"""Create a new launderette."""
model = Launderette
fields = ["name"]
template_name = "core/create.jinja"
permission_required = "launderette.add_launderette"
def form_valid(self, form):
club = Club.objects.get(id=settings.SITH_LAUNDERETTE_CLUB_ID)
c = Counter(name=form.instance.name, club=club, type="OFFICE")
c.save()
form.instance.counter = c
return super().form_valid(form)
class ManageTokenForm(forms.Form):
action = forms.ChoiceField(
choices=[("BACK", _("Back")), ("ADD", _("Add")), ("DEL", _("Delete"))],
initial="BACK",
label=_("Action"),
widget=forms.RadioSelect,
)
token_type = forms.ChoiceField(
choices=settings.SITH_LAUNDERETTE_MACHINE_TYPES,
label=_("Type"),
initial="WASHING",
widget=forms.RadioSelect,
)
tokens = forms.CharField(
max_length=512,
widget=forms.widgets.Textarea,
label=_("Tokens, separated by spaces"),
)
def process(self, launderette):
cleaned_data = self.cleaned_data
token_list = cleaned_data["tokens"].strip(" \n\r").split(" ")
token_type = cleaned_data["token_type"]
self.data = {}
if cleaned_data["action"] not in ["BACK", "ADD", "DEL"]:
return
tokens = list(
Token.objects.filter(
launderette=launderette, type=token_type, name__in=token_list
)
)
existing_names = {t.name for t in tokens}
if cleaned_data["action"] in ["BACK", "DEL"]:
for t in set(token_list) - existing_names:
self.add_error(
None,
_("Token %(token_name)s does not exists") % {"token_name": t},
)
if cleaned_data["action"] == "BACK":
Token.objects.filter(id__in=[t.id for t in tokens]).update(
borrow_date=None, user=None
)
elif cleaned_data["action"] == "DEL":
Token.objects.filter(id__in=[t.id for t in tokens]).delete()
elif cleaned_data["action"] == "ADD":
for name in existing_names:
self.add_error(
None,
_("Token %(token_name)s already exists") % {"token_name": name},
)
for t in token_list:
if t == "":
self.add_error(None, _("Token name can not be blank"))
else:
Token(launderette=launderette, type=token_type, name=t).save()
class LaunderetteAdminView(CanEditPropMixin, BaseFormView, DetailView):
"""The admin page of the launderette."""
model = Launderette
pk_url_kwarg = "launderette_id"
template_name = "launderette/launderette_admin.jinja"
form_class = ManageTokenForm
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return super().post(request, *args, **kwargs)
def form_valid(self, form):
"""We handle here the redirection, passing the user id of the asked customer."""
form.process(self.object)
if form.is_valid():
return super().form_valid(form)
else:
return super().form_invalid(form)
def get_context_data(self, **kwargs):
"""We handle here the login form for the barman."""
kwargs = super().get_context_data(**kwargs)
if self.request.method == "GET":
kwargs["form"] = self.get_form()
return kwargs
def get_success_url(self):
return reverse_lazy(
"launderette:launderette_admin", args=self.args, kwargs=self.kwargs
)
class GetLaunderetteUserForm(GetUserForm):
def clean(self):
cleaned_data = super().clean()
sub = cleaned_data["user"]
if sub.slots.all().count() <= 0:
raise forms.ValidationError(_("User has booked no slot"))
return cleaned_data
class LaunderetteMainClickView(CanEditMixin, BaseFormView, DetailView):
"""The click page of the launderette."""
model = Launderette
pk_url_kwarg = "launderette_id"
template_name = "counter/counter_main.jinja"
form_class = GetLaunderetteUserForm # Form to enter a client code and get the corresponding user id
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return super().post(request, *args, **kwargs)
def form_valid(self, form):
"""We handle here the redirection, passing the user id of the asked customer."""
self.kwargs["user_id"] = form.cleaned_data["user_id"]
return super().form_valid(form)
def get_context_data(self, **kwargs):
"""We handle here the login form for the barman."""
kwargs = super().get_context_data(**kwargs)
kwargs["counter"] = self.object.counter
kwargs["form"] = self.get_form()
kwargs["barmen"] = [self.request.user]
if "last_basket" in self.request.session:
kwargs["last_basket"] = self.request.session.pop("last_basket", None)
kwargs["last_customer"] = self.request.session.pop("last_customer", None)
kwargs["last_total"] = self.request.session.pop("last_total", None)
kwargs["new_customer_amount"] = self.request.session.pop(
"new_customer_amount", None
)
return kwargs
def get_success_url(self):
return reverse_lazy("launderette:click", args=self.args, kwargs=self.kwargs)
class ClickTokenForm(forms.BaseForm):
def clean(self):
with transaction.atomic():
operator = User.objects.filter(id=self.operator_id).first()
customer = Customer.objects.filter(user__id=self.subscriber_id).first()
counter = Counter.objects.filter(id=self.counter_id).first()
subscriber = customer.user
self.last_basket = {
"last_basket": [],
"last_customer": customer.user.get_display_name(),
}
total = 0
for k, t in self.cleaned_data.items():
if t is not None:
slot_id = int(k[5:])
slot = Slot.objects.filter(id=slot_id).first()
slot.token = t
slot.save()
t.user = subscriber
t.borrow_date = datetime.now().replace(tzinfo=tz.utc)
t.save()
price = settings.SITH_LAUNDERETTE_PRICES[t.type]
s = Selling(
label="Jeton " + t.get_type_display() + "" + t.name,
club=counter.club,
product=None,
counter=counter,
unit_price=price,
quantity=1,
seller=operator,
customer=customer,
)
s.save()
total += price
self.last_basket["last_basket"].append(
"Jeton " + t.get_type_display() + "" + t.name
)
self.last_basket["new_customer_amount"] = str(customer.amount)
self.last_basket["last_total"] = str(total)
return self.cleaned_data
class LaunderetteClickView(CanEditMixin, DetailView, BaseFormView):
"""The click page of the launderette."""
model = Launderette
pk_url_kwarg = "launderette_id"
template_name = "launderette/launderette_click.jinja"
def get_form_class(self):
fields = OrderedDict()
kwargs = {}
def clean_field_factory(field_name, slot):
def clean_field(self2):
t_name = str(self2.data[field_name])
if t_name != "":
t = Token.objects.filter(
name=str(self2.data[field_name]),
type=slot.type,
launderette=self.object,
user=None,
).first()
if t is None:
raise forms.ValidationError(_("Token not found"))
return t
return clean_field
for s in self.subscriber.slots.filter(
token=None, start_date__gte=timezone.now().replace(tzinfo=None)
).all():
field_name = "slot-%s" % (str(s.id))
fields[field_name] = forms.CharField(
max_length=5,
required=False,
label="%s - %s"
% (
s.get_type_display(),
defaultfilters.date(s.start_date, "j N Y H:i"),
),
)
# XXX l10n settings.DATETIME_FORMAT didn't work here :/
kwargs["clean_" + field_name] = clean_field_factory(field_name, s)
kwargs["subscriber_id"] = self.subscriber.id
kwargs["counter_id"] = self.object.counter.id
kwargs["operator_id"] = self.operator.id
kwargs["base_fields"] = fields
return type("ClickForm", (ClickTokenForm,), kwargs)
def get(self, request, *args, **kwargs):
"""Simple get view."""
self.customer = Customer.objects.filter(user__id=self.kwargs["user_id"]).first()
self.subscriber = self.customer.user
self.operator = request.user
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
"""Handle the many possibilities of the post request."""
self.object = self.get_object()
self.customer = Customer.objects.filter(user__id=self.kwargs["user_id"]).first()
self.subscriber = self.customer.user
self.operator = request.user
return super().post(request, *args, **kwargs)
def form_valid(self, form):
"""We handle here the redirection, passing the user id of the asked customer."""
self.request.session.update(form.last_basket)
return super().form_valid(form)
def get_context_data(self, **kwargs):
"""We handle here the login form for the barman."""
kwargs = super().get_context_data(**kwargs)
if "form" not in kwargs:
kwargs["form"] = self.get_form()
kwargs["counter"] = self.object.counter
kwargs["customer"] = self.customer
return kwargs
def get_success_url(self):
self.kwargs.pop("user_id", None)
return reverse_lazy(
"launderette:main_click", args=self.args, kwargs=self.kwargs
)
class MachineEditView(CanEditPropMixin, UpdateView):
"""Edit a machine."""
model = Machine
pk_url_kwarg = "machine_id"
fields = ["name", "launderette", "type", "is_working"]
template_name = "core/edit.jinja"
class MachineDeleteView(CanEditPropMixin, DeleteView):
"""Edit a machine."""
model = Machine
pk_url_kwarg = "machine_id"
template_name = "core/delete_confirm.jinja"
success_url = reverse_lazy("launderette:launderette_list")
class MachineCreateView(PermissionRequiredMixin, CreateView):
"""Create a new machine."""
model = Machine
fields = ["name", "launderette", "type"]
template_name = "core/create.jinja"
permission_required = "launderette.add_machine"
def get_initial(self):
ret = super().get_initial()
if "launderette" in self.request.GET:
obj = Launderette.objects.filter(
id=int(self.request.GET["launderette"])
).first()
if obj is not None:
ret["launderette"] = obj.id
return ret

View File

@ -113,9 +113,6 @@ nav:
- galaxy: - galaxy:
- reference/galaxy/models.md - reference/galaxy/models.md
- reference/galaxy/views.md - reference/galaxy/views.md
- launderette:
- reference/launderette/models.md
- reference/launderette/views.md
- matmat: - matmat:
- reference/matmat/models.md - reference/matmat/models.md
- reference/matmat/views.md - reference/matmat/views.md

View File

@ -182,7 +182,6 @@ TEMPLATES = [
"can_edit": "core.auth.mixins.can_edit", "can_edit": "core.auth.mixins.can_edit",
"can_view": "core.auth.mixins.can_view", "can_view": "core.auth.mixins.can_view",
"settings": "sith.settings", "settings": "sith.settings",
"Launderette": "launderette.models.Launderette",
"Counter": "counter.models.Counter", "Counter": "counter.models.Counter",
"timezone": "django.utils.timezone", "timezone": "django.utils.timezone",
"get_sith": "com.views.sith", "get_sith": "com.views.sith",
@ -658,10 +657,6 @@ with open(
) as f: ) as f:
SITH_EBOUTIC_PUB_KEY = f.read() SITH_EBOUTIC_PUB_KEY = f.read()
# Launderette variables
SITH_LAUNDERETTE_MACHINE_TYPES = [("WASHING", _("Washing")), ("DRYING", _("Drying"))]
SITH_LAUNDERETTE_PRICES = {"WASHING": 1.0, "DRYING": 0.75}
SITH_NOTIFICATIONS = [ SITH_NOTIFICATIONS = [
("POSTER_MODERATION", _("A new poster needs to be moderated")), ("POSTER_MODERATION", _("A new poster needs to be moderated")),
("MAILING_MODERATION", _("A new mailing list needs to be moderated")), ("MAILING_MODERATION", _("A new mailing list needs to be moderated")),

View File

@ -42,10 +42,6 @@ urlpatterns = [
path("club/", include(("club.urls", "club"), namespace="club")), path("club/", include(("club.urls", "club"), namespace="club")),
path("counter/", include(("counter.urls", "counter"), namespace="counter")), path("counter/", include(("counter.urls", "counter"), namespace="counter")),
path("eboutic/", include(("eboutic.urls", "eboutic"), namespace="eboutic")), path("eboutic/", include(("eboutic.urls", "eboutic"), namespace="eboutic")),
path(
"launderette/",
include(("launderette.urls", "launderette"), namespace="launderette"),
),
path("sas/", include(("sas.urls", "sas"), namespace="sas")), path("sas/", include(("sas.urls", "sas"), namespace="sas")),
path("election/", include(("election.urls", "election"), namespace="election")), path("election/", include(("election.urls", "election"), namespace="election")),
path("forum/", include(("forum.urls", "forum"), namespace="forum")), path("forum/", include(("forum.urls", "forum"), namespace="forum")),