Skip to content

Commit

Permalink
Merge pull request #953 from ae-utbm/taiste
Browse files Browse the repository at this point in the history
Counter views split, unique student cards, student cards and reloads HTMXification, refactors and fixes
  • Loading branch information
imperosol authored Dec 17, 2024
2 parents 35c5f96 + 6416de2 commit c1be55a
Show file tree
Hide file tree
Showing 49 changed files with 3,679 additions and 3,113 deletions.
18 changes: 18 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,21 @@ jobs:
poetry run ./manage.py compilemessages
sudo systemctl restart uwsgi
sentry:
runs-on: ubuntu-latest
environment: production
timeout-minutes: 30
needs: deployment
steps:
- uses: actions/checkout@v4

- name: Sentry Release
uses: getsentry/[email protected]
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
SENTRY_URL: ${{ secrets.SENTRY_URL }}
with:
environment: production
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.6.9
rev: v0.8.3
hooks:
- id: ruff # just check the code, and print the errors
- id: ruff # actually fix the fixable errors, but print nothing
Expand All @@ -14,7 +14,7 @@ repos:
- id: biome-check
additional_dependencies: ["@biomejs/[email protected]"]
- repo: https://github.com/rtts/djhtml
rev: 3.0.6
rev: 3.0.7
hooks:
- id: djhtml
name: format templates
Expand Down
9 changes: 4 additions & 5 deletions core/management/commands/populate.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def reset_index(self, *args):
# sqlite doesn't support this operation
return
sqlcmd = StringIO()
call_command("sqlsequencereset", *args, stdout=sqlcmd)
call_command("sqlsequencereset", "--no-color", *args, stdout=sqlcmd)
cursor = connection.cursor()
cursor.execute(sqlcmd.getvalue())

Expand Down Expand Up @@ -137,11 +137,10 @@ def handle(self, *args, **options):
)

self.reset_index("club")
for bar_id, bar_name in settings.SITH_COUNTER_BARS:
Counter(id=bar_id, name=bar_name, club=bar_club, type="BAR").save()
self.reset_index("counter")
counters = [
*[
Counter(id=bar_id, name=bar_name, club=bar_club, type="BAR")
for bar_id, bar_name in settings.SITH_COUNTER_BARS
],
Counter(name="Eboutic", club=main_club, type="EBOUTIC"),
Counter(name="AE", club=main_club, type="OFFICE"),
Counter(name="Vidage comptes AE", club=main_club, type="OFFICE"),
Expand Down
5 changes: 3 additions & 2 deletions core/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from annotated_types import MinLen
from django.contrib.staticfiles.storage import staticfiles_storage
from django.db.models import Q
from django.urls import reverse
from django.utils.text import slugify
from haystack.query import SearchQuerySet
from ninja import FilterSchema, ModelSchema, Schema
Expand Down Expand Up @@ -37,13 +38,13 @@ def resolve_display_name(obj: User) -> str:

@staticmethod
def resolve_profile_url(obj: User) -> str:
return obj.get_absolute_url()
return reverse("core:user_profile", kwargs={"user_id": obj.pk})

@staticmethod
def resolve_profile_pict(obj: User) -> str:
if obj.profile_pict_id is None:
return staticfiles_storage.url("core/img/unknown.jpg")
return obj.profile_pict.get_download_url()
return reverse("core:download", kwargs={"file_id": obj.profile_pict_id})


class SithFileSchema(ModelSchema):
Expand Down
14 changes: 13 additions & 1 deletion core/static/bundled/core/components/ajax-select-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ export class AutoCompleteSelectBase extends inheritHtmlElement("select") {
export abstract class AjaxSelect extends AutoCompleteSelectBase {
protected filter?: (items: TomOption[]) => TomOption[] = null;
protected minCharNumberForSearch = 2;
/**
* A cache of researches that have been made using this input.
* For each record, the key is the user's query and the value
* is the list of results sent back by the server.
*/
protected cache = {} as Record<string, TomOption[]>;

protected abstract valueField: string;
protected abstract labelField: string;
Expand Down Expand Up @@ -135,7 +141,13 @@ export abstract class AjaxSelect extends AutoCompleteSelectBase {
this.widget.clearOptions();
}

const resp = await this.search(query);
// Check in the cache if this query has already been typed
// and do an actual HTTP request only if the result isn't cached
let resp = this.cache[query];
if (!resp) {
resp = await this.search(query);
this.cache[query] = resp;
}

if (this.filter) {
callback(this.filter(resp), []);
Expand Down
8 changes: 8 additions & 0 deletions core/static/bundled/htmx-index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
import htmx from "htmx.org";

document.body.addEventListener("htmx:beforeRequest", (event) => {
event.target.ariaBusy = true;
});

document.body.addEventListener("htmx:afterRequest", (event) => {
event.originalTarget.ariaBusy = null;
});

Object.assign(window, { htmx });
11 changes: 10 additions & 1 deletion core/static/bundled/utils/web-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,16 @@
**/
export function registerComponent(name: string, options?: ElementDefinitionOptions) {
return (component: CustomElementConstructor) => {
window.customElements.define(name, component, options);
try {
window.customElements.define(name, component, options);
} catch (e) {
if (e instanceof DOMException) {
// biome-ignore lint/suspicious/noConsole: it's handy to troobleshot
console.warn(e.message);
return;
}
throw e;
}
};
}

Expand Down
7 changes: 6 additions & 1 deletion core/static/core/colors.scss
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,9 @@ $shadow-color: rgb(223, 223, 223);

$background-button-color: hsl(0, 0%, 95%);

$deepblue: #354a5f;
$deepblue: #354a5f;

@mixin shadow {
box-shadow: rgba(60, 64, 67, 0.3) 0 1px 3px 0,
rgba(60, 64, 67, 0.15) 0 4px 8px 3px;
}
33 changes: 31 additions & 2 deletions core/static/core/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,32 @@ body {
}
}

[tooltip] {
position: relative;
}

[tooltip]::before {
@include shadow;
opacity: 0;
z-index: 1;
content: attr(tooltip);
background: hsl(219.6, 20.8%, 96%);
color: $black-color;
border: 0.5px solid hsl(0, 0%, 50%);
;
border-radius: 5px;
padding: 5px;
top: 1em;
position: absolute;
margin-top: 5px;
white-space: nowrap;
transition: opacity 500ms ease-out;
}

[tooltip]:hover::before {
opacity: 1;
}

.ib {
display: inline-block;
padding: 1px;
Expand Down Expand Up @@ -79,8 +105,7 @@ body {
}

.shadow {
box-shadow: rgba(60, 64, 67, 0.3) 0 1px 3px 0,
rgba(60, 64, 67, 0.15) 0 4px 8px 3px;
@include shadow;
}

.w_big {
Expand Down Expand Up @@ -308,6 +333,7 @@ body {
font-size: 120%;
background-color: unset;
position: relative;

&:after {
content: '';
position: absolute;
Expand All @@ -318,14 +344,17 @@ body {
border-radius: 2px;
transition: all 0.2s ease-in-out;
}

&:hover:after {
border-bottom-color: darken($primary-neutral-light-color, 20%);
}

&.active:after {
border-bottom-color: $primary-dark-color;
}
}
}

section {
padding: 20px;
}
Expand Down
38 changes: 8 additions & 30 deletions core/templates/core/user_preferences.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -28,42 +28,20 @@
</form>

{% else %}
<p>{% trans trombi=user.trombi_user.trombi %}You already choose to be in that Trombi: {{ trombi }}.{% endtrans %}
<p>{% trans trombi=profile.trombi_user.trombi %}You already choose to be in that Trombi: {{ trombi }}.{% endtrans %}
<br />
<a href="{{ url('trombi:user_tools') }}">{% trans %}Go to my Trombi tools{% endtrans %}</a>
</p>
{% endif %}


{% if profile.customer %}
<h3>{% trans %}Student cards{% endtrans %}</h3>

{% if profile.customer.student_cards.exists() %}
<ul class="student-cards">
{% for card in profile.customer.student_cards.all() %}
<li>
{{ card.uid }}
&nbsp;-&nbsp;
<a href="{{ url('counter:delete_student_card', customer_id=profile.customer.pk, card_id=card.id) }}">
{% trans %}Delete{% endtrans %}
</a>
</li>
{% endfor %}
</ul>
{% else %}
<em class="no-cards">{% trans %}No student card registered.{% endtrans %}</em>
<p class="justify">
{% trans %}You can add a card by asking at a counter or add it yourself here. If you want to manually
add a student card yourself, you'll need a NFC reader. We store the UID of the card which is 14 characters long.{% endtrans %}
</p>
{% endif %}

<form class="form form-cards" action="{{ url('counter:add_student_card', customer_id=profile.customer.pk) }}"
method="post">
{% csrf_token %}
{{ student_card_form.as_p() }}
<input class="form-submit-btn" type="submit" value="{% trans %}Save{% endtrans %}" />
</form>
{% if student_card_fragment %}
<h3>{% trans %}Student card{% endtrans %}</h3>
{{ student_card_fragment }}
<p class="justify">
{% trans %}You can add a card by asking at a counter or add it yourself here. If you want to manually
add a student card yourself, you'll need a NFC reader. We store the UID of the card which is 14 characters long.{% endtrans %}
</p>
{% endif %}
</div>
{% endblock %}
25 changes: 22 additions & 3 deletions core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,41 @@
#
#

from dataclasses import dataclass
from datetime import date

# Image utils
from io import BytesIO
from typing import Optional
from typing import Any

import PIL
from django.conf import settings
from django.core.files.base import ContentFile
from django.forms import BaseForm
from django.http import HttpRequest
from django.template.loader import render_to_string
from django.utils.html import SafeString
from django.utils.timezone import localdate
from PIL import ExifTags
from PIL.Image import Image, Resampling


def get_start_of_semester(today: Optional[date] = None) -> date:
@dataclass
class FormFragmentTemplateData[T: BaseForm]:
"""Dataclass used to pre-render form fragments"""

form: T
template: str
context: dict[str, Any]

def render(self, request: HttpRequest) -> SafeString:
# Request is needed for csrf_tokens
return render_to_string(
self.template, context={"form": self.form, **self.context}, request=request
)


def get_start_of_semester(today: date | None = None) -> date:
"""Return the date of the start of the semester of the given date.
If no date is given, return the start date of the current semester.
Expand Down Expand Up @@ -58,7 +77,7 @@ def get_start_of_semester(today: Optional[date] = None) -> date:
return autumn.replace(year=autumn.year - 1)


def get_semester_code(d: Optional[date] = None) -> str:
def get_semester_code(d: date | None = None) -> str:
"""Return the semester code of the given date.
If no date is given, return the semester code of the current semester.
Expand Down
15 changes: 5 additions & 10 deletions core/views/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@
UserGodfathersForm,
UserProfileForm,
)
from counter.forms import StudentCardForm
from counter.models import Refilling, Selling
from counter.views.student_card import StudentCardFormView
from eboutic.models import Invoice
from subscription.models import Subscription
from trombi.views import UserTrombiForm
Expand Down Expand Up @@ -559,10 +559,6 @@ class UserPreferencesView(UserTabsMixin, CanEditMixin, UpdateView):
context_object_name = "profile"
current_tab = "prefs"

def get_object(self, queryset=None):
user = get_object_or_404(User, pk=self.kwargs["user_id"])
return user

def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
pref = self.object.preferences
Expand All @@ -572,13 +568,12 @@ def get_form_kwargs(self):
def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs)

if not (
hasattr(self.object, "trombi_user") and self.request.user.trombi_user.trombi
):
if not hasattr(self.object, "trombi_user"):
kwargs["trombi_form"] = UserTrombiForm()

if hasattr(self.object, "customer"):
kwargs["student_card_form"] = StudentCardForm()
kwargs["student_card_fragment"] = StudentCardFormView.get_template_data(
self.object.customer
).render(self.request)
return kwargs


Expand Down
6 changes: 6 additions & 0 deletions counter/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _

PAYMENT_METHOD = [
("CHECK", _("Check")),
("CASH", _("Cash")),
("CARD", _("Credit card")),
]


class CounterConfig(AppConfig):
name = "counter"
Expand Down
Loading

0 comments on commit c1be55a

Please sign in to comment.