mirror of
https://github.com/ae-utbm/sith.git
synced 2024-11-22 06:03:20 +00:00
Merge pull request #775 from ae-utbm/user-pictures-ajax
Render user picture page with ajax to improve performances
This commit is contained in:
commit
4036bfd703
@ -17,58 +17,54 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main>
|
<main x-data="user_pictures">
|
||||||
{% if user.id == object.id and albums|length > 0 %}
|
{% if user.id == object.id %}
|
||||||
<div x-data="picture_download" x-cloak>
|
<div x-show="pictures.length > 0" x-cloak>
|
||||||
<button
|
<button
|
||||||
:disabled="in_progress"
|
:disabled="is_downloading"
|
||||||
class="btn btn-blue"
|
class="btn btn-blue"
|
||||||
@click="download_zip()"
|
@click="download_zip()"
|
||||||
>
|
>
|
||||||
<i class="fa fa-download"></i>
|
<i class="fa fa-download"></i>
|
||||||
{% trans %}Download all my pictures{% endtrans %}
|
{% trans %}Download all my pictures{% endtrans %}
|
||||||
</button>
|
</button>
|
||||||
<progress x-ref="progress" x-show="in_progress"></progress>
|
<progress x-ref="progress" x-show="is_downloading"></progress>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for album, pictures in albums|items %}
|
|
||||||
<h4>{{ album }}</h4>
|
<template x-for="[album, pictures] in Object.entries(albums)" x-cloak>
|
||||||
<br />
|
<section>
|
||||||
<div class="photos">
|
<br />
|
||||||
{% for picture in pictures %}
|
<h4 x-text="album"></h4>
|
||||||
{% if picture.can_be_viewed_by(user) %}
|
<div class="photos">
|
||||||
<a href="{{ url("sas:picture", picture_id=picture.id) }}#pict">
|
<template x-for="picture in pictures">
|
||||||
|
<a :href="`/sas/picture/${picture.id}#pict`">
|
||||||
<div
|
<div
|
||||||
class="photo{% if not picture.is_moderated %} not_moderated{% endif %}"
|
class="photo"
|
||||||
style="background-image: url('{% if picture.file %}{{ picture.get_download_thumb_url() }}{% else %}{{ static('core/img/sas.jpg') }}{% endif %}');"
|
: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="overlay"> </div>
|
||||||
<div class="text">{% trans %}To be moderated{% endtrans %}</div>
|
<div class="text">{% trans %}To be moderated{% endtrans %}</div>
|
||||||
{% else %}
|
</template>
|
||||||
|
<template x-if="picture.is_moderated">
|
||||||
<div class="text"> </div>
|
<div class="text"> </div>
|
||||||
{% endif %}
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
</template>
|
||||||
<div>
|
</div>
|
||||||
<div class="photo">
|
</section>
|
||||||
<div class="text">{% trans %}Picture Unavailable{% endtrans %}</div>
|
</template>
|
||||||
</div>
|
<div class="photos" :aria-busy="loading"></div>
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
{% endfor %}
|
|
||||||
</main>
|
</main>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
{% block script %}
|
{% block script %}
|
||||||
|
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
{% if user.id == object.id %}
|
<script>
|
||||||
<script>
|
|
||||||
/**
|
/**
|
||||||
* @typedef Picture
|
* @typedef Picture
|
||||||
* @property {number} id
|
* @property {number} id
|
||||||
@ -82,62 +78,74 @@
|
|||||||
* @property {string} album
|
* @property {string} album
|
||||||
*/
|
*/
|
||||||
|
|
||||||
document.addEventListener("alpine:init", () => {
|
document.addEventListener("alpine:init", () => {
|
||||||
Alpine.data("picture_download", () => ({
|
Alpine.data("user_pictures", () => ({
|
||||||
in_progress: false,
|
is_downloading: false,
|
||||||
|
loading: true,
|
||||||
|
pictures: [],
|
||||||
|
albums: {},
|
||||||
|
|
||||||
/**
|
async init() {
|
||||||
* @return {Promise<Picture[]>}
|
this.pictures = await this.get_pictures();
|
||||||
*/
|
this.albums = Object.groupBy(this.pictures, ({album}) => album);
|
||||||
async get_pictures() {
|
this.loading = false;
|
||||||
{# 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()
|
|
||||||
},
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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(){
|
let first_page = (await ( await fetch(url)).json());
|
||||||
this.in_progress = true;
|
let promises = [first_page.results];
|
||||||
const bar = this.$refs.progress;
|
|
||||||
bar.value = 0;
|
|
||||||
const pictures = await this.get_pictures();
|
|
||||||
bar.max = pictures.length;
|
|
||||||
|
|
||||||
const fileHandle = await window.showSaveFilePicker({
|
const nb_pictures = first_page.count
|
||||||
_preferPolyfill: false,
|
const nb_pages = Math.ceil(nb_pictures / max_per_page);
|
||||||
suggestedName: "{%- trans -%} pictures {%- endtrans -%}.zip",
|
|
||||||
types: {},
|
|
||||||
excludeAcceptAllOption: false,
|
|
||||||
})
|
|
||||||
const zipWriter = new zip.ZipWriter(await fileHandle.createWritable());
|
|
||||||
|
|
||||||
await Promise.all(pictures.map(p => {
|
for (let i = 2; i <= nb_pages; i++) {
|
||||||
const img_name = p.album + "/IMG_" + p.date.replaceAll(/[:\-]/g, "_") + p.name.slice(p.name.lastIndexOf("."));
|
promises.push(
|
||||||
return zipWriter.add(
|
fetch(url + `&page=${i}`).then(res => res.json().then(json => json.results))
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}))
|
return (await Promise.all(promises)).flat()
|
||||||
});
|
},
|
||||||
</script>
|
|
||||||
{% endif %}
|
|
||||||
|
async download_zip(){
|
||||||
|
this.is_downloading = 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.is_downloading = false;
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{% endblock script %}
|
{% endblock script %}
|
||||||
|
@ -21,7 +21,6 @@
|
|||||||
# Place - Suite 330, Boston, MA 02111-1307, USA.
|
# Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
import itertools
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
# This file contains all the views that concern the user model
|
# 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.forms import PasswordChangeForm
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.core.exceptions import PermissionDenied, ValidationError
|
from django.core.exceptions import PermissionDenied, ValidationError
|
||||||
from django.db.models import F
|
|
||||||
from django.forms import CheckboxSelectMultiple
|
from django.forms import CheckboxSelectMultiple
|
||||||
from django.forms.models import modelform_factory
|
from django.forms.models import modelform_factory
|
||||||
from django.http import Http404, HttpResponse
|
from django.http import Http404, HttpResponse
|
||||||
@ -70,7 +68,6 @@ from core.views.forms import (
|
|||||||
UserProfileForm,
|
UserProfileForm,
|
||||||
)
|
)
|
||||||
from counter.forms import StudentCardForm
|
from counter.forms import StudentCardForm
|
||||||
from sas.models import Picture
|
|
||||||
from subscription.models import Subscription
|
from subscription.models import Subscription
|
||||||
from trombi.views import UserTrombiForm
|
from trombi.views import UserTrombiForm
|
||||||
|
|
||||||
@ -312,20 +309,6 @@ class UserPicturesView(UserTabsMixin, CanViewMixin, DetailView):
|
|||||||
template_name = "core/user_pictures.jinja"
|
template_name = "core/user_pictures.jinja"
|
||||||
current_tab = "pictures"
|
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):
|
def delete_user_godfather(request, user_id, godfather_id, is_father):
|
||||||
user_is_admin = request.user.is_root or request.user.is_board_member
|
user_is_admin = request.user.is_root or request.user.is_board_member
|
||||||
|
@ -64,7 +64,11 @@
|
|||||||
<div class="photos" :aria-busy="loading">
|
<div class="photos" :aria-busy="loading">
|
||||||
<template x-for="picture in pictures.results">
|
<template x-for="picture in pictures.results">
|
||||||
<a :href="`/sas/picture/${picture.id}#pict`">
|
<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">
|
<template x-if="!picture.is_moderated">
|
||||||
<div class="overlay"> </div>
|
<div class="overlay"> </div>
|
||||||
<div class="text">{% trans %}To be moderated{% endtrans %}</div>
|
<div class="text">{% trans %}To be moderated{% endtrans %}</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user