mirror of
https://github.com/ae-utbm/sith.git
synced 2025-01-09 16:41:12 +00:00
Merge pull request #975 from ae-utbm/unified-calendar
Unified calendar widget on main com page with external and internal events
This commit is contained in:
commit
d1e604e7a5
32
com/api.py
Normal file
32
com/api.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.http import Http404
|
||||||
|
from ninja_extra import ControllerBase, api_controller, route
|
||||||
|
|
||||||
|
from com.calendar import IcsCalendar
|
||||||
|
from core.views.files import send_raw_file
|
||||||
|
|
||||||
|
|
||||||
|
@api_controller("/calendar")
|
||||||
|
class CalendarController(ControllerBase):
|
||||||
|
CACHE_FOLDER: Path = settings.MEDIA_ROOT / "com" / "calendars"
|
||||||
|
|
||||||
|
@route.get("/external.ics", url_name="calendar_external")
|
||||||
|
def calendar_external(self):
|
||||||
|
"""Return the ICS file of the AE Google Calendar
|
||||||
|
|
||||||
|
Because of Google's cors rules, we can't just do a request to google ics
|
||||||
|
from the frontend. Google is blocking CORS request in it's responses headers.
|
||||||
|
The only way to do it from the frontend is to use Google Calendar API with an API key
|
||||||
|
This is not especially desirable as your API key is going to be provided to the frontend.
|
||||||
|
|
||||||
|
This is why we have this backend based solution.
|
||||||
|
"""
|
||||||
|
if (calendar := IcsCalendar.get_external()) is not None:
|
||||||
|
return send_raw_file(calendar)
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
@route.get("/internal.ics", url_name="calendar_internal")
|
||||||
|
def calendar_internal(self):
|
||||||
|
return send_raw_file(IcsCalendar.get_internal())
|
9
com/apps.py
Normal file
9
com/apps.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ComConfig(AppConfig):
|
||||||
|
name = "com"
|
||||||
|
verbose_name = "News and communication"
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
import com.signals # noqa F401
|
76
com/calendar.py
Normal file
76
com/calendar.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
from datetime import datetime, timedelta
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import final
|
||||||
|
|
||||||
|
import urllib3
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
|
from django.conf import settings
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils import timezone
|
||||||
|
from ical.calendar import Calendar
|
||||||
|
from ical.calendar_stream import IcsCalendarStream
|
||||||
|
from ical.event import Event
|
||||||
|
|
||||||
|
from com.models import NewsDate
|
||||||
|
|
||||||
|
|
||||||
|
@final
|
||||||
|
class IcsCalendar:
|
||||||
|
_CACHE_FOLDER: Path = settings.MEDIA_ROOT / "com" / "calendars"
|
||||||
|
_EXTERNAL_CALENDAR = _CACHE_FOLDER / "external.ics"
|
||||||
|
_INTERNAL_CALENDAR = _CACHE_FOLDER / "internal.ics"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_external(cls, expiration: timedelta = timedelta(hours=1)) -> Path | None:
|
||||||
|
if (
|
||||||
|
cls._EXTERNAL_CALENDAR.exists()
|
||||||
|
and timezone.make_aware(
|
||||||
|
datetime.fromtimestamp(cls._EXTERNAL_CALENDAR.stat().st_mtime)
|
||||||
|
)
|
||||||
|
+ expiration
|
||||||
|
> timezone.now()
|
||||||
|
):
|
||||||
|
return cls._EXTERNAL_CALENDAR
|
||||||
|
return cls.make_external()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def make_external(cls) -> Path | None:
|
||||||
|
calendar = urllib3.request(
|
||||||
|
"GET",
|
||||||
|
"https://calendar.google.com/calendar/ical/ae.utbm%40gmail.com/public/basic.ics",
|
||||||
|
)
|
||||||
|
if calendar.status != 200:
|
||||||
|
return None
|
||||||
|
|
||||||
|
cls._CACHE_FOLDER.mkdir(parents=True, exist_ok=True)
|
||||||
|
with open(cls._EXTERNAL_CALENDAR, "wb") as f:
|
||||||
|
_ = f.write(calendar.data)
|
||||||
|
return cls._EXTERNAL_CALENDAR
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_internal(cls) -> Path:
|
||||||
|
if not cls._INTERNAL_CALENDAR.exists():
|
||||||
|
return cls.make_internal()
|
||||||
|
return cls._INTERNAL_CALENDAR
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def make_internal(cls) -> Path:
|
||||||
|
# Updated through a post_save signal on News in com.signals
|
||||||
|
calendar = Calendar()
|
||||||
|
for news_date in NewsDate.objects.filter(
|
||||||
|
news__is_moderated=True,
|
||||||
|
end_date__gte=timezone.now() - (relativedelta(months=6)),
|
||||||
|
).prefetch_related("news"):
|
||||||
|
event = Event(
|
||||||
|
summary=news_date.news.title,
|
||||||
|
start=news_date.start_date,
|
||||||
|
end=news_date.end_date,
|
||||||
|
url=reverse("com:news_detail", kwargs={"news_id": news_date.news.id}),
|
||||||
|
)
|
||||||
|
calendar.events.append(event)
|
||||||
|
|
||||||
|
# Create a file so we can offload the download to the reverse proxy if available
|
||||||
|
cls._CACHE_FOLDER.mkdir(parents=True, exist_ok=True)
|
||||||
|
with open(cls._INTERNAL_CALENDAR, "wb") as f:
|
||||||
|
_ = f.write(IcsCalendarStream.calendar_to_ics(calendar).encode("utf-8"))
|
||||||
|
return cls._INTERNAL_CALENDAR
|
@ -17,11 +17,12 @@
|
|||||||
# details.
|
# details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License along with
|
# You should have received a copy of the GNU General Public License along with
|
||||||
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
|
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||||
# Place - Suite 330, Boston, MA 02111-1307, USA.
|
# Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.mail import EmailMultiAlternatives
|
from django.core.mail import EmailMultiAlternatives
|
||||||
|
10
com/signals.py
Normal file
10
com/signals.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from django.db.models.base import post_save
|
||||||
|
from django.dispatch import receiver
|
||||||
|
|
||||||
|
from com.calendar import IcsCalendar
|
||||||
|
from com.models import News
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=News, dispatch_uid="update_internal_ics")
|
||||||
|
def update_internal_ics(*args, **kwargs):
|
||||||
|
_ = IcsCalendar.make_internal()
|
197
com/static/bundled/com/components/ics-calendar-index.ts
Normal file
197
com/static/bundled/com/components/ics-calendar-index.ts
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
import { makeUrl } from "#core:utils/api";
|
||||||
|
import { inheritHtmlElement, registerComponent } from "#core:utils/web-components";
|
||||||
|
import { Calendar, type EventClickArg } from "@fullcalendar/core";
|
||||||
|
import type { EventImpl } from "@fullcalendar/core/internal";
|
||||||
|
import enLocale from "@fullcalendar/core/locales/en-gb";
|
||||||
|
import frLocale from "@fullcalendar/core/locales/fr";
|
||||||
|
import dayGridPlugin from "@fullcalendar/daygrid";
|
||||||
|
import iCalendarPlugin from "@fullcalendar/icalendar";
|
||||||
|
import listPlugin from "@fullcalendar/list";
|
||||||
|
import { calendarCalendarExternal, calendarCalendarInternal } from "#openapi";
|
||||||
|
|
||||||
|
@registerComponent("ics-calendar")
|
||||||
|
export class IcsCalendar extends inheritHtmlElement("div") {
|
||||||
|
static observedAttributes = ["locale"];
|
||||||
|
private calendar: Calendar;
|
||||||
|
private locale = "en";
|
||||||
|
|
||||||
|
attributeChangedCallback(name: string, _oldValue?: string, newValue?: string) {
|
||||||
|
if (name !== "locale") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.locale = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
isMobile() {
|
||||||
|
return window.innerWidth < 765;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentView() {
|
||||||
|
// Get view type based on viewport
|
||||||
|
return this.isMobile() ? "listMonth" : "dayGridMonth";
|
||||||
|
}
|
||||||
|
|
||||||
|
currentToolbar() {
|
||||||
|
if (this.isMobile()) {
|
||||||
|
return {
|
||||||
|
left: "prev,next",
|
||||||
|
center: "title",
|
||||||
|
right: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
left: "prev,next today",
|
||||||
|
center: "title",
|
||||||
|
right: "dayGridMonth,dayGridWeek,dayGridDay",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
formatDate(date: Date) {
|
||||||
|
return new Intl.DateTimeFormat(this.locale, {
|
||||||
|
dateStyle: "medium",
|
||||||
|
timeStyle: "short",
|
||||||
|
}).format(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
createEventDetailPopup(event: EventClickArg) {
|
||||||
|
// Delete previous popup
|
||||||
|
const oldPopup = document.getElementById("event-details");
|
||||||
|
if (oldPopup !== null) {
|
||||||
|
oldPopup.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const makePopupInfo = (info: HTMLElement, iconClass: string) => {
|
||||||
|
const row = document.createElement("div");
|
||||||
|
const icon = document.createElement("i");
|
||||||
|
|
||||||
|
row.setAttribute("class", "event-details-row");
|
||||||
|
|
||||||
|
icon.setAttribute("class", `event-detail-row-icon fa-xl ${iconClass}`);
|
||||||
|
|
||||||
|
row.appendChild(icon);
|
||||||
|
row.appendChild(info);
|
||||||
|
|
||||||
|
return row;
|
||||||
|
};
|
||||||
|
|
||||||
|
const makePopupTitle = (event: EventImpl) => {
|
||||||
|
const row = document.createElement("div");
|
||||||
|
const title = document.createElement("h4");
|
||||||
|
const time = document.createElement("span");
|
||||||
|
|
||||||
|
title.setAttribute("class", "event-details-row-content");
|
||||||
|
title.textContent = event.title;
|
||||||
|
|
||||||
|
time.setAttribute("class", "event-details-row-content");
|
||||||
|
time.textContent = `${this.formatDate(event.start)} - ${this.formatDate(event.end)}`;
|
||||||
|
|
||||||
|
row.appendChild(title);
|
||||||
|
row.appendChild(time);
|
||||||
|
return makePopupInfo(
|
||||||
|
row,
|
||||||
|
"fa-solid fa-calendar-days fa-xl event-detail-row-icon",
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const makePopupLocation = (event: EventImpl) => {
|
||||||
|
if (event.extendedProps.location === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const info = document.createElement("div");
|
||||||
|
info.innerText = event.extendedProps.location;
|
||||||
|
|
||||||
|
return makePopupInfo(info, "fa-solid fa-location-dot");
|
||||||
|
};
|
||||||
|
|
||||||
|
const makePopupUrl = (event: EventImpl) => {
|
||||||
|
if (event.url === "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const url = document.createElement("a");
|
||||||
|
url.href = event.url;
|
||||||
|
url.textContent = gettext("More info");
|
||||||
|
|
||||||
|
return makePopupInfo(url, "fa-solid fa-link");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create new popup
|
||||||
|
const popup = document.createElement("div");
|
||||||
|
const popupContainer = document.createElement("div");
|
||||||
|
|
||||||
|
popup.setAttribute("id", "event-details");
|
||||||
|
popupContainer.setAttribute("class", "event-details-container");
|
||||||
|
|
||||||
|
popupContainer.appendChild(makePopupTitle(event.event));
|
||||||
|
|
||||||
|
const location = makePopupLocation(event.event);
|
||||||
|
if (location !== null) {
|
||||||
|
popupContainer.appendChild(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = makePopupUrl(event.event);
|
||||||
|
if (url !== null) {
|
||||||
|
popupContainer.appendChild(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
popup.appendChild(popupContainer);
|
||||||
|
|
||||||
|
// We can't just add the element relative to the one we want to appear under
|
||||||
|
// Otherwise, it either gets clipped by the boundaries of the calendar or resize cells
|
||||||
|
// Here, we create a popup outside the calendar that follows the clicked element
|
||||||
|
this.node.appendChild(popup);
|
||||||
|
const follow = (node: HTMLElement) => {
|
||||||
|
const rect = node.getBoundingClientRect();
|
||||||
|
popup.setAttribute(
|
||||||
|
"style",
|
||||||
|
`top: calc(${rect.top + window.scrollY}px + ${rect.height}px); left: ${rect.left + window.scrollX}px;`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
follow(event.el);
|
||||||
|
window.addEventListener("resize", () => {
|
||||||
|
follow(event.el);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
this.calendar = new Calendar(this.node, {
|
||||||
|
plugins: [dayGridPlugin, iCalendarPlugin, listPlugin],
|
||||||
|
locales: [frLocale, enLocale],
|
||||||
|
height: "auto",
|
||||||
|
locale: this.locale,
|
||||||
|
initialView: this.currentView(),
|
||||||
|
headerToolbar: this.currentToolbar(),
|
||||||
|
eventSources: [
|
||||||
|
{
|
||||||
|
url: await makeUrl(calendarCalendarInternal),
|
||||||
|
format: "ics",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: await makeUrl(calendarCalendarExternal),
|
||||||
|
format: "ics",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
windowResize: () => {
|
||||||
|
this.calendar.changeView(this.currentView());
|
||||||
|
this.calendar.setOption("headerToolbar", this.currentToolbar());
|
||||||
|
},
|
||||||
|
eventClick: (event) => {
|
||||||
|
// Avoid our popup to be deleted because we clicked outside of it
|
||||||
|
event.jsEvent.stopPropagation();
|
||||||
|
// Don't auto-follow events URLs
|
||||||
|
event.jsEvent.preventDefault();
|
||||||
|
this.createEventDetailPopup(event);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.calendar.render();
|
||||||
|
|
||||||
|
window.addEventListener("click", (event: MouseEvent) => {
|
||||||
|
// Auto close popups when clicking outside of it
|
||||||
|
const popup = document.getElementById("event-details");
|
||||||
|
if (popup !== null && !popup.contains(event.target as Node)) {
|
||||||
|
popup.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
101
com/static/com/components/ics-calendar.scss
Normal file
101
com/static/com/components/ics-calendar.scss
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
@import "core/static/core/colors";
|
||||||
|
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--fc-button-border-color: #fff;
|
||||||
|
--fc-button-hover-border-color: #fff;
|
||||||
|
--fc-button-active-border-color: #fff;
|
||||||
|
--fc-button-text-color: #fff;
|
||||||
|
--fc-button-bg-color: #1a78b3;
|
||||||
|
--fc-button-active-bg-color: #15608F;
|
||||||
|
--fc-button-hover-bg-color: #15608F;
|
||||||
|
--fc-today-bg-color: rgba(26, 120, 179, 0.1);
|
||||||
|
--fc-border-color: #DDDDDD;
|
||||||
|
--event-details-background-color: white;
|
||||||
|
--event-details-padding: 20px;
|
||||||
|
--event-details-border: 1px solid #EEEEEE;
|
||||||
|
--event-details-border-radius: 4px;
|
||||||
|
--event-details-box-shadow: 0px 6px 20px 4px rgb(0 0 0 / 16%);
|
||||||
|
--event-details-max-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ics-calendar {
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
#event-details {
|
||||||
|
z-index: 10;
|
||||||
|
max-width: 1151px;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
.event-details-container {
|
||||||
|
display: flex;
|
||||||
|
color: black;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 200px;
|
||||||
|
max-width: var(--event-details-max-width);
|
||||||
|
padding: var(--event-details-padding);
|
||||||
|
border: var(--event-details-border);
|
||||||
|
border-radius: var(--event-details-border-radius);
|
||||||
|
background-color: var(--event-details-background-color);
|
||||||
|
box-shadow: var(--event-details-box-shadow);
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-detail-row-icon {
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-right: 20px;
|
||||||
|
align-content: center;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-details-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-details-row-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: start;
|
||||||
|
flex-direction: row;
|
||||||
|
background-color: var(--event-details-background-color);
|
||||||
|
margin-top: 0px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.fc-col-header-cell-cushion,
|
||||||
|
a.fc-col-header-cell-cushion:hover {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.fc-daygrid-day-number,
|
||||||
|
a.fc-daygrid-day-number:hover {
|
||||||
|
color: rgb(34, 34, 34);
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
overflow-x: visible; // Show events on multiple days
|
||||||
|
}
|
||||||
|
|
||||||
|
//Reset from style.scss
|
||||||
|
table {
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: 0px;
|
||||||
|
-moz-border-radius: 0px;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset from style.scss
|
||||||
|
thead {
|
||||||
|
background-color: white;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset from style.scss
|
||||||
|
tbody>tr {
|
||||||
|
&:nth-child(even):not(.highlight) {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
66
com/static/com/css/news-detail.scss
Normal file
66
com/static/com/css/news-detail.scss
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
@import "core/static/core/colors";
|
||||||
|
|
||||||
|
#news_details {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 0.4em;
|
||||||
|
width: 80%;
|
||||||
|
background: $white-color;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin-top: 1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.club_logo {
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
width: 19%;
|
||||||
|
float: left;
|
||||||
|
min-width: 15em;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-height: 15em;
|
||||||
|
max-width: 12em;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.share_button {
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 1.2em;
|
||||||
|
border-radius: 2px;
|
||||||
|
float: right;
|
||||||
|
display: block;
|
||||||
|
margin-left: 0.3em;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: lightgrey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.facebook {
|
||||||
|
background: $faceblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twitter {
|
||||||
|
background: $twitblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.news_meta {
|
||||||
|
margin-top: 10em;
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.helptext {
|
||||||
|
margin-top: 10px;
|
||||||
|
display: block;
|
||||||
|
}
|
297
com/static/com/css/news-list.scss
Normal file
297
com/static/com/css/news-list.scss
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
@import "core/static/core/colors";
|
||||||
|
@import "core/static/core/devices";
|
||||||
|
|
||||||
|
#news {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#news_admin {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#right_column {
|
||||||
|
flex: 20%;
|
||||||
|
margin: 3.2px;
|
||||||
|
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
#left_column {
|
||||||
|
flex: 79%;
|
||||||
|
margin: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
background: $second-color;
|
||||||
|
box-shadow: $shadow-color 1px 1px 1px;
|
||||||
|
padding: 0.4em;
|
||||||
|
margin: 0 0 0.5em 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 17px;
|
||||||
|
|
||||||
|
&:not(:first-of-type) {
|
||||||
|
margin: 2em 0 1em 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $small-devices) {
|
||||||
|
|
||||||
|
#left_column,
|
||||||
|
#right_column {
|
||||||
|
flex: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LINKS/BIRTHDAYS */
|
||||||
|
#links,
|
||||||
|
#birthdays {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
background: white;
|
||||||
|
font-size: 70%;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#links_content {
|
||||||
|
overflow: auto;
|
||||||
|
box-shadow: $shadow-color 1px 1px 1px;
|
||||||
|
height: 20em;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
margin-left: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin: 10px;
|
||||||
|
|
||||||
|
.fa-facebook {
|
||||||
|
color: $faceblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-discord {
|
||||||
|
color: $discordblurple;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-square-instagram::before {
|
||||||
|
background: $instagradient;
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
width: 25px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#birthdays_content {
|
||||||
|
ul.birthdays_year {
|
||||||
|
margin: 0;
|
||||||
|
list-style-type: none;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
>li {
|
||||||
|
padding: 0.5em;
|
||||||
|
|
||||||
|
&:nth-child(even) {
|
||||||
|
background: $secondary-neutral-light-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
margin-left: 1em;
|
||||||
|
list-style-type: square;
|
||||||
|
list-style-position: inside;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* END AGENDA/BIRTHDAYS */
|
||||||
|
|
||||||
|
/* EVENTS TODAY AND NEXT FEW DAYS */
|
||||||
|
.news_events_group {
|
||||||
|
box-shadow: $shadow-color 1px 1px 1px;
|
||||||
|
margin-left: 1em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
|
||||||
|
.news_events_group_date {
|
||||||
|
display: table-cell;
|
||||||
|
padding: 0.6em;
|
||||||
|
vertical-align: middle;
|
||||||
|
background: $primary-neutral-dark-color;
|
||||||
|
color: $white-color;
|
||||||
|
text-transform: uppercase;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 1.4em;
|
||||||
|
border-radius: 7px 0 0 7px;
|
||||||
|
|
||||||
|
div {
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
.day {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.news_events_group_items {
|
||||||
|
display: table-cell;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.news_event:nth-of-type(odd) {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.news_event:nth-of-type(even) {
|
||||||
|
background: $primary-neutral-light-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.news_event {
|
||||||
|
display: block;
|
||||||
|
padding: 0.4em;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-bottom: 1px solid grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
margin: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin-top: 1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.club_logo {
|
||||||
|
float: left;
|
||||||
|
min-width: 7em;
|
||||||
|
max-width: 9em;
|
||||||
|
margin: 0;
|
||||||
|
margin-right: 1em;
|
||||||
|
margin-top: 0.8em;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-height: 6em;
|
||||||
|
max-width: 8em;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.news_date {
|
||||||
|
font-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.news_content {
|
||||||
|
clear: left;
|
||||||
|
|
||||||
|
.button_bar {
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
.fb {
|
||||||
|
color: $faceblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twitter {
|
||||||
|
color: $twitblue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* END EVENTS TODAY AND NEXT FEW DAYS */
|
||||||
|
|
||||||
|
/* COMING SOON */
|
||||||
|
.news_coming_soon {
|
||||||
|
display: list-item;
|
||||||
|
list-style-type: square;
|
||||||
|
list-style-position: inside;
|
||||||
|
margin-left: 1em;
|
||||||
|
padding-left: 0;
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.news_date {
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* END COMING SOON */
|
||||||
|
|
||||||
|
/* NOTICES */
|
||||||
|
.news_notice {
|
||||||
|
margin: 0 0 1em 1em;
|
||||||
|
padding: 0.4em;
|
||||||
|
padding-left: 1em;
|
||||||
|
background: $secondary-neutral-light-color;
|
||||||
|
box-shadow: $shadow-color 0 0 2px;
|
||||||
|
border-radius: 18px 5px 18px 5px;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.news_content {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* END NOTICES */
|
||||||
|
|
||||||
|
/* CALLS */
|
||||||
|
.news_call {
|
||||||
|
margin: 0 0 1em 1em;
|
||||||
|
padding: 0.4em;
|
||||||
|
padding-left: 1em;
|
||||||
|
background: $secondary-neutral-light-color;
|
||||||
|
border: 1px solid grey;
|
||||||
|
box-shadow: $shadow-color 1px 1px 1px;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.news_date {
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.news_content {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* END CALLS */
|
||||||
|
|
||||||
|
.news_empty {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.news_date {
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
}
|
230
com/static/com/css/posters.scss
Normal file
230
com/static/com/css/posters.scss
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
#poster_list,
|
||||||
|
#screen_list,
|
||||||
|
#poster_edit,
|
||||||
|
#screen_edit {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
#title {
|
||||||
|
position: relative;
|
||||||
|
padding: 10px;
|
||||||
|
margin: 10px;
|
||||||
|
border-bottom: 2px solid black;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#links {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
bottom: 5px;
|
||||||
|
|
||||||
|
&.left {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.right {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
padding: 5px;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
margin-left: 5px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background-color: hsl(40, 100%, 50%);
|
||||||
|
color: black;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: black;
|
||||||
|
background-color: hsl(40, 58%, 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.delete {
|
||||||
|
background-color: hsl(0, 100%, 40%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#posters,
|
||||||
|
#screens {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
#no-posters,
|
||||||
|
#no-screens {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poster,
|
||||||
|
.screen {
|
||||||
|
min-width: 10%;
|
||||||
|
max-width: 20%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 10px;
|
||||||
|
border: 2px solid darkgrey;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: lightgrey;
|
||||||
|
|
||||||
|
* {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
padding-bottom: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
border-bottom: 1px solid whitesmoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
flex-grow: 1;
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
border-bottom: 1px solid whitesmoke;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-height: 20vw;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 10;
|
||||||
|
content: "Click to expand";
|
||||||
|
color: white;
|
||||||
|
background-color: rgba(black, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dates {
|
||||||
|
padding-bottom: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
border-bottom: 1px solid whitesmoke;
|
||||||
|
|
||||||
|
* {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.begin,
|
||||||
|
.end {
|
||||||
|
width: 48%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.begin {
|
||||||
|
border-right: 1px solid whitesmoke;
|
||||||
|
padding-right: 2%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit,
|
||||||
|
.moderate,
|
||||||
|
.slideshow {
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background-color: hsl(40, 100%, 50%);
|
||||||
|
color: black;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: black;
|
||||||
|
background-color: hsl(40, 58%, 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(2n) {
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip {
|
||||||
|
visibility: hidden;
|
||||||
|
width: 120px;
|
||||||
|
background-color: hsl(210, 20%, 98%);
|
||||||
|
color: hsl(0, 0%, 0%);
|
||||||
|
text-align: center;
|
||||||
|
padding: 5px 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin-left: 0;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: list-item;
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.not_moderated {
|
||||||
|
border: 1px solid red;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .tooltip {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#view {
|
||||||
|
position: fixed;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 10;
|
||||||
|
visibility: hidden;
|
||||||
|
background-color: rgba(10, 10, 10, 0.9);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
#placeholder {
|
||||||
|
width: 80vw;
|
||||||
|
height: 80vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,11 @@
|
|||||||
{{ gen_news_metatags(news) }}
|
{{ gen_news_metatags(news) }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block additional_css %}
|
||||||
|
<link rel="stylesheet" href="{{ static('com/css/news-detail.scss') }}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<p><a href="{{ url('com:news_list') }}">{% trans %}Back to news{% endtrans %}</a></p>
|
<p><a href="{{ url('com:news_list') }}">{% trans %}Back to news{% endtrans %}</a></p>
|
||||||
<section id="news_details">
|
<section id="news_details">
|
||||||
|
@ -5,6 +5,15 @@
|
|||||||
{% trans %}News{% endtrans %}
|
{% trans %}News{% endtrans %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block additional_css %}
|
||||||
|
<link rel="stylesheet" href="{{ static('com/css/news-list.scss') }}">
|
||||||
|
<link rel="stylesheet" href="{{ static('com/components/ics-calendar.scss') }}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block additional_js %}
|
||||||
|
<script type="module" src={{ static("bundled/com/components/ics-calendar-index.ts") }}></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if user.is_com_admin %}
|
{% if user.is_com_admin %}
|
||||||
<div id="news_admin">
|
<div id="news_admin">
|
||||||
@ -83,60 +92,55 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% set coming_soon = object_list.filter(dates__start_date__gte=timezone.now()+timedelta(days=5),
|
|
||||||
type="EVENT").order_by('dates__start_date') %}
|
|
||||||
{% if coming_soon %}
|
|
||||||
<h3>{% trans %}Coming soon... don't miss!{% endtrans %}</h3>
|
|
||||||
{% for news in coming_soon %}
|
|
||||||
<section class="news_coming_soon">
|
|
||||||
<a href="{{ url('com:news_detail', news_id=news.id) }}">{{ news.title }}</a>
|
|
||||||
<span class="news_date">{{ news.dates.first().start_date|localtime|date(DATETIME_FORMAT) }}
|
|
||||||
{{ news.dates.first().start_date|localtime|time(DATETIME_FORMAT) }} -
|
|
||||||
{{ news.dates.first().end_date|localtime|date(DATETIME_FORMAT) }}
|
|
||||||
{{ news.dates.first().end_date|localtime|time(DATETIME_FORMAT) }}</span>
|
|
||||||
</section>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<h3>{% trans %}All coming events{% endtrans %}</h3>
|
<h3>{% trans %}All coming events{% endtrans %}</h3>
|
||||||
<iframe
|
<ics-calendar locale="{{ get_language() }}"></ics-calendar>
|
||||||
src="https://embed.styledcalendar.com/#2mF2is8CEXhr4ADcX6qN"
|
|
||||||
title="Styled Calendar"
|
|
||||||
class="styled-calendar-container"
|
|
||||||
style="width: 100%; border: none; height: 1060px"
|
|
||||||
data-cy="calendar-embed-iframe">
|
|
||||||
</iframe>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="right_column" class="news_column">
|
<div id="right_column">
|
||||||
<div id="agenda">
|
<div id="links">
|
||||||
<div id="agenda_title">{% trans %}Agenda{% endtrans %}</div>
|
<h3>{% trans %}Links{% endtrans %}</h3>
|
||||||
<div id="agenda_content">
|
<div id="links_content">
|
||||||
{% for d in NewsDate.objects.filter(end_date__gte=timezone.now(),
|
<h4>{% trans %}Our services{% endtrans %}</h4>
|
||||||
news__is_moderated=True, news__type__in=["WEEKLY",
|
<ul>
|
||||||
"EVENT"]).order_by('start_date', 'end_date') %}
|
<li>
|
||||||
<div class="agenda_item">
|
<i class="fa-solid fa-graduation-cap fa-xl"></i>
|
||||||
<div class="agenda_date">
|
<a href="{{ url("pedagogy:guide") }}">{% trans %}UV Guide{% endtrans %}</a>
|
||||||
<strong>{{ d.start_date|localtime|date('D d M Y') }}</strong>
|
</li>
|
||||||
</div>
|
<li>
|
||||||
<div class="agenda_time">
|
<i class="fa-solid fa-magnifying-glass fa-xl"></i>
|
||||||
<span>{{ d.start_date|localtime|time(DATETIME_FORMAT) }}</span> -
|
<a href="{{ url("matmat:search_clear") }}">{% trans %}Matmatronch{% endtrans %}</a>
|
||||||
<span>{{ d.end_date|localtime|time(DATETIME_FORMAT) }}</span>
|
</li>
|
||||||
</div>
|
<li>
|
||||||
<div>
|
<i class="fa-solid fa-check-to-slot fa-xl"></i>
|
||||||
<strong><a href="{{ url('com:news_detail', news_id=d.news.id) }}">{{ d.news.title }}</a></strong>
|
<a href="{{ url("election:list") }}">{% trans %}Elections{% endtrans %}</a>
|
||||||
<a href="{{ d.news.club.get_absolute_url() }}">{{ d.news.club }}</a>
|
</li>
|
||||||
</div>
|
</ul>
|
||||||
<div class="agenda_item_content">{{ d.news.summary|markdown }}</div>
|
<br>
|
||||||
</div>
|
<h4>{% trans %}Social media{% endtrans %}</h4>
|
||||||
{% endfor %}
|
<ul>
|
||||||
|
<li>
|
||||||
|
<i class="fa-brands fa-discord fa-xl"></i>
|
||||||
|
<a rel="nofollow" target="#" href="https://discord.gg/QvTm3XJrHR">{% trans %}Discord{% endtrans %}</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<i class="fa-brands fa-facebook fa-xl"></i>
|
||||||
|
<a rel="nofollow" target="#" href="https://www.facebook.com/@AEUTBM/">{% trans %}Facebook{% endtrans %}</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<i class="fa-brands fa-square-instagram fa-xl"></i>
|
||||||
|
<a rel="nofollow" target="#" href="https://www.instagram.com/ae_utbm">{% trans %}Instagram{% endtrans %}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="birthdays">
|
<div id="birthdays">
|
||||||
<div id="birthdays_title">{% trans %}Birthdays{% endtrans %}</div>
|
<h3>{% trans %}Birthdays{% endtrans %}</h3>
|
||||||
<div id="birthdays_content">
|
<div id="birthdays_content">
|
||||||
{%- if user.is_subscribed -%}
|
{%- if user.was_subscribed -%}
|
||||||
<ul class="birthdays_year">
|
<ul class="birthdays_year">
|
||||||
{%- for year, users in birthdays -%}
|
{%- for year, users in birthdays -%}
|
||||||
<li>
|
<li>
|
||||||
@ -150,12 +154,14 @@ type="EVENT").order_by('dates__start_date') %}
|
|||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
</ul>
|
</ul>
|
||||||
{%- else -%}
|
{%- else -%}
|
||||||
<p>{% trans %}You need an up to date subscription to access this content{% endtrans %}</p>
|
<p>{% trans %}You need to subscribe to access this content{% endtrans %}</p>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -10,6 +10,10 @@
|
|||||||
{% trans %}Poster{% endtrans %}
|
{% trans %}Poster{% endtrans %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block additional_css %}
|
||||||
|
<link rel="stylesheet" href="{{ static('com/css/posters.scss') }}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="poster_list">
|
<div id="poster_list">
|
||||||
|
|
||||||
|
@ -5,6 +5,10 @@
|
|||||||
<script src="{{ static('com/js/poster_list.js') }}"></script>
|
<script src="{{ static('com/js/poster_list.js') }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block additional_css %}
|
||||||
|
<link rel="stylesheet" href="{{ static('com/css/posters.scss') }}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="poster_list">
|
<div id="poster_list">
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<title>{% trans %}Slideshow{% endtrans %}</title>
|
<title>{% trans %}Slideshow{% endtrans %}</title>
|
||||||
<link href="{{ static('css/slideshow.scss') }}" rel="stylesheet" type="text/css" />
|
<link href="{{ static('css/slideshow.scss') }}" rel="stylesheet" type="text/css" />
|
||||||
<script type="module" src="{{ static('bundled/jquery-index.js') }}"></script>
|
<script src="{{ static('bundled/vendored/jquery.min.js') }}"></script>
|
||||||
<script src="{{ static('com/js/slideshow.js') }}"></script>
|
<script src="{{ static('com/js/slideshow.js') }}"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
122
com/tests/test_api.py
Normal file
122
com/tests/test_api.py
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Callable
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from django.conf import settings
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.test.client import Client
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from com.calendar import IcsCalendar
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MockResponse:
|
||||||
|
status: int
|
||||||
|
value: str
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self):
|
||||||
|
return self.value.encode("utf8")
|
||||||
|
|
||||||
|
|
||||||
|
def accel_redirect_to_file(response: HttpResponse) -> Path | None:
|
||||||
|
redirect = Path(response.headers.get("X-Accel-Redirect", ""))
|
||||||
|
if not redirect.is_relative_to(Path("/") / settings.MEDIA_ROOT.stem):
|
||||||
|
return None
|
||||||
|
return settings.MEDIA_ROOT / redirect.relative_to(
|
||||||
|
Path("/") / settings.MEDIA_ROOT.stem
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
class TestExternalCalendar:
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_request(self):
|
||||||
|
mock = MagicMock()
|
||||||
|
with patch("urllib3.request", mock):
|
||||||
|
yield mock
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_current_time(self):
|
||||||
|
mock = MagicMock()
|
||||||
|
original = timezone.now
|
||||||
|
with patch("django.utils.timezone.now", mock):
|
||||||
|
yield mock, original
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def clear_cache(self):
|
||||||
|
IcsCalendar._EXTERNAL_CALENDAR.unlink(missing_ok=True)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("error_code", [403, 404, 500])
|
||||||
|
def test_fetch_error(
|
||||||
|
self, client: Client, mock_request: MagicMock, error_code: int
|
||||||
|
):
|
||||||
|
mock_request.return_value = MockResponse(error_code, "not allowed")
|
||||||
|
assert client.get(reverse("api:calendar_external")).status_code == 404
|
||||||
|
|
||||||
|
def test_fetch_success(self, client: Client, mock_request: MagicMock):
|
||||||
|
external_response = MockResponse(200, "Definitely an ICS")
|
||||||
|
mock_request.return_value = external_response
|
||||||
|
response = client.get(reverse("api:calendar_external"))
|
||||||
|
assert response.status_code == 200
|
||||||
|
out_file = accel_redirect_to_file(response)
|
||||||
|
assert out_file is not None
|
||||||
|
assert out_file.exists()
|
||||||
|
with open(out_file, "r") as f:
|
||||||
|
assert f.read() == external_response.value
|
||||||
|
|
||||||
|
def test_fetch_caching(
|
||||||
|
self,
|
||||||
|
client: Client,
|
||||||
|
mock_request: MagicMock,
|
||||||
|
mock_current_time: tuple[MagicMock, Callable[[], datetime]],
|
||||||
|
):
|
||||||
|
fake_current_time, original_timezone = mock_current_time
|
||||||
|
start_time = original_timezone()
|
||||||
|
|
||||||
|
fake_current_time.return_value = start_time
|
||||||
|
external_response = MockResponse(200, "Definitely an ICS")
|
||||||
|
mock_request.return_value = external_response
|
||||||
|
|
||||||
|
with open(
|
||||||
|
accel_redirect_to_file(client.get(reverse("api:calendar_external"))), "r"
|
||||||
|
) as f:
|
||||||
|
assert f.read() == external_response.value
|
||||||
|
|
||||||
|
mock_request.return_value = MockResponse(200, "This should be ignored")
|
||||||
|
with open(
|
||||||
|
accel_redirect_to_file(client.get(reverse("api:calendar_external"))), "r"
|
||||||
|
) as f:
|
||||||
|
assert f.read() == external_response.value
|
||||||
|
|
||||||
|
mock_request.assert_called_once()
|
||||||
|
|
||||||
|
fake_current_time.return_value = start_time + timedelta(hours=1, seconds=1)
|
||||||
|
external_response = MockResponse(200, "This won't be ignored")
|
||||||
|
mock_request.return_value = external_response
|
||||||
|
|
||||||
|
with open(
|
||||||
|
accel_redirect_to_file(client.get(reverse("api:calendar_external"))), "r"
|
||||||
|
) as f:
|
||||||
|
assert f.read() == external_response.value
|
||||||
|
|
||||||
|
assert mock_request.call_count == 2
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
class TestInternalCalendar:
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def clear_cache(self):
|
||||||
|
IcsCalendar._INTERNAL_CALENDAR.unlink(missing_ok=True)
|
||||||
|
|
||||||
|
def test_fetch_success(self, client: Client):
|
||||||
|
response = client.get(reverse("api:calendar_internal"))
|
||||||
|
assert response.status_code == 200
|
||||||
|
out_file = accel_redirect_to_file(response)
|
||||||
|
assert out_file is not None
|
||||||
|
assert out_file.exists()
|
@ -97,9 +97,7 @@ class TestCom(TestCase):
|
|||||||
response = self.client.get(reverse("core:index"))
|
response = self.client.get(reverse("core:index"))
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
response,
|
response,
|
||||||
text=html.escape(
|
text=html.escape(_("You need to subscribe to access this content")),
|
||||||
_("You need an up to date subscription to access this content")
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_birthday_subscibed_user(self):
|
def test_birthday_subscibed_user(self):
|
||||||
@ -107,9 +105,16 @@ class TestCom(TestCase):
|
|||||||
|
|
||||||
self.assertNotContains(
|
self.assertNotContains(
|
||||||
response,
|
response,
|
||||||
text=html.escape(
|
text=html.escape(_("You need to subscribe to access this content")),
|
||||||
_("You need an up to date subscription to access this content")
|
)
|
||||||
),
|
|
||||||
|
def test_birthday_old_subscibed_user(self):
|
||||||
|
self.client.force_login(User.objects.get(username="old_subscriber"))
|
||||||
|
response = self.client.get(reverse("core:index"))
|
||||||
|
|
||||||
|
self.assertNotContains(
|
||||||
|
response,
|
||||||
|
text=html.escape(_("You need to subscribe to access this content")),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -685,8 +685,12 @@ class PosterEditBaseView(UpdateView):
|
|||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
return {
|
return {
|
||||||
"date_begin": self.object.date_begin.strftime("%Y-%m-%d %H:%M:%S"),
|
"date_begin": self.object.date_begin.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
"date_end": self.object.date_end.strftime("%Y-%m-%d %H:%M:%S"),
|
if self.object.date_begin
|
||||||
|
else None,
|
||||||
|
"date_end": self.object.date_end.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
if self.object.date_end
|
||||||
|
else None,
|
||||||
}
|
}
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
@ -46,6 +46,7 @@ from accounting.models import (
|
|||||||
SimplifiedAccountingType,
|
SimplifiedAccountingType,
|
||||||
)
|
)
|
||||||
from club.models import Club, Membership
|
from club.models import Club, Membership
|
||||||
|
from com.calendar import IcsCalendar
|
||||||
from com.models import News, NewsDate, Sith, Weekmail
|
from com.models import News, NewsDate, Sith, Weekmail
|
||||||
from core.models import Group, Page, PageRev, SithFile, User
|
from core.models import Group, Page, PageRev, SithFile, User
|
||||||
from core.utils import resize_image
|
from core.utils import resize_image
|
||||||
@ -738,7 +739,7 @@ Welcome to the wiki page!
|
|||||||
NewsDate(
|
NewsDate(
|
||||||
news=n,
|
news=n,
|
||||||
start_date=friday + timedelta(hours=24 * 7 + 1),
|
start_date=friday + timedelta(hours=24 * 7 + 1),
|
||||||
end_date=self.now + timedelta(hours=24 * 7 + 9),
|
end_date=friday + timedelta(hours=24 * 7 + 9),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
# Weekly
|
# Weekly
|
||||||
@ -764,8 +765,9 @@ Welcome to the wiki page!
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
NewsDate.objects.bulk_create(news_dates)
|
NewsDate.objects.bulk_create(news_dates)
|
||||||
|
IcsCalendar.make_internal() # Force refresh of the calendar after a bulk_create
|
||||||
|
|
||||||
# Create som data for pedagogy
|
# Create some data for pedagogy
|
||||||
|
|
||||||
UV(
|
UV(
|
||||||
code="PA00",
|
code="PA00",
|
||||||
|
@ -24,6 +24,8 @@ $black-color: hsl(0, 0%, 17%);
|
|||||||
|
|
||||||
$faceblue: hsl(221, 44%, 41%);
|
$faceblue: hsl(221, 44%, 41%);
|
||||||
$twitblue: hsl(206, 82%, 63%);
|
$twitblue: hsl(206, 82%, 63%);
|
||||||
|
$discordblurple: #7289da;
|
||||||
|
$instagradient: radial-gradient(circle at 30% 107%, #fdf497 0%, #fdf497 5%, #fd5949 45%, #d6249f 60%, #285AEB 90%);
|
||||||
|
|
||||||
$shadow-color: rgb(223, 223, 223);
|
$shadow-color: rgb(223, 223, 223);
|
||||||
|
|
||||||
|
5
core/static/core/devices.scss
Normal file
5
core/static/core/devices.scss
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/*--------------------------MEDIA QUERY HELPERS------------------------*/
|
||||||
|
|
||||||
|
$small-devices: 576px;
|
||||||
|
$medium-devices: 768px;
|
||||||
|
$large-devices: 992px;
|
@ -1,10 +1,6 @@
|
|||||||
@import "colors";
|
@import "colors";
|
||||||
@import "forms";
|
@import "forms";
|
||||||
|
@import "devices";
|
||||||
/*--------------------------MEDIA QUERY HELPERS------------------------*/
|
|
||||||
$small-devices: 576px;
|
|
||||||
$medium-devices: 768px;
|
|
||||||
$large-devices: 992px;
|
|
||||||
|
|
||||||
/*--------------------------------GENERAL------------------------------*/
|
/*--------------------------------GENERAL------------------------------*/
|
||||||
|
|
||||||
@ -453,302 +449,6 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*---------------------------------NEWS--------------------------------*/
|
|
||||||
#news {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
@media (max-width: 800px) {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.news_column {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
#news_admin {
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#right_column {
|
|
||||||
flex: 20%;
|
|
||||||
float: right;
|
|
||||||
margin: 0.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#left_column {
|
|
||||||
flex: 79%;
|
|
||||||
margin: 0.2em;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
background: $second-color;
|
|
||||||
box-shadow: $shadow-color 1px 1px 1px;
|
|
||||||
padding: 0.4em;
|
|
||||||
margin: 0 0 0.5em 0;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: 1.1em;
|
|
||||||
|
|
||||||
&:not(:first-of-type) {
|
|
||||||
margin: 2em 0 1em 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: $small-devices) {
|
|
||||||
|
|
||||||
#left_column,
|
|
||||||
#right_column {
|
|
||||||
flex: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* AGENDA/BIRTHDAYS */
|
|
||||||
#agenda,
|
|
||||||
#birthdays {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
background: white;
|
|
||||||
font-size: 70%;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
|
|
||||||
#agenda_title,
|
|
||||||
#birthdays_title {
|
|
||||||
margin: 0;
|
|
||||||
border-radius: 5px 5px 0 0;
|
|
||||||
box-shadow: $shadow-color 1px 1px 1px;
|
|
||||||
padding: 0.5em;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 150%;
|
|
||||||
text-align: center;
|
|
||||||
text-transform: uppercase;
|
|
||||||
background: $second-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#agenda_content {
|
|
||||||
overflow: auto;
|
|
||||||
box-shadow: $shadow-color 1px 1px 1px;
|
|
||||||
height: 20em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#agenda_content,
|
|
||||||
#birthdays_content {
|
|
||||||
.agenda_item {
|
|
||||||
padding: 0.5em;
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
|
|
||||||
&:nth-of-type(even) {
|
|
||||||
background: $secondary-neutral-light-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agenda_time {
|
|
||||||
font-size: 90%;
|
|
||||||
color: grey;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agenda_item_content {
|
|
||||||
p {
|
|
||||||
margin-top: 0.2em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.birthdays_year {
|
|
||||||
margin: 0;
|
|
||||||
list-style-type: none;
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
>li {
|
|
||||||
padding: 0.5em;
|
|
||||||
|
|
||||||
&:nth-child(even) {
|
|
||||||
background: $secondary-neutral-light-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
margin: 0;
|
|
||||||
margin-left: 1em;
|
|
||||||
list-style-type: square;
|
|
||||||
list-style-position: inside;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* END AGENDA/BIRTHDAYS */
|
|
||||||
|
|
||||||
/* EVENTS TODAY AND NEXT FEW DAYS */
|
|
||||||
.news_events_group {
|
|
||||||
box-shadow: $shadow-color 1px 1px 1px;
|
|
||||||
margin-left: 1em;
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
|
|
||||||
.news_events_group_date {
|
|
||||||
display: table-cell;
|
|
||||||
padding: 0.6em;
|
|
||||||
vertical-align: middle;
|
|
||||||
background: $primary-neutral-dark-color;
|
|
||||||
color: $white-color;
|
|
||||||
text-transform: uppercase;
|
|
||||||
text-align: center;
|
|
||||||
font-weight: bold;
|
|
||||||
font-family: monospace;
|
|
||||||
font-size: 1.4em;
|
|
||||||
border-radius: 7px 0 0 7px;
|
|
||||||
|
|
||||||
div {
|
|
||||||
margin: 0 auto;
|
|
||||||
|
|
||||||
.day {
|
|
||||||
font-size: 1.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.news_events_group_items {
|
|
||||||
display: table-cell;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.news_event:nth-of-type(odd) {
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.news_event:nth-of-type(even) {
|
|
||||||
background: $primary-neutral-light-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.news_event {
|
|
||||||
display: block;
|
|
||||||
padding: 0.4em;
|
|
||||||
|
|
||||||
&:not(:last-child) {
|
|
||||||
border-bottom: 1px solid grey;
|
|
||||||
}
|
|
||||||
|
|
||||||
div {
|
|
||||||
margin: 0.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
margin-top: 1em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.club_logo {
|
|
||||||
float: left;
|
|
||||||
min-width: 7em;
|
|
||||||
max-width: 9em;
|
|
||||||
margin: 0;
|
|
||||||
margin-right: 1em;
|
|
||||||
margin-top: 0.8em;
|
|
||||||
|
|
||||||
img {
|
|
||||||
max-height: 6em;
|
|
||||||
max-width: 8em;
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.news_date {
|
|
||||||
font-size: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.news_content {
|
|
||||||
clear: left;
|
|
||||||
|
|
||||||
.button_bar {
|
|
||||||
text-align: right;
|
|
||||||
|
|
||||||
.fb {
|
|
||||||
color: $faceblue;
|
|
||||||
}
|
|
||||||
|
|
||||||
.twitter {
|
|
||||||
color: $twitblue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* END EVENTS TODAY AND NEXT FEW DAYS */
|
|
||||||
|
|
||||||
/* COMING SOON */
|
|
||||||
.news_coming_soon {
|
|
||||||
display: list-item;
|
|
||||||
list-style-type: square;
|
|
||||||
list-style-position: inside;
|
|
||||||
margin-left: 1em;
|
|
||||||
padding-left: 0;
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-weight: bold;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.news_date {
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* END COMING SOON */
|
|
||||||
|
|
||||||
/* NOTICES */
|
|
||||||
.news_notice {
|
|
||||||
margin: 0 0 1em 1em;
|
|
||||||
padding: 0.4em;
|
|
||||||
padding-left: 1em;
|
|
||||||
background: $secondary-neutral-light-color;
|
|
||||||
box-shadow: $shadow-color 0 0 2px;
|
|
||||||
border-radius: 18px 5px 18px 5px;
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.news_content {
|
|
||||||
margin-left: 1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* END NOTICES */
|
|
||||||
|
|
||||||
/* CALLS */
|
|
||||||
.news_call {
|
|
||||||
margin: 0 0 1em 1em;
|
|
||||||
padding: 0.4em;
|
|
||||||
padding-left: 1em;
|
|
||||||
background: $secondary-neutral-light-color;
|
|
||||||
border: 1px solid grey;
|
|
||||||
box-shadow: $shadow-color 1px 1px 1px;
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.news_date {
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.news_content {
|
|
||||||
margin-left: 1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* END CALLS */
|
|
||||||
|
|
||||||
.news_empty {
|
|
||||||
margin-left: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.news_date {
|
|
||||||
color: grey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $small-devices) {
|
@media screen and (max-width: $small-devices) {
|
||||||
@ -757,304 +457,6 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#news_details {
|
|
||||||
display: inline-block;
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 0.4em;
|
|
||||||
width: 80%;
|
|
||||||
background: $white-color;
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
margin-top: 1em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.club_logo {
|
|
||||||
display: inline-block;
|
|
||||||
text-align: center;
|
|
||||||
width: 19%;
|
|
||||||
float: left;
|
|
||||||
min-width: 15em;
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
img {
|
|
||||||
max-height: 15em;
|
|
||||||
max-width: 12em;
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.share_button {
|
|
||||||
border: none;
|
|
||||||
color: white;
|
|
||||||
padding: 0.5em 1em;
|
|
||||||
text-align: center;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 1.2em;
|
|
||||||
border-radius: 2px;
|
|
||||||
float: right;
|
|
||||||
display: block;
|
|
||||||
margin-left: 0.3em;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: lightgrey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.facebook {
|
|
||||||
background: $faceblue;
|
|
||||||
}
|
|
||||||
|
|
||||||
.twitter {
|
|
||||||
background: $twitblue;
|
|
||||||
}
|
|
||||||
|
|
||||||
.news_meta {
|
|
||||||
margin-top: 10em;
|
|
||||||
font-size: small;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.helptext {
|
|
||||||
margin-top: 10px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*---------------------------POSTERS----------------------------*/
|
|
||||||
|
|
||||||
#poster_list,
|
|
||||||
#screen_list,
|
|
||||||
#poster_edit,
|
|
||||||
#screen_edit {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
#title {
|
|
||||||
position: relative;
|
|
||||||
padding: 10px;
|
|
||||||
margin: 10px;
|
|
||||||
border-bottom: 2px solid black;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#links {
|
|
||||||
position: absolute;
|
|
||||||
display: flex;
|
|
||||||
bottom: 5px;
|
|
||||||
|
|
||||||
&.left {
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.right {
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link {
|
|
||||||
padding: 5px;
|
|
||||||
padding-left: 20px;
|
|
||||||
padding-right: 20px;
|
|
||||||
margin-left: 5px;
|
|
||||||
border-radius: 20px;
|
|
||||||
background-color: hsl(40, 100%, 50%);
|
|
||||||
color: black;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: black;
|
|
||||||
background-color: hsl(40, 58%, 50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.delete {
|
|
||||||
background-color: hsl(0, 100%, 40%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#posters,
|
|
||||||
#screens {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
#no-posters,
|
|
||||||
#no-screens {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.poster,
|
|
||||||
.screen {
|
|
||||||
min-width: 10%;
|
|
||||||
max-width: 20%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin: 10px;
|
|
||||||
border: 2px solid darkgrey;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 10px;
|
|
||||||
background-color: lightgrey;
|
|
||||||
|
|
||||||
* {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
|
||||||
padding-bottom: 5px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
border-bottom: 1px solid whitesmoke;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image {
|
|
||||||
flex-grow: 1;
|
|
||||||
position: relative;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
border-bottom: 1px solid whitesmoke;
|
|
||||||
|
|
||||||
img {
|
|
||||||
max-height: 20vw;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
&::before {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 10;
|
|
||||||
content: "Click to expand";
|
|
||||||
color: white;
|
|
||||||
background-color: rgba(black, 0.5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dates {
|
|
||||||
padding-bottom: 5px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
border-bottom: 1px solid whitesmoke;
|
|
||||||
|
|
||||||
* {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-left: 5px;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.begin,
|
|
||||||
.end {
|
|
||||||
width: 48%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.begin {
|
|
||||||
border-right: 1px solid whitesmoke;
|
|
||||||
padding-right: 2%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit,
|
|
||||||
.moderate,
|
|
||||||
.slideshow {
|
|
||||||
padding: 5px;
|
|
||||||
border-radius: 20px;
|
|
||||||
background-color: hsl(40, 100%, 50%);
|
|
||||||
color: black;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: black;
|
|
||||||
background-color: hsl(40, 58%, 50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(2n) {
|
|
||||||
margin-top: 5px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip {
|
|
||||||
visibility: hidden;
|
|
||||||
width: 120px;
|
|
||||||
background-color: hsl(210, 20%, 98%);
|
|
||||||
color: hsl(0, 0%, 0%);
|
|
||||||
text-align: center;
|
|
||||||
padding: 5px 0;
|
|
||||||
border-radius: 6px;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 10;
|
|
||||||
|
|
||||||
ul {
|
|
||||||
margin-left: 0;
|
|
||||||
display: inline-block;
|
|
||||||
|
|
||||||
li {
|
|
||||||
display: list-item;
|
|
||||||
list-style-type: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.not_moderated {
|
|
||||||
border: 1px solid red;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover .tooltip {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#view {
|
|
||||||
position: fixed;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 10;
|
|
||||||
visibility: hidden;
|
|
||||||
background-color: rgba(10, 10, 10, 0.9);
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
#placeholder {
|
|
||||||
width: 80vw;
|
|
||||||
height: 80vh;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
|
|
||||||
img {
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*---------------------------ACCOUNTING----------------------------*/
|
/*---------------------------ACCOUNTING----------------------------*/
|
||||||
#accounting {
|
#accounting {
|
||||||
.journal-table {
|
.journal-table {
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
{% extends "core/base.jinja" %}
|
|
||||||
|
|
||||||
{% block script %}
|
|
||||||
{{ super() }}
|
|
||||||
<script src="{{ static('com/js/poster_list.js') }}"></script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
{% trans %}Poster{% endtrans %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div id="poster_list">
|
|
||||||
|
|
||||||
<div id="title">
|
|
||||||
<h3>{% trans %}Posters{% endtrans %}</h3>
|
|
||||||
<div id="links" class="right">
|
|
||||||
<a id="create" class="link" href="{{ url(app + ":poster_list") }}">{% trans %}Create{% endtrans %}</a>
|
|
||||||
{% if app == "com" %}
|
|
||||||
<a id="moderation" class="link" href="{{ url("com:poster_moderate_list") }}">{% trans %}Moderation{% endtrans %}</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="posters">
|
|
||||||
|
|
||||||
{% if poster_list.count() == 0 %}
|
|
||||||
<div id="no-posters">{% trans %}No posters{% endtrans %}</div>
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
{% for poster in poster_list %}
|
|
||||||
<div class="poster">
|
|
||||||
<div class="name">{{ poster.name }}</div>
|
|
||||||
<div class="image"><img src="{{ poster.file.url }}"></img></div>
|
|
||||||
<div class="dates">
|
|
||||||
<div class="begin">{{ poster.date_begin | date("d/M/Y H:m") }}</div>
|
|
||||||
<div class="end">{{ poster.date_end | date("d/M/Y H:m") }}</div>
|
|
||||||
</div>
|
|
||||||
<a class="edit" href="{{ url(poster_edit_url_name, poster.id) }}">{% trans %}Edit{% endtrans %}</a>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="view"><div id="placeholder"></div></div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
import mimetypes
|
import mimetypes
|
||||||
|
from pathlib import Path
|
||||||
from urllib.parse import quote, urljoin
|
from urllib.parse import quote, urljoin
|
||||||
|
|
||||||
# This file contains all the views that concern the page model
|
# This file contains all the views that concern the page model
|
||||||
@ -48,6 +49,41 @@ from core.views.widgets.select import (
|
|||||||
from counter.utils import is_logged_in_counter
|
from counter.utils import is_logged_in_counter
|
||||||
|
|
||||||
|
|
||||||
|
def send_raw_file(path: Path) -> HttpResponse:
|
||||||
|
"""Send a file located in the MEDIA_ROOT
|
||||||
|
|
||||||
|
This handles all the logic of using production reverse proxy or debug server.
|
||||||
|
|
||||||
|
THIS DOESN'T CHECK ANY PERMISSIONS !
|
||||||
|
"""
|
||||||
|
if not path.is_relative_to(settings.MEDIA_ROOT):
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
if not path.is_file() or not path.exists():
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
response = HttpResponse(
|
||||||
|
headers={"Content-Disposition": f'inline; filename="{quote(path.name)}"'}
|
||||||
|
)
|
||||||
|
if not settings.DEBUG:
|
||||||
|
# When receiving a response with the Accel-Redirect header,
|
||||||
|
# the reverse proxy will automatically handle the file sending.
|
||||||
|
# This is really hard to test (thus isn't tested)
|
||||||
|
# so please do not mess with this.
|
||||||
|
response["Content-Type"] = "" # automatically set by nginx
|
||||||
|
response["X-Accel-Redirect"] = quote(
|
||||||
|
urljoin(settings.MEDIA_URL, str(path.relative_to(settings.MEDIA_ROOT)))
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
with open(path, "rb") as filename:
|
||||||
|
response.content = FileWrapper(filename)
|
||||||
|
response["Content-Type"] = mimetypes.guess_type(path)[0]
|
||||||
|
response["Last-Modified"] = http_date(path.stat().st_mtime)
|
||||||
|
response["Content-Length"] = path.stat().st_size
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
def send_file(
|
def send_file(
|
||||||
request: HttpRequest,
|
request: HttpRequest,
|
||||||
file_id: int,
|
file_id: int,
|
||||||
@ -66,28 +102,7 @@ def send_file(
|
|||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
name = getattr(f, file_attr).name
|
name = getattr(f, file_attr).name
|
||||||
|
|
||||||
response = HttpResponse(
|
return send_raw_file(settings.MEDIA_ROOT / name)
|
||||||
headers={"Content-Disposition": f'inline; filename="{quote(name)}"'}
|
|
||||||
)
|
|
||||||
if not settings.DEBUG:
|
|
||||||
# When receiving a response with the Accel-Redirect header,
|
|
||||||
# the reverse proxy will automatically handle the file sending.
|
|
||||||
# This is really hard to test (thus isn't tested)
|
|
||||||
# so please do not mess with this.
|
|
||||||
response["Content-Type"] = "" # automatically set by nginx
|
|
||||||
response["X-Accel-Redirect"] = quote(urljoin(settings.MEDIA_URL, name))
|
|
||||||
return response
|
|
||||||
|
|
||||||
filepath = settings.MEDIA_ROOT / name
|
|
||||||
# check if file exists on disk
|
|
||||||
if not filepath.exists():
|
|
||||||
raise Http404
|
|
||||||
with open(filepath, "rb") as filename:
|
|
||||||
response.content = FileWrapper(filename)
|
|
||||||
response["Content-Type"] = mimetypes.guess_type(filepath)[0]
|
|
||||||
response["Last-Modified"] = http_date(f.date.timestamp())
|
|
||||||
response["Content-Length"] = filepath.stat().st_size
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
class MultipleFileInput(forms.ClearableFileInput):
|
class MultipleFileInput(forms.ClearableFileInput):
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-01-04 21:59+0100\n"
|
"POT-Creation-Date: 2025-01-04 23:05+0100\n"
|
||||||
"PO-Revision-Date: 2016-07-18\n"
|
"PO-Revision-Date: 2016-07-18\n"
|
||||||
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
|
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
|
||||||
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
||||||
@ -356,9 +356,8 @@ msgstr "Nouveau compte club"
|
|||||||
#: com/templates/com/news_admin_list.jinja com/templates/com/poster_list.jinja
|
#: com/templates/com/news_admin_list.jinja com/templates/com/poster_list.jinja
|
||||||
#: com/templates/com/screen_list.jinja com/templates/com/weekmail.jinja
|
#: com/templates/com/screen_list.jinja com/templates/com/weekmail.jinja
|
||||||
#: core/templates/core/file.jinja core/templates/core/group_list.jinja
|
#: core/templates/core/file.jinja core/templates/core/group_list.jinja
|
||||||
#: core/templates/core/page.jinja core/templates/core/poster_list.jinja
|
#: core/templates/core/page.jinja core/templates/core/user_tools.jinja
|
||||||
#: core/templates/core/user_tools.jinja core/views/user.py
|
#: core/views/user.py counter/templates/counter/cash_summary_list.jinja
|
||||||
#: counter/templates/counter/cash_summary_list.jinja
|
|
||||||
#: counter/templates/counter/counter_list.jinja
|
#: counter/templates/counter/counter_list.jinja
|
||||||
#: election/templates/election/election_detail.jinja
|
#: election/templates/election/election_detail.jinja
|
||||||
#: forum/templates/forum/macros.jinja
|
#: forum/templates/forum/macros.jinja
|
||||||
@ -1140,7 +1139,7 @@ msgid "New Trombi"
|
|||||||
msgstr "Nouveau Trombi"
|
msgstr "Nouveau Trombi"
|
||||||
|
|
||||||
#: club/templates/club/club_tools.jinja com/templates/com/poster_list.jinja
|
#: club/templates/club/club_tools.jinja com/templates/com/poster_list.jinja
|
||||||
#: core/templates/core/poster_list.jinja core/templates/core/user_tools.jinja
|
#: core/templates/core/user_tools.jinja
|
||||||
msgid "Posters"
|
msgid "Posters"
|
||||||
msgstr "Affiches"
|
msgstr "Affiches"
|
||||||
|
|
||||||
@ -1558,17 +1557,46 @@ msgstr "Événements aujourd'hui et dans les prochains jours"
|
|||||||
msgid "Nothing to come..."
|
msgid "Nothing to come..."
|
||||||
msgstr "Rien à venir..."
|
msgstr "Rien à venir..."
|
||||||
|
|
||||||
#: com/templates/com/news_list.jinja
|
|
||||||
msgid "Coming soon... don't miss!"
|
|
||||||
msgstr "Prochainement... à ne pas rater!"
|
|
||||||
|
|
||||||
#: com/templates/com/news_list.jinja
|
#: com/templates/com/news_list.jinja
|
||||||
msgid "All coming events"
|
msgid "All coming events"
|
||||||
msgstr "Tous les événements à venir"
|
msgstr "Tous les événements à venir"
|
||||||
|
|
||||||
#: com/templates/com/news_list.jinja
|
#: com/templates/com/news_list.jinja
|
||||||
msgid "Agenda"
|
msgid "Links"
|
||||||
msgstr "Agenda"
|
msgstr "Liens"
|
||||||
|
|
||||||
|
#: com/templates/com/news_list.jinja
|
||||||
|
msgid "Our services"
|
||||||
|
msgstr "Nos services"
|
||||||
|
|
||||||
|
#: com/templates/com/news_list.jinja pedagogy/templates/pedagogy/guide.jinja
|
||||||
|
msgid "UV Guide"
|
||||||
|
msgstr "Guide des UVs"
|
||||||
|
|
||||||
|
#: com/templates/com/news_list.jinja core/templates/core/base/navbar.jinja
|
||||||
|
msgid "Matmatronch"
|
||||||
|
msgstr "Matmatronch"
|
||||||
|
|
||||||
|
#: com/templates/com/news_list.jinja core/templates/core/base/navbar.jinja
|
||||||
|
#: core/templates/core/user_tools.jinja
|
||||||
|
msgid "Elections"
|
||||||
|
msgstr "Élections"
|
||||||
|
|
||||||
|
#: com/templates/com/news_list.jinja
|
||||||
|
msgid "Social media"
|
||||||
|
msgstr "Réseaux sociaux"
|
||||||
|
|
||||||
|
#: com/templates/com/news_list.jinja
|
||||||
|
msgid "Discord"
|
||||||
|
msgstr "Discord"
|
||||||
|
|
||||||
|
#: com/templates/com/news_list.jinja
|
||||||
|
msgid "Facebook"
|
||||||
|
msgstr "Facebook"
|
||||||
|
|
||||||
|
#: com/templates/com/news_list.jinja
|
||||||
|
msgid "Instagram"
|
||||||
|
msgstr "Instagram"
|
||||||
|
|
||||||
#: com/templates/com/news_list.jinja
|
#: com/templates/com/news_list.jinja
|
||||||
msgid "Birthdays"
|
msgid "Birthdays"
|
||||||
@ -1580,11 +1608,10 @@ msgid "%(age)s year old"
|
|||||||
msgstr "%(age)s ans"
|
msgstr "%(age)s ans"
|
||||||
|
|
||||||
#: com/templates/com/news_list.jinja com/tests.py
|
#: com/templates/com/news_list.jinja com/tests.py
|
||||||
msgid "You need an up to date subscription to access this content"
|
msgid "You need to subscribe to access this content"
|
||||||
msgstr "Votre cotisation doit être à jour pour accéder à cette section"
|
msgstr "Vous devez cotiser pour accéder à ce contenu"
|
||||||
|
|
||||||
#: com/templates/com/poster_edit.jinja com/templates/com/poster_list.jinja
|
#: com/templates/com/poster_edit.jinja com/templates/com/poster_list.jinja
|
||||||
#: core/templates/core/poster_list.jinja
|
|
||||||
msgid "Poster"
|
msgid "Poster"
|
||||||
msgstr "Affiche"
|
msgstr "Affiche"
|
||||||
|
|
||||||
@ -1598,15 +1625,15 @@ msgid "Posters - edit"
|
|||||||
msgstr "Affiche - modifier"
|
msgstr "Affiche - modifier"
|
||||||
|
|
||||||
#: com/templates/com/poster_list.jinja com/templates/com/screen_list.jinja
|
#: com/templates/com/poster_list.jinja com/templates/com/screen_list.jinja
|
||||||
#: core/templates/core/poster_list.jinja sas/templates/sas/main.jinja
|
#: sas/templates/sas/main.jinja
|
||||||
msgid "Create"
|
msgid "Create"
|
||||||
msgstr "Créer"
|
msgstr "Créer"
|
||||||
|
|
||||||
#: com/templates/com/poster_list.jinja core/templates/core/poster_list.jinja
|
#: com/templates/com/poster_list.jinja
|
||||||
msgid "Moderation"
|
msgid "Moderation"
|
||||||
msgstr "Modération"
|
msgstr "Modération"
|
||||||
|
|
||||||
#: com/templates/com/poster_list.jinja core/templates/core/poster_list.jinja
|
#: com/templates/com/poster_list.jinja
|
||||||
msgid "No posters"
|
msgid "No posters"
|
||||||
msgstr "Aucune affiche"
|
msgstr "Aucune affiche"
|
||||||
|
|
||||||
@ -2233,10 +2260,6 @@ msgstr "Les clubs de L'AE"
|
|||||||
msgid "Others UTBM's Associations"
|
msgid "Others UTBM's Associations"
|
||||||
msgstr "Les autres associations de l'UTBM"
|
msgstr "Les autres associations de l'UTBM"
|
||||||
|
|
||||||
#: core/templates/core/base/navbar.jinja core/templates/core/user_tools.jinja
|
|
||||||
msgid "Elections"
|
|
||||||
msgstr "Élections"
|
|
||||||
|
|
||||||
#: core/templates/core/base/navbar.jinja
|
#: core/templates/core/base/navbar.jinja
|
||||||
msgid "Big event"
|
msgid "Big event"
|
||||||
msgstr "Grandes Activités"
|
msgstr "Grandes Activités"
|
||||||
@ -2264,10 +2287,6 @@ msgstr "Eboutic"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Services"
|
msgstr "Services"
|
||||||
|
|
||||||
#: core/templates/core/base/navbar.jinja
|
|
||||||
msgid "Matmatronch"
|
|
||||||
msgstr "Matmatronch"
|
|
||||||
|
|
||||||
#: core/templates/core/base/navbar.jinja launderette/models.py
|
#: core/templates/core/base/navbar.jinja launderette/models.py
|
||||||
#: launderette/templates/launderette/launderette_book.jinja
|
#: launderette/templates/launderette/launderette_book.jinja
|
||||||
#: launderette/templates/launderette/launderette_book_choose.jinja
|
#: launderette/templates/launderette/launderette_book_choose.jinja
|
||||||
@ -4859,10 +4878,6 @@ msgstr "signalant"
|
|||||||
msgid "reason"
|
msgid "reason"
|
||||||
msgstr "raison"
|
msgstr "raison"
|
||||||
|
|
||||||
#: pedagogy/templates/pedagogy/guide.jinja
|
|
||||||
msgid "UV Guide"
|
|
||||||
msgstr "Guide des UVs"
|
|
||||||
|
|
||||||
#: pedagogy/templates/pedagogy/guide.jinja
|
#: pedagogy/templates/pedagogy/guide.jinja
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(display_name)s"
|
msgid "%(display_name)s"
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-01-04 22:00+0100\n"
|
"POT-Creation-Date: 2025-01-04 23:07+0100\n"
|
||||||
"PO-Revision-Date: 2024-09-17 11:54+0200\n"
|
"PO-Revision-Date: 2024-09-17 11:54+0200\n"
|
||||||
"Last-Translator: Sli <antoine@bartuccio.fr>\n"
|
"Last-Translator: Sli <antoine@bartuccio.fr>\n"
|
||||||
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
||||||
@ -17,6 +17,10 @@ msgstr ""
|
|||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
|
||||||
|
#: com/static/bundled/com/components/ics-calendar-index.ts
|
||||||
|
msgid "More info"
|
||||||
|
msgstr "Plus d'informations"
|
||||||
|
|
||||||
#: core/static/bundled/core/components/ajax-select-base.ts
|
#: core/static/bundled/core/components/ajax-select-base.ts
|
||||||
msgid "Remove"
|
msgid "Remove"
|
||||||
msgstr "Retirer"
|
msgstr "Retirer"
|
||||||
|
52
package-lock.json
generated
52
package-lock.json
generated
@ -11,6 +11,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alpinejs/sort": "^3.14.7",
|
"@alpinejs/sort": "^3.14.7",
|
||||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||||
|
"@fullcalendar/core": "^6.1.15",
|
||||||
|
"@fullcalendar/daygrid": "^6.1.15",
|
||||||
|
"@fullcalendar/icalendar": "^6.1.15",
|
||||||
|
"@fullcalendar/list": "^6.1.15",
|
||||||
"@hey-api/client-fetch": "^0.4.0",
|
"@hey-api/client-fetch": "^0.4.0",
|
||||||
"@sentry/browser": "^8.34.0",
|
"@sentry/browser": "^8.34.0",
|
||||||
"@zip.js/zip.js": "^2.7.52",
|
"@zip.js/zip.js": "^2.7.52",
|
||||||
@ -2384,6 +2388,39 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fullcalendar/core": {
|
||||||
|
"version": "6.1.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.15.tgz",
|
||||||
|
"integrity": "sha512-BuX7o6ALpLb84cMw1FCB9/cSgF4JbVO894cjJZ6kP74jzbUZNjtwffwRdA+Id8rrLjT30d/7TrkW90k4zbXB5Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"preact": "~10.12.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fullcalendar/daygrid": {
|
||||||
|
"version": "6.1.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.15.tgz",
|
||||||
|
"integrity": "sha512-j8tL0HhfiVsdtOCLfzK2J0RtSkiad3BYYemwQKq512cx6btz6ZZ2RNc/hVnIxluuWFyvx5sXZwoeTJsFSFTEFA==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@fullcalendar/core": "~6.1.15"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fullcalendar/icalendar": {
|
||||||
|
"version": "6.1.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fullcalendar/icalendar/-/icalendar-6.1.15.tgz",
|
||||||
|
"integrity": "sha512-iroDc02fjxWCEYE9Lg8x+4HCJTrt04ZgDddwm0LLaWUbtx24rEcnzJP34NUx0KOTLsBjel6U/33lXvU9qDCrhg==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@fullcalendar/core": "~6.1.15",
|
||||||
|
"ical.js": "^1.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fullcalendar/list": {
|
||||||
|
"version": "6.1.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-6.1.15.tgz",
|
||||||
|
"integrity": "sha512-U1bce04tYDwkFnuVImJSy2XalYIIQr6YusOWRPM/5ivHcJh67Gm8CIMSWpi3KdRSNKFkqBxLPkfZGBMaOcJYug==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@fullcalendar/core": "~6.1.15"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@hey-api/client-fetch": {
|
"node_modules/@hey-api/client-fetch": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.4.0.tgz",
|
||||||
@ -4162,6 +4199,12 @@
|
|||||||
"node": ">=16.17.0"
|
"node": ">=16.17.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ical.js": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ical.js/-/ical.js-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-7ZxMkogUkkaCx810yp0ZGKvq1ZpRgJeornPttpoxe6nYZ3NLesZe1wWMXDdwTkj/b5NtXT+Y16Aakph/ao98ZQ==",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/import-from-esm": {
|
"node_modules/import-from-esm": {
|
||||||
"version": "1.3.4",
|
"version": "1.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-1.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-1.3.4.tgz",
|
||||||
@ -4924,6 +4967,15 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/preact": {
|
||||||
|
"version": "10.12.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz",
|
||||||
|
"integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/preact"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/queue-microtask": {
|
"node_modules/queue-microtask": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||||
|
@ -19,7 +19,8 @@
|
|||||||
"#openapi": "./staticfiles/generated/openapi/index.ts",
|
"#openapi": "./staticfiles/generated/openapi/index.ts",
|
||||||
"#core:*": "./core/static/bundled/*",
|
"#core:*": "./core/static/bundled/*",
|
||||||
"#pedagogy:*": "./pedagogy/static/bundled/*",
|
"#pedagogy:*": "./pedagogy/static/bundled/*",
|
||||||
"#counter:*": "./counter/static/bundled/*"
|
"#counter:*": "./counter/static/bundled/*",
|
||||||
|
"#com:*": "./com/static/bundled/*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.25.2",
|
||||||
@ -36,6 +37,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alpinejs/sort": "^3.14.7",
|
"@alpinejs/sort": "^3.14.7",
|
||||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||||
|
"@fullcalendar/core": "^6.1.15",
|
||||||
|
"@fullcalendar/daygrid": "^6.1.15",
|
||||||
|
"@fullcalendar/icalendar": "^6.1.15",
|
||||||
|
"@fullcalendar/list": "^6.1.15",
|
||||||
"@hey-api/client-fetch": "^0.4.0",
|
"@hey-api/client-fetch": "^0.4.0",
|
||||||
"@sentry/browser": "^8.34.0",
|
"@sentry/browser": "^8.34.0",
|
||||||
"@zip.js/zip.js": "^2.7.52",
|
"@zip.js/zip.js": "^2.7.52",
|
||||||
|
33
poetry.lock
generated
33
poetry.lock
generated
@ -931,6 +931,23 @@ files = [
|
|||||||
{file = "hiredis-3.1.0.tar.gz", hash = "sha256:51d40ac3611091020d7dea6b05ed62cb152bff595fa4f931e7b6479d777acf7c"},
|
{file = "hiredis-3.1.0.tar.gz", hash = "sha256:51d40ac3611091020d7dea6b05ed62cb152bff595fa4f931e7b6479d777acf7c"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ical"
|
||||||
|
version = "8.3.0"
|
||||||
|
description = "Python iCalendar implementation (rfc 2445)"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
files = [
|
||||||
|
{file = "ical-8.3.0-py3-none-any.whl", hash = "sha256:606f2f561bd8b75cb726710dddbb20f3f84dfa1d6323550947dba97359423850"},
|
||||||
|
{file = "ical-8.3.0.tar.gz", hash = "sha256:e277cc518cbb0132e6827c318c8ec3b379b125ebf0a2a44337f08795d5530937"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
pydantic = ">=1.9.1"
|
||||||
|
pyparsing = ">=3.0.9"
|
||||||
|
python-dateutil = ">=2.8.2"
|
||||||
|
tzdata = ">=2023.3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "identify"
|
name = "identify"
|
||||||
version = "2.6.3"
|
version = "2.6.3"
|
||||||
@ -1883,6 +1900,20 @@ pyyaml = "*"
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
extra = ["pygments (>=2.12)"]
|
extra = ["pygments (>=2.12)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyparsing"
|
||||||
|
version = "3.2.1"
|
||||||
|
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
files = [
|
||||||
|
{file = "pyparsing-3.2.1-py3-none-any.whl", hash = "sha256:506ff4f4386c4cec0590ec19e6302d3aedb992fdc02c761e90416f158dacf8e1"},
|
||||||
|
{file = "pyparsing-3.2.1.tar.gz", hash = "sha256:61980854fd66de3a90028d679a954d5f2623e83144b5afe5ee86f43d762e5f0a"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
diagrams = ["jinja2", "railroad-diagrams"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "8.3.4"
|
version = "8.3.4"
|
||||||
@ -2724,4 +2755,4 @@ filelock = ">=3.4"
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.12"
|
python-versions = "^3.12"
|
||||||
content-hash = "5836c1a8ad42645d7d045194c8c371754b19957ebdcd2aaa902a2fb3dc97cc53"
|
content-hash = "7f348f74a05c27e29aaaf25a5584bba9b416f42c3f370db234dd69e5e10dc8df"
|
||||||
|
@ -45,6 +45,7 @@ Sphinx = "^5" # Needed for building xapian
|
|||||||
tomli = "^2.2.1"
|
tomli = "^2.2.1"
|
||||||
django-honeypot = "^1.2.1"
|
django-honeypot = "^1.2.1"
|
||||||
pydantic-extra-types = "^2.10.1"
|
pydantic-extra-types = "^2.10.1"
|
||||||
|
ical = "^8.3.0"
|
||||||
|
|
||||||
[tool.poetry.group.prod.dependencies]
|
[tool.poetry.group.prod.dependencies]
|
||||||
# deps used in prod, but unnecessary for development
|
# deps used in prod, but unnecessary for development
|
||||||
|
@ -163,6 +163,7 @@ TEMPLATES = [
|
|||||||
"ProductType": "counter.models.ProductType",
|
"ProductType": "counter.models.ProductType",
|
||||||
"timezone": "django.utils.timezone",
|
"timezone": "django.utils.timezone",
|
||||||
"get_sith": "com.views.sith",
|
"get_sith": "com.views.sith",
|
||||||
|
"get_language": "django.utils.translation.get_language",
|
||||||
},
|
},
|
||||||
"bytecode_cache": {
|
"bytecode_cache": {
|
||||||
"name": "default",
|
"name": "default",
|
||||||
|
@ -16,7 +16,8 @@
|
|||||||
"#openapi": ["./staticfiles/generated/openapi/index.ts"],
|
"#openapi": ["./staticfiles/generated/openapi/index.ts"],
|
||||||
"#core:*": ["./core/static/bundled/*"],
|
"#core:*": ["./core/static/bundled/*"],
|
||||||
"#pedagogy:*": ["./pedagogy/static/bundled/*"],
|
"#pedagogy:*": ["./pedagogy/static/bundled/*"],
|
||||||
"#counter:*": ["./counter/static/bundled/*"]
|
"#counter:*": ["./counter/static/bundled/*"],
|
||||||
|
"#com:*": ["./com/static/bundled/*"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user