Add galaxy (#562)

* style.scss: lint

* style.scss: add 'th' padding

* core: populate: add much more data for development

* Add galaxy
This commit is contained in:
Skia authored and GitHub committed 2023-02-07 12:08:25 +01:00
1 parent 394e17d599
commit 585923c827
30 files changed
+1368 -174

No files matched your search

File renamed without changes.
File renamed without changes.
File renamed without changes.
Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

File renamed without changes.
Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

+8 -1
View File
@@ -6,8 +6,15 @@ from django.core.management.base import BaseCommand
# see https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
# added "v?"
# Please note that this does not match the version of the three.js library.
# Hence, you shall have to check this one by yourself
semver_regex = re.compile(
"""^v?(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"""
r"^v?"
r"(?P<major>\d+)"
r"\.(?P<minor>\d+)"
r"\.(?P<patch>\d+)"
r"(?:-(?P<prerelease>(?:\d+|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:\d+|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?"
r"(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
)
+174 -21
View File
@@ -1,7 +1,7 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# Copyright 2016,2017,2023
# - Skia <skia@hya.sk>
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
@@ -25,6 +25,7 @@
import os
from datetime import date, datetime, timedelta
from io import StringIO, BytesIO
from pathlib import Path
from django.contrib.auth.models import Permission
from django.core.management.base import BaseCommand
@@ -54,6 +55,7 @@ from com.models import Sith, Weekmail, News, NewsDate
from election.models import Election, Role, Candidature, ElectionList
from forum.models import Forum, ForumTopic
from pedagogy.models import UV
from sas.models import Album, Picture, PeoplePictureRelation
class Command(BaseCommand):
@@ -71,9 +73,7 @@ class Command(BaseCommand):
def handle(self, *args, **options):
os.environ["DJANGO_COLORS"] = "nocolor"
Site(id=4000, domain=settings.SITH_URL, name=settings.SITH_NAME).save()
root_path = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
)
root_path = Path(__file__).parent.parent.parent.parent
root_group, _ = Group.objects.get_or_create(name="Root")
Group(name="Public").save()
Group(name="Subscribers").save()
@@ -84,7 +84,7 @@ class Command(BaseCommand):
Group(name="Banned from buying alcohol").save()
Group(name="Banned from counters").save()
Group(name="Banned to subscribe").save()
Group(name="SAS admin").save()
sas_admin, _ = Group.objects.get_or_create(name="SAS admin")
Group(name="Forum admin").save()
Group(name="Pedagogy admin").save()
self.reset_index("core", "auth")
@@ -119,7 +119,8 @@ class Command(BaseCommand):
club_root = SithFile(parent=None, name="clubs", is_folder=True, owner=root)
club_root.save()
SithFile(parent=None, name="SAS", is_folder=True, owner=root).save()
sas = SithFile(parent=None, name="SAS", is_folder=True, owner=root)
sas.save()
main_club = Club(
id=1,
name=settings.SITH_MAIN_CLUB["name"],
@@ -223,7 +224,15 @@ Welcome to the wiki page!
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
]
skia.save()
skia_profile_path = os.path.join(root_path, "core/fixtures/images/3.jpg")
skia_profile_path = (
root_path
/ "core"
/ "fixtures"
/ "images"
/ "sas"
/ "Family"
/ "skia.jpg"
)
with open(skia_profile_path, "rb") as f:
name = str(skia.id) + "_profile.jpg"
skia_profile = SithFile(
@@ -233,7 +242,7 @@ Welcome to the wiki page!
owner=skia,
is_folder=False,
mime_type="image/jpeg",
size=os.path.getsize(skia_profile_path),
size=skia_profile_path.stat().st_size,
)
skia_profile.file.name = name
skia_profile.save()
@@ -351,23 +360,48 @@ Welcome to the wiki page!
]
u.save()
# Adding user Richard Batsbak
r = User(
richard = User(
username="rbatsbak",
last_name="Batsbak",
first_name="Richard",
email="richard@git.an",
date_of_birth="1982-06-12",
)
r.set_password("plop")
r.save()
r.view_groups = [
richard.set_password("plop")
richard.save()
richard.godfathers.add(comptable)
richard_profile_path = (
root_path
/ "core"
/ "fixtures"
/ "images"
/ "sas"
/ "Family"
/ "richard.jpg"
)
with open(richard_profile_path, "rb") as f:
name = f"{richard.id}_profile.jpg"
richard_profile = SithFile(
parent=profiles_root,
name=name,
file=resize_image(Image.open(BytesIO(f.read())), 400, "JPEG"),
owner=richard,
is_folder=False,
mime_type="image/jpeg",
size=richard_profile_path.stat().st_size,
)
richard_profile.file.name = name
richard_profile.save()
richard.profile_pict = richard_profile
richard.save()
richard.view_groups = [
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
]
r.save()
richard.save()
# Adding syntax help page
p = Page(name="Aide_sur_la_syntaxe")
p.save(force_lock=True)
with open(os.path.join(root_path) + "/doc/SYNTAX.md", "r") as rm:
with open(root_path / "doc" / "SYNTAX.md", "r") as rm:
PageRev(
page=p, title="Aide sur la syntaxe", author=skia, content=rm.read()
).save()
@@ -442,7 +476,7 @@ Welcome to the wiki page!
s.save()
# Richard
s = Subscription(
member=User.objects.filter(pk=r.pk).first(),
member=User.objects.filter(pk=richard.pk).first(),
subscription_type=default_subscription,
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0],
)
@@ -514,7 +548,7 @@ Welcome to the wiki page!
subscribers = Group.objects.get(name="Subscribers")
old_subscribers = Group.objects.get(name="Old subscribers")
Customer(user=skia, account_id="6568j", amount=0).save()
Customer(user=r, account_id="4000k", amount=0).save()
Customer(user=richard, account_id="4000k", amount=0).save()
p = ProductType(name="Bières bouteilles")
p.save()
c = ProductType(name="Cotisations")
@@ -825,7 +859,15 @@ Welcome to the wiki page!
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
]
sli.save()
sli_profile_path = os.path.join(root_path, "core/fixtures/images/5.jpg")
sli_profile_path = (
root_path
/ "core"
/ "fixtures"
/ "images"
/ "sas"
/ "Family"
/ "sli.jpg"
)
with open(sli_profile_path, "rb") as f:
name = str(sli.id) + "_profile.jpg"
sli_profile = SithFile(
@@ -835,7 +877,7 @@ Welcome to the wiki page!
owner=sli,
is_folder=False,
mime_type="image/jpeg",
size=os.path.getsize(sli_profile_path),
size=sli_profile_path.stat().st_size,
)
sli_profile.file.name = name
sli_profile.save()
@@ -851,7 +893,15 @@ Welcome to the wiki page!
)
krophil.set_password("plop")
krophil.save()
krophil_profile_path = os.path.join(root_path, "core/fixtures/images/6.jpg")
krophil_profile_path = (
root_path
/ "core"
/ "fixtures"
/ "images"
/ "sas"
/ "Family"
/ "krophil.jpg"
)
with open(krophil_profile_path, "rb") as f:
name = str(krophil.id) + "_profile.jpg"
krophil_profile = SithFile(
@@ -861,7 +911,7 @@ Welcome to the wiki page!
owner=krophil,
is_folder=False,
mime_type="image/jpeg",
size=os.path.getsize(krophil_profile_path),
size=krophil_profile_path.stat().st_size,
)
krophil_profile.file.name = name
krophil_profile.save()
@@ -1164,3 +1214,106 @@ Welcome to the wiki page!
hours_THE=121,
hours_TE=4,
).save()
# SAS
skia.groups.add(sas_admin.id)
sas_fixtures_path = root_path / "core" / "fixtures" / "images" / "sas"
for f in sas_fixtures_path.glob("*"):
if f.is_dir():
album = Album(
parent=sas,
name=f.name,
owner=root,
is_folder=True,
is_in_sas=True,
is_moderated=True,
)
album.clean()
album.save()
for p in f.iterdir():
pict = Picture(
parent=album,
name=p.name,
file=resize_image(
Image.open(BytesIO(p.read_bytes())), 1000, "JPEG"
),
owner=root,
is_folder=False,
is_in_sas=True,
is_moderated=True,
mime_type="image/jpeg",
size=p.stat().st_size,
)
pict.file.name = p.name
pict.clean()
pict.generate_thumbnails()
pict.save()
p = Picture.objects.get(name="skia.jpg")
PeoplePictureRelation(user=skia, picture=p).save()
p = Picture.objects.get(name="sli.jpg")
PeoplePictureRelation(user=sli, picture=p).save()
p = Picture.objects.get(name="krophil.jpg")
PeoplePictureRelation(user=krophil, picture=p).save()
p = Picture.objects.get(name="skia_sli.jpg")
PeoplePictureRelation(user=skia, picture=p).save()
PeoplePictureRelation(user=sli, picture=p).save()
p = Picture.objects.get(name="skia_sli_krophil.jpg")
PeoplePictureRelation(user=skia, picture=p).save()
PeoplePictureRelation(user=sli, picture=p).save()
PeoplePictureRelation(user=krophil, picture=p).save()
p = Picture.objects.get(name="richard.jpg")
PeoplePictureRelation(user=richard, picture=p).save()
with open(skia_profile_path, "rb") as f:
name = str(skia.id) + "_profile.jpg"
skia_profile = SithFile(
parent=profiles_root,
name=name,
file=resize_image(Image.open(BytesIO(f.read())), 400, "JPEG"),
owner=skia,
is_folder=False,
mime_type="image/jpeg",
size=skia_profile_path.stat().st_size,
)
skia_profile.file.name = name
skia_profile.save()
skia.profile_pict = skia_profile
skia.save()
# Create some additional data for galaxy to work with
root.godfathers.add(skia)
skia.godfathers.add(root)
sli.godfathers.add(skia)
richard.godchildren.add(subscriber)
richard.godchildren.add(public)
Membership(
user=sli,
club=troll,
role=9,
description="Padawan Troll",
start_date=timezone.now() - timedelta(days=17),
).save()
Membership(
user=krophil,
club=troll,
role=10,
description="Maitre Troll",
start_date=timezone.now() - timedelta(days=200),
).save()
Membership(
user=skia,
club=troll,
role=2,
description="Grand Ancien Troll",
start_date=timezone.now() - timedelta(days=400),
end_date=timezone.now() - timedelta(days=86),
).save()
Membership(
user=richard,
club=troll,
role=2,
description="",
start_date=timezone.now() - timedelta(days=200),
end_date=timezone.now() - timedelta(days=100),
).save()
+159 -114
View File
@@ -11,7 +11,6 @@ $primary-dark-color: hsl(203, 75%, 40%);
$secondary-light-color: hsl(40, 68%, 65%);
$secondary-dark-color: hsl(40, 68%, 35%);
$primary-neutral-color: hsl(219.6, 20.8%, 50%);
$primary-neutral-light-color: hsl(0, 0%, 94%);
$primary-neutral-dark-color: hsl(210, 29%, 29%);
@@ -29,7 +28,7 @@ $pinktober: #ff5674;
$pinktober-secondary: #8a2536;
$pinktober-primary-text: white;
$pinktober-bar-closed: $pinktober-secondary;
$pinktober-bar-opened: #388E3C;
$pinktober-bar-opened: #388e3c;
$shadow-color: rgb(223, 223, 223);
@@ -49,7 +48,11 @@ body {
font-family: sans-serif;
}
button, input[type=button], input[type=submit], input[type=reset],input[type=file] {
button,
input[type="button"],
input[type="submit"],
input[type="reset"],
input[type="file"] {
border: none;
text-decoration: none;
background-color: $background-button-color;
@@ -62,15 +65,24 @@ button, input[type=button], input[type=submit], input[type=reset],input[type=fil
}
}
input[type=button], input[type=submit], input[type=reset],input[type=file] {
input[type="button"],
input[type="submit"],
input[type="reset"],
input[type="file"] {
font-weight: bold;
}
button:not(:disabled), input[type=button]:not(:disabled), input[type=submit]:not(:disabled), input[type=reset]:not(:disabled),input[type=file]:not(:disabled) {
button:not(:disabled),
input[type="button"]:not(:disabled),
input[type="submit"]:not(:disabled),
input[type="reset"]:not(:disabled),
input[type="file"]:not(:disabled) {
cursor: pointer;
}
input,textarea[type=text],[type=number]{
input,
textarea[type="text"],
[type="number"] {
border: none;
text-decoration: none;
background-color: $background-button-color;
@@ -80,7 +92,7 @@ input,textarea[type=text],[type=number]{
border-radius: 5px;
max-width: 95%;
}
textarea{
textarea {
border: none;
text-decoration: none;
background-color: $background-button-color;
@@ -88,7 +100,7 @@ textarea{
font-size: 1.2em;
border-radius: 5px;
}
select{
select {
border: none;
text-decoration: none;
font-size: 1.2em;
@@ -143,7 +155,8 @@ a {
}
.shadow {
box-shadow: rgba(60, 64, 67, .3) 0 1px 3px 0, rgba(60, 64, 67, .15) 0 4px 8px 3px;
box-shadow: rgba(60, 64, 67, 0.3) 0 1px 3px 0,
rgba(60, 64, 67, 0.15) 0 4px 8px 3px;
}
.w_big {
@@ -162,7 +175,9 @@ a {
cursor: pointer;
}
[x-cloak] { display: none !important; }
[x-cloak] {
display: none !important;
}
/*--------------------------------HEADER-------------------------------*/
@@ -195,7 +210,6 @@ header {
background-color: $primary-neutral-dark-color;
border-radius: 0px 0px 10px 10px;
#header_logo {
background-color: $white-color;
padding: 0.2em;
@@ -338,7 +352,8 @@ header {
flex-wrap: wrap;
width: 90%;
margin: 1em auto;
#alert_box, #info_box {
#alert_box,
#info_box {
flex: 49%;
font-size: 0.9em;
margin: 0.2em;
@@ -369,7 +384,6 @@ header {
}
}
#page {
width: 90%;
margin: 20px auto 0;
@@ -470,11 +484,11 @@ header {
}
&.btn-grey.clickable:not(:disabled):hover {
background-color:hsl(210,5%,30%);
background-color: hsl(210, 5%, 30%);
}
}
/*--------------------------------CONTENT------------------------------*/
/*--------------------------------CONTENT------------------------------*/
#quick_notif {
width: 100%;
margin: 0 auto;
@@ -485,7 +499,6 @@ header {
}
}
#content {
padding: 1em 1%;
box-shadow: $shadow-color 0 5px 10px;
@@ -510,7 +523,7 @@ header {
}
&.alert-red {
background-color: rgb(255,245,245);
background-color: rgb(255, 245, 245);
color: #c53030;
border: #fc8181 1px solid;
}
@@ -554,7 +567,7 @@ header {
}
}
/*---------------------------------NEWS--------------------------------*/
/*---------------------------------NEWS--------------------------------*/
#news {
display: flex;
flex-wrap: wrap;
@@ -586,20 +599,23 @@ header {
}
}
}
@media screen and (max-width: $small-devices){
#left_column, #right_column {
@media screen and (max-width: $small-devices) {
#left_column,
#right_column {
flex: 100%;
}
}
/* AGENDA/BIRTHDAYS */
#agenda,#birthdays {
/* AGENDA/BIRTHDAYS */
#agenda,
#birthdays {
display: block;
width: 100%;
background: white;
font-size: 70%;
margin-bottom: 1em;
#agenda_title,#birthdays_title {
#agenda_title,
#birthdays_title {
margin: 0em;
border-radius: 5px 5px 0 0;
box-shadow: $shadow-color 1px 1px 1px;
@@ -615,7 +631,8 @@ header {
box-shadow: $shadow-color 1px 1px 1px;
height: 20em;
}
#agenda_content,#birthdays_content {
#agenda_content,
#birthdays_content {
.agenda_item {
padding: 0.5em;
margin-bottom: 0.5em;
@@ -636,7 +653,7 @@ header {
margin: 0em;
list-style-type: none;
font-weight: bold;
>li {
> li {
padding: 0.5em;
&:nth-child(even) {
background: $secondary-neutral-light-color;
@@ -652,9 +669,9 @@ header {
}
}
}
/* END AGENDA/BIRTHDAYS */
/* END AGENDA/BIRTHDAYS */
/* EVENTS TODAY AND NEXT FEW DAYS */
/* EVENTS TODAY AND NEXT FEW DAYS */
.news_events_group {
box-shadow: $shadow-color 1px 1px 1px;
margin-left: 1em;
@@ -733,9 +750,9 @@ header {
}
}
}
/* END EVENTS TODAY AND NEXT FEW DAYS */
/* END EVENTS TODAY AND NEXT FEW DAYS */
/* COMING SOON */
/* COMING SOON */
.news_coming_soon {
display: list-item;
list-style-type: square;
@@ -750,9 +767,9 @@ header {
font-size: 0.9em;
}
}
/* END COMING SOON */
/* END COMING SOON */
/* NOTICES */
/* NOTICES */
.news_notice {
margin: 0em 0em 1em 1em;
padding: 0.4em;
@@ -767,9 +784,9 @@ header {
margin-left: 1em;
}
}
/* END NOTICES */
/* END NOTICES */
/* CALLS */
/* CALLS */
.news_call {
margin: 0em 0em 1em 1em;
padding: 0.4em;
@@ -787,7 +804,7 @@ header {
margin-left: 1em;
}
}
/* END CALLS */
/* END CALLS */
.news_empty {
margin-left: 1em;
@@ -798,7 +815,7 @@ header {
}
}
@media screen and (max-width: $small-devices){
@media screen and (max-width: $small-devices) {
#page {
width: 98%;
}
@@ -867,29 +884,32 @@ header {
/*---------------------------POSTERS----------------------------*/
#poster_list, #screen_list, #poster_edit, #screen_edit{
#poster_list,
#screen_list,
#poster_edit,
#screen_edit {
position: relative;
#title{
#title {
position: relative;
padding: 10px;
margin: 10px;
border-bottom: 2px solid black;
h3{
h3 {
display: flex;
justify-content: center;
align-items: center;
}
#links{
#links {
position: absolute;
display: flex;
bottom: 5px;
&.left{
&.left {
left: 0;
}
&.right{
&.right {
right: 0;
}
.link{
.link {
padding: 5px;
padding-left: 20px;
padding-right: 20px;
@@ -897,26 +917,29 @@ header {
border-radius: 20px;
background-color: hsl(40, 100%, 50%);
color: black;
&:hover{
&:hover {
color: black;
background-color: hsl(40, 58%, 50%);
}
&.delete{
&.delete {
background-color: hsl(0, 100%, 40%);
}
}
}
}
#posters, #screens{
#posters,
#screens {
position: relative;
display: flex;
flex-wrap: wrap;
#no-posters, #no-screens{
#no-posters,
#no-screens {
display: flex;
justify-content: center;
align-items: center;
}
.poster, .screen{
.poster,
.screen {
min-width: 10%;
max-width: 20%;
display: flex;
@@ -926,28 +949,28 @@ header {
border-radius: 4px;
padding: 10px;
background-color: lightgrey;
*{
* {
display: flex;
justify-content: center;
align-items: center;
}
.name{
.name {
padding-bottom: 5px;
margin-bottom: 5px;
border-bottom: 1px solid whitesmoke;
}
.image{
.image {
flex-grow: 1;
position: relative;
padding-bottom: 5px;
margin-bottom: 5px;
border-bottom: 1px solid whitesmoke;
img{
img {
max-height: 20vw;
max-width: 100%;
}
&:hover{
&::before{
&:hover {
&::before {
position: absolute;
width: 100%;
height: 100%;
@@ -964,11 +987,11 @@ header {
}
}
}
.dates{
.dates {
padding-bottom: 5px;
margin-bottom: 5px;
border-bottom: 1px solid whitesmoke;
*{
* {
display: flex;
justify-content: center;
align-items: center;
@@ -976,29 +999,32 @@ header {
margin-left: 5px;
margin-right: 5px;
}
.begin, .end{
.begin,
.end {
width: 48%;
}
.begin{
.begin {
border-right: 1px solid whitesmoke;
padding-right: 2%;
}
}
.edit, .moderate, .slideshow{
.edit,
.moderate,
.slideshow {
padding: 5px;
border-radius: 20px;
background-color: hsl(40, 100%, 50%);
color: black;
&:hover{
&:hover {
color: black;
background-color: hsl(40, 58%, 50%);
}
&:nth-child(2n){
&:nth-child(2n) {
margin-top: 5px;
margin-bottom: 5px;
}
}
.tooltip{
.tooltip {
visibility: hidden;
width: 120px;
background-color: hsl(210, 20%, 98%);
@@ -1008,25 +1034,24 @@ header {
border-radius: 6px;
position: absolute;
z-index: 10;
ul{
ul {
margin-left: 0;
display: inline-block;
li{
li {
display: list-item;
list-style-type: none;
}
}
}
&.not_moderated
{
&.not_moderated {
border: 1px solid red;
}
&:hover .tooltip{
&:hover .tooltip {
visibility: visible;
}
}
}
#view{
#view {
position: fixed;
width: 100vw;
height: 100vh;
@@ -1039,10 +1064,10 @@ header {
visibility: hidden;
background-color: rgba(10, 10, 10, 0.9);
overflow: hidden;
&.active{
&.active {
visibility: visible;
}
#placeholder{
#placeholder {
width: 80vw;
height: 80vh;
display: flex;
@@ -1050,7 +1075,7 @@ header {
align-items: center;
top: 0;
left: 0;
img{
img {
max-width: 100%;
max-height: 100%;
}
@@ -1058,7 +1083,6 @@ header {
}
}
/*---------------------------ACCOUNTING----------------------------*/
#accounting {
.journal-table {
@@ -1084,7 +1108,12 @@ header {
}
/*-----------------------------GENERAL-----------------------------*/
h1, h2, h3, h4, h5, h6 {
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: bold;
margin-top: 0.5em;
}
@@ -1119,12 +1148,15 @@ h6 {
margin-left: 50px;
}
p, pre {
p,
pre {
margin-top: 0.8em;
margin-left: 0;
}
ul, ol, dl {
ul,
ol,
dl {
margin-top: 1em;
margin-bottom: 1em;
margin-left: 25px;
@@ -1167,7 +1199,11 @@ blockquote h5:first-child {
table {
width: 100%;
font-size: 0.90em;
font-size: 0.9em;
}
th {
padding: 4px;
}
td {
@@ -1207,11 +1243,13 @@ sub {
font-size: smaller;
}
b, strong {
b,
strong {
font-weight: bold;
}
i, em {
i,
em {
font-style: italic;
}
@@ -1220,7 +1258,8 @@ i, em {
font-weight: bold;
}
u, .underline {
u,
.underline {
text-decoration: underline;
}
@@ -1273,7 +1312,8 @@ u, .underline {
#user_profile_infos_items {
margin-top: 3em;
}
.user_profile_infos_item, .user_profile_infos_item_value {
.user_profile_infos_item,
.user_profile_infos_item_value {
vertical-align: top;
display: inline-block;
width: 49%;
@@ -1293,7 +1333,8 @@ u, .underline {
text-align: right;
color: grey;
font-style: italic;
&:after, &:before {
&:after,
&:before {
content: "\201C";
vertical-align: middle;
}
@@ -1329,8 +1370,9 @@ u, .underline {
}
}
}
@media screen and (max-width: $small-devices){
#user_profile_infos, #user_profile_pictures {
@media screen and (max-width: $small-devices) {
#user_profile_infos,
#user_profile_pictures {
flex-basis: 50%;
}
}
@@ -1439,7 +1481,6 @@ u, .underline {
}
}
/*---------------------------------PAGE--------------------------------*/
.page_content {
@@ -1510,7 +1551,7 @@ textarea {
.last_message span {
white-space: nowrap;
text-overflow: ellipsis;
overflow:hidden;
overflow: hidden;
width: 100%;
display: block;
}
@@ -1720,7 +1761,8 @@ label {
}
}
#cash_summary_form label, .inline {
#cash_summary_form label,
.inline {
display: inline;
}
@@ -1775,13 +1817,19 @@ label {
/*--------------------------------JQuery-------------------------------*/
.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header
.ui-state-active, a.ui-button:active, .ui-button:active,
.ui-state-active,
.ui-widget-content .ui-state-active,
.ui-widget-header .ui-state-active,
a.ui-button:active,
.ui-button:active,
.ui-button.ui-state-active:hover {
background: $primary-color;
border-color: $primary-color;
}
.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-top,
.ui-corner-all,
.ui-corner-bottom,
.ui-corner-right,
.ui-corner-top,
.ui-corner-left {
border-radius: 0;
}
@@ -1795,7 +1843,6 @@ label {
max-width: 10em;
}
}
}
/* --------------------------------------pedagogy-----------------------------------*/
@@ -1808,7 +1855,7 @@ $pedagogy-white-text: #f0f0f0;
.pedagogy {
&.star-not-checked {
color : #f7f7f7;
color: #f7f7f7;
margin-bottom: 0;
margin-top: 0;
}
@@ -1819,10 +1866,10 @@ $pedagogy-white-text: #f0f0f0;
}
&.grade-without-star {
display: none
display: none;
}
@media screen and (max-width: $large-devices){
@media screen and (max-width: $large-devices) {
&.star-not-checked {
margin-left: 5px;
margin-right: 5px;
@@ -1833,7 +1880,7 @@ $pedagogy-white-text: #f0f0f0;
}
}
@media screen and (max-width: $small-devices){
@media screen and (max-width: $small-devices) {
&.grade-without-star {
display: block;
}
@@ -1851,11 +1898,9 @@ $pedagogy-white-text: #f0f0f0;
text-align: center;
border: none;
}
}
#search_form {
.search-form-container {
display: grid;
grid-template-columns: auto auto;
@@ -1879,13 +1924,13 @@ $pedagogy-white-text: #f0f0f0;
grid-template-rows: auto;
grid-template-areas: "search-bar-input search-bar-button";
@media screen and (max-width: $medium-devices){
@media screen and (max-width: $medium-devices) {
grid-template-columns: auto auto;
grid-template-rows: auto;
grid-template-areas: "search-bar-input search-bar-button";
}
@media screen and (max-width: $small-devices){
@media screen and (max-width: $small-devices) {
grid-template-columns: auto;
grid-template-rows: auto;
grid-template-areas: "search-bar-input";
@@ -1921,8 +1966,9 @@ $pedagogy-white-text: #f0f0f0;
grid-area: radio-semester;
}
.radio-guide input[type="radio"],input[type="checkbox"] {
display:none;
.radio-guide input[type="radio"],
input[type="checkbox"] {
display: none;
}
.radio-guide {
margin-top: 10px;
@@ -1956,7 +2002,7 @@ $pedagogy-white-text: #f0f0f0;
grid-template-rows: auto auto;
grid-template-areas:
"hours-cm hours-td hours-tp hours-te hours-the"
"department credit-type semester . ." ;
"department credit-type semester . .";
}
.department {
@@ -2005,7 +2051,7 @@ $pedagogy-white-text: #f0f0f0;
grid-template-rows: 100%;
grid-template-areas: "stars comment";
@media screen and (max-width: $large-devices){
@media screen and (max-width: $large-devices) {
grid-template-columns: 100%;
grid-template-rows: auto auto;
grid-template-areas:
@@ -2033,7 +2079,7 @@ $pedagogy-white-text: #f0f0f0;
color: $pedagogy-white-text;
clip-path: polygon(0 0%, 0 100%, 30% 100%, 33% 0);
@media screen and (max-width: $large-devices){
@media screen and (max-width: $large-devices) {
clip-path: none;
}
}
@@ -2060,7 +2106,7 @@ $pedagogy-white-text: #f0f0f0;
"grade grade-stars uv-infos"
". . uv-infos";
@media screen and (max-width: $large-devices){
@media screen and (max-width: $large-devices) {
grid-template-columns: 50% 50%;
grid-template-rows: auto auto;
grid-template-areas:
@@ -2104,14 +2150,14 @@ $pedagogy-white-text: #f0f0f0;
margin-bottom: 30px;
margin-top: 10px;
@media screen and (max-width: $large-devices){
@media screen and (max-width: $large-devices) {
grid-template-columns: auto;
grid-template-rows: auto auto auto auto;
grid-template-areas:
"grade-block"
"comment"
"info"
"comment-end-bar"
"comment-end-bar";
}
.grade-block {
@@ -2131,10 +2177,10 @@ $pedagogy-white-text: #f0f0f0;
background-color: $pedagogy-blue;
@media screen and (max-width: $large-devices){
@media screen and (max-width: $large-devices) {
grid-template-columns: 50% auto;
grid-template-rows: auto;
grid-template-areas:"grade-type grade-stars";
grid-template-areas: "grade-type grade-stars";
width: auto;
clip-path: none;
align-content: space-evenly;
@@ -2171,7 +2217,7 @@ $pedagogy-white-text: #f0f0f0;
"anchor"
"markdown";
@media screen and (max-width: $large-devices){
@media screen and (max-width: $large-devices) {
border-left: solid;
border-right: solid;
border-color: $pedagogy-blue;
@@ -2199,7 +2245,7 @@ $pedagogy-white-text: #f0f0f0;
grid-area: info;
padding-bottom: 10px;
@media screen and (max-width: $large-devices){
@media screen and (max-width: $large-devices) {
border-left: solid;
border-right: solid;
border-color: $pedagogy-blue;
@@ -2227,7 +2273,7 @@ $pedagogy-white-text: #f0f0f0;
background-color: $pedagogy-blue;
margin-top: -1px;
@media screen and (max-width: $large-devices){
@media screen and (max-width: $large-devices) {
grid-template-columns: auto;
grid-template-rows: auto auto auto;
grid-template-areas:
@@ -2247,7 +2293,7 @@ $pedagogy-white-text: #f0f0f0;
background-color: $pedagogy-orange;
clip-path: polygon(0 10px, 0 100%, 350px 200%, 300px 10px);
@media screen and (max-width: $large-devices){
@media screen and (max-width: $large-devices) {
clip-path: none;
padding: 0;
padding-bottom: 7px;
@@ -2261,14 +2307,13 @@ $pedagogy-white-text: #f0f0f0;
a:hover {
color: $pedagogy-hover-blue;
}
}
.date {
grid-area: date;
color: $pedagogy-white-text;
@media screen and (max-width: $large-devices){
@media screen and (max-width: $large-devices) {
padding-bottom: 7px;
}
}
@@ -2287,7 +2332,7 @@ $pedagogy-white-text: #f0f0f0;
color: $pedagogy-hover-blue;
}
@media screen and (max-width: $large-devices){
@media screen and (max-width: $large-devices) {
text-align: center;
justify-self: inherit;
padding-bottom: 7px;
+28 -38
View File
@@ -189,78 +189,70 @@ class UserTabsMixin(TabedViewMixin):
return self.object.get_display_name()
def get_list_of_tabs(self):
tab_list = []
tab_list.append(
user: User = self.object
tab_list = [
{
"url": reverse("core:user_profile", kwargs={"user_id": self.object.id}),
"url": reverse("core:user_profile", kwargs={"user_id": user.id}),
"slug": "infos",
"name": _("Infos"),
}
)
tab_list.append(
},
{
"url": reverse(
"core:user_godfathers", kwargs={"user_id": self.object.id}
),
"url": reverse("core:user_godfathers", kwargs={"user_id": user.id}),
"slug": "godfathers",
"name": _("Family"),
}
)
tab_list.append(
},
{
"url": reverse(
"core:user_pictures", kwargs={"user_id": self.object.id}
),
"url": reverse("core:user_pictures", kwargs={"user_id": user.id}),
"slug": "pictures",
"name": _("Pictures"),
},
]
if self.request.user.was_subscribed:
tab_list.append(
{
"url": reverse("galaxy:user", kwargs={"user_id": user.id}),
"slug": "galaxy",
"name": _("Galaxy"),
}
)
if self.request.user == self.object:
if self.request.user == user:
tab_list.append(
{"url": reverse("core:user_tools"), "slug": "tools", "name": _("Tools")}
)
if self.request.user.can_edit(self.object):
if self.request.user.can_edit(user):
tab_list.append(
{
"url": reverse(
"core:user_edit", kwargs={"user_id": self.object.id}
),
"url": reverse("core:user_edit", kwargs={"user_id": user.id}),
"slug": "edit",
"name": _("Edit"),
}
)
tab_list.append(
{
"url": reverse(
"core:user_prefs", kwargs={"user_id": self.object.id}
),
"url": reverse("core:user_prefs", kwargs={"user_id": user.id}),
"slug": "prefs",
"name": _("Preferences"),
}
)
if self.request.user.can_view(self.object):
if self.request.user.can_view(user):
tab_list.append(
{
"url": reverse(
"core:user_clubs", kwargs={"user_id": self.object.id}
),
"url": reverse("core:user_clubs", kwargs={"user_id": user.id}),
"slug": "clubs",
"name": _("Clubs"),
}
)
if self.request.user.is_owner(self.object):
if self.request.user.is_owner(user):
tab_list.append(
{
"url": reverse(
"core:user_groups", kwargs={"user_id": self.object.id}
),
"url": reverse("core:user_groups", kwargs={"user_id": user.id}),
"slug": "groups",
"name": _("Groups"),
}
)
try:
if self.object.customer and (
self.object == self.request.user
if user.customer and (
user == self.request.user
or self.request.user.is_in_group(
settings.SITH_GROUP_ACCOUNTING_ADMIN_ID
)
@@ -271,9 +263,7 @@ class UserTabsMixin(TabedViewMixin):
):
tab_list.append(
{
"url": reverse(
"core:user_stats", kwargs={"user_id": self.object.id}
),
"url": reverse("core:user_stats", kwargs={"user_id": user.id}),
"slug": "stats",
"name": _("Stats"),
}
@@ -281,10 +271,10 @@ class UserTabsMixin(TabedViewMixin):
tab_list.append(
{
"url": reverse(
"core:user_account", kwargs={"user_id": self.object.id}
"core:user_account", kwargs={"user_id": user.id}
),
"slug": "account",
"name": _("Account") + " (%s €)" % self.object.customer.amount,
"name": _("Account") + " (%s €)" % user.customer.amount,
}
)
except:
View File
Whitespace-only changes.
+30
View File
@@ -0,0 +1,30 @@
# -*- coding:utf-8 -*
#
# Copyright 2023
# - Skia <skia@hya.sk>
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# 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
# Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
from django.apps import AppConfig
class GalaxyConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "galaxy"
+61
View File
@@ -0,0 +1,61 @@
# -*- coding:utf-8 -*
#
# Copyright 2023
# - Skia <skia@hya.sk>
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# 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
# Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
from django.core.management.base import BaseCommand
from django.db import connection
from galaxy.models import Galaxy
import logging
class Command(BaseCommand):
help = (
"Rule the Galaxy! "
"Reset the whole galaxy and compute once again all the relation scores of all users. "
"As the sith's users are rather numerous, this command might be quite expensive in memory "
"and CPU time. Please keep this fact in mind when scheduling calls to this command in a production "
"environment."
)
def handle(self, *args, **options):
logger = logging.getLogger("main")
if options["verbosity"] > 1:
logger.setLevel(logging.DEBUG)
elif options["verbosity"] > 0:
logger.setLevel(logging.INFO)
else:
logger.setLevel(logging.NOTSET)
logger.info("The Galaxy is being ruled by the Sith.")
Galaxy.rule()
logger.info(
"Caching current Galaxy state for a quicker display of the Empire's power."
)
Galaxy.make_state()
logger.info("Ruled the galaxy in {} queries.".format(len(connection.queries)))
if options["verbosity"] > 2:
for q in connection.queries:
logger.debug(q)
+113
View File
@@ -0,0 +1,113 @@
# Generated by Django 3.2.16 on 2023-02-03 10:31
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="Galaxy",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("state", models.JSONField(verbose_name="current state")),
],
),
migrations.CreateModel(
name="GalaxyStar",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"mass",
models.PositiveIntegerField(default=0, verbose_name="star mass"),
),
(
"owner",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="galaxy_user",
to=settings.AUTH_USER_MODEL,
verbose_name="galaxy user",
),
),
],
),
migrations.CreateModel(
name="GalaxyLane",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"distance",
models.PositiveIntegerField(
default=0,
help_text="Distance separating star1 and star2",
verbose_name="distance",
),
),
(
"family",
models.PositiveIntegerField(default=0, verbose_name="family score"),
),
(
"pictures",
models.PositiveIntegerField(
default=0, verbose_name="pictures score"
),
),
(
"clubs",
models.PositiveIntegerField(default=0, verbose_name="clubs score"),
),
(
"star1",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="lanes1",
to="galaxy.galaxystar",
verbose_name="galaxy lanes 1",
),
),
(
"star2",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="lanes2",
to="galaxy.galaxystar",
verbose_name="galaxy lanes 2",
),
),
],
),
]
View File
Whitespace-only changes.
+377
View File
@@ -0,0 +1,377 @@
# -*- coding:utf-8 -*
#
# Copyright 2023
# - Skia <skia@hya.sk>
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# 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
# Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
import math
import logging
from django.db import models
from django.db.models import Q, Case, F, Value, When, Count
from django.db.models.functions import Concat
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from typing import List, TypedDict
from core.models import User
from club.models import Club
from sas.models import Picture
class GalaxyStar(models.Model):
"""
This class defines a star (vertex -> user) in the galaxy graph, storing a reference to its owner citizen, and being
referenced by GalaxyLane.
It also stores the individual mass of this star, used to push it towards the center of the galaxy.
"""
owner = models.OneToOneField(
User,
verbose_name=_("galaxy user"),
related_name="galaxy_user",
on_delete=models.CASCADE,
)
mass = models.PositiveIntegerField(
_("star mass"),
default=0,
)
def __str__(self):
return str(self.owner)
class GalaxyLane(models.Model):
"""
This class defines a lane (edge -> link between galaxy citizen) in the galaxy map, storing a reference to both its
ends and the distance it covers.
Score details between citizen owning the stars is also stored here.
"""
star1 = models.ForeignKey(
GalaxyStar,
verbose_name=_("galaxy lanes 1"),
related_name="lanes1",
on_delete=models.CASCADE,
)
star2 = models.ForeignKey(
GalaxyStar,
verbose_name=_("galaxy lanes 2"),
related_name="lanes2",
on_delete=models.CASCADE,
)
distance = models.PositiveIntegerField(
_("distance"),
default=0,
help_text=_("Distance separating star1 and star2"),
)
family = models.PositiveIntegerField(
_("family score"),
default=0,
)
pictures = models.PositiveIntegerField(
_("pictures score"),
default=0,
)
clubs = models.PositiveIntegerField(
_("clubs score"),
default=0,
)
class StarDict(TypedDict):
id: int
name: str
mass: int
class GalaxyDict(TypedDict):
nodes: List[StarDict]
links: List
class Galaxy(models.Model):
logger = logging.getLogger("main")
GALAXY_SCALE_FACTOR = 2_000
FAMILY_LINK_POINTS = 366 # Equivalent to a leap year together in a club, because.
PICTURE_POINTS = 2 # Equivalent to two days as random members of a club.
CLUBS_POINTS = 1 # One day together as random members in a club is one point.
state = models.JSONField("current state")
@staticmethod
def make_state() -> GalaxyDict:
"""
Compute JSON structure to send to 3d-force-graph: https://github.com/vasturiano/3d-force-graph/
"""
without_nickname = Concat(
F("owner__first_name"), Value(" "), F("owner__last_name")
)
with_nickname = Concat(
F("owner__first_name"),
Value(" "),
F("owner__last_name"),
Value(" ("),
F("owner__nick_name"),
Value(")"),
)
stars = GalaxyStar.objects.annotate(
owner_name=Case(
When(owner__nick_name=None, then=without_nickname),
default=with_nickname,
)
)
lanes = GalaxyLane.objects.annotate(
star1_owner=F("star1__owner__id"),
star2_owner=F("star2__owner__id"),
)
json = GalaxyDict(
nodes=[
StarDict(id=star.owner_id, name=star.owner_name, mass=star.mass)
for star in stars
],
links=[],
)
# Make bidirectional links
# TODO: see if this impacts performance with a big graph
for path in lanes:
json["links"].append(
{
"source": path.star1_owner,
"target": path.star2_owner,
"value": path.distance,
}
)
json["links"].append(
{
"source": path.star2_owner,
"target": path.star1_owner,
"value": path.distance,
}
)
Galaxy.objects.all().delete()
Galaxy(state=json).save()
###################
# User self score #
###################
@classmethod
def compute_user_score(cls, user):
"""
This compute an individual score for each citizen. It will later be used by the graph algorithm to push
higher scores towards the center of the galaxy.
Idea: This could be added to the computation:
- Forum posts
- Picture count
- Counter consumption
- Barman time
- ...
"""
user_score = 1
user_score += cls.query_user_score(user)
# TODO:
# Scale that value with some magic number to accommodate to typical data
# Really active galaxy citizen after 5 years typically have a score of about XXX
# Citizen that were seen regularly without taking much part in organizations typically have a score of about XXX
# Citizen that only went to a few events typically score about XXX
user_score = int(math.log2(user_score))
return user_score
@classmethod
def query_user_score(cls, user):
score_query = (
User.objects.filter(id=user.id)
.annotate(
godchildren_count=Count("godchildren", distinct=True)
* cls.FAMILY_LINK_POINTS,
godfathers_count=Count("godfathers", distinct=True)
* cls.FAMILY_LINK_POINTS,
pictures_score=Count("pictures", distinct=True) * cls.PICTURE_POINTS,
clubs_score=Count("memberships", distinct=True) * cls.CLUBS_POINTS,
)
.aggregate(
score=models.Sum(
F("godchildren_count")
+ F("godfathers_count")
+ F("pictures_score")
+ F("clubs_score")
)
)
)
return score_query.get("score")
####################
# Inter-user score #
####################
@classmethod
def compute_users_score(cls, user1, user2):
family = cls.compute_users_family_score(user1, user2)
pictures = cls.compute_users_pictures_score(user1, user2)
clubs = cls.compute_users_clubs_score(user1, user2)
score = family + pictures + clubs
return score, family, pictures, clubs
@classmethod
def compute_users_family_score(cls, user1, user2):
link_count = User.objects.filter(
Q(id=user1.id, godfathers=user2) | Q(id=user2.id, godfathers=user1)
).count()
if link_count:
cls.logger.debug(
f"\t\t- '{user1}' and '{user2}' have {link_count} direct family link"
)
return link_count * cls.FAMILY_LINK_POINTS
@classmethod
def compute_users_pictures_score(cls, user1, user2):
picture_count = (
Picture.objects.filter(people__user__in=(user1,))
.filter(people__user__in=(user2,))
.count()
)
if picture_count:
cls.logger.debug(
f"\t\t- '{user1}' was pictured with '{user2}' {picture_count} times"
)
return picture_count * cls.PICTURE_POINTS
@classmethod
def compute_users_clubs_score(cls, user1, user2):
common_clubs = Club.objects.filter(members__in=user1.memberships.all()).filter(
members__in=user2.memberships.all()
)
user1_memberships = user1.memberships.filter(club__in=common_clubs)
user2_memberships = user2.memberships.filter(club__in=common_clubs)
score = 0
for user1_membership in user1_memberships:
if user1_membership.end_date is None:
user1_membership.end_date = timezone.now().date()
query = Q( # start2 <= start1 <= end2
start_date__lte=user1_membership.start_date,
end_date__gte=user1_membership.start_date,
)
query |= Q( # start2 <= start1 <= now
start_date__lte=user1_membership.start_date, end_date=None
)
query |= Q( # start1 <= start2 <= end2
start_date__gte=user1_membership.start_date,
start_date__lte=user1_membership.end_date,
)
for user2_membership in user2_memberships.filter(
query, club=user1_membership.club
):
if user2_membership.end_date is None:
user2_membership.end_date = timezone.now().date()
latest_start = max(
user1_membership.start_date, user2_membership.start_date
)
earliest_end = min(user1_membership.end_date, user2_membership.end_date)
cls.logger.debug(
"\t\t- '%s' was with '%s' in %s starting on %s until %s (%s days)"
% (
user1,
user2,
user2_membership.club,
latest_start,
earliest_end,
(earliest_end - latest_start).days,
)
)
score += cls.CLUBS_POINTS * (earliest_end - latest_start).days
return score
###################
# Rule the galaxy #
###################
@classmethod
def rule(cls):
GalaxyStar.objects.all().delete()
# The following is a no-op thanks to cascading, but in case that changes in the future, better keep it anyway.
GalaxyLane.objects.all().delete()
rulable_users = (
User.objects.filter(subscriptions__isnull=False)
.filter(
Q(godchildren__isnull=False)
| Q(godfathers__isnull=False)
| Q(pictures__isnull=False)
| Q(memberships__isnull=False)
)
.distinct()
)
# force fetch of the whole query to make sure there won't
# be any more db hits
# this is memory expensive but prevents a lot of db hits, therefore
# is far more time efficient
rulable_users = list(rulable_users)
while len(rulable_users) > 0:
user1 = rulable_users.pop()
for user2 in rulable_users:
cls.logger.debug("")
cls.logger.debug(f"\t> Ruling '{user1}' against '{user2}'")
star1, _ = GalaxyStar.objects.get_or_create(owner=user1)
star2, _ = GalaxyStar.objects.get_or_create(owner=user2)
if star1.mass == 0:
star1.mass = cls.compute_user_score(user1)
star1.save()
if star2.mass == 0:
star2.mass = cls.compute_user_score(user2)
star2.save()
users_score, family, pictures, clubs = cls.compute_users_score(
user1, user2
)
if users_score > 0:
GalaxyLane(
star1=star1,
star2=star2,
distance=cls.scale_distance(users_score),
family=family,
pictures=pictures,
clubs=clubs,
).save()
@classmethod
def scale_distance(cls, value):
# TODO: this will need adjustements with the real, typical data on Taiste
cls.logger.debug(f"\t\t> Score: {value}")
# Invert score to draw close users together
value = 1 / value # Cannot be 0
value += 2 # We use log2 just below and need to stay above 1
value = ( # Let's get something in the range ]0; log2(3)-1≈0.58[ that we can multiply later
math.log2(value) - 1
)
value *= ( # Scale that value with a magic number to accommodate to typical data
# Really close galaxy citizen after 5 years typically have a score of about XXX
# Citizen that were in the same year without being really friends typically have a score of about XXX
# Citizen that have met once or twice only have a couple of pictures together typically score about XXX
cls.GALAXY_SCALE_FACTOR
)
cls.logger.debug(f"\t\t> Scaled distance: {value}")
return int(value)
+5
View File
@@ -0,0 +1,5 @@
// Version 1.70.19 3d-force-graph - https://github.com/vasturiano/3d-force-graph
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).ForceGraph3D=e()}(this,(function(){"use strict";function t(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);e&&(i=i.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,i)}return n}function e(e){for(var i=1;i<arguments.length;i++){var r=null!=arguments[i]?arguments[i]:{};i%2?t(Object(r),!0).forEach((function(t){n(e,t,r[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):t(Object(r)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(r,t))}))}return e}function n(t,e,n){return(e=function(t){var e=function(t,e){if("object"!=typeof t||null===t)return t;var n=t[Symbol.toPrimitive];if(void 0!==n){var i=n.call(t,e||"default");if("object"!=typeof i)return i;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===e?String:Number)(t)}(t,"string");return"symbol"==typeof e?e:String(e)}(e))in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function i(t){return function(t){if(Array.isArray(t))return r(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||function(t,e){if(!t)return;if("string"==typeof t)return r(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);"Object"===n&&t.constructor&&(n=t.constructor.name);if("Map"===n||"Set"===n)return Array.from(t);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return r(t,e)}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function r(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,i=new Array(e);n<e;n++)i[n]=t[n];return i}!function(t,e){void 0===e&&(e={});var n=e.insertAt;if(t&&"undefined"!=typeof document){var i=document.head||document.getElementsByTagName("head")[0],r=document.createElement("style");r.type="text/css","top"===n&&i.firstChild?i.insertBefore(r,i.firstChild):i.appendChild(r),r.styleSheet?r.styleSheet.cssText=t:r.appendChild(document.createTextNode(t))}}(".graph-info-msg {\n top: 50%;\n width: 100%;\n text-align: center;\n color: lavender;\n opacity: 0.7;\n font-size: 22px;\n position: absolute;\n font-family: Sans-serif;\n}\n\n.scene-container .clickable {\n cursor: pointer;\n}\n\n.scene-container .grabbable {\n cursor: move;\n cursor: grab;\n cursor: -moz-grab;\n cursor: -webkit-grab;\n}\n\n.scene-container .grabbable:active {\n cursor: grabbing;\n cursor: -moz-grabbing;\n cursor: -webkit-grabbing;\n}");const a="148",o={LEFT:0,MIDDLE:1,RIGHT:2,ROTATE:0,DOLLY:1,PAN:2},s=0,l=1,c=2,u=3,h=100,d=301,p=302,f=306,m=1e3,g=1001,v=1002,_=1003,y=1005,x=1006,b=1008,w=1009,M=1014,S=1015,E=1016,T=1020,A=1023,C=1026,L=1027,P=33776,R=33777,D=33778,O=33779,I=3e3,k=3001,N="srgb",z="srgb-linear",U=7680,F="300 es",B=1035;class j{addEventListener(t,e){void 0===this._listeners&&(this._listeners={});const n=this._listeners;void 0===n[t]&&(n[t]=[]),-1===n[t].indexOf(e)&&n[t].push(e)}hasEventListener(t,e){if(void 0===this._listeners)return!1;const n=this._listeners;return void 0!==n[t]&&-1!==n[t].indexOf(e)}removeEventListener(t,e){if(void 0===this._listeners)return;const n=this._listeners[t];if(void 0!==n){const t=n.indexOf(e);-1!==t&&n.splice(t,1)}}dispatchEvent(t){if(void 0===this._listeners)return;const e=this._listeners[t.type];if(void 0!==e){t.target=this;const n=e.slice(0);for(let e=0,i=n.length;e<i;e++)n[e].call(this,t);t.target=null}}}const G=["00","01","02","03","04","05","06","07","08","09","0a","0b","0c","0d","0e","0f","10","11","12","13","14","15","16","17","18","19","1a","1b","1c","1d","1e","1f","20","21","22","23","24","25","26","27","28","29","2a","2b","2c","2d","2e","2f","30","31","32","33","34","35","36","37","38","39","3a","3b","3c","3d","3e","3f","40","41","42","43","44","45","46","47","48","49","4a","4b","4c","4d","4e","4f","50","51","52","53","54","55","56","57","58","59","5a","5b","5c","5d","5e","5f","60","61","62","63","64","65","66","67","68","69","6a","6b","6c","6d","6e","6f","70","71","72","73","74","75","76","77","78","79","7a","7b","7c","7d","7e","7f","80","81","82","83","84","85","86","87","88","89","8a","8b","8c","8d","8e","8f","90","91","92","93","94","95","96","97","98","99","9a","9b","9c","9d","9e","9f","a0","a1","a2","a3","a4","a5","a6","a7","a8","a9","aa","ab","ac","ad","ae","af","b0","b1","b2","b3","b4","b5","b6","b7","b8","b9","ba","bb","bc","bd","be","bf","c0","c1","c2","c3","c4","c5","c6","c7","c8","c9","ca","cb","cc","cd","ce","cf","d0","d1","d2","d3","d4","d5","d6","d7","d8","d9","da","db","dc","dd","de","df","e0","e1","e2","e3","e4","e5","e6","e7","e8","e9","ea","eb","ec","ed","ee"Line truncated
// <http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef (WCAG Version 2)
// Analyze the 2 colors and returns the color contrast defined by (WCAG Version 2)
c.readability=function(t,n){var i=c(t),r=c(n);return(e.max(i.getLuminance(),r.getLuminance())+.05)/(e.min(i.getLuminance(),r.getLuminance())+.05)},c.isReadable=function(t,e,n){var i,r,a=c.readability(t,e);switch(r=!1,(i=function(t){var e,n;e=((t=t||{level:"AA",size:"small"}).level||"AA").toUpperCase(),n=(t.size||"small").toLowerCase(),"AA"!==e&&"AAA"!==e&&(e="AA");"small"!==n&&"large"!==n&&(n="small");return{level:e,size:n}}(n)).level+i.size){case"AAsmall":case"AAAlarge":r=a>=4.5;break;case"AAlarge":r=a>=3;break;case"AAAsmall":r=a>=7}return r},c.mostReadable=function(t,e,n){var i,r,a,o,s=null,l=0;r=(n=n||{}).includeFallbackColors,a=n.level,o=n.size;for(var u=0;u<e.length;u++)(i=c.readability(t,e[u]))>l&&(l=i,s=c(e[u]));return c.isReadable(t,s,{level:a,size:o})||!r?s:(n.includeFallbackColors=!1,c.mostReadable(t,["#fff","#000"],n))};var A=c.names={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"0ff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000",blanchedalmond:"ffebcd",blue:"00f",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",burntsienna:"ea7e5d",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"0ff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkgrey:"a9a9a9",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkslategrey:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dimgrey:"696969",dodgerblue:"1e90ff",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"f0f",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",grey:"808080",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgray:"d3d3d3",lightgreen:"90ee90",lightgrey:"d3d3d3",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslategray:"789",lightslategrey:"789",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"0f0",limegreen:"32cd32",linen:"faf0e6",magenta:"f0f",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370db",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"db7093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",rebeccapurple:"663399",red:"f00",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",slategrey:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",wheat:"f5deb3",white:"fff",whitesmoke:"f5f5f5",yellow:"ff0",yellowgreen:"9acd32"},C=c.hexNames=function(t){var e={};for(var n in t)t.hasOwnProperty(n)&&(e[t[n]]=n);return e}(A);function L(t){return t=parseFloat(t),(isNaN(t)||t<0||t>1)&&(t=1),t}function P(t,n){(function(t){return"string"==typeof t&&-1!=t.indexOf(".")&&1===parseFloat(t)})(t)&&(t="100%");var i=function(t){return"string"==typeof t&&-1!=t.indexOf("%")}(t);return t=o(n,s(0,parseFloat(t))),i&&(t=parseInt(t*n,10)/100),e.abs(t-n)<1e-6?1:t%n/parseFloat(n)}function R(t){return o(1,s(0,t))}function D(t){return parseInt(t,16)}function O(t){return 1==t.length?"0"+t:""+t}function I(t){return t<=1&&(t=100*t+"%"),t}function k(t){return e.round(255*parseFloat(t)).toString(16)}function N(t){return D(t)/255}var z,U,F,B=(U="[\\s|\\(]+("+(z="(?:[-\\+]?\\d*\\.\\d+%?)|(?:[-\\+]?\\d+%?)")+")[,|\\s]+("+z+")[,|\\s]+("+z+")\\s*\\)?",F="[\\s|\\(]+("+z+")[,|\\s]+("+z+")[,|\\s]+("+z+")[,|\\s]+("+z+")\\s*\\)?",{CSS_UNIT:new RegExp(z),rgb:new RegExp("rgb"+U),rgba:new RegExp("rgba"+F),hsl:new RegExp("hsl"+U),hsla:new RegExp("hsla"+F),hsv:new RegExp("hsv"+U),hsva:new RegExp("hsva"+F),hex3:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex6:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,hex4:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex8:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/});function j(t){return!!B.CSSLine truncated
+2
View File
@@ -0,0 +1,2 @@
// https://github.com/vasturiano/d3-force-3d v3.0.3 Copyright 2022 Vasco Asturiano
!function(n,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("d3-binarytree"),require("d3-quadtree"),require("d3-octree"),require("d3-dispatch"),require("d3-timer")):"function"==typeof define&&define.amd?define(["exports","d3-binarytree","d3-quadtree","d3-octree","d3-dispatch","d3-timer"],t):t((n="undefined"!=typeof globalThis?globalThis:n||self).d3=n.d3||{},n.d3,n.d3,n.d3,n.d3,n.d3)}(this,(function(n,t,e,r,i,u){"use strict";function o(n){return function(){return n}}function f(n){return 1e-6*(n()-.5)}function a(n){return n.x+n.vx}function c(n){return n.y+n.vy}function l(n){return n.z+n.vz}function h(n){return n.index}function v(n,t){var e=n.get(t);if(!e)throw new Error("node not found: "+t);return e}const d=4294967296;function y(n){return n.x}function s(n){return n.y}function g(n){return n.z}var x=Math.PI*(3-Math.sqrt(5)),z=20*Math.PI/(9+Math.sqrt(221));n.forceCenter=function(n,t,e){var r,i=1;function u(){var u,o,f=r.length,a=0,c=0,l=0;for(u=0;u<f;++u)a+=(o=r[u]).x||0,c+=o.y||0,l+=o.z||0;for(a=(a/f-n)*i,c=(c/f-t)*i,l=(l/f-e)*i,u=0;u<f;++u)o=r[u],a&&(o.x-=a),c&&(o.y-=c),l&&(o.z-=l)}return null==n&&(n=0),null==t&&(t=0),null==e&&(e=0),u.initialize=function(n){r=n},u.x=function(t){return arguments.length?(n=+t,u):n},u.y=function(n){return arguments.length?(t=+n,u):t},u.z=function(n){return arguments.length?(e=+n,u):e},u.strength=function(n){return arguments.length?(i=+n,u):i},u},n.forceCollide=function(n){var i,u,h,v,d=1,y=1;function s(){for(var n,o,s,x,z,p,M,w,q=i.length,N=0;N<y;++N)for(o=(1===u?t.binarytree(i,a):2===u?e.quadtree(i,a,c):3===u?r.octree(i,a,c,l):null).visitAfter(g),n=0;n<q;++n)s=i[n],M=h[s.index],w=M*M,x=s.x+s.vx,u>1&&(z=s.y+s.vy),u>2&&(p=s.z+s.vz),o.visit(m);function m(n,t,e,r,i,o,a){var c=[t,e,r,i,o,a],l=c[0],h=c[1],y=c[2],g=c[u],q=c[u+1],N=c[u+2],m=n.data,A=n.r,b=M+A;if(!m)return l>x+b||g<x-b||u>1&&(h>z+b||q<z-b)||u>2&&(y>p+b||N<p-b);if(m.index>s.index){var k=x-m.x-m.vx,E=u>1?z-m.y-m.vy:0,j=u>2?p-m.z-m.vz:0,D=k*k+E*E+j*j;D<b*b&&(0===k&&(D+=(k=f(v))*k),u>1&&0===E&&(D+=(E=f(v))*E),u>2&&0===j&&(D+=(j=f(v))*j),D=(b-(D=Math.sqrt(D)))/D*d,s.vx+=(k*=D)*(b=(A*=A)/(w+A)),u>1&&(s.vy+=(E*=D)*b),u>2&&(s.vz+=(j*=D)*b),m.vx-=k*(b=1-b),u>1&&(m.vy-=E*b),u>2&&(m.vz-=j*b))}}}function g(n){if(n.data)return n.r=h[n.data.index];for(var t=n.r=0;t<Math.pow(2,u);++t)n[t]&&n[t].r>n.r&&(n.r=n[t].r)}function x(){if(i){var t,e,r=i.length;for(h=new Array(r),t=0;t<r;++t)e=i[t],h[e.index]=+n(e,t,i)}}return"function"!=typeof n&&(n=o(null==n?1:+n)),s.initialize=function(n,...t){i=n,v=t.find((n=>"function"==typeof n))||Math.random,u=t.find((n=>[1,2,3].includes(n)))||2,x()},s.iterations=function(n){return arguments.length?(y=+n,s):y},s.strength=function(n){return arguments.length?(d=+n,s):d},s.radius=function(t){return arguments.length?(n="function"==typeof t?t:o(+t),x(),s):n},s},n.forceLink=function(n){var t,e,r,i,u,a,c,l=h,d=function(n){return 1/Math.min(u[n.source.index],u[n.target.index])},y=o(30),s=1;function g(r){for(var u=0,o=n.length;u<s;++u)for(var l,h,v,d,y,g=0,x=0,z=0,p=0;g<o;++g)h=(l=n[g]).source,x=(v=l.target).x+v.vx-h.x-h.vx||f(c),i>1&&(z=v.y+v.vy-h.y-h.vy||f(c)),i>2&&(p=v.z+v.vz-h.z-h.vz||f(c)),x*=d=((d=Math.sqrt(x*x+z*z+p*p))-e[g])/d*r*t[g],z*=d,p*=d,v.vx-=x*(y=a[g]),i>1&&(v.vy-=z*y),i>2&&(v.vz-=p*y),h.vx+=x*(y=1-y),i>1&&(h.vy+=z*y),i>2&&(h.vz+=p*y)}function x(){if(r){var i,o,f=r.length,c=n.length,h=new Map(r.map(((n,t)=>[l(n,t,r),n])));for(i=0,u=new Array(f);i<c;++i)(o=n[i]).index=i,"object"!=typeof o.source&&(o.source=v(h,o.source)),"object"!=typeof o.target&&(o.target=v(h,o.target)),u[o.source.index]=(u[o.source.index]||0)+1,u[o.target.index]=(u[o.target.index]||0)+1;for(i=0,a=new Array(c);i<c;++i)o=n[i],a[i]=u[o.source.index]/(u[o.source.index]+u[o.target.index]);t=new Array(c),z(),e=new Array(c),p()}}function z(){if(r)for(var e=0,i=n.length;e<i;++e)t[e]=+d(n[e],e,n)}function p(){if(r)for(var t=0,i=n.length;t<i;++t)e[t]=+y(n[t],t,n)}return null==n&&(n=[]),g.initialize=function(n,...t){r=n,c=t.find((n=>"function"==typeof n))||Math.random,i=t.find((n=>[1,2,3].includes(n)))||2,x()},g.links=function(t){return arguments.length?(n=t,x(),g):n},g.id=function(n){return arguments.length?(l=n,g):l},g.iterations=function(n){return arguments.length?(s=+n,g):s},g.strength=function(n){return arguments.length?(d="function"==typeof n?n:o(+n),z(),g):d},g.distance=function(n){return arguments.length?(y="function"==typeof n?n:o(+n),p(),g):y},g},n.forceManyBody=function(){var n,i,u,a,c,l,h=o(-30),v=1,d=1/0,x=.81;function z(o){var f,a=n.length,l=(1===i?t.binarytree(n,y):2===i?e.quadtree(n,y,s):3===i?r.octree(n,y,s,g):null).visitAfter(M);for(c=o,f=0;f<a;++f)u=n[f],l.visit(w)}function p(){if(n){var t,e,r=n.length;for(l=new Array(r),t=0;t<r;++t)e=n[t],l[e.index]=+h(e,t,n)}}function M(n){var t,e,r,u,o,f,a=0,c=0,h=n.length;if(h){for(r=u=o=f=0;f<h;++f)(t=n[f])&&(e=Math.abs(t.value))&&(a+=t.value,c+=e,r+=e*(t.x||0),u+=e*(t.y||0),o+=e*(t.z||0));a*=Math.sqrt(4/h),n.x=r/c,i>1&&(n.y=u/c),i>2&&(n.z=o/c)}else{(t=n).Line truncated
+2
View File
@@ -0,0 +1,2 @@
// Version 1.6.5 three-spritetext - https://github.com/vasturiano/three-spritetext
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("three")):"function"==typeof define&&define.amd?define(["three"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).SpriteText=e(t.THREE)}(this,(function(t){"use strict";function e(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function r(t,e){for(var r=0;r<e.length;r++){var i=e[r];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,i.key,i)}}function i(t){return i=Object.setPrototypeOf?Object.getPrototypeOf:function(t){return t.__proto__||Object.getPrototypeOf(t)},i(t)}function n(t,e){return n=Object.setPrototypeOf||function(t,e){return t.__proto__=e,t},n(t,e)}function o(t,e){if(e&&("object"==typeof e||"function"==typeof e))return e;if(void 0!==e)throw new TypeError("Derived constructors may only return object or undefined");return function(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}(t)}function a(t){var e=function(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(t){return!1}}();return function(){var r,n=i(t);if(e){var a=i(this).constructor;r=Reflect.construct(n,arguments,a)}else r=n.apply(this,arguments);return o(this,r)}}function h(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){var r=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null==r)return;var i,n,o=[],a=!0,h=!1;try{for(r=r.call(t);!(a=(i=r.next()).done)&&(o.push(i.value),!e||o.length!==e);a=!0);}catch(t){h=!0,n=t}finally{try{a||null==r.return||r.return()}finally{if(h)throw n}}return o}(t,e)||u(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function s(t){return function(t){if(Array.isArray(t))return c(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||u(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function u(t,e){if(t){if("string"==typeof t)return c(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);return"Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r?Array.from(t):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?c(t,e):void 0}}function c(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,i=new Array(e);r<e;r++)i[r]=t[r];return i}var f="undefined"!=typeof window&&window.THREE?window.THREE:{LinearFilter:t.LinearFilter,Sprite:t.Sprite,SpriteMaterial:t.SpriteMaterial,Texture:t.Texture},l=function(t){!function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),Object.defineProperty(t,"prototype",{writable:!1}),e&&n(t,e)}(l,t);var i,o,u,c=a(l);function l(){var t,r=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:10,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"rgba(255, 255, 255, 1)";return e(this,l),(t=c.call(this,new f.SpriteMaterial))._text="".concat(r),t._textHeight=i,t._color=n,t._backgroundColor=!1,t._padding=0,t._borderWidth=0,t._borderRadius=0,t._borderColor="white",t._strokeWidth=0,t._strokeColor="white",t._fontFace="Arial",t._fontSize=90,t._fontWeight="normal",t._canvas=document.createElement("canvas"),t._genCanvas(),t}return i=l,(o=[{key:"text",get:function(){return this._text},set:function(t){this._text=t,this._genCanvas()}},{key:"textHeight",get:function(){return this._textHeight},set:function(t){this._textHeight=t,this._genCanvas()}},{key:"color",get:function(){return this._color},set:function(t){this._color=t,this._genCanvas()}},{key:"backgroundColor",get:function(){return this._backgroundColor},set:function(t){this._backgroundColor=t,this._genCanvas()}},{key:"padding",get:function(){return this._padding},set:function(t){this._padding=t,this._genCanvas()}},{key:"borderWidth",get:function(){return this._borderWidth},set:function(t){this._borderWidth=t,this._genCanvas()}},{key:"borderRadius",get:function(){return this._borderRadius},set:function(t){this._borderRadius=t,this._genCanvas()}},{key:"borderColor",get:function(){return this._borderColor},set:function(t){this._borderColor=t,this._genCanvas()}},{key:"fontFace",get:function(){return this._fontFace},set:function(t){this._fontFace=t,this._genCanvas()}},{key:"fontSize",get:function(){return this._fontSize},set:function(t){this._fontSize=t,this._genCanvas()}},{key:"fontWeight",get:function(){Line truncated
+6
View File
@@ -0,0 +1,6 @@
/**
* @license
* Copyright 2010-2022 Three.js Authors
* SPDX-License-Identifier: MIT
*/
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).THREE={})}(this,(function(t){"use strict";const e="148",i=100,n=300,r=301,s=302,a=303,o=304,l=306,c=1e3,h=1001,u=1002,d=1003,p=1004,m=1005,f=1006,g=1007,v=1008,x=1009,_=1012,y=1014,M=1015,b=1016,S=1020,w=1023,T=1026,A=1027,E=33776,C=33777,L=33778,R=33779,P=35840,I=35841,D=35842,N=35843,O=37492,z=37496,U=37808,B=37809,F=37810,k=37811,G=37812,V=37813,H=37814,W=37815,j=37816,q=37817,X=37818,Y=37819,Z=37820,J=37821,K=36492,$=2300,Q=2301,tt=2302,et=2400,it=2401,nt=2402,rt=2500,st=2501,at=3e3,ot=3001,lt="srgb",ct="srgb-linear",ht=7680,ut=35044,dt="300 es",pt=1035;class mt{addEventListener(t,e){void 0===this._listeners&&(this._listeners={});const i=this._listeners;void 0===i[t]&&(i[t]=[]),-1===i[t].indexOf(e)&&i[t].push(e)}hasEventListener(t,e){if(void 0===this._listeners)return!1;const i=this._listeners;return void 0!==i[t]&&-1!==i[t].indexOf(e)}removeEventListener(t,e){if(void 0===this._listeners)return;const i=this._listeners[t];if(void 0!==i){const t=i.indexOf(e);-1!==t&&i.splice(t,1)}}dispatchEvent(t){if(void 0===this._listeners)return;const e=this._listeners[t.type];if(void 0!==e){t.target=this;const i=e.slice(0);for(let e=0,n=i.length;e<n;e++)i[e].call(this,t);t.target=null}}}const ft=["00","01","02","03","04","05","06","07","08","09","0a","0b","0c","0d","0e","0f","10","11","12","13","14","15","16","17","18","19","1a","1b","1c","1d","1e","1f","20","21","22","23","24","25","26","27","28","29","2a","2b","2c","2d","2e","2f","30","31","32","33","34","35","36","37","38","39","3a","3b","3c","3d","3e","3f","40","41","42","43","44","45","46","47","48","49","4a","4b","4c","4d","4e","4f","50","51","52","53","54","55","56","57","58","59","5a","5b","5c","5d","5e","5f","60","61","62","63","64","65","66","67","68","69","6a","6b","6c","6d","6e","6f","70","71","72","73","74","75","76","77","78","79","7a","7b","7c","7d","7e","7f","80","81","82","83","84","85","86","87","88","89","8a","8b","8c","8d","8e","8f","90","91","92","93","94","95","96","97","98","99","9a","9b","9c","9d","9e","9f","a0","a1","a2","a3","a4","a5","a6","a7","a8","a9","aa","ab","ac","ad","ae","af","b0","b1","b2","b3","b4","b5","b6","b7","b8","b9","ba","bb","bc","bd","be","bf","c0","c1","c2","c3","c4","c5","c6","c7","c8","c9","ca","cb","cc","cd","ce","cf","d0","d1","d2","d3","d4","d5","d6","d7","d8","d9","da","db","dc","dd","de","df","e0","e1","e2","e3","e4","e5","e6","e7","e8","e9","ea","eb","ec","ed","ee","ef","f0","f1","f2","f3","f4","f5","f6","f7","f8","f9","fa","fb","fc","fd","fe","ff"];let gt=1234567;const vt=Math.PI/180,xt=180/Math.PI;function _t(){const t=4294967295*Math.random()|0,e=4294967295*Math.random()|0,i=4294967295*Math.random()|0,n=4294967295*Math.random()|0;return(ft[255&t]+ft[t>>8&255]+ft[t>>16&255]+ft[t>>24&255]+"-"+ft[255&e]+ft[e>>8&255]+"-"+ft[e>>16&15|64]+ft[e>>24&255]+"-"+ft[63&i|128]+ft[i>>8&255]+"-"+ft[i>>16&255]+ft[i>>24&255]+ft[255&n]+ft[n>>8&255]+ft[n>>16&255]+ft[n>>24&255]).toLowerCase()}function yt(t,e,i){return Math.max(e,Math.min(i,t))}function Mt(t,e){return(t%e+e)%e}function bt(t,e,i){return(1-i)*t+i*e}function St(t){return 0==(t&t-1)&&0!==t}function wt(t){return Math.pow(2,Math.ceil(Math.log(t)/Math.LN2))}function Tt(t){return Math.pow(2,Math.floor(Math.log(t)/Math.LN2))}function At(t,e){switch(e.constructor){case Float32Array:return t;case Uint16Array:return t/65535;case Uint8Array:return t/255;case Int16Array:return Math.max(t/32767,-1);case Int8Array:return Math.max(t/127,-1);default:throw new Error("Invalid component type.")}}function Et(t,e){switch(e.constructor){case Float32Array:return t;case Uint16Array:return Math.round(65535*t);case Uint8Array:return Math.round(255*t);case Int16Array:return Math.round(32767*t);case Int8Array:return Math.round(127*t);default:throw new Error("Invalid component type.")}}var Ct=Object.freeze({__proto__:null,DEG2RAD:vt,RAD2DEG:xt,generateUUID:_t,clamp:yt,euclideanModulo:Mt,mapLinear:function(t,e,i,n,r){return n+(t-e)*(r-n)/(i-e)},inverseLerp:function(t,e,i){return t!==e?(i-t)/(e-t):0},lerp:bt,damp:function(t,e,i,n){return bt(t,e,1-Math.exp(-i*n))},pingpong:function(t,e=1){return e-Math.abs(Mt(t,2*e)-e)},smoothstep:function(t,e,i){return t<=e?0:t>=i?1:(t=(t-e)/(i-e))*t*(3-2*t)},smootherstep:function(t,e,i){return t<=e?0:t>=i?1:(t=(t-e)/(i-e))*t*t*(t*(6*t-15)+10)},randInt:function(t,e){return t+Math.floor(Math.random()*(e-t+1))},randFloat:function(t,e){return t+Math.random()*(e-t)},randFloatSpread:function(t){return t*(.5-Math.random())},seededRandom:function(t){void 0!==t&&(gt=t);let e=gt+=1831565813;return e=Math.imul(e^e>>>15,1|e),e^=e+Math.imul(e^e>>>7,61|e),((e^e>>>14)>>>0)/4294967296},degToRad:function(t){return t*vt},radToDeg:function(t){return t*xt},isPowerOfTwo:St,ceilPowerOfTwo:wt,floorPowerOfTwo:Tt,setQuaternionFromProperEuler:function(t,e,i,n,r){const s=Math.cos,a=Math.sin,o=s(i/2)Line truncated
+108
View File
@@ -0,0 +1,108 @@
{% extends "core/base.jinja" %}
{% block title %}
{% trans user_name=user.get_display_name() %}{{ user_name }}'s Galaxy{% endtrans %}
{% endblock %}
{% block content %}
{% if object.galaxy_user %}
<p><a onclick="focus_node(get_node_from_id({{ object.id }}))">Reset on {{ object.get_display_name() }}</a></p>
<p>Self score: {{ object.galaxy_user.mass }}</p>
<table style="width: initial;">
<tr>
<th></th>
<th>Citizen</th>
<th>Score</th>
<th>Distance</th>
<th>Family</th>
<th>Pictures</th>
<th>Clubs</th>
</tr>
{% for lane in lanes %}
<tr>
<td><a onclick="focus_node(get_node_from_id({{ lane.other_star_id }}))">Locate</a></td>
<td><a href="{{ url("galaxy:user", user_id=lane.other_star_id) }}">{{ lane.other_star_name }}</a></td>
<td>{{ lane.other_star_mass }}</td>
<td>{{ lane.distance }}</td>
<td>{{ lane.family }}</td>
<td>{{ lane.pictures }}</td>
<td>{{ lane.clubs }}</td>
</tr>
{% endfor %}
</table>
<div id="3d-graph" style="margin: 1em;"></div>
{% else %}
<p>This citizen has not yet joined the galaxy</p>
{% endif %}
{% endblock %}
{% block script %}
{{ super() }}
<script src="{{ static('galaxy/js/three.min.js') }}" defer></script>
<script src="{{ static('galaxy/js/three-spritetext.min.js') }}" defer></script>
<script src="{{ static('galaxy/js/3d-force-graph.min.js') }}" defer></script>
<script src="{{ static('galaxy/js/d3-force-3d.min.js') }}" defer></script>
<script>
var Graph;
function get_node_from_id(id) {
return Graph.graphData().nodes.find(n => n.id === id);
}
function focus_node(node) {
// Aim at node from outside it
const distance = 200;
const distRatio = 1 + distance/Math.hypot(node.x, node.y, node.z);
const newPos = node.x || node.y || node.z
? { x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio }
: { x: 0, y: 0, z: distance }; // special case if node is in (0,0,0)
Graph.cameraPosition(
newPos, // new position
node, // lookAt ({ x, y, z })
3000 // ms transition duration
);
}
document.addEventListener("DOMContentLoaded", () => {
Graph = ForceGraph3D();
Graph(document.getElementById('3d-graph'));
Graph
.jsonUrl('{{ url("galaxy:data") }}')
.width(1000)
.height(700)
.nodeAutoColorBy('id')
.nodeLabel(node => `${node.name}`)
.onNodeClick(node => focus_node(node))
.linkDirectionalParticles(3)
.linkDirectionalParticleWidth(0.8)
.linkDirectionalParticleSpeed(0.006)
.nodeThreeObject(node => {
const sprite = new SpriteText(node.name);
sprite.material.depthWrite = false; // make sprite background transparent
sprite.color = node.color;
sprite.textHeight = 5;
return sprite;
});
// Set distance between stars
Graph.d3Force('link').distance(link => link.value);
// Set high masses nearer the center of the galaxy
// TODO: quick and dirty strength computation, this will need tuning.
Graph.d3Force('positionX', d3.forceX().strength(node => { return 1 - (1 / node.mass); }));
Graph.d3Force('positionY', d3.forceY().strength(node => { return 1 - (1 / node.mass); }));
Graph.d3Force('positionZ', d3.forceZ().strength(node => { return 1 - (1 / node.mass); }));
// Focus current user
setTimeout(() => focus_node(get_node_from_id({{ object.id }})), 1000);
})
</script>
{% endblock %}
+129
View File
@@ -0,0 +1,129 @@
# -*- coding:utf-8 -*
#
# Copyright 2023
# - Skia <skia@hya.sk>
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# 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
# Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
from django.test import TestCase
from django.core.management import call_command
from core.models import User
from galaxy.models import Galaxy
class GalaxyTest(TestCase):
def setUp(self):
call_command("populate")
self.root = User.objects.get(username="root")
self.skia = User.objects.get(username="skia")
self.sli = User.objects.get(username="sli")
self.krophil = User.objects.get(username="krophil")
self.richard = User.objects.get(username="rbatsbak")
self.subscriber = User.objects.get(username="subscriber")
self.public = User.objects.get(username="public")
self.com = User.objects.get(username="comunity")
def test_user_self_score(self):
with self.assertNumQueries(8):
self.assertEqual(Galaxy.compute_user_score(self.root), 9)
self.assertEqual(Galaxy.compute_user_score(self.skia), 10)
self.assertEqual(Galaxy.compute_user_score(self.sli), 8)
self.assertEqual(Galaxy.compute_user_score(self.krophil), 2)
self.assertEqual(Galaxy.compute_user_score(self.richard), 10)
self.assertEqual(Galaxy.compute_user_score(self.subscriber), 8)
self.assertEqual(Galaxy.compute_user_score(self.public), 8)
self.assertEqual(Galaxy.compute_user_score(self.com), 1)
def test_users_score(self):
expected_scores = {
"krophil": {
"comunity": {"clubs": 0, "family": 0, "pictures": 0, "score": 0},
"public": {"clubs": 0, "family": 0, "pictures": 0, "score": 0},
"rbatsbak": {"clubs": 100, "family": 0, "pictures": 0, "score": 100},
"subscriber": {"clubs": 0, "family": 0, "pictures": 0, "score": 0},
},
"public": {
"comunity": {"clubs": 0, "family": 0, "pictures": 0, "score": 0}
},
"rbatsbak": {
"comunity": {"clubs": 0, "family": 0, "pictures": 0, "score": 0},
"public": {"clubs": 0, "family": 366, "pictures": 0, "score": 366},
"subscriber": {"clubs": 0, "family": 366, "pictures": 0, "score": 366},
},
"root": {
"comunity": {"clubs": 0, "family": 0, "pictures": 0, "score": 0},
"krophil": {"clubs": 0, "family": 0, "pictures": 0, "score": 0},
"public": {"clubs": 0, "family": 0, "pictures": 0, "score": 0},
"rbatsbak": {"clubs": 0, "family": 0, "pictures": 0, "score": 0},
"skia": {"clubs": 0, "family": 732, "pictures": 0, "score": 732},
"sli": {"clubs": 0, "family": 0, "pictures": 0, "score": 0},
"subscriber": {"clubs": 0, "family": 0, "pictures": 0, "score": 0},
},
"skia": {
"comunity": {"clubs": 0, "family": 0, "pictures": 0, "score": 0},
"krophil": {"clubs": 114, "family": 0, "pictures": 2, "score": 116},
"public": {"clubs": 0, "family": 0, "pictures": 0, "score": 0},
"rbatsbak": {"clubs": 100, "family": 0, "pictures": 0, "score": 100},
"sli": {"clubs": 0, "family": 366, "pictures": 4, "score": 370},
"subscriber": {"clubs": 0, "family": 0, "pictures": 0, "score": 0},
},
"sli": {
"comunity": {"clubs": 0, "family": 0, "pictures": 0, "score": 0},
"krophil": {"clubs": 17, "family": 0, "pictures": 2, "score": 19},
"public": {"clubs": 0, "family": 0, "pictures": 0, "score": 0},
"rbatsbak": {"clubs": 0, "family": 0, "pictures": 0, "score": 0},
"subscriber": {"clubs": 0, "family": 0, "pictures": 0, "score": 0},
},
"subscriber": {
"comunity": {"clubs": 0, "family": 0, "pictures": 0, "score": 0},
"public": {"clubs": 0, "family": 0, "pictures": 0, "score": 0},
},
}
computed_scores = {}
users = [
self.root,
self.skia,
self.sli,
self.krophil,
self.richard,
self.subscriber,
self.public,
self.com,
]
with self.assertNumQueries(100):
while len(users) > 0:
user1 = users.pop(0)
for user2 in users:
score, family, pictures, clubs = Galaxy.compute_users_score(
user1, user2
)
u1 = computed_scores.get(user1.username, {})
u1[user2.username] = {
"score": score,
"family": family,
"pictures": pictures,
"clubs": clubs,
}
computed_scores[user1.username] = u1
self.maxDiff = None # Yes, we want to see the diff if any
self.assertDictEqual(expected_scores, computed_scores)
+40
View File
@@ -0,0 +1,40 @@
# -*- coding:utf-8 -*
#
# Copyright 2023
# - Skia <skia@hya.sk>
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# 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
# Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
from django.urls import path
from galaxy.views import *
urlpatterns = [
path(
"<int:user_id>/",
GalaxyUserView.as_view(),
name="user",
),
path(
"data.json",
GalaxyDataView.as_view(),
name="data",
),
]
+97
View File
@@ -0,0 +1,97 @@
# -*- coding:utf-8 -*
#
# Copyright 2023
# - Skia <skia@hya.sk>
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# 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
# Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
from django.views.generic import DetailView, View
from django.http import JsonResponse
from django.db.models import Q, Case, F, When, Value
from django.db.models.functions import Concat
from core.views import (
CanViewMixin,
FormerSubscriberMixin,
)
from core.models import User
from core.views import UserTabsMixin
from galaxy.models import Galaxy, GalaxyLane
class GalaxyUserView(CanViewMixin, UserTabsMixin, DetailView):
model = User
pk_url_kwarg = "user_id"
template_name = "galaxy/user.jinja"
current_tab = "galaxy"
def get_queryset(self):
return super(GalaxyUserView, self).get_queryset().select_related("galaxy_user")
def get_context_data(self, **kwargs):
kwargs = super(GalaxyUserView, self).get_context_data(**kwargs)
kwargs["lanes"] = (
GalaxyLane.objects.filter(
Q(star1=self.object.galaxy_user) | Q(star2=self.object.galaxy_user)
)
.order_by("distance")
.annotate(
other_star_id=Case(
When(star1=self.object.galaxy_user, then=F("star2__owner__id")),
default=F("star1__owner__id"),
),
other_star_mass=Case(
When(star1=self.object.galaxy_user, then=F("star2__mass")),
default=F("star1__mass"),
),
other_star_name=Case(
When(
star1=self.object.galaxy_user,
then=Case(
When(
star2__owner__nick_name=None,
then=Concat(
F("star2__owner__first_name"),
Value(" "),
F("star2__owner__last_name"),
),
)
),
),
default=Case(
When(
star1__owner__nick_name=None,
then=Concat(
F("star1__owner__first_name"),
Value(" "),
F("star1__owner__last_name"),
),
)
),
),
)
.select_related("star1", "star2")
)
return kwargs
class GalaxyDataView(FormerSubscriberMixin, View):
def get(self, request, *args, **kwargs):
return JsonResponse(Galaxy.objects.first().state)
+28
View File
@@ -97,6 +97,7 @@ INSTALLED_APPS = (
"trombi",
"matmat",
"pedagogy",
"galaxy",
)
MIDDLEWARE = (
@@ -210,6 +211,29 @@ DATABASES = {
}
}
# Logging
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"simple": {"format": "%(levelname)s %(message)s"},
},
"handlers": {
"log_to_stdout": {
"level": "DEBUG",
"class": "logging.StreamHandler",
"formatter": "simple",
},
},
"loggers": {
"main": {
"handlers": ["log_to_stdout"],
"level": "INFO",
"propagate": True,
}
},
}
# Internationalization
# https://docs.djangoproject.com/en/1.8/topics/i18n/
@@ -690,4 +714,8 @@ SITH_FRONT_DEP_VERSIONS = {
"https://github.com/getsentry/sentry-javascript/": "4.0.6",
"https://github.com/jhuckaby/webcamjs/": "1.0.0",
"https://github.com/alpinejs/alpine": "3.10.5",
"https://github.com/mrdoob/three.js/": "r148",
"https://github.com/vasturiano/three-spritetext": "1.6.5",
"https://github.com/vasturiano/3d-force-graph/": "1.70.19",
"https://github.com/vasturiano/d3-force-3d": "3.0.3",
}
+1
View File
@@ -76,6 +76,7 @@ urlpatterns = [
path("api/v1/", include(("api.urls", "api"), namespace="api")),
path("election/", include(("election.urls", "election"), namespace="election")),
path("forum/", include(("forum.urls", "forum"), namespace="forum")),
path("galaxy/", include(("galaxy.urls", "galaxy"), namespace="galaxy")),
path("trombi/", include(("trombi.urls", "trombi"), namespace="trombi")),
path("matmatronch/", include(("matmat.urls", "matmat"), namespace="matmat")),
path("pedagogy/", include(("pedagogy.urls", "pedagogy"), namespace="pedagogy")),