mirror of
https://github.com/ae-utbm/sith.git
synced 2026-03-29 23:09:40 +00:00
Compare commits
5 Commits
fix-link-s
...
upgrade_xa
| Author | SHA1 | Date | |
|---|---|---|---|
|
e47b6ba105
|
|||
| fcce34fde5 | |||
|
a213bc058d
|
|||
|
71bfdf68b7
|
|||
|
2a9bb46e97
|
@@ -1,7 +1,11 @@
|
||||
{% extends "core/base.jinja" %}
|
||||
{% from 'core/macros.jinja' import user_profile_link, select_all_checkbox %}
|
||||
|
||||
{% block additional_js %}
|
||||
<script type="module" src="{{ static("bundled/core/components/ajax-select-index.ts") }}"></script>
|
||||
{% endblock %}
|
||||
{% block additional_css %}
|
||||
<link rel="stylesheet" href="{{ static("bundled/core/components/ajax-select-index.css") }}">
|
||||
<link rel="stylesheet" href="{{ static("club/members.scss") }}">
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -39,12 +39,16 @@ class Command(BaseCommand):
|
||||
return None
|
||||
return xapian.version_string()
|
||||
|
||||
def _desired_version(self) -> str:
|
||||
def _desired_version(self) -> tuple[str, str, str]:
|
||||
with open(
|
||||
Path(__file__).parent.parent.parent.parent / "pyproject.toml", "rb"
|
||||
) as f:
|
||||
pyproject = tomli.load(f)
|
||||
return pyproject["tool"]["xapian"]["version"]
|
||||
return (
|
||||
pyproject["tool"]["xapian"]["version"],
|
||||
pyproject["tool"]["xapian"]["core-sha256"],
|
||||
pyproject["tool"]["xapian"]["bindings-sha256"],
|
||||
)
|
||||
|
||||
def handle(self, *args, force: bool, **options):
|
||||
if not os.environ.get("VIRTUAL_ENV", None):
|
||||
@@ -53,7 +57,7 @@ class Command(BaseCommand):
|
||||
)
|
||||
return
|
||||
|
||||
desired = self._desired_version()
|
||||
desired, core_checksum, bindings_checksum = self._desired_version()
|
||||
if desired == self._current_version():
|
||||
if not force:
|
||||
self.stdout.write(
|
||||
@@ -65,7 +69,12 @@ class Command(BaseCommand):
|
||||
f"Installing xapian version {desired} at {os.environ['VIRTUAL_ENV']}"
|
||||
)
|
||||
subprocess.run(
|
||||
[str(Path(__file__).parent / "install_xapian.sh"), desired],
|
||||
[
|
||||
str(Path(__file__).parent / "install_xapian.sh"),
|
||||
desired,
|
||||
core_checksum,
|
||||
bindings_checksum,
|
||||
],
|
||||
env=dict(os.environ),
|
||||
check=True,
|
||||
)
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# Originates from https://gist.github.com/jorgecarleitao/ab6246c86c936b9c55fd
|
||||
# first argument of the script is Xapian version (e.g. 1.2.19)
|
||||
# second argument of the script is core sha256
|
||||
# second argument of the script is binding sha256
|
||||
VERSION="$1"
|
||||
CORE_SHA256="$2"
|
||||
BINDINGS_SHA256="$3"
|
||||
|
||||
# Cleanup env vars for auto discovery mechanism
|
||||
unset CPATH
|
||||
@@ -21,9 +25,15 @@ BINDINGS=xapian-bindings-$VERSION
|
||||
|
||||
# download
|
||||
echo "Downloading source..."
|
||||
curl -O "https://oligarchy.co.uk/xapian/$VERSION/${CORE}.tar.xz"
|
||||
curl -O "https://oligarchy.co.uk/xapian/$VERSION/${CORE}.tar.xz" || exit 1
|
||||
|
||||
echo "${CORE_SHA256} ${CORE}.tar.xz" | sha256sum -c - || exit 1
|
||||
|
||||
curl -O "https://oligarchy.co.uk/xapian/$VERSION/${BINDINGS}.tar.xz"
|
||||
|
||||
echo "${BINDINGS_SHA256} ${BINDINGS}.tar.xz" | sha256sum -c - || exit 1
|
||||
|
||||
|
||||
# extract
|
||||
echo "Extracting source..."
|
||||
tar xf "${CORE}.tar.xz"
|
||||
|
||||
@@ -1,106 +1,18 @@
|
||||
import { inheritHtmlElement, registerComponent } from "#core:utils/web-components.ts";
|
||||
|
||||
/**
|
||||
* Create an abstract class for ElementOnce types Web Components
|
||||
*
|
||||
* Those class aren't really abstract because that would be complicated with the
|
||||
* multiple inheritance involved
|
||||
* Instead, we just raise an unimplemented error
|
||||
**/
|
||||
function elementOnce<K extends keyof HTMLElementTagNameMap>(tagName: K) {
|
||||
return class ElementOnce extends inheritHtmlElement(tagName) {
|
||||
getElementQuerySelector(): string {
|
||||
throw new Error("Unimplemented");
|
||||
}
|
||||
|
||||
clearNode() {
|
||||
while (this.firstChild) {
|
||||
this.removeChild(this.lastChild);
|
||||
}
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.clearNode();
|
||||
if (document.querySelectorAll(this.getElementQuerySelector()).length === 0) {
|
||||
this.appendChild(this.node);
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback(false);
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
// The MutationObserver can't see web components being removed
|
||||
// It also can't see if something is removed inside after the component gets deleted
|
||||
// We need to manually clear the containing node to trigger the observer
|
||||
this.clearNode();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Set of ElementOnce type components to refresh with the observer
|
||||
const registeredComponents: Set<string> = new Set();
|
||||
|
||||
/**
|
||||
* Helper to register ElementOnce types Web Components
|
||||
* It's a wrapper around registerComponent that registers that component on
|
||||
* a MutationObserver that activates a refresh on them when elements are removed
|
||||
**/
|
||||
function registerElementOnce(name: string, options?: ElementDefinitionOptions) {
|
||||
registeredComponents.add(name);
|
||||
return registerComponent(name, options);
|
||||
}
|
||||
|
||||
const startObserver = (observer: MutationObserver) => {
|
||||
observer.observe(document, {
|
||||
// We want to also listen for elements contained in the header (eg: link)
|
||||
subtree: true,
|
||||
childList: true,
|
||||
});
|
||||
};
|
||||
|
||||
// Refresh *-once components when changes happens
|
||||
const observer = new MutationObserver((mutations: MutationRecord[]) => {
|
||||
observer.disconnect();
|
||||
for (const mutation of mutations) {
|
||||
for (const node of mutation.removedNodes) {
|
||||
if (node.nodeType !== node.ELEMENT_NODE) {
|
||||
continue;
|
||||
}
|
||||
const refreshElement = (componentName: string, tagName: string) => {
|
||||
for (const element of document.getElementsByTagName(componentName)) {
|
||||
// We can't guess if an element is compatible before we get one
|
||||
// We exit the function completely if it's not compatible
|
||||
if (
|
||||
(element as any).inheritedTagName.toUpperCase() !== tagName.toUpperCase()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
(element as any).refresh();
|
||||
}
|
||||
};
|
||||
for (const registered of registeredComponents) {
|
||||
refreshElement(registered, (node as HTMLElement).tagName);
|
||||
}
|
||||
}
|
||||
}
|
||||
startObserver(observer);
|
||||
});
|
||||
|
||||
startObserver(observer);
|
||||
|
||||
/**
|
||||
* Web component used to import css files only once
|
||||
* If called multiple times or the file was already imported, it does nothing
|
||||
**/
|
||||
@registerElementOnce("link-once")
|
||||
export class LinkOnce extends elementOnce("link") {
|
||||
getElementQuerySelector(): string {
|
||||
@registerComponent("link-once")
|
||||
export class LinkOnce extends inheritHtmlElement("link") {
|
||||
connectedCallback() {
|
||||
super.connectedCallback(false);
|
||||
// We get href from node.attributes instead of node.href to avoid getting the domain part
|
||||
return `link[href='${this.node.attributes.getNamedItem("href").nodeValue}']`;
|
||||
const href = this.node.attributes.getNamedItem("href").nodeValue;
|
||||
if (document.querySelectorAll(`link[href='${href}']`).length === 0) {
|
||||
this.appendChild(this.node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,10 +20,14 @@ export class LinkOnce extends elementOnce("link") {
|
||||
* Web component used to import javascript files only once
|
||||
* If called multiple times or the file was already imported, it does nothing
|
||||
**/
|
||||
@registerElementOnce("script-once")
|
||||
@registerComponent("script-once")
|
||||
export class ScriptOnce extends inheritHtmlElement("script") {
|
||||
getElementQuerySelector(): string {
|
||||
// We get href from node.attributes instead of node.src to avoid getting the domain part
|
||||
return `script[src='${this.node.attributes.getNamedItem("src").nodeValue}']`;
|
||||
connectedCallback() {
|
||||
super.connectedCallback(false);
|
||||
// We get src from node.attributes instead of node.src to avoid getting the domain part
|
||||
const src = this.node.attributes.getNamedItem("src").nodeValue;
|
||||
if (document.querySelectorAll(`script[src='${src}']`).length === 0) {
|
||||
this.appendChild(this.node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ export function registerComponent(name: string, options?: ElementDefinitionOptio
|
||||
**/
|
||||
export function inheritHtmlElement<K extends keyof HTMLElementTagNameMap>(tagName: K) {
|
||||
return class Inherited extends HTMLElement {
|
||||
readonly inheritedTagName = tagName;
|
||||
protected node: HTMLElementTagNameMap[K];
|
||||
|
||||
connectedCallback(autoAddNode?: boolean) {
|
||||
|
||||
@@ -48,6 +48,6 @@
|
||||
>{% trans %}Delete{% endtrans %}</button></p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{{ paginate_htmx(page_obj, paginator) }}
|
||||
{{ paginate_htmx(request, page_obj, paginator) }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -118,20 +118,21 @@
|
||||
</nav>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro paginate_jinja(current_page, paginator) %}
|
||||
{% macro paginate_jinja(request, current_page, paginator) %}
|
||||
{# Add pagination buttons for pages without Alpine.
|
||||
|
||||
This must be coupled with a view that handles pagination
|
||||
with the Django Paginator object.
|
||||
|
||||
Parameters:
|
||||
request (django.http.request.HttpRequest): the current django request
|
||||
current_page (django.core.paginator.Page): the current page object
|
||||
paginator (django.core.paginator.Paginator): the paginator object
|
||||
#}
|
||||
{{ paginate_server_side(current_page, paginator, False) }}
|
||||
{{ paginate_server_side(request, current_page, paginator, False) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro paginate_htmx(current_page, paginator) %}
|
||||
{% macro paginate_htmx(request, current_page, paginator) %}
|
||||
{# Add pagination buttons for pages without Alpine but supporting fragments.
|
||||
|
||||
This must be coupled with a view that handles pagination
|
||||
@@ -140,24 +141,25 @@
|
||||
The replaced fragment will be #content so make sure you are calling this macro inside your content block.
|
||||
|
||||
Parameters:
|
||||
request (django.http.request.HttpRequest): the current django request
|
||||
current_page (django.core.paginator.Page): the current page object
|
||||
paginator (django.core.paginator.Paginator): the paginator object
|
||||
#}
|
||||
{{ paginate_server_side(current_page, paginator, True) }}
|
||||
{{ paginate_server_side(request, current_page, paginator, True) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro paginate_server_side(current_page, paginator, use_htmx) %}
|
||||
{% macro paginate_server_side(request, current_page, paginator, use_htmx) %}
|
||||
<nav class="pagination">
|
||||
{% if current_page.has_previous() %}
|
||||
<a
|
||||
{% if use_htmx -%}
|
||||
hx-get="?{{ querystring(page=current_page.previous_page_number()) }}"
|
||||
hx-get="?{{ querystring(request, page=current_page.previous_page_number()) }}"
|
||||
hx-swap="innerHTML"
|
||||
hx-target="#content"
|
||||
hx-push-url="true"
|
||||
hx-trigger="click, keyup[key=='ArrowLeft'] from:body"
|
||||
{%- else -%}
|
||||
href="?{{ querystring(page=current_page.previous_page_number()) }}"
|
||||
href="?{{ querystring(request, page=current_page.previous_page_number()) }}"
|
||||
{%- endif -%}
|
||||
>
|
||||
<button>
|
||||
@@ -175,12 +177,12 @@
|
||||
{% else %}
|
||||
<a
|
||||
{% if use_htmx -%}
|
||||
hx-get="?{{ querystring(page=i) }}"
|
||||
hx-get="?{{ querystring(request, page=i) }}"
|
||||
hx-swap="innerHTML"
|
||||
hx-target="#content"
|
||||
hx-push-url="true"
|
||||
{%- else -%}
|
||||
href="?{{ querystring(page=i) }}"
|
||||
href="?{{ querystring(request, page=i) }}"
|
||||
{%- endif -%}
|
||||
>
|
||||
<button>{{ i }}</button>
|
||||
@@ -190,13 +192,13 @@
|
||||
{% if current_page.has_next() %}
|
||||
<a
|
||||
{% if use_htmx -%}
|
||||
hx-get="?{{querystring(page=current_page.next_page_number())}}"
|
||||
hx-get="?{{querystring(request, page=current_page.next_page_number())}}"
|
||||
hx-swap="innerHTML"
|
||||
hx-target="#content"
|
||||
hx-push-url="true"
|
||||
hx-trigger="click, keyup[key=='ArrowRight'] from:body"
|
||||
{%- else -%}
|
||||
href="?{{querystring(page=current_page.next_page_number())}}"
|
||||
href="?{{querystring(request, page=current_page.next_page_number())}}"
|
||||
{%- endif -%}
|
||||
><button>
|
||||
<i class="fa fa-caret-right"></i>
|
||||
@@ -247,15 +249,8 @@
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro querystring() %}
|
||||
{%- for key, values in request.GET.lists() -%}
|
||||
{%- if key not in kwargs -%}
|
||||
{%- for value in values -%}
|
||||
{{ key }}={{ value }}&
|
||||
{%- endfor -%}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%- for key, value in kwargs.items() -%}
|
||||
{{ key }}={{ value }}&
|
||||
{%- endfor -%}
|
||||
{% macro querystring(request) %}
|
||||
{%- set qs = request.GET.copy() -%}
|
||||
{%- do qs.update(kwargs) -%}
|
||||
{{- qs | urlencode -}}
|
||||
{% endmacro %}
|
||||
@@ -1,7 +1,14 @@
|
||||
{% extends "core/base.jinja" %}
|
||||
|
||||
{%- block additional_js -%}
|
||||
<script type="module" src="{{ static("bundled/core/components/ajax-select-index.ts") }}"></script>
|
||||
{%- endblock -%}
|
||||
|
||||
{%- block additional_css -%}
|
||||
<link rel="stylesheet" href="{{ static('user/user_preferences.scss') }}">
|
||||
{# importing ajax-select-index is necessary for it to be applied after HTMX reload #}
|
||||
<link rel="stylesheet" href="{{ static("bundled/core/components/ajax-select-index.css") }}">
|
||||
<link rel="stylesheet" href="{{ static("core/components/ajax-select.scss") }}">
|
||||
{%- endblock -%}
|
||||
|
||||
{% block title %}
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
</table>
|
||||
<br>
|
||||
{% if is_paginated %}
|
||||
{{ paginate_jinja(page_obj, paginator) }}
|
||||
{{ paginate_jinja(request, page_obj, paginator) }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% trans %}There is no cash register summary in this website.{% endtrans %}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
{%- endfor %}
|
||||
</table>
|
||||
{% if is_paginated %}
|
||||
{{ paginate_jinja(page_obj, paginator) }}
|
||||
{{ paginate_jinja(request, page_obj, paginator) }}
|
||||
{% endif %}
|
||||
{%- endblock %}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
</section>
|
||||
{%- endfor %}
|
||||
{% if is_paginated %}
|
||||
{{ paginate_jinja(page_obj, paginator) }}
|
||||
{{ paginate_jinja(request, page_obj, paginator) }}
|
||||
{% endif %}
|
||||
{%- endblock %}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</p>
|
||||
|
||||
{{ display_search_bar(request) }}
|
||||
{{ paginate_jinja(msgs, msgs.paginator) }}
|
||||
{{ paginate_jinja(request, msgs, msgs.paginator) }}
|
||||
|
||||
<main class="message-list">
|
||||
{% for m in msgs %}
|
||||
@@ -44,7 +44,9 @@
|
||||
|
||||
<p><a class="ib button" href="{{ url('forum:new_message', topic_id=topic.id) }}">{% trans %}Reply{% endtrans %}</a></p>
|
||||
|
||||
{{ paginate_jinja(msgs, msgs.paginator) }}
|
||||
{% if is_paginated %}
|
||||
{{ paginate_jinja(request, msgs, msgs.paginator) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -59,9 +59,7 @@ homepage = "https://ae.utbm.fr/"
|
||||
documentation = "https://sith-ae.readthedocs.io/"
|
||||
|
||||
[dependency-groups]
|
||||
prod = [
|
||||
"psycopg[c]>=3.3.3,<4.0.0",
|
||||
]
|
||||
prod = ["psycopg[c]>=3.3.3,<4.0.0"]
|
||||
dev = [
|
||||
"django-debug-toolbar>=6.2.0,<7",
|
||||
"ipython>=9.11.0,<10.0.0",
|
||||
@@ -92,7 +90,10 @@ docs = [
|
||||
default-groups = ["dev", "tests", "docs"]
|
||||
|
||||
[tool.xapian]
|
||||
version = "1.4.29"
|
||||
version = "1.4.31"
|
||||
core-sha256 = "fecf609ea2efdc8a64be369715aac733336a11f7480a6545244964ae6bc80811"
|
||||
bindings-sha256 = "a38cc7ba4188cc0bd27dc7369f03906772047087a1c54f1b93355d5e9103c304"
|
||||
|
||||
|
||||
[tool.ruff]
|
||||
output-format = "concise" # makes ruff error logs easier to read
|
||||
@@ -125,7 +126,7 @@ select = [
|
||||
ignore = [
|
||||
"DJ001", # null=True in CharField/TextField. this one would require a migration
|
||||
"E501", # line too long. The rule is too harsh, and the formatter deals with it in most cases
|
||||
"RUF012" # mutable class attributes. This rule doesn't integrate well with django
|
||||
"RUF012", # mutable class attributes. This rule doesn't integrate well with django
|
||||
]
|
||||
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
|
||||
@@ -28,5 +28,7 @@
|
||||
</table>
|
||||
|
||||
<br>
|
||||
{{ paginate_jinja(page_obj, paginator) }}
|
||||
{% if is_paginated %}
|
||||
{{ paginate_jinja(request, page_obj, paginator) }}
|
||||
{% endif %}
|
||||
{% endblock content %}
|
||||
@@ -6,8 +6,14 @@
|
||||
{% trans %}New subscription{% endtrans %}
|
||||
{% endblock %}
|
||||
|
||||
{# The following statics are bundled with our autocomplete select.
|
||||
However, if one tries to swap a form by another, then the urls in script-once
|
||||
and link-once disappear.
|
||||
So we give them here.
|
||||
If the aforementioned bug is resolved, you can remove this. #}
|
||||
{% block additional_js %}
|
||||
<script type="module" src="{{ static('bundled/core/components/tabs-index.ts') }}"></script>
|
||||
<script type="module" src="{{ static("bundled/core/components/ajax-select-index.ts") }}"></script>
|
||||
<script
|
||||
type="module"
|
||||
src="{{ static("bundled/subscription/creation-form-existing-user-index.ts") }}"
|
||||
@@ -15,6 +21,8 @@
|
||||
{% endblock %}
|
||||
{% block additional_css %}
|
||||
<link rel="stylesheet" href="{{ static("core/components/tabs.scss") }}">
|
||||
<link rel="stylesheet" href="{{ static("bundled/core/components/ajax-select-index.css") }}">
|
||||
<link rel="stylesheet" href="{{ static("core/components/ajax-select.scss") }}">
|
||||
<link rel="stylesheet" href="{{ static("subscription/css/subscription.scss") }}">
|
||||
{% endblock %}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user