Skip to content

Commit

Permalink
Merge pull request #849 from ae-utbm/taiste
Browse files Browse the repository at this point in the history
New 3DSv2 fields and Bugfixes
  • Loading branch information
imperosol authored Sep 30, 2024
2 parents ec434be + c67155f commit 3548dee
Show file tree
Hide file tree
Showing 30 changed files with 884 additions and 370 deletions.
11 changes: 9 additions & 2 deletions core/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import annotated_types
from django.conf import settings
from django.db.models import F
from django.http import HttpResponse
from ninja import Query
from ninja_extra import ControllerBase, api_controller, paginate, route
Expand Down Expand Up @@ -49,10 +50,16 @@ class UserController(ControllerBase):
def fetch_profiles(self, pks: Query[set[int]]):
return User.objects.filter(pk__in=pks)

@route.get("/search", response=PaginatedResponseSchema[UserProfileSchema])
@route.get(
"/search",
response=PaginatedResponseSchema[UserProfileSchema],
url_name="search_users",
)
@paginate(PageNumberPaginationExtra, page_size=20)
def search_users(self, filters: Query[UserFilterSchema]):
return filters.filter(User.objects.all())
return filters.filter(
User.objects.order_by(F("last_login").desc(nulls_last=True))
)


DepthValue = Annotated[int, annotated_types.Ge(0), annotated_types.Le(10)]
Expand Down
20 changes: 20 additions & 0 deletions core/baker_recipes.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from datetime import timedelta

from django.conf import settings
from django.utils.timezone import now
from model_bakery import seq
from model_bakery.recipe import Recipe, related

from club.models import Membership
from core.models import User
from subscription.models import Subscription

Expand All @@ -24,9 +26,27 @@
last_name=seq("user "),
subscriptions=related(active_subscription),
)
"""A user with an active subscription."""

old_subscriber_user = Recipe(
User,
first_name="old subscriber",
last_name=seq("user "),
subscriptions=related(ended_subscription),
)
"""A user with an ended subscription."""

ae_board_membership = Recipe(
Membership,
start_date=now() - timedelta(days=30),
club_id=settings.SITH_MAIN_CLUB_ID,
role=settings.SITH_CLUB_ROLES_ID["Board member"],
)

board_user = Recipe(
User,
first_name="AE",
last_name=seq("member "),
memberships=related(ae_board_membership),
)
"""A user which is in the board of the AE."""
34 changes: 26 additions & 8 deletions core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1057,20 +1057,38 @@ def clean(self):
if self.is_file and (self.file is None or self.file == ""):
raise ValidationError(_("You must provide a file"))

def apply_rights_recursively(self, *, only_folders=False):
children = self.children.all()
if only_folders:
children = children.filter(is_folder=True)
for c in children:
c.copy_rights()
c.apply_rights_recursively(only_folders=only_folders)
def apply_rights_recursively(self, *, only_folders: bool = False) -> None:
"""Apply the rights of this file to all children recursively.
Args:
only_folders: If True, only apply the rights to SithFiles that are folders.
"""
file_ids = []
explored_ids = [self.id]
while len(explored_ids) > 0: # find all children recursively
file_ids.extend(explored_ids)
next_level = SithFile.objects.filter(parent_id__in=explored_ids)
if only_folders:
next_level = next_level.filter(is_folder=True)
explored_ids = list(next_level.values_list("id", flat=True))
for through in (SithFile.view_groups.through, SithFile.edit_groups.through):
# force evaluation. Without this, the iterator yields nothing
groups = list(
through.objects.filter(sithfile_id=self.id).values_list(
"group_id", flat=True
)
)
# delete previous rights
through.objects.filter(sithfile_id__in=file_ids).delete()
through.objects.bulk_create( # create new rights
[through(sithfile_id=f, group_id=g) for f in file_ids for g in groups]
)

def copy_rights(self):
"""Copy, if possible, the rights of the parent folder."""
if self.parent is not None:
self.edit_groups.set(self.parent.edit_groups.all())
self.view_groups.set(self.parent.view_groups.all())
self.save()

def move_to(self, parent):
"""Move a file to a new parent.
Expand Down
1 change: 0 additions & 1 deletion core/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ def filter_search(self, value: str | None) -> Q:
SearchQuerySet()
.models(User)
.autocomplete(auto=slugify(value).replace("-", " "))
.order_by("-last_update")
.values_list("pk", flat=True)
)
)
Expand Down
3 changes: 3 additions & 0 deletions core/search_indexes.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class UserIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
auto = indexes.EdgeNgramField(use_template=True)
last_update = indexes.DateTimeField(model_attr="last_update")
last_login = indexes.DateTimeField(
model_attr="last_login", default="1970-01-01T00:00:00Z"
)

def get_model(self):
return User
Expand Down
5 changes: 3 additions & 2 deletions core/static/core/js/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ function update_query_string(key, value, action = History.REPLACE, url = null) {
return url;
}


// TODO : If one day a test workflow is made for JS in this project
// please test this function. A all cost.
/**
* Given a paginated endpoint, fetch all the items of this endpoint,
* performing multiple API calls if necessary.
Expand All @@ -135,7 +136,7 @@ async function fetch_paginated(url) {
fetch(paginated_url).then(res => res.json().then(json => json.results))
);
}
results.push(...await Promise.all(promises))
results.push(...(await Promise.all(promises)).flat())
}
return results;
}
6 changes: 6 additions & 0 deletions core/static/core/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,12 @@ a:not(.button) {
align-items: center;
text-align: justify;

&.alert-yellow {
background-color: rgb(255, 255, 240);
color: rgb(99, 87, 6);
border: rgb(192, 180, 16) 1px solid;
}

&.alert-green {
background-color: rgb(245, 255, 245);
color: rgb(3, 84, 63);
Expand Down
22 changes: 14 additions & 8 deletions core/static/user/js/family_graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,11 @@ function create_graph(container, data, active_user_id) {
fit: true,
klay: {
addUnnecessaryBendpoints: true,
direction: 'DOWN',
nodePlacement: 'INTERACTIVE',
layoutHierarchy: true
}
}
direction: "DOWN",
nodePlacement: "INTERACTIVE",
layoutHierarchy: true,
},
},
});
let active_user = cy
.getElementById(active_user_id)
Expand Down Expand Up @@ -178,7 +178,9 @@ document.addEventListener("alpine:init", () => {
typeof depth_min === "undefined" ||
typeof depth_max === "undefined"
) {
console.error("Some constants are not set before using the family_graph script, please look at the documentation");
console.error(
"Some constants are not set before using the family_graph script, please look at the documentation",
);
return;
}

Expand All @@ -194,7 +196,7 @@ document.addEventListener("alpine:init", () => {
loading: false,
godfathers_depth: get_initial_depth("godfathers_depth"),
godchildren_depth: get_initial_depth("godchildren_depth"),
reverse: initialUrlParams.get("reverse")?.toLowerCase?.() === 'true',
reverse: initialUrlParams.get("reverse")?.toLowerCase?.() === "true",
graph: undefined,
graph_data: {},

Expand Down Expand Up @@ -227,7 +229,11 @@ document.addEventListener("alpine:init", () => {
async screenshot() {
const link = document.createElement("a");
link.href = this.graph.jpg();
link.download = gettext("family_tree.%(extension)s", "jpg");
link.download = interpolate(
gettext("family_tree.%(extension)s"),
{ extension: "jpg" },
true,
);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
Expand Down
110 changes: 110 additions & 0 deletions core/static/user/js/user_edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
function alpine_webcam_builder(
default_picture,
delete_url,
can_delete_picture,
) {
return () => ({
can_edit_picture: false,

loading: false,
is_camera_enabled: false,
is_camera_error: false,
picture: null,
video: null,
picture_form: null,

init() {
this.video = this.$refs.video;
this.picture_form = this.$refs.form.getElementsByTagName("input");
if (this.picture_form.length > 0) {
this.picture_form = this.picture_form[0];
this.can_edit_picture = true;

// Link the displayed element to the form input
this.picture_form.onchange = (event) => {
let files = event.srcElement.files;
if (files.length > 0) {
this.picture = (window.URL || window.webkitURL).createObjectURL(
event.srcElement.files[0],
);
} else {
this.picture = null;
}
};
}
},

get_picture() {
return this.picture || default_picture;
},

delete_picture() {
// Only remove currently displayed picture
if (!!this.picture) {
let list = new DataTransfer();
this.picture_form.files = list.files;
this.picture_form.dispatchEvent(new Event("change"));
return;
}
if (!can_delete_picture) {
return;
}
// Remove user picture if correct rights are available
window.open(delete_url, "_self");
},

enable_camera() {
this.picture = null;
this.loading = true;
this.is_camera_error = false;
navigator.mediaDevices
.getUserMedia({ video: true, audio: false })
.then((stream) => {
this.loading = false;
this.is_camera_enabled = true;
this.video.srcObject = stream;
this.video.play();
})
.catch((err) => {
this.is_camera_error = true;
this.loading = false;
});
},

take_picture() {
let canvas = document.createElement("canvas");
const context = canvas.getContext("2d");

/* Create the image */
let settings = this.video.srcObject.getTracks()[0].getSettings();
canvas.width = settings.width;
canvas.height = settings.height;
context.drawImage(this.video, 0, 0, canvas.width, canvas.height);

/* Stop camera */
this.video.pause();
this.video.srcObject.getTracks().forEach((track) => {
if (track.readyState === "live") {
track.stop();
}
});

canvas.toBlob((blob) => {
const filename = interpolate(gettext("captured.%s"), ["webp"]);
let file = new File([blob], filename, {
type: "image/webp",
});

let list = new DataTransfer();
list.items.add(file);
this.picture_form.files = list.files;

// No change event is triggered, we trigger it manually #}
this.picture_form.dispatchEvent(new Event("change"));
}, "image/webp");

canvas.remove();
this.is_camera_enabled = false;
},
});
}
Loading

0 comments on commit 3548dee

Please sign in to comment.