mirror of
https://github.com/ae-utbm/sith.git
synced 2024-12-22 15:51:19 +00:00
Render user picture page with ajax to improve performances
This commit is contained in:
parent
57a8215c6b
commit
0eeaf1ce21
@ -17,9 +17,9 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main>
|
||||
{% if user.id == object.id and albums|length > 0 %}
|
||||
<div x-data="picture_download" x-cloak>
|
||||
<main x-data="user_pictures" :aria-busy="loading">
|
||||
{% if user.id == object.id %}
|
||||
<div x-show="pictures.length > 0" x-cloak>
|
||||
<button
|
||||
:disabled="in_progress"
|
||||
class="btn btn-blue"
|
||||
@ -31,44 +31,41 @@
|
||||
<progress x-ref="progress" x-show="in_progress"></progress>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% for album, pictures in albums|items %}
|
||||
<h4>{{ album }}</h4>
|
||||
<br />
|
||||
<div class="photos">
|
||||
{% for picture in pictures %}
|
||||
{% if picture.can_be_viewed_by(user) %}
|
||||
<a href="{{ url("sas:picture", picture_id=picture.id) }}#pict">
|
||||
|
||||
<template x-for="title in [...Object.keys(albums)]">
|
||||
<section>
|
||||
<h4 x-text="title"></h4>
|
||||
<br />
|
||||
<div class="photos">
|
||||
<template x-for="picture in albums[title]">
|
||||
<a :href="`/sas/picture/${picture.id}#pict`">
|
||||
<div
|
||||
class="photo{% if not picture.is_moderated %} not_moderated{% endif %}"
|
||||
style="background-image: url('{% if picture.file %}{{ picture.get_download_thumb_url() }}{% else %}{{ static('core/img/sas.jpg') }}{% endif %}');"
|
||||
class="photo"
|
||||
:class="{not_moderated: !picture.is_moderated}"
|
||||
:style="`background-image: url(${picture.thumb_url})`"
|
||||
>
|
||||
{% if not picture.is_moderated %}
|
||||
<template x-if="!picture.is_moderated">
|
||||
<div class="overlay"> </div>
|
||||
<div class="text">{% trans %}To be moderated{% endtrans %}</div>
|
||||
{% else %}
|
||||
</template>
|
||||
<template x-if="picture.is_moderated">
|
||||
<div class="text"> </div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</a>
|
||||
{% else %}
|
||||
<div>
|
||||
<div class="photo">
|
||||
<div class="text">{% trans %}Picture Unavailable{% endtrans %}</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
<br>
|
||||
{% endfor %}
|
||||
</section>
|
||||
</template>
|
||||
</main>
|
||||
{% endblock content %}
|
||||
|
||||
{% block script %}
|
||||
|
||||
{{ super() }}
|
||||
{% if user.id == object.id %}
|
||||
<script>
|
||||
<script>
|
||||
/**
|
||||
* @typedef Picture
|
||||
* @property {number} id
|
||||
@ -82,62 +79,74 @@
|
||||
* @property {string} album
|
||||
*/
|
||||
|
||||
document.addEventListener("alpine:init", () => {
|
||||
Alpine.data("picture_download", () => ({
|
||||
in_progress: false,
|
||||
document.addEventListener("alpine:init", () => {
|
||||
Alpine.data("user_pictures", () => ({
|
||||
in_progress: false,
|
||||
loading: true,
|
||||
pictures: [],
|
||||
albums: {},
|
||||
|
||||
/**
|
||||
* @return {Promise<Picture[]>}
|
||||
*/
|
||||
async get_pictures() {
|
||||
{# The API forbids to get more than 199 items at once
|
||||
from paginated routes.
|
||||
In order to download all the user pictures, it may be needed
|
||||
to performs multiple requests #}
|
||||
const max_per_page = 199;
|
||||
const url = "{{ url("api:pictures") }}"
|
||||
+ "?users_identified={{ object.id }}"
|
||||
+ `&page_size=${max_per_page}`;
|
||||
let promises = [];
|
||||
const nb_pages = Math.ceil({{ nb_pictures }} / max_per_page);
|
||||
for (let i = 1; i <= nb_pages; i++) {
|
||||
promises.push(
|
||||
fetch(url + `&page=${i}`).then(res => res.json().then(json => json.results))
|
||||
);
|
||||
}
|
||||
return (await Promise.all(promises)).flat()
|
||||
},
|
||||
async init() {
|
||||
this.pictures = await this.get_pictures();
|
||||
this.albums = Object.groupBy(this.pictures, ({album}) => album);
|
||||
this.loading = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {Promise<Picture[]>}
|
||||
*/
|
||||
async get_pictures() {
|
||||
{# The API forbids to get more than 199 items at once
|
||||
from paginated routes.
|
||||
In order to download all the user pictures, it may be needed
|
||||
to performs multiple requests #}
|
||||
const max_per_page = 199;
|
||||
const url = "{{ url("api:pictures") }}"
|
||||
+ "?users_identified={{ object.id }}"
|
||||
+ `&page_size=${max_per_page}`;
|
||||
|
||||
async download_zip(){
|
||||
this.in_progress = true;
|
||||
const bar = this.$refs.progress;
|
||||
bar.value = 0;
|
||||
const pictures = await this.get_pictures();
|
||||
bar.max = pictures.length;
|
||||
let first_page = (await ( await fetch(url)).json());
|
||||
let promises = [first_page.results];
|
||||
|
||||
const fileHandle = await window.showSaveFilePicker({
|
||||
_preferPolyfill: false,
|
||||
suggestedName: "{%- trans -%} pictures {%- endtrans -%}.zip",
|
||||
types: {},
|
||||
excludeAcceptAllOption: false,
|
||||
})
|
||||
const zipWriter = new zip.ZipWriter(await fileHandle.createWritable());
|
||||
const nb_pictures = first_page.count
|
||||
const nb_pages = Math.ceil(nb_pictures / max_per_page);
|
||||
|
||||
await Promise.all(pictures.map(p => {
|
||||
const img_name = p.album + "/IMG_" + p.date.replaceAll(/[:\-]/g, "_") + p.name.slice(p.name.lastIndexOf("."));
|
||||
return zipWriter.add(
|
||||
img_name,
|
||||
new zip.HttpReader(p.full_size_url),
|
||||
{level: 9, lastModDate: new Date(p.date), onstart: () => bar.value += 1}
|
||||
);
|
||||
}));
|
||||
|
||||
await zipWriter.close();
|
||||
this.in_progress = false;
|
||||
for (let i = 2; i <= nb_pages; i++) {
|
||||
promises.push(
|
||||
fetch(url + `&page=${i}`).then(res => res.json().then(json => json.results))
|
||||
);
|
||||
}
|
||||
}))
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
return (await Promise.all(promises)).flat()
|
||||
},
|
||||
|
||||
|
||||
async download_zip(){
|
||||
this.in_progress = true;
|
||||
const bar = this.$refs.progress;
|
||||
bar.value = 0;
|
||||
bar.max = this.pictures.length;
|
||||
|
||||
const fileHandle = await window.showSaveFilePicker({
|
||||
_preferPolyfill: false,
|
||||
suggestedName: "{%- trans -%} pictures {%- endtrans -%}.zip",
|
||||
types: {},
|
||||
excludeAcceptAllOption: false,
|
||||
})
|
||||
const zipWriter = new zip.ZipWriter(await fileHandle.createWritable());
|
||||
|
||||
await Promise.all(this.pictures.map(p => {
|
||||
const img_name = p.album + "/IMG_" + p.date.replaceAll(/[:\-]/g, "_") + p.name.slice(p.name.lastIndexOf("."));
|
||||
return zipWriter.add(
|
||||
img_name,
|
||||
new zip.HttpReader(p.full_size_url),
|
||||
{level: 9, lastModDate: new Date(p.date), onstart: () => bar.value += 1}
|
||||
);
|
||||
}));
|
||||
|
||||
await zipWriter.close();
|
||||
this.in_progress = false;
|
||||
}
|
||||
}))
|
||||
});
|
||||
</script>
|
||||
{% endblock script %}
|
||||
|
@ -21,7 +21,6 @@
|
||||
# Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
#
|
||||
#
|
||||
import itertools
|
||||
import logging
|
||||
|
||||
# This file contains all the views that concern the user model
|
||||
@ -33,7 +32,6 @@ from django.contrib.auth import login, views
|
||||
from django.contrib.auth.forms import PasswordChangeForm
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.core.exceptions import PermissionDenied, ValidationError
|
||||
from django.db.models import F
|
||||
from django.forms import CheckboxSelectMultiple
|
||||
from django.forms.models import modelform_factory
|
||||
from django.http import Http404, HttpResponse
|
||||
@ -70,7 +68,6 @@ from core.views.forms import (
|
||||
UserProfileForm,
|
||||
)
|
||||
from counter.forms import StudentCardForm
|
||||
from sas.models import Picture
|
||||
from subscription.models import Subscription
|
||||
from trombi.views import UserTrombiForm
|
||||
|
||||
@ -312,20 +309,6 @@ class UserPicturesView(UserTabsMixin, CanViewMixin, DetailView):
|
||||
template_name = "core/user_pictures.jinja"
|
||||
current_tab = "pictures"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs = super().get_context_data(**kwargs)
|
||||
pictures = list(
|
||||
Picture.objects.filter(people__user_id=self.object.id)
|
||||
.order_by("-parent__date", "-date")
|
||||
.annotate(album=F("parent__name"))
|
||||
)
|
||||
kwargs["nb_pictures"] = len(pictures)
|
||||
kwargs["albums"] = {
|
||||
album: list(picts)
|
||||
for album, picts in itertools.groupby(pictures, lambda i: i.album)
|
||||
}
|
||||
return kwargs
|
||||
|
||||
|
||||
def delete_user_godfather(request, user_id, godfather_id, is_father):
|
||||
user_is_admin = request.user.is_root or request.user.is_board_member
|
||||
|
@ -64,7 +64,11 @@
|
||||
<div class="photos" :aria-busy="loading">
|
||||
<template x-for="picture in pictures.results">
|
||||
<a :href="`/sas/picture/${picture.id}#pict`">
|
||||
<div class="photo" :style="`background-image: url(${picture.thumb_url})`">
|
||||
<div
|
||||
class="photo"
|
||||
:class="{not_moderated: !picture.is_moderated}"
|
||||
:style="`background-image: url(${picture.thumb_url})`"
|
||||
>
|
||||
<template x-if="!picture.is_moderated">
|
||||
<div class="overlay"> </div>
|
||||
<div class="text">{% trans %}To be moderated{% endtrans %}</div>
|
||||
|
Loading…
Reference in New Issue
Block a user