Compare commits
13 commits
ae1ad6965f
...
737f073287
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
737f073287 | ||
|
|
87fbcdc013 | ||
|
|
8cda627753 | ||
|
|
69a4a3ca9d | ||
|
|
64f3b0cdbf | ||
|
|
a41dfb6f94 | ||
|
|
320411fd18 | ||
|
|
9cc99a0d13 | ||
|
|
836e0b2cd0 | ||
|
|
116614a620 | ||
|
|
3e1920cb84 | ||
|
|
9a63dea0cb | ||
|
|
981c2c37c0 |
37 changed files with 927 additions and 1051 deletions
39
.env.example
39
.env.example
|
|
@ -14,3 +14,42 @@ ADMINS=admin:photos-admin@lists.crans.org
|
||||||
|
|
||||||
# Email address used as sender for server emails
|
# Email address used as sender for server emails
|
||||||
SERVER_EMAIL=photos@crans.org
|
SERVER_EMAIL=photos@crans.org
|
||||||
|
|
||||||
|
# Email verification: 'mandatory', 'optional', or 'none'
|
||||||
|
EMAIL_VERIFICATION=mandatory
|
||||||
|
|
||||||
|
# Mail server settings
|
||||||
|
SMTP_HOST=localhost
|
||||||
|
SMTP_PORT=25
|
||||||
|
#SMTP_USER=
|
||||||
|
#SMTP_PASSWORD=
|
||||||
|
SMTP_USE_TLS=False
|
||||||
|
|
||||||
|
# OAuth2 settings
|
||||||
|
# Enable OAuth2 login
|
||||||
|
OAUTH_ENABLED=False
|
||||||
|
# Disable normal username/password login (requires OAUTH_ENABLED=True)
|
||||||
|
OAUTH_ONLY=False
|
||||||
|
# OAuth2 server base URL (e.g. auth.example.com)
|
||||||
|
#OAUTH_SERVER_URL=
|
||||||
|
# OAuth2 app credentials
|
||||||
|
#OAUTH_CLIENT_ID=
|
||||||
|
#OAUTH_CLIENT_SECRET=
|
||||||
|
# Button appearance on the login page
|
||||||
|
#OAUTH_BUTTON_TEXT=Login with OAuth
|
||||||
|
#OAUTH_BUTTON_IMAGE=
|
||||||
|
# Space-separated OAuth2 scopes
|
||||||
|
#OAUTH_SCOPE=openid profile email
|
||||||
|
|
||||||
|
# Database engine: 'sqlite' or 'postgres'
|
||||||
|
DB_ENGINE=sqlite
|
||||||
|
|
||||||
|
# PostgreSQL settings (only used when DB_ENGINE=postgres)
|
||||||
|
#DB_NAME=photo21
|
||||||
|
#DB_USER=photo21
|
||||||
|
#DB_PASSWORD=
|
||||||
|
#DB_HOST=localhost
|
||||||
|
#DB_PORT=5432
|
||||||
|
|
||||||
|
# SQLite settings (only used when DB_ENGINE=sqlite)
|
||||||
|
#DB_PATH=/app/data/db.sqlite3
|
||||||
|
|
|
||||||
33
.forgejo/workflows/docker.yml
Normal file
33
.forgejo/workflows/docker.yml
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
name: Docker
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types:
|
||||||
|
- published
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set image tag
|
||||||
|
id: meta
|
||||||
|
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Login to Forgejo registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: git.sinfonie.org
|
||||||
|
username: ${{ secrets.REGISTRY_USER }}
|
||||||
|
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push image
|
||||||
|
run: |
|
||||||
|
docker build \
|
||||||
|
-t git.sinfonie.org/sinfonie/photo26:${{ steps.meta.outputs.TAG }} \
|
||||||
|
-t git.sinfonie.org/sinfonie/photo26:latest \
|
||||||
|
.
|
||||||
|
docker push git.sinfonie.org/sinfonie/photo26:${{ steps.meta.outputs.TAG }}
|
||||||
|
docker push git.sinfonie.org/sinfonie/photo26:latest
|
||||||
|
|
||||||
23
Dockerfile
Normal file
23
Dockerfile
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y gettext && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN SECRET_KEY=dummy python manage.py compilemessages
|
||||||
|
|
||||||
|
# Create volume mount points
|
||||||
|
RUN mkdir -p /app/media /app/static /app/data
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
RUN chmod +x entrypoint.sh
|
||||||
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
130
README.md
130
README.md
|
|
@ -1,64 +1,121 @@
|
||||||
# Photo server 2021-2023
|
# Photo server
|
||||||
|
|
||||||
[](https://www.gnu.org/licenses/gpl-3.0.txt)
|
[](https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
This is the source code for the webserver hosting pictures from the
|
This is the source code for the webserver hosting pictures from the
|
||||||
ENS Paris-Saclay student life.
|
ENS Rennes student life.
|
||||||
|
|
||||||
The philosophy of this project is to keep this code as simple as possible to
|
The philosophy of this project is to keep this code as simple as possible to
|
||||||
run and to maintain.
|
run and to maintain.
|
||||||
|
|
||||||
## Setup
|
This project is a fork of [Photo21](https://gitlab.crans.org/bde/photo21/),
|
||||||
|
originally developed at ENS Paris-Saclay.
|
||||||
|
|
||||||
1. **Dependency installation.**
|
## Docker install (recommended for production)
|
||||||
|
|
||||||
|
1. Create a `docker-compose.yml` (a ready-to-use file is provided in the repository):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: "3.9"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
photo26:
|
||||||
|
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:16
|
||||||
|
container_name: photo26-db
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: photo26
|
||||||
|
POSTGRES_USER: photo26
|
||||||
|
POSTGRES_PASSWORD: change-me
|
||||||
|
volumes:
|
||||||
|
- ./postgres_data:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
- photo26
|
||||||
|
|
||||||
|
photo26:
|
||||||
|
image: git.sinfonie.org/sinfonie/photo26:latest
|
||||||
|
container_name: photo26-app
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
environment:
|
||||||
|
DB_ENGINE: postgres
|
||||||
|
DB_NAME: photo26
|
||||||
|
DB_USER: photo26
|
||||||
|
DB_PASSWORD: change-me
|
||||||
|
DB_HOST: db
|
||||||
|
DB_PORT: 5432
|
||||||
|
SECRET_KEY: change-me
|
||||||
|
EXTRA_HOSTS: photos.example.org
|
||||||
|
volumes:
|
||||||
|
- ./media:/app/media
|
||||||
|
ports:
|
||||||
|
- "8080:8000"
|
||||||
|
networks:
|
||||||
|
- photo26
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Start the stack:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
On first start the container will run migrations and create a default admin account automatically.
|
||||||
|
|
||||||
|
3. **Default credentials** — change these immediately after first login:
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|----------|-----------------|
|
||||||
|
| Username | `admin` |
|
||||||
|
| Password | `admin` |
|
||||||
|
| Email | `admin@localhost` |
|
||||||
|
|
||||||
|
Admin panel: `http://localhost:8080/admin/`
|
||||||
|
|
||||||
|
4. **Passwords to change** in `docker-compose.yml` before going to production:
|
||||||
|
- `POSTGRES_PASSWORD` / `DB_PASSWORD` — database password
|
||||||
|
- `SECRET_KEY` — Django secret key (use a long random string)
|
||||||
|
- Log in to the admin panel and change the `admin` user password
|
||||||
|
|
||||||
|
## Development setup
|
||||||
|
|
||||||
|
1. **Cloning.**
|
||||||
|
Change directory to where you want the project to be.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://codeberg.org/krek0/photo21.git && cd photo21
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Dependency installation.**
|
||||||
If you are not using Debian, please feel free to adapt the following instructions.
|
If you are not using Debian, please feel free to adapt the following instructions.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt install git gettext python3-django python3-django-allauth python3-django-crispy-forms python3-docutils python3-exifread python3-pil
|
sudo apt install git gettext python3-django python3-django-allauth python3-django-crispy-forms python3-docutils python3-exifread python3-pil
|
||||||
|
|
||||||
# Only for production
|
|
||||||
sudo apt install nginx uwsgi uwsgi-plugin-python3 python3-certbot-nginx
|
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Cloning.**
|
3. **Configuration.**
|
||||||
Change directory to where you want the project to be.
|
|
||||||
In production, we usually use `/var/www/photos/` as the `root` user.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://gitlab.crans.org/bde/photo21.git && cd photo21
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Configuration (production only).**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Only for production
|
|
||||||
sudo mkdir static media
|
sudo mkdir static media
|
||||||
sudo cp docs/maintenance.html static/maintenance.html
|
|
||||||
sudo chown www-data:www-data -R static media
|
|
||||||
sudo chmod g+rwx -R static media
|
|
||||||
sudo chmod +x maintenance_tool.sh
|
sudo chmod +x maintenance_tool.sh
|
||||||
sudo cp docs/uwsgi_photos.ini /etc/uwsgi/apps-available/uwsgi_photos.ini
|
|
||||||
sudo ln -s /etc/uwsgi/apps-available/uwsgi_photos.ini /etc/uwsgi/apps-enabled/
|
|
||||||
sudo cp docs/nginx_photos_maintenance /etc/nginx/sites-available/photos.crans.org
|
|
||||||
sudo ln -s /etc/nginx/sites-available/photos.crans.org /etc/nginx/sites-enabled/
|
|
||||||
sudo cp docs/letsencrypt_photos.crans.org /etc/letsencrypt/conf.d/photos.crans.org
|
|
||||||
sudo cp docs/renewal-hooks_post_nginx /etc/letsencrypt/renewal-hooks/post/nginx
|
|
||||||
sudo certbot --config /etc/letsencrypt/conf.d/photos.crans.org.ini certonly
|
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Database (production only).**
|
4. **Database.**
|
||||||
In development, you may use SQLite (no setup).
|
In development, you may use SQLite (no setup).
|
||||||
In production, we use PostgreSQL which require a bit of setup:
|
In production, we use PostgreSQL which require a bit of setup:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt install postgresql postgresql-contrib
|
sudo apt install postgresql postgresql-contrib
|
||||||
sudo -u postgres psql
|
sudo -u postgres psql
|
||||||
postgres=# CREATE USER photo21 WITH PASSWORD 'un_mot_de_passe_sur';
|
postgres=# CREATE USER photo21 WITH PASSWORD 'your_password';
|
||||||
postgres=# CREATE DATABASE photo21 OWNER photo21;
|
postgres=# CREATE DATABASE photo21 OWNER photo21;
|
||||||
```
|
```
|
||||||
|
|
||||||
5. **Initialization.**,
|
5. **Initialization.**
|
||||||
In production, please use `www-data` user.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
./manage.py collectstatic
|
./manage.py collectstatic
|
||||||
|
|
@ -69,19 +126,14 @@ run and to maintain.
|
||||||
# Only when creating a new database
|
# Only when creating a new database
|
||||||
./manage.py loaddata initial
|
./manage.py loaddata initial
|
||||||
./manage.py createsuperuser
|
./manage.py createsuperuser
|
||||||
# change DEBUG to True in photo21/settings.py
|
|
||||||
```
|
```
|
||||||
|
|
||||||
6. **Maintenance Mode.**,
|
6. **Maintenance Mode.**
|
||||||
In production to toggle the server mainteance mode
|
In production to toggle the server maintenance mode
|
||||||
|
|
||||||
```./maintenance_tool.sh```
|
```./maintenance_tool.sh```
|
||||||
|
|
||||||
|
7. *Enjoy \o/*
|
||||||
6. *Enjoy \o/*
|
|
||||||
|
|
||||||
In production, the NGINX site should now work.
|
|
||||||
In development, you can launch the development server using:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
(env)$ ./manage.py runserver
|
(env)$ ./manage.py runserver
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
# This file is part of photo21
|
# This file is part of photo21
|
||||||
# Copyright (C) 2022 Amicale des élèves de l'ENS Paris-Saclay
|
# Copyright (C) 2022 Amicale des élèves de l'ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
default_app_config = "allauth_oauth.apps.AllauthOAuthConfig"
|
||||||
|
|
@ -7,15 +7,15 @@ from allauth.socialaccount.providers.base import ProviderAccount
|
||||||
from allauth.socialaccount.providers.oauth2.provider import OAuth2Provider
|
from allauth.socialaccount.providers.oauth2.provider import OAuth2Provider
|
||||||
|
|
||||||
|
|
||||||
class NoteKfetAccount(ProviderAccount):
|
class OAuthAccount(ProviderAccount):
|
||||||
def to_str(self):
|
def to_str(self):
|
||||||
return self.account.extra_data.get("username")
|
return self.account.extra_data.get("username")
|
||||||
|
|
||||||
|
|
||||||
class NoteKfetProvider(OAuth2Provider):
|
class OAuthProvider(OAuth2Provider):
|
||||||
id = "notekfet"
|
id = "oauth"
|
||||||
name = "Note Kfet"
|
name = "OAuth"
|
||||||
account_class = NoteKfetAccount
|
account_class = OAuthAccount
|
||||||
|
|
||||||
def extract_uid(self, data):
|
def extract_uid(self, data):
|
||||||
return str(data["username"])
|
return str(data["username"])
|
||||||
|
|
@ -39,4 +39,4 @@ class NoteKfetProvider(OAuth2Provider):
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
provider_classes = [NoteKfetProvider]
|
provider_classes = [OAuthProvider]
|
||||||
|
|
@ -4,6 +4,6 @@
|
||||||
|
|
||||||
from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns
|
from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns
|
||||||
|
|
||||||
from .provider import NoteKfetProvider
|
from .provider import OAuthProvider
|
||||||
|
|
||||||
urlpatterns = default_urlpatterns(NoteKfetProvider)
|
urlpatterns = default_urlpatterns(OAuthProvider)
|
||||||
|
|
@ -10,11 +10,11 @@ from allauth.socialaccount.providers.oauth2.views import (
|
||||||
OAuth2LoginView,
|
OAuth2LoginView,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .provider import NoteKfetProvider
|
from .provider import OAuthProvider
|
||||||
|
|
||||||
|
|
||||||
class NoteKfetOAuth2Adapter(OAuth2Adapter):
|
class OAuthAdapter(OAuth2Adapter):
|
||||||
provider_id = NoteKfetProvider.id
|
provider_id = OAuthProvider.id
|
||||||
|
|
||||||
def complete_login(self, request, app, token, **kwargs):
|
def complete_login(self, request, app, token, **kwargs):
|
||||||
headers = {
|
headers = {
|
||||||
|
|
@ -31,7 +31,7 @@ class NoteKfetOAuth2Adapter(OAuth2Adapter):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def domain(self):
|
def domain(self):
|
||||||
return self.settings.get("DOMAIN", "note.crans.org")
|
return self.settings.get("DOMAIN", "")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def access_token_url(self):
|
def access_token_url(self):
|
||||||
|
|
@ -46,5 +46,5 @@ class NoteKfetOAuth2Adapter(OAuth2Adapter):
|
||||||
return f"https://{self.domain}/api/me/"
|
return f"https://{self.domain}/api/me/"
|
||||||
|
|
||||||
|
|
||||||
oauth2_login = OAuth2LoginView.adapter_view(NoteKfetOAuth2Adapter)
|
oauth2_login = OAuth2LoginView.adapter_view(OAuthAdapter)
|
||||||
oauth2_callback = OAuth2CallbackView.adapter_view(NoteKfetOAuth2Adapter)
|
oauth2_callback = OAuth2CallbackView.adapter_view(OAuthAdapter)
|
||||||
40
docker-compose.yml
Normal file
40
docker-compose.yml
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
version: "3.9"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
photo26:
|
||||||
|
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:16
|
||||||
|
container_name: photo26-db
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: photo26
|
||||||
|
POSTGRES_USER: photo26
|
||||||
|
POSTGRES_PASSWORD: change-me
|
||||||
|
volumes:
|
||||||
|
- ./postgres_data:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
- photo26
|
||||||
|
|
||||||
|
photo26:
|
||||||
|
image: git.sinfonie.org/sinfonie/photo26:latest
|
||||||
|
container_name: photo26-app
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
environment:
|
||||||
|
DB_ENGINE: postgres
|
||||||
|
DB_NAME: photo26
|
||||||
|
DB_USER: photo26
|
||||||
|
DB_PASSWORD: change-me
|
||||||
|
DB_HOST: db
|
||||||
|
DB_PORT: 5432
|
||||||
|
SECRET_KEY: change-me
|
||||||
|
EXTRA_HOSTS: photos.example.org
|
||||||
|
volumes:
|
||||||
|
- ./media:/app/media
|
||||||
|
ports:
|
||||||
|
- "8080:8000"
|
||||||
|
networks:
|
||||||
|
- photo26
|
||||||
8
entrypoint.sh
Normal file
8
entrypoint.sh
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
python manage.py collectstatic --noinput
|
||||||
|
python manage.py migrate --noinput
|
||||||
|
python manage.py loaddata initial
|
||||||
|
python manage.py create_default_admin
|
||||||
|
exec gunicorn photo21.wsgi:application --bind 0.0.0.0:8000 --workers 3
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
"height": 180,
|
"height": 180,
|
||||||
"quality": 70,
|
"quality": 70,
|
||||||
"upscale": false,
|
"upscale": false,
|
||||||
"crop": true,
|
"crop": false,
|
||||||
"pre_cache": true,
|
"pre_cache": true,
|
||||||
"increment_count": false
|
"increment_count": false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,7 @@ class CustomSignupForm(SignupForm):
|
||||||
|
|
||||||
# Add description on email field
|
# Add description on email field
|
||||||
self.fields["email"].help_text = _(
|
self.fields["email"].help_text = _(
|
||||||
"Please enter a valid email address ending with `@crans.org` or "
|
"Please enter a valid email address ending with `@ens-rennes.fr`"
|
||||||
"`@ens-paris-saclay.fr`."
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def clean_email(self):
|
def clean_email(self):
|
||||||
|
|
@ -22,10 +21,8 @@ class CustomSignupForm(SignupForm):
|
||||||
Check that the email address ends with a trusted domain.
|
Check that the email address ends with a trusted domain.
|
||||||
"""
|
"""
|
||||||
email = super().clean_email()
|
email = super().clean_email()
|
||||||
if not email.endswith("@crans.org") and not email.endswith(
|
if not email.endswith("@ens-rennes.fr"):
|
||||||
"@ens-paris-saclay.fr"
|
|
||||||
):
|
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(
|
||||||
_("Must end with `@crans.org` or `@ens-paris-saclay.fr`.")
|
_("Must end with `@ens-rennes.fr`.")
|
||||||
)
|
)
|
||||||
return email
|
return email
|
||||||
|
|
|
||||||
|
|
@ -1,321 +0,0 @@
|
||||||
# This file is part of photo21
|
|
||||||
# Copyright (C) 2022 Amicale des élèves de l'ENS Paris-Saclay
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
#
|
|
||||||
#, fuzzy
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2025-12-07 20:03+0100\n"
|
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
||||||
"Language: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
||||||
|
|
||||||
#: .\photo21\forms.py:16
|
|
||||||
msgid ""
|
|
||||||
"Please enter a valid email address ending with `@crans.org` or `@ens-paris-"
|
|
||||||
"saclay.fr`."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\forms.py:29
|
|
||||||
msgid "Must end with `@crans.org` or `@ens-paris-saclay.fr`."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\settings.py:171
|
|
||||||
msgid "German"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\settings.py:172
|
|
||||||
msgid "English"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\settings.py:173
|
|
||||||
msgid "Spanish"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\settings.py:174
|
|
||||||
msgid "French"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\400.html:12
|
|
||||||
msgid "Bad request"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\400.html:16
|
|
||||||
msgid ""
|
|
||||||
"Sorry, your request was bad. Don't know what could be wrong. An email has "
|
|
||||||
"been sent to webmasters with the details of the error. You can now drink a "
|
|
||||||
"coke."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\403.html:12
|
|
||||||
msgid "Permission denied"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\403.html:15
|
|
||||||
msgid "You don't have the right to perform this request."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\403.html:17 .\photo21\templates\404.html:21
|
|
||||||
msgid "Exception message:"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\404.html:12
|
|
||||||
msgid "Page not found"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\404.html:16
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"The requested path <code>%(request_path)s</code> was not found on the server."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\500.html:12
|
|
||||||
msgid "Server error"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\500.html:16
|
|
||||||
msgid ""
|
|
||||||
"Sorry, an error occurred when processing your request. An email has been "
|
|
||||||
"sent to webmasters with the detail of the error, and this will be fixed "
|
|
||||||
"soon. You can go drink a soft."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:8
|
|
||||||
#: .\photo21\templates\account\email.html:16
|
|
||||||
#: .\photo21\templates\socialaccount\connections.html:16
|
|
||||||
msgid "E-mail Addresses"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:11 .\photo21\templates\base.html:63
|
|
||||||
#: .\photo21\templates\socialaccount\connections.html:11
|
|
||||||
msgid "Account"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:19
|
|
||||||
#: .\photo21\templates\socialaccount\connections.html:19
|
|
||||||
msgid "Social connections"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:25
|
|
||||||
msgid "The following e-mail addresses are associated with your account:"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:36
|
|
||||||
msgid "Verified"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:38
|
|
||||||
msgid "Unverified"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:40
|
|
||||||
msgid "Primary"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:46
|
|
||||||
msgid "Make Primary"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:47
|
|
||||||
msgid "Re-send Verification"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:48
|
|
||||||
#: .\photo21\templates\socialaccount\connections.html:47
|
|
||||||
msgid "Remove"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:53
|
|
||||||
msgid "Warning:"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:53
|
|
||||||
msgid ""
|
|
||||||
"You currently do not have any e-mail address set up. You should really add "
|
|
||||||
"an e-mail address so you can receive notifications, reset your password, etc."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:57
|
|
||||||
msgid "Add E-mail Address"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:62
|
|
||||||
msgid "Add E-mail"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\login.html:8
|
|
||||||
#: .\photo21\templates\account\login.html:36
|
|
||||||
msgid "Sign In"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\login.html:19
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"Please sign in with one of your existing third party accounts. Or, <a "
|
|
||||||
"href=\"%(signup_url)s\">sign up</a> for a %(site_name)s account and sign in "
|
|
||||||
"below:"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\login.html:26
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"If you have not created an account yet, then please <a "
|
|
||||||
"href=\"%(signup_url)s\">sign up</a> first."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\login.html:39
|
|
||||||
msgid "Forgot Password?"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\login.html:42
|
|
||||||
msgid "If any problem, please contact the server owners at"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\logout.html:8
|
|
||||||
#: .\photo21\templates\account\logout.html:13
|
|
||||||
#: .\photo21\templates\account\logout.html:22
|
|
||||||
msgid "Sign Out"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\logout.html:16
|
|
||||||
msgid "Are you sure you want to sign out?"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\signup.html:8
|
|
||||||
msgid "Signup"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\signup.html:13
|
|
||||||
#: .\photo21\templates\account\signup.html:24
|
|
||||||
msgid "Sign Up"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\signup.html:16
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"Already have an account? Then please <a href=\"%(login_url)s\">sign in</a>."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:16
|
|
||||||
msgid "The ENS Paris-Saclay pictures server."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:41
|
|
||||||
msgid "Galleries"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:46
|
|
||||||
msgid "Upload"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:51
|
|
||||||
msgid "Manage"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:72
|
|
||||||
msgid "Log out"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:82
|
|
||||||
msgid "Log in"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:91
|
|
||||||
msgid "Sign up"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:116
|
|
||||||
msgid "Connected as"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:118
|
|
||||||
msgid "Source code"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:8
|
|
||||||
msgid "Home"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:11
|
|
||||||
msgid "Welcome to the pictures server!"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:13
|
|
||||||
msgid ""
|
|
||||||
"This website aims to collect the pictures and movies taken in the student "
|
|
||||||
"life of ENS Paris-Saclay or involving its students."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:20
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"The pictures are visible in <a href=\"%(gallery_archive_url)s\">the "
|
|
||||||
"galleries</a> and are downloadable. <b>However, the agreement of the "
|
|
||||||
"photographer and the persons present on the photo is necessary before any "
|
|
||||||
"republication on another platform. </b>"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:29
|
|
||||||
msgid ""
|
|
||||||
"If you want a photo to be deleted, please let us know: <a "
|
|
||||||
"href=\"mailto:photos@crans.org?subject=[ABUS] Nouvelle requête\" class=\"btn "
|
|
||||||
"btn-secondary btn-sm\">Abuse request</a>"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:36
|
|
||||||
msgid ""
|
|
||||||
"If you want to obtain the right to upload pictures, please let us know: <a "
|
|
||||||
"href=\"mailto:photos@crans.org?subject=[Photographe] Demande de droits "
|
|
||||||
"photographe\" class=\"btn btn-secondary btn-sm\">Become a photograph</a>"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:43
|
|
||||||
msgid "Last galleries"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:52
|
|
||||||
msgid "Behind the scene"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:54
|
|
||||||
msgid ""
|
|
||||||
"Because we value your privacy, we do not sell the data on this site, unlike "
|
|
||||||
"many free online platforms. The dedicated server running this website is "
|
|
||||||
"kindly hosted by the <a href=\"https://www.crans.org/\">Crans</a> at the ENS "
|
|
||||||
"Paris-Saclay basement. It is not managed by the Crans. Current active "
|
|
||||||
"administrators are:"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:63
|
|
||||||
msgid "They should be contacted at"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\socialaccount\connections.html:8
|
|
||||||
msgid "Account Connections"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\socialaccount\connections.html:25
|
|
||||||
msgid ""
|
|
||||||
"You can sign in to your account using any of the following third party "
|
|
||||||
"accounts:"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\socialaccount\connections.html:53
|
|
||||||
msgid ""
|
|
||||||
"You currently have no social network accounts connected to this account."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\socialaccount\connections.html:56
|
|
||||||
msgid "Add a 3rd Party Account"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\socialaccount\snippets\provider_list.html:20
|
|
||||||
msgid "Sign in with"
|
|
||||||
msgstr ""
|
|
||||||
|
|
@ -1,320 +0,0 @@
|
||||||
# This file is part of photo21
|
|
||||||
# Copyright (C) 2022 Amicale des élèves de l'ENS Paris-Saclay
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
#
|
|
||||||
#, fuzzy
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2025-12-07 20:03+0100\n"
|
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
||||||
"Language: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
|
|
||||||
#: .\photo21\forms.py:16
|
|
||||||
msgid ""
|
|
||||||
"Please enter a valid email address ending with `@crans.org` or `@ens-paris-"
|
|
||||||
"saclay.fr`."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\forms.py:29
|
|
||||||
msgid "Must end with `@crans.org` or `@ens-paris-saclay.fr`."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\settings.py:171
|
|
||||||
msgid "German"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\settings.py:172
|
|
||||||
msgid "English"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\settings.py:173
|
|
||||||
msgid "Spanish"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\settings.py:174
|
|
||||||
msgid "French"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\400.html:12
|
|
||||||
msgid "Bad request"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\400.html:16
|
|
||||||
msgid ""
|
|
||||||
"Sorry, your request was bad. Don't know what could be wrong. An email has "
|
|
||||||
"been sent to webmasters with the details of the error. You can now drink a "
|
|
||||||
"coke."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\403.html:12
|
|
||||||
msgid "Permission denied"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\403.html:15
|
|
||||||
msgid "You don't have the right to perform this request."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\403.html:17 .\photo21\templates\404.html:21
|
|
||||||
msgid "Exception message:"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\404.html:12
|
|
||||||
msgid "Page not found"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\404.html:16
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"The requested path <code>%(request_path)s</code> was not found on the server."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\500.html:12
|
|
||||||
msgid "Server error"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\500.html:16
|
|
||||||
msgid ""
|
|
||||||
"Sorry, an error occurred when processing your request. An email has been "
|
|
||||||
"sent to webmasters with the detail of the error, and this will be fixed "
|
|
||||||
"soon. You can go drink a soft."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:8
|
|
||||||
#: .\photo21\templates\account\email.html:16
|
|
||||||
#: .\photo21\templates\socialaccount\connections.html:16
|
|
||||||
msgid "E-mail Addresses"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:11 .\photo21\templates\base.html:63
|
|
||||||
#: .\photo21\templates\socialaccount\connections.html:11
|
|
||||||
msgid "Account"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:19
|
|
||||||
#: .\photo21\templates\socialaccount\connections.html:19
|
|
||||||
msgid "Social connections"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:25
|
|
||||||
msgid "The following e-mail addresses are associated with your account:"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:36
|
|
||||||
msgid "Verified"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:38
|
|
||||||
msgid "Unverified"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:40
|
|
||||||
msgid "Primary"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:46
|
|
||||||
msgid "Make Primary"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:47
|
|
||||||
msgid "Re-send Verification"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:48
|
|
||||||
#: .\photo21\templates\socialaccount\connections.html:47
|
|
||||||
msgid "Remove"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:53
|
|
||||||
msgid "Warning:"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:53
|
|
||||||
msgid ""
|
|
||||||
"You currently do not have any e-mail address set up. You should really add "
|
|
||||||
"an e-mail address so you can receive notifications, reset your password, etc."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:57
|
|
||||||
msgid "Add E-mail Address"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:62
|
|
||||||
msgid "Add E-mail"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\login.html:8
|
|
||||||
#: .\photo21\templates\account\login.html:36
|
|
||||||
msgid "Sign In"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\login.html:19
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"Please sign in with one of your existing third party accounts. Or, <a "
|
|
||||||
"href=\"%(signup_url)s\">sign up</a> for a %(site_name)s account and sign in "
|
|
||||||
"below:"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\login.html:26
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"If you have not created an account yet, then please <a "
|
|
||||||
"href=\"%(signup_url)s\">sign up</a> first."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\login.html:39
|
|
||||||
msgid "Forgot Password?"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\login.html:42
|
|
||||||
msgid "If any problem, please contact the server owners at"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\logout.html:8
|
|
||||||
#: .\photo21\templates\account\logout.html:13
|
|
||||||
#: .\photo21\templates\account\logout.html:22
|
|
||||||
msgid "Sign Out"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\logout.html:16
|
|
||||||
msgid "Are you sure you want to sign out?"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\signup.html:8
|
|
||||||
msgid "Signup"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\signup.html:13
|
|
||||||
#: .\photo21\templates\account\signup.html:24
|
|
||||||
msgid "Sign Up"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\account\signup.html:16
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"Already have an account? Then please <a href=\"%(login_url)s\">sign in</a>."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:16
|
|
||||||
msgid "The ENS Paris-Saclay pictures server."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:41
|
|
||||||
msgid "Galleries"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:46
|
|
||||||
msgid "Upload"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:51
|
|
||||||
msgid "Manage"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:72
|
|
||||||
msgid "Log out"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:82
|
|
||||||
msgid "Log in"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:91
|
|
||||||
msgid "Sign up"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:116
|
|
||||||
msgid "Connected as"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:118
|
|
||||||
msgid "Source code"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:8
|
|
||||||
msgid "Home"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:11
|
|
||||||
msgid "Welcome to the pictures server!"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:13
|
|
||||||
msgid ""
|
|
||||||
"This website aims to collect the pictures and movies taken in the student "
|
|
||||||
"life of ENS Paris-Saclay or involving its students."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:20
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"The pictures are visible in <a href=\"%(gallery_archive_url)s\">the "
|
|
||||||
"galleries</a> and are downloadable. <b>However, the agreement of the "
|
|
||||||
"photographer and the persons present on the photo is necessary before any "
|
|
||||||
"republication on another platform. </b>"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:29
|
|
||||||
msgid ""
|
|
||||||
"If you want a photo to be deleted, please let us know: <a "
|
|
||||||
"href=\"mailto:photos@crans.org?subject=[ABUS] Nouvelle requête\" class=\"btn "
|
|
||||||
"btn-secondary btn-sm\">Abuse request</a>"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:36
|
|
||||||
msgid ""
|
|
||||||
"If you want to obtain the right to upload pictures, please let us know: <a "
|
|
||||||
"href=\"mailto:photos@crans.org?subject=[Photographe] Demande de droits "
|
|
||||||
"photographe\" class=\"btn btn-secondary btn-sm\">Become a photograph</a>"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:43
|
|
||||||
msgid "Last galleries"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:52
|
|
||||||
msgid "Behind the scene"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:54
|
|
||||||
msgid ""
|
|
||||||
"Because we value your privacy, we do not sell the data on this site, unlike "
|
|
||||||
"many free online platforms. The dedicated server running this website is "
|
|
||||||
"kindly hosted by the <a href=\"https://www.crans.org/\">Crans</a> at the ENS "
|
|
||||||
"Paris-Saclay basement. It is not managed by the Crans. Current active "
|
|
||||||
"administrators are:"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:63
|
|
||||||
msgid "They should be contacted at"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\socialaccount\connections.html:8
|
|
||||||
msgid "Account Connections"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\socialaccount\connections.html:25
|
|
||||||
msgid ""
|
|
||||||
"You can sign in to your account using any of the following third party "
|
|
||||||
"accounts:"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\socialaccount\connections.html:53
|
|
||||||
msgid ""
|
|
||||||
"You currently have no social network accounts connected to this account."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\socialaccount\connections.html:56
|
|
||||||
msgid "Add a 3rd Party Account"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\templates\socialaccount\snippets\provider_list.html:20
|
|
||||||
msgid "Sign in with"
|
|
||||||
msgstr ""
|
|
||||||
|
|
@ -1,163 +1,158 @@
|
||||||
# This file is part of photo21
|
# This file is part of photo21
|
||||||
# Copyright (C) 2022 Amicale des élèves de l'ENS Paris-Saclay
|
# Copyright (C) 2022 Amicale des élèves de l'ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
|
||||||
#, fuzzy
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: photo21\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-12-07 20:03+0100\n"
|
"Language-Team: French\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"Language: fr\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
||||||
"Language: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
|
||||||
#: .\photo21\forms.py:16
|
#: photo21/forms.py:16
|
||||||
msgid ""
|
msgid "Please enter a valid email address ending with `@ens-rennes.fr`"
|
||||||
"Please enter a valid email address ending with `@crans.org` or `@ens-paris-"
|
msgstr "Veuillez entrer une adresse e-mail valide finissant par `@ens-rennes.fr`."
|
||||||
"saclay.fr`."
|
|
||||||
msgstr ""
|
|
||||||
"Veuillez entrer une adresse email valide finissant par `@crans.org` ou `@ens-"
|
|
||||||
"paris-saclay.fr`."
|
|
||||||
|
|
||||||
#: .\photo21\forms.py:29
|
#: photo21/forms.py:26
|
||||||
msgid "Must end with `@crans.org` or `@ens-paris-saclay.fr`."
|
msgid "Must end with `@ens-rennes.fr`."
|
||||||
msgstr "Doit finir par `@crans.org` ou `@ens-paris-saclay.fr`."
|
msgstr "Doit finir par `@ens-rennes.fr`."
|
||||||
|
|
||||||
#: .\photo21\settings.py:171
|
#: photo21/settings.py:201
|
||||||
msgid "German"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\settings.py:172
|
|
||||||
msgid "English"
|
msgid "English"
|
||||||
msgstr ""
|
msgstr "Anglais"
|
||||||
|
|
||||||
#: .\photo21\settings.py:173
|
#: photo21/settings.py:202
|
||||||
msgid "Spanish"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photo21\settings.py:174
|
|
||||||
msgid "French"
|
msgid "French"
|
||||||
msgstr ""
|
msgstr "Français"
|
||||||
|
|
||||||
#: .\photo21\templates\400.html:12
|
#: photo21/templates/400.html:12
|
||||||
msgid "Bad request"
|
msgid "Bad request"
|
||||||
msgstr ""
|
msgstr "Requête incorrecte"
|
||||||
|
|
||||||
#: .\photo21\templates\400.html:16
|
#: photo21/templates/400.html:16
|
||||||
msgid ""
|
msgid ""
|
||||||
"Sorry, your request was bad. Don't know what could be wrong. An email has "
|
"Sorry, your request was bad. Don't know what could be wrong. An email has "
|
||||||
"been sent to webmasters with the details of the error. You can now drink a "
|
"been sent to webmasters with the details of the error. You can now drink a "
|
||||||
"coke."
|
"coke."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Désolé, votre requête était incorrecte. Un e-mail a été envoyé aux "
|
||||||
|
"administrateurs avec les détails de l'erreur. Vous pouvez aller boire un "
|
||||||
|
"coca."
|
||||||
|
|
||||||
#: .\photo21\templates\403.html:12
|
#: photo21/templates/403.html:12
|
||||||
msgid "Permission denied"
|
msgid "Permission denied"
|
||||||
msgstr ""
|
msgstr "Permission refusée"
|
||||||
|
|
||||||
#: .\photo21\templates\403.html:15
|
#: photo21/templates/403.html:15
|
||||||
msgid "You don't have the right to perform this request."
|
msgid "You don't have the right to perform this request."
|
||||||
msgstr ""
|
msgstr "Vous n'avez pas le droit d'effectuer cette requête."
|
||||||
|
|
||||||
#: .\photo21\templates\403.html:17 .\photo21\templates\404.html:21
|
#: photo21/templates/403.html:17 photo21/templates/404.html:21
|
||||||
msgid "Exception message:"
|
msgid "Exception message:"
|
||||||
msgstr ""
|
msgstr "Message d'exception :"
|
||||||
|
|
||||||
#: .\photo21\templates\404.html:12
|
#: photo21/templates/404.html:12
|
||||||
msgid "Page not found"
|
msgid "Page not found"
|
||||||
msgstr ""
|
msgstr "Page introuvable"
|
||||||
|
|
||||||
#: .\photo21\templates\404.html:16
|
#: photo21/templates/404.html:16
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"The requested path <code>%(request_path)s</code> was not found on the server."
|
"The requested path <code>%(request_path)s</code> was not found on the server."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Le chemin demandé <code>%(request_path)s</code> n'a pas été trouvé sur le "
|
||||||
|
"serveur."
|
||||||
|
|
||||||
#: .\photo21\templates\500.html:12
|
#: photo21/templates/500.html:12
|
||||||
msgid "Server error"
|
msgid "Server error"
|
||||||
msgstr ""
|
msgstr "Erreur serveur"
|
||||||
|
|
||||||
#: .\photo21\templates\500.html:16
|
#: photo21/templates/500.html:16
|
||||||
msgid ""
|
msgid ""
|
||||||
"Sorry, an error occurred when processing your request. An email has been "
|
"Sorry, an error occurred when processing your request. An email has been "
|
||||||
"sent to webmasters with the detail of the error, and this will be fixed "
|
"sent to webmasters with the detail of the error, and this will be fixed "
|
||||||
"soon. You can go drink a soft."
|
"soon. You can go drink a soft."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Désolé, une erreur s'est produite lors du traitement de votre requête. Un e-"
|
||||||
|
"mail a été envoyé aux administrateurs avec les détails de l'erreur, et cela "
|
||||||
|
"sera corrigé prochainement. Vous pouvez aller boire un soda."
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:8
|
#: photo21/templates/account/email.html:8
|
||||||
#: .\photo21\templates\account\email.html:16
|
#: photo21/templates/account/email.html:16
|
||||||
#: .\photo21\templates\socialaccount\connections.html:16
|
#: photo21/templates/socialaccount/connections.html:16
|
||||||
msgid "E-mail Addresses"
|
msgid "E-mail Addresses"
|
||||||
msgstr ""
|
msgstr "Adresses e-mail"
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:11 .\photo21\templates\base.html:63
|
#: photo21/templates/account/email.html:11 photo21/templates/base.html:66
|
||||||
#: .\photo21\templates\socialaccount\connections.html:11
|
#: photo21/templates/socialaccount/connections.html:11
|
||||||
msgid "Account"
|
msgid "Account"
|
||||||
msgstr "Compte"
|
msgstr "Compte"
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:19
|
#: photo21/templates/account/email.html:19
|
||||||
#: .\photo21\templates\socialaccount\connections.html:19
|
#: photo21/templates/socialaccount/connections.html:19
|
||||||
msgid "Social connections"
|
msgid "Social connections"
|
||||||
msgstr "Connexions sociales"
|
msgstr "Connexions sociales"
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:25
|
#: photo21/templates/account/email.html:25
|
||||||
msgid "The following e-mail addresses are associated with your account:"
|
msgid "The following e-mail addresses are associated with your account:"
|
||||||
msgstr ""
|
msgstr "Les adresses e-mail suivantes sont associées à votre compte :"
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:36
|
#: photo21/templates/account/email.html:36
|
||||||
msgid "Verified"
|
msgid "Verified"
|
||||||
msgstr ""
|
msgstr "Vérifié"
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:38
|
#: photo21/templates/account/email.html:38
|
||||||
msgid "Unverified"
|
msgid "Unverified"
|
||||||
msgstr ""
|
msgstr "Non vérifié"
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:40
|
#: photo21/templates/account/email.html:40
|
||||||
msgid "Primary"
|
msgid "Primary"
|
||||||
msgstr ""
|
msgstr "Principal"
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:46
|
#: photo21/templates/account/email.html:46
|
||||||
msgid "Make Primary"
|
msgid "Make Primary"
|
||||||
msgstr ""
|
msgstr "Définir comme principal"
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:47
|
#: photo21/templates/account/email.html:47
|
||||||
msgid "Re-send Verification"
|
msgid "Re-send Verification"
|
||||||
msgstr ""
|
msgstr "Renvoyer la vérification"
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:48
|
#: photo21/templates/account/email.html:48
|
||||||
#: .\photo21\templates\socialaccount\connections.html:47
|
#: photo21/templates/socialaccount/connections.html:47
|
||||||
msgid "Remove"
|
msgid "Remove"
|
||||||
msgstr ""
|
msgstr "Supprimer"
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:53
|
#: photo21/templates/account/email.html:53
|
||||||
msgid "Warning:"
|
msgid "Warning:"
|
||||||
msgstr ""
|
msgstr "Avertissement :"
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:53
|
#: photo21/templates/account/email.html:53
|
||||||
msgid ""
|
msgid ""
|
||||||
"You currently do not have any e-mail address set up. You should really add "
|
"You currently do not have any e-mail address set up. You should really add "
|
||||||
"an e-mail address so you can receive notifications, reset your password, etc."
|
"an e-mail address so you can receive notifications, reset your password, etc."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Vous n'avez actuellement aucune adresse e-mail configurée. Vous devriez "
|
||||||
|
"vraiment en ajouter une pour recevoir des notifications, réinitialiser votre "
|
||||||
|
"mot de passe, etc."
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:57
|
#: photo21/templates/account/email.html:57
|
||||||
msgid "Add E-mail Address"
|
msgid "Add E-mail Address"
|
||||||
msgstr "Ajouter une Adresse E-mail"
|
msgstr "Ajouter une Adresse E-mail"
|
||||||
|
|
||||||
#: .\photo21\templates\account\email.html:62
|
#: photo21/templates/account/email.html:62
|
||||||
msgid "Add E-mail"
|
msgid "Add E-mail"
|
||||||
msgstr "Ajouter un E-mail"
|
msgstr "Ajouter un E-mail"
|
||||||
|
|
||||||
#: .\photo21\templates\account\login.html:8
|
#: photo21/templates/account/login.html:8
|
||||||
#: .\photo21\templates\account\login.html:36
|
#: photo21/templates/account/login.html:36
|
||||||
msgid "Sign In"
|
msgid "Sign In"
|
||||||
msgstr "Se Connecter"
|
msgstr "Se Connecter"
|
||||||
|
|
||||||
#: .\photo21\templates\account\login.html:19
|
#: photo21/templates/account/login.html:19
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Please sign in with one of your existing third party accounts. Or, <a "
|
"Please sign in with one of your existing third party accounts. Or, <a "
|
||||||
|
|
@ -168,7 +163,7 @@ msgstr ""
|
||||||
"href=\"%(signup_url)s\">inscrivez-vous</a> pour un compte sur %(site_name)s "
|
"href=\"%(signup_url)s\">inscrivez-vous</a> pour un compte sur %(site_name)s "
|
||||||
"et identifiez-vous ci-dessous :"
|
"et identifiez-vous ci-dessous :"
|
||||||
|
|
||||||
#: .\photo21\templates\account\login.html:26
|
#: photo21/templates/account/login.html:26
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"If you have not created an account yet, then please <a "
|
"If you have not created an account yet, then please <a "
|
||||||
|
|
@ -177,92 +172,94 @@ msgstr ""
|
||||||
"Si vous n'avez pas déjà créé de compte, veuillez vous <a "
|
"Si vous n'avez pas déjà créé de compte, veuillez vous <a "
|
||||||
"href=\"%(signup_url)s\">inscrire</a>."
|
"href=\"%(signup_url)s\">inscrire</a>."
|
||||||
|
|
||||||
#: .\photo21\templates\account\login.html:39
|
#: photo21/templates/account/login.html:39
|
||||||
msgid "Forgot Password?"
|
msgid "Forgot Password?"
|
||||||
msgstr "Mot de passe oublié ?"
|
msgstr "Mot de passe oublié ?"
|
||||||
|
|
||||||
#: .\photo21\templates\account\login.html:42
|
#: photo21/templates/account/login.html:42
|
||||||
msgid "If any problem, please contact the server owners at"
|
msgid "If any problem, please contact the server owners at"
|
||||||
msgstr "En cas de problème, contactez les administrateurs à"
|
msgstr "En cas de problème, contactez les administrateurs à"
|
||||||
|
|
||||||
#: .\photo21\templates\account\logout.html:8
|
#: photo21/templates/account/logout.html:8
|
||||||
#: .\photo21\templates\account\logout.html:13
|
#: photo21/templates/account/logout.html:13
|
||||||
#: .\photo21\templates\account\logout.html:22
|
#: photo21/templates/account/logout.html:22
|
||||||
msgid "Sign Out"
|
msgid "Sign Out"
|
||||||
msgstr "Déconnexion"
|
msgstr "Déconnexion"
|
||||||
|
|
||||||
#: .\photo21\templates\account\logout.html:16
|
#: photo21/templates/account/logout.html:16
|
||||||
msgid "Are you sure you want to sign out?"
|
msgid "Are you sure you want to sign out?"
|
||||||
msgstr ""
|
msgstr "Êtes-vous sûr de vouloir vous déconnecter ?"
|
||||||
|
|
||||||
#: .\photo21\templates\account\signup.html:8
|
#: photo21/templates/account/signup.html:8
|
||||||
msgid "Signup"
|
msgid "Signup"
|
||||||
msgstr ""
|
msgstr "Inscription"
|
||||||
|
|
||||||
#: .\photo21\templates\account\signup.html:13
|
#: photo21/templates/account/signup.html:13
|
||||||
#: .\photo21\templates\account\signup.html:24
|
#: photo21/templates/account/signup.html:24
|
||||||
msgid "Sign Up"
|
msgid "Sign Up"
|
||||||
msgstr ""
|
msgstr "S'inscrire"
|
||||||
|
|
||||||
#: .\photo21\templates\account\signup.html:16
|
#: photo21/templates/account/signup.html:16
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Already have an account? Then please <a href=\"%(login_url)s\">sign in</a>."
|
"Already have an account? Then please <a href=\"%(login_url)s\">sign in</a>."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Vous avez déjà un compte ? Veuillez alors <a href=\"%(login_url)s\">vous "
|
||||||
|
"connecter</a>."
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:16
|
#: photo21/templates/base.html:16
|
||||||
msgid "The ENS Paris-Saclay pictures server."
|
msgid "The ENS Rennes pictures server."
|
||||||
msgstr ""
|
msgstr "Le serveur photos de l'ENS Rennes."
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:41
|
#: photo21/templates/base.html:43
|
||||||
msgid "Galleries"
|
msgid "Galleries"
|
||||||
msgstr "Galeries"
|
msgstr "Galeries"
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:46
|
#: photo21/templates/base.html:48
|
||||||
msgid "Upload"
|
msgid "Upload"
|
||||||
msgstr "Téléversement"
|
msgstr "Upload"
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:51
|
#: photo21/templates/base.html:53
|
||||||
msgid "Manage"
|
msgid "Manage"
|
||||||
msgstr "Gestion"
|
msgstr "Manage"
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:72
|
#: photo21/templates/base.html:75
|
||||||
msgid "Log out"
|
msgid "Log out"
|
||||||
msgstr ""
|
msgstr "Déconnection"
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:82
|
#: photo21/templates/base.html:85
|
||||||
msgid "Log in"
|
msgid "Log in"
|
||||||
msgstr ""
|
msgstr "Connection"
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:91
|
#: photo21/templates/base.html:94
|
||||||
msgid "Sign up"
|
msgid "Sign up"
|
||||||
msgstr "Inscription"
|
msgstr "Inscription"
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:116
|
#: photo21/templates/base.html:119
|
||||||
msgid "Connected as"
|
msgid "Connected as"
|
||||||
msgstr "Connecté en tant que"
|
msgstr "Connecté en tant que"
|
||||||
|
|
||||||
#: .\photo21\templates\base.html:118
|
#: photo21/templates/base.html:121
|
||||||
msgid "Source code"
|
msgid "Source code"
|
||||||
msgstr "Code source"
|
msgstr "Code source"
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:8
|
#: photo21/templates/index.html:8
|
||||||
msgid "Home"
|
msgid "Home"
|
||||||
msgstr "Accueil"
|
msgstr "Accueil"
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:11
|
#: photo21/templates/index.html:11
|
||||||
msgid "Welcome to the pictures server!"
|
msgid "Welcome to the pictures server!"
|
||||||
msgstr "Bienvenue sur le serveur photos !"
|
msgstr "Bienvenue sur le serveur photos !"
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:13
|
#: photo21/templates/index.html:13
|
||||||
msgid ""
|
msgid ""
|
||||||
"This website aims to collect the pictures and movies taken in the student "
|
"This website aims to collect the pictures and movies taken in the student "
|
||||||
"life of ENS Paris-Saclay or involving its students."
|
"life of ENS Rennes or involving its students."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Ce site à pour objectif de recenser les photos et films pris dans la vie "
|
"Ce site a pour objectif de recenser les photos et films pris dans la vie "
|
||||||
"associative de l'ENS Paris-Saclay ou impliquant ses usager·ères."
|
"associative de l'ENS Rennes ou impliquant ses étudiant·es."
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:20
|
#: photo21/templates/index.html:20
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"The pictures are visible in <a href=\"%(gallery_archive_url)s\">the "
|
"The pictures are visible in <a href=\"%(gallery_archive_url)s\">the "
|
||||||
|
|
@ -275,73 +272,77 @@ msgstr ""
|
||||||
"photographe et des personnes présentes sur la photo est nécessaire avant "
|
"photographe et des personnes présentes sur la photo est nécessaire avant "
|
||||||
"toute republication sur un autre site.</b>"
|
"toute republication sur un autre site.</b>"
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:29
|
#: photo21/templates/index.html:30
|
||||||
msgid ""
|
|
||||||
"If you want a photo to be deleted, please let us know: <a "
|
|
||||||
"href=\"mailto:photos@crans.org?subject=[ABUS] Nouvelle requête\" class=\"btn "
|
|
||||||
"btn-secondary btn-sm\">Abuse request</a>"
|
|
||||||
msgstr ""
|
|
||||||
"Si vous souhaitez qu'une photo soit supprimée, signalez le nous : <a "
|
|
||||||
"href=\"mailto:photos@crans.org?subject=[ABUS] Nouvelle requête\" class=\"btn "
|
|
||||||
"btn-secondary btn-sm\">Signaler un abus</a>"
|
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:36
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"If you want to obtain the right to upload pictures, please let us know: <a "
|
"If you want to obtain the right to upload pictures, please let us know: <a "
|
||||||
"href=\"mailto:photos@crans.org?subject=[Photographe] Demande de droits "
|
"href=\"mailto:sinfonie@ens-rennes.fr?subject=[Photographe] Demande de droits "
|
||||||
"photographe\" class=\"btn btn-secondary btn-sm\">Become a photograph</a>"
|
"photographe\" class=\"btn btn-secondary btn-sm\">Become a photograph</a>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Si vous souhaitez obtenir les droits photographes pour téléverser vos "
|
"Si vous souhaitez obtenir les droits photographes pour téléverser vos "
|
||||||
"photos, signalez le nous : <a href=\"mailto:photos@crans.org?"
|
"photos, contactez-nous : <a href=\"mailto:sinfonie@ens-rennes.fr?"
|
||||||
"subject=[Photographe] Demande de droits photographe\" class=\"btn btn-"
|
"subject=[Photographe] Demande de droits photographe\" class=\"btn btn-"
|
||||||
"secondary btn-sm\">Devenir photographe</a>"
|
"secondary btn-sm\">Devenir photographe</a>"
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:43
|
#: photo21/templates/index.html:38
|
||||||
msgid "Last galleries"
|
msgid "Last galleries"
|
||||||
msgstr "Galeries récentes"
|
msgstr "Galeries récentes"
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:52
|
#: photo21/templates/index.html:47
|
||||||
msgid "Behind the scene"
|
msgid "Behind the scene"
|
||||||
msgstr "Derrière la scène"
|
msgstr "Behind the scene"
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:54
|
#: photo21/templates/index.html:49
|
||||||
msgid ""
|
msgid ""
|
||||||
"Because we value your privacy, we do not sell the data on this site, unlike "
|
"<!-- This project if a fork of <a href=\"https://gitlab.crans.org/bde/"
|
||||||
"many free online platforms. The dedicated server running this website is "
|
"photo21/\">Photo21</a>. --> <!-- Because we value your privacy, we do not "
|
||||||
"kindly hosted by the <a href=\"https://www.crans.org/\">Crans</a> at the ENS "
|
"sell the data on this site, --> <!-- unlike many free online platforms. --> "
|
||||||
"Paris-Saclay basement. It is not managed by the Crans. Current active "
|
"The dedicated server running this website is kindly hosted by <a "
|
||||||
"administrators are:"
|
"href=\"https://sinfonie.org/\">Sinfonie</a> at the ENS Rennes. <!-- Current "
|
||||||
|
"active administrators are: --> <!--"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Comme nous accordons une grande importance au respect de la vie privée, nous "
|
"<!-- Ce projet est un fork de <a href=\"https://gitlab.crans.org/bde/"
|
||||||
"ne vendons pas les données de ce site, contrairement à de nombreuses "
|
"photo21/\">Photo21</a>. --> <!-- Comme nous accordons une grande importance "
|
||||||
"plateformes en ligne gratuites. Le serveur dédié qui fait fonctionner ce "
|
"au respect de la vie privée, nous ne vendons pas les données de ce site, --> "
|
||||||
"site est gentiment hébergé par le <a href=\"https://www.crans.org/\">Crans</"
|
"<!-- contrairement à de nombreuses plateformes en ligne gratuites. --> "
|
||||||
"a> au sous-sol de l'ENS Paris-Saclay. <b>Il n'est pas géré par le Crans.</b> "
|
"Le serveur qui fait fonctionner ce site est gentiment hébergé par "
|
||||||
"Les administrateur·trices sont :"
|
"<a href=\"https://sinfonie.org/\">Sinfonie</a> à l'ENS Rennes. "
|
||||||
|
"<!-- Les administrateur·trices actuel·les sont : --> <!--"
|
||||||
|
|
||||||
#: .\photo21\templates\index.html:63
|
#: photo21/templates/index.html:58
|
||||||
msgid "They should be contacted at"
|
msgid "They should be contacted at"
|
||||||
msgstr "Ils peuvent être contactés à"
|
msgstr "Ils peuvent être contactés à"
|
||||||
|
|
||||||
#: .\photo21\templates\socialaccount\connections.html:8
|
#: photo21/templates/socialaccount/connections.html:8
|
||||||
msgid "Account Connections"
|
msgid "Account Connections"
|
||||||
msgstr ""
|
msgstr "Connexions de compte"
|
||||||
|
|
||||||
#: .\photo21\templates\socialaccount\connections.html:25
|
#: photo21/templates/socialaccount/connections.html:25
|
||||||
msgid ""
|
msgid ""
|
||||||
"You can sign in to your account using any of the following third party "
|
"You can sign in to your account using any of the following third party "
|
||||||
"accounts:"
|
"accounts:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Vous pouvez vous connecter à votre compte en utilisant l'un des comptes "
|
||||||
|
"tiers suivants :"
|
||||||
|
|
||||||
#: .\photo21\templates\socialaccount\connections.html:53
|
#: photo21/templates/socialaccount/connections.html:53
|
||||||
msgid ""
|
msgid ""
|
||||||
"You currently have no social network accounts connected to this account."
|
"You currently have no social network accounts connected to this account."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Vous n'avez actuellement aucun compte de réseau social connecté à ce compte."
|
||||||
|
|
||||||
#: .\photo21\templates\socialaccount\connections.html:56
|
#: photo21/templates/socialaccount/connections.html:56
|
||||||
msgid "Add a 3rd Party Account"
|
msgid "Add a 3rd Party Account"
|
||||||
msgstr ""
|
msgstr "Ajouter un compte tiers"
|
||||||
|
|
||||||
#: .\photo21\templates\socialaccount\snippets\provider_list.html:20
|
#: photo21/templates/socialaccount/snippets/provider_list.html:20
|
||||||
msgid "Sign in with"
|
msgid "Sign in with"
|
||||||
msgstr "Se Connecter avec"
|
msgstr "Se Connecter avec"
|
||||||
|
|
||||||
|
#~ msgid ""
|
||||||
|
#~ "If you want a photo to be deleted, please let us know: <a "
|
||||||
|
#~ "href=\"mailto:photos@crans.org?subject=[ABUS] Nouvelle requête\" "
|
||||||
|
#~ "class=\"btn btn-secondary btn-sm\">Abuse request</a>"
|
||||||
|
#~ msgstr ""
|
||||||
|
#~ "Si vous souhaitez qu'une photo soit supprimée, signalez le nous : <a "
|
||||||
|
#~ "href=\"mailto:photos@crans.org?subject=[ABUS] Nouvelle requête\" "
|
||||||
|
#~ "class=\"btn btn-secondary btn-sm\">Signaler un abus</a>"
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,9 @@ ADMINS = [tuple(a.split(":")) for a in config("ADMINS", default="", cast=Csv())
|
||||||
SESSION_COOKIE_SECURE = not DEBUG
|
SESSION_COOKIE_SECURE = not DEBUG
|
||||||
CSRF_COOKIE_SECURE = not DEBUG
|
CSRF_COOKIE_SECURE = not DEBUG
|
||||||
|
|
||||||
|
# Trust Caddy's forwarded proto header
|
||||||
|
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||||
|
|
||||||
# Remember HTTPS for 1 year
|
# Remember HTTPS for 1 year
|
||||||
SECURE_HSTS_SECONDS = 31536000
|
SECURE_HSTS_SECONDS = 31536000
|
||||||
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
|
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
|
||||||
|
|
@ -53,6 +56,15 @@ SECURE_HSTS_PRELOAD = True
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
|
OAUTH_ENABLED = config("OAUTH_ENABLED", default=False, cast=bool)
|
||||||
|
OAUTH_ONLY = config("OAUTH_ONLY", default=False, cast=bool)
|
||||||
|
OAUTH_CLIENT_ID = config("OAUTH_CLIENT_ID", default="")
|
||||||
|
OAUTH_CLIENT_SECRET = config("OAUTH_CLIENT_SECRET", default="")
|
||||||
|
OAUTH_SERVER_URL = config("OAUTH_SERVER_URL", default="")
|
||||||
|
OAUTH_BUTTON_TEXT = config("OAUTH_BUTTON_TEXT", default="Login with OAuth")
|
||||||
|
OAUTH_BUTTON_IMAGE = config("OAUTH_BUTTON_IMAGE", default="")
|
||||||
|
OAUTH_SCOPE = config("OAUTH_SCOPE", default="openid profile email", cast=Csv(delimiter=" "))
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
"django.contrib.admin",
|
"django.contrib.admin",
|
||||||
"django.contrib.admindocs",
|
"django.contrib.admindocs",
|
||||||
|
|
@ -66,17 +78,20 @@ INSTALLED_APPS = [
|
||||||
"allauth",
|
"allauth",
|
||||||
"allauth.account",
|
"allauth.account",
|
||||||
"allauth.socialaccount",
|
"allauth.socialaccount",
|
||||||
"allauth_note_kfet",
|
|
||||||
"crispy_forms",
|
"crispy_forms",
|
||||||
"photologue",
|
"photologue",
|
||||||
"photo21",
|
"photo21",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if OAUTH_ENABLED:
|
||||||
|
INSTALLED_APPS += ["allauth_oauth"]
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
INSTALLED_APPS += ["debug_toolbar",] # For debug and optimisations
|
INSTALLED_APPS += ["debug_toolbar",] # For debug and optimisations
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
"django.middleware.security.SecurityMiddleware",
|
"django.middleware.security.SecurityMiddleware",
|
||||||
|
"whitenoise.middleware.WhiteNoiseMiddleware",
|
||||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
"django.middleware.common.CommonMiddleware",
|
"django.middleware.common.CommonMiddleware",
|
||||||
"django.middleware.csrf.CsrfViewMiddleware",
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
|
|
@ -121,12 +136,31 @@ WSGI_APPLICATION = "photo21.wsgi.application"
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
|
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
|
||||||
|
|
||||||
DATABASES = {
|
_db_engine = config("DB_ENGINE", default="sqlite").strip().lower()
|
||||||
|
|
||||||
|
if _db_engine == "postgres":
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.postgresql",
|
||||||
|
"NAME": config("DB_NAME", default="photo21"),
|
||||||
|
"USER": config("DB_USER", default="photo21"),
|
||||||
|
"PASSWORD": config("DB_PASSWORD", default=""),
|
||||||
|
"HOST": config("DB_HOST", default="localhost"),
|
||||||
|
"PORT": config("DB_PORT", default="5432"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elif _db_engine == "sqlite":
|
||||||
|
DATABASES = {
|
||||||
"default": {
|
"default": {
|
||||||
"ENGINE": "django.db.backends.sqlite3",
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
|
"NAME": config("DB_PATH", default=os.path.join(BASE_DIR, "db.sqlite3")),
|
||||||
|
"OPTIONS": {
|
||||||
|
"timeout": 10,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown DB_ENGINE '{_db_engine}'. Must be 'sqlite' or 'postgres'.")
|
||||||
|
|
||||||
CACHES = {
|
CACHES = {
|
||||||
"default": {
|
"default": {
|
||||||
|
|
@ -164,9 +198,7 @@ USE_TZ = True
|
||||||
|
|
||||||
# Limit available languages to this subset
|
# Limit available languages to this subset
|
||||||
LANGUAGES = [
|
LANGUAGES = [
|
||||||
("de", _("German")),
|
|
||||||
("en", _("English")),
|
("en", _("English")),
|
||||||
("es", _("Spanish")),
|
|
||||||
("fr", _("French")),
|
("fr", _("French")),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -189,9 +221,20 @@ STATIC_ROOT = os.path.join(BASE_DIR, "static/")
|
||||||
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
|
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
|
||||||
MEDIA_URL = "/media/"
|
MEDIA_URL = "/media/"
|
||||||
|
|
||||||
|
STORAGES = {
|
||||||
|
"default": {
|
||||||
|
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
||||||
|
},
|
||||||
|
"staticfiles": {
|
||||||
|
"BACKEND": "photo21.storage.CompressedManifestStorage",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
WHITENOISE_MANIFEST_STRICT = False
|
||||||
|
|
||||||
LOCALE_PATHS = [os.path.join(BASE_DIR, "photo21/locale")]
|
LOCALE_PATHS = [os.path.join(BASE_DIR, "photo21/locale")]
|
||||||
|
|
||||||
FIXTURE_DIRS = [os.path.join(BASE_DIR, "photo21/fixtures")]
|
|
||||||
|
|
||||||
# Do not send email during debug
|
# Do not send email during debug
|
||||||
# By default Django sends mails to localhost:25 without authentification
|
# By default Django sends mails to localhost:25 without authentification
|
||||||
|
|
@ -202,6 +245,11 @@ if DEBUG:
|
||||||
SERVER_EMAIL = config("SERVER_EMAIL", default="photos@crans.org")
|
SERVER_EMAIL = config("SERVER_EMAIL", default="photos@crans.org")
|
||||||
DEFAULT_FROM_EMAIL = f"Serveur photos <{SERVER_EMAIL}>"
|
DEFAULT_FROM_EMAIL = f"Serveur photos <{SERVER_EMAIL}>"
|
||||||
EMAIL_SUBJECT_PREFIX = "[Serveur photos] "
|
EMAIL_SUBJECT_PREFIX = "[Serveur photos] "
|
||||||
|
EMAIL_HOST = config("SMTP_HOST", default="localhost")
|
||||||
|
EMAIL_PORT = config("SMTP_PORT", default=25, cast=int)
|
||||||
|
EMAIL_HOST_USER = config("SMTP_USER", default="")
|
||||||
|
EMAIL_HOST_PASSWORD = config("SMTP_PASSWORD", default="")
|
||||||
|
EMAIL_USE_TLS = config("SMTP_USE_TLS", default=False, cast=bool)
|
||||||
|
|
||||||
# After login redirect user to transfer page
|
# After login redirect user to transfer page
|
||||||
LOGIN_REDIRECT_URL = "/"
|
LOGIN_REDIRECT_URL = "/"
|
||||||
|
|
@ -221,16 +269,23 @@ MESSAGE_TAGS = {
|
||||||
# Allauth configuration ## For the django =< 5.0
|
# Allauth configuration ## For the django =< 5.0
|
||||||
ACCOUNT_EMAIL_REQUIRED = True
|
ACCOUNT_EMAIL_REQUIRED = True
|
||||||
# ACCOUNT_SIGNUP_FIELDS = ['email*', 'username*', 'password1*', 'password2*'] ## For the django =< 5.0
|
# ACCOUNT_SIGNUP_FIELDS = ['email*', 'username*', 'password1*', 'password2*'] ## For the django =< 5.0
|
||||||
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
|
ACCOUNT_EMAIL_VERIFICATION = config("EMAIL_VERIFICATION", default="mandatory")
|
||||||
ACCOUNT_AUTHENTICATION_METHOD = "username_email"
|
ACCOUNT_AUTHENTICATION_METHOD = "username_email"
|
||||||
# ACCOUNT_LOGIN_METHODS = {'username', 'email'}
|
# ACCOUNT_LOGIN_METHODS = {'username', 'email'}
|
||||||
ACCOUNT_FORMS = {"signup": "photo21.forms.CustomSignupForm"}
|
ACCOUNT_FORMS = {"signup": "photo21.forms.CustomSignupForm"}
|
||||||
SOCIALACCOUNT_PROVIDERS = {
|
|
||||||
"notekfet": {
|
if OAUTH_ENABLED:
|
||||||
# Fetch user profile
|
SOCIALACCOUNT_ONLY = OAUTH_ONLY
|
||||||
"SCOPE": ["1_1"],
|
SOCIALACCOUNT_PROVIDERS = {
|
||||||
|
"oauth": {
|
||||||
|
"SCOPE": OAUTH_SCOPE,
|
||||||
|
"DOMAIN": OAUTH_SERVER_URL,
|
||||||
|
"APP": {
|
||||||
|
"client_id": OAUTH_CLIENT_ID,
|
||||||
|
"secret": OAUTH_CLIENT_SECRET,
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
}
|
||||||
|
|
||||||
# Use Bootstrap forms
|
# Use Bootstrap forms
|
||||||
CRISPY_TEMPLATE_PACK = "bootstrap4"
|
CRISPY_TEMPLATE_PACK = "bootstrap4"
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,9 @@
|
||||||
// Copyright (C) 2022 Amicale des élèves de l'ENS Paris-Saclay
|
// Copyright (C) 2022 Amicale des élèves de l'ENS Paris-Saclay
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// On language selection, submit form
|
document.querySelectorAll('[data-lang]').forEach(btn => {
|
||||||
const langSelect = document.getElementsByName("language")[0];
|
btn.addEventListener('click', () => {
|
||||||
if (langSelect) {
|
document.getElementById('lang-input').value = btn.dataset.lang;
|
||||||
langSelect.addEventListener("change", (e) => {
|
document.getElementById('lang-form').submit();
|
||||||
e.target.form.submit();
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,29 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
background-color: rgba(163, 163, 163, 0.274);
|
background-color: rgba(163, 163, 163, 0.274);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Gallery - Google Photos style justified grid */
|
||||||
|
#lightgallery {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-item {
|
||||||
|
position: absolute;
|
||||||
|
overflow: hidden;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-item img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-item img.photo-private {
|
||||||
|
outline: 5px solid var(--bs-danger);
|
||||||
|
outline-offset: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Language selector */
|
/* Language selector */
|
||||||
.lang-select {
|
.lang-select {
|
||||||
border: none;
|
border: none;
|
||||||
|
|
|
||||||
12
photo21/storage.py
Normal file
12
photo21/storage.py
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
from whitenoise.storage import CompressedManifestStaticFilesStorage
|
||||||
|
|
||||||
|
|
||||||
|
class CompressedManifestStorage(CompressedManifestStaticFilesStorage):
|
||||||
|
"""Like CompressedManifestStaticFilesStorage but silently skips missing
|
||||||
|
referenced files (e.g. source maps not included in the package)."""
|
||||||
|
|
||||||
|
def hashed_name(self, name, content=None, filename=None):
|
||||||
|
try:
|
||||||
|
return super().hashed_name(name, content, filename)
|
||||||
|
except ValueError:
|
||||||
|
return name
|
||||||
|
|
@ -39,5 +39,5 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
<a class="link-secondary" href="{% url 'account_reset_password' %}">{% trans "Forgot Password?" %}</a>
|
<a class="link-secondary" href="{% url 'account_reset_password' %}">{% trans "Forgot Password?" %}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="small text-center mt-1">{% trans "If any problem, please contact the server owners at" %} <code>photos[at]crans.org</code>.</p>
|
<!-- <p class="small text-center mt-1">{% trans "If any problem, please contact the server owners at" %} <code>photos[at]crans.org</code>.</p> -->
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'">
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'">
|
||||||
<meta http-equiv="Referrer-Policy" content="no-referrer">
|
<meta http-equiv="Referrer-Policy" content="no-referrer">
|
||||||
<title>{% block title %}{{ title }}{% endblock title %} - {{ request.site.name }}</title>
|
<title>{% block title %}{{ title }}{% endblock title %} - {{ request.site.name }}</title>
|
||||||
<meta name="description" content="{% trans "The ENS Paris-Saclay pictures server." %}">
|
<meta name="description" content="{% trans "The ENS Rennes pictures server." %}">
|
||||||
<script src="{% static "theme.js" %}"></script>
|
<script src="{% static "theme.js" %}"></script>
|
||||||
<link rel="stylesheet" href="{% static "bootstrap5/css/bootstrap.min.css" %}">
|
<link rel="stylesheet" href="{% static "bootstrap5/css/bootstrap.min.css" %}">
|
||||||
<link rel="stylesheet" href="{% static "layout.css" %}">
|
<link rel="stylesheet" href="{% static "layout.css" %}">
|
||||||
|
|
@ -56,6 +56,21 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav">
|
||||||
|
{% get_available_languages as LANGUAGES %}
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<button class="btn nav-link dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
{{ LANGUAGE_CODE|upper }}
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
|
{% for lang_code, lang_name in LANGUAGES %}
|
||||||
|
<li>
|
||||||
|
<button class="dropdown-item{% if lang_code == LANGUAGE_CODE %} active{% endif %}" type="button" data-lang="{{ lang_code }}">
|
||||||
|
{{ lang_name }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
{% url 'account_email' as url %}
|
{% url 'account_email' as url %}
|
||||||
|
|
@ -112,26 +127,18 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</main>
|
</main>
|
||||||
<footer>
|
<footer>
|
||||||
<form action="{% url 'set_language' %}" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<p class="small text-center text-muted mt-1">
|
<p class="small text-center text-muted mt-1">
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
{% trans "Connected as" %} <code>{{ request.user.username }}</code> ·
|
{% trans "Connected as" %} <code>{{ request.user.username }}</code> ·
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a class="text-reset" href="https://gitlab.crans.org/bde/photo21/">{% trans "Source code" %}</a> ·
|
<a class="text-reset" href="https://git.sinfonie.org/sinfonie/photo26">{% trans "Source code" %}</a>
|
||||||
<select title="language" name="language" class="lang-select">
|
|
||||||
{% get_current_language as LANGUAGE_CODE %}
|
|
||||||
{% get_available_languages as LANGUAGES %}
|
|
||||||
{% for lang_code, lang_name in LANGUAGES %}
|
|
||||||
<option value="{{ lang_code }}" {% if lang_code == LANGUAGE_CODE %}selected{% endif %}>
|
|
||||||
{{ lang_name }} ({{ lang_code }})
|
|
||||||
</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
<noscript><input type="submit"></noscript>
|
|
||||||
</p>
|
</p>
|
||||||
</form>
|
|
||||||
</footer>
|
</footer>
|
||||||
|
<form id="lang-form" action="{% url 'set_language' %}" method="post" hidden>
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="language" id="lang-input">
|
||||||
|
<input type="hidden" name="next" value="{{ request.path }}">
|
||||||
|
</form>
|
||||||
|
|
||||||
<script src="{% static "lang-select.js" %}"></script>
|
<script src="{% static "lang-select.js" %}"></script>
|
||||||
<script src="{% static "bootstrap5/js/bootstrap.bundle.min.js" %}"></script>
|
<script src="{% static "bootstrap5/js/bootstrap.bundle.min.js" %}"></script>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
<p>
|
<p>
|
||||||
{% blocktrans trimmed %}
|
{% blocktrans trimmed %}
|
||||||
This website aims to collect the pictures and movies taken in the student
|
This website aims to collect the pictures and movies taken in the student
|
||||||
life of ENS Paris-Saclay or involving its students.
|
life of ENS Rennes or involving its students.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
|
@ -25,21 +25,16 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
|
||||||
{% blocktrans trimmed %}
|
|
||||||
If you want a photo to be deleted, please let us know:
|
|
||||||
<a href="mailto:photos@crans.org?subject=[ABUS] Nouvelle requête" class="btn btn-secondary btn-sm">Abuse request</a>
|
|
||||||
{% endblocktrans %}
|
|
||||||
</p>
|
|
||||||
{% if not perms.photologue.add_photo %}
|
{% if not perms.photologue.add_photo %}
|
||||||
<p>
|
<p>
|
||||||
{% blocktrans trimmed %}
|
{% blocktrans trimmed %}
|
||||||
If you want to obtain the right to upload pictures, please let us know:
|
If you want to obtain the right to upload pictures, please let us know:
|
||||||
<a href="mailto:photos@crans.org?subject=[Photographe] Demande de droits photographe" class="btn btn-secondary btn-sm">Become a photograph</a>
|
<a href="mailto:sinfonie@ens-rennes.fr?subject=[Photographe] Demande de droits photographe" class="btn btn-secondary btn-sm">Become a photograph</a>
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
<h3>{% trans "Last galleries" %}</h3>
|
<h3>{% trans "Last galleries" %}</h3>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
{% for gallery in object_list %}
|
{% for gallery in object_list %}
|
||||||
|
|
@ -52,15 +47,15 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
<h3 class="mt-4">{% trans "Behind the scene" %}</h3>
|
<h3 class="mt-4">{% trans "Behind the scene" %}</h3>
|
||||||
<p>
|
<p>
|
||||||
{% blocktrans trimmed %}
|
{% blocktrans trimmed %}
|
||||||
Because we value your privacy, we do not sell the data on this site,
|
<!-- This project if a fork of <a href="https://gitlab.crans.org/bde/photo21/">Photo21</a>. -->
|
||||||
unlike many free online platforms.
|
<!-- Because we value your privacy, we do not sell the data on this site, -->
|
||||||
The dedicated server running this website is kindly hosted by the
|
<!-- unlike many free online platforms. -->
|
||||||
<a href="https://www.crans.org/">Crans</a> at the ENS Paris-Saclay
|
The dedicated server running this website is kindly hosted by
|
||||||
basement.
|
<a href="https://sinfonie.org/">Sinfonie</a> at the ENS Rennes.
|
||||||
It is not managed by the Crans. Current active administrators are:
|
<!-- Current active administrators are: -->
|
||||||
{% endblocktrans %}
|
<!-- {% endblocktrans %} -->
|
||||||
{% for user in superusers %} <code>{{ user.username }}</code>{% endfor %}.
|
<!-- {% for user in superusers %} <code>{{ user.username }}</code>{% endfor %}. -->
|
||||||
{% trans "They should be contacted at" %}
|
<!-- {% trans "They should be contacted at" %} -->
|
||||||
<a href="mailto:photos@crans.org">photos@crans.org</a>.
|
<!-- <a href="mailto:photos@crans.org">photos@crans.org</a>. -->
|
||||||
</p>
|
</p>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,29 @@
|
||||||
# Copyright (C) 2021-2022 Amicale des élèves de l'ENS Paris-Saclay
|
# Copyright (C) 2021-2022 Amicale des élèves de l'ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.http import HttpResponse
|
from django.http import FileResponse, Http404
|
||||||
from django.views.generic import ListView, View
|
from django.views.generic import ListView, View
|
||||||
from photologue.models import Gallery
|
from photologue.models import Gallery
|
||||||
|
|
||||||
|
|
||||||
|
class MediaAccess(View):
|
||||||
class MediaAccess(LoginRequiredMixin, View):
|
|
||||||
def get(self, request, path):
|
def get(self, request, path):
|
||||||
response = HttpResponse()
|
if not request.user.is_authenticated and not request.session.get('public_gallery_access'):
|
||||||
# Content-type will be detected by nginx
|
from django.contrib.auth.views import redirect_to_login
|
||||||
del response["Content-Type"]
|
return redirect_to_login(request.get_full_path())
|
||||||
response["X-Accel-Redirect"] = "/protected/media/" + path
|
media_root = os.path.realpath(settings.MEDIA_ROOT)
|
||||||
response["Cache-Control"] = 'max-age=2678400'
|
file_path = os.path.realpath(os.path.join(media_root, path))
|
||||||
|
if not file_path.startswith(media_root + os.sep):
|
||||||
|
raise Http404
|
||||||
|
if not os.path.isfile(file_path):
|
||||||
|
raise Http404
|
||||||
|
response = FileResponse(open(file_path, 'rb'))
|
||||||
|
response['Cache-Control'] = 'max-age=2678400'
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.contrib.admin import EmptyFieldListFilter
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from .models import Gallery, Photo, Tag
|
from .models import Gallery, Photo, Tag
|
||||||
|
|
@ -10,7 +11,7 @@ from .models import Gallery, Photo, Tag
|
||||||
|
|
||||||
class GalleryAdmin(admin.ModelAdmin):
|
class GalleryAdmin(admin.ModelAdmin):
|
||||||
list_display = ("title", "date_start", "photo_count", "get_tags")
|
list_display = ("title", "date_start", "photo_count", "get_tags")
|
||||||
list_filter = ["date_start", "tags"]
|
list_filter = ["date_start", "tags", ("public_token", EmptyFieldListFilter)]
|
||||||
date_hierarchy = "date_start"
|
date_hierarchy = "date_start"
|
||||||
prepopulated_fields = {"slug": ("title",)}
|
prepopulated_fields = {"slug": ("title",)}
|
||||||
model = Gallery
|
model = Gallery
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,25 @@
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
from django.db.backends.signals import connection_created
|
||||||
|
|
||||||
|
|
||||||
class PhotologueConfig(AppConfig):
|
class PhotologueConfig(AppConfig):
|
||||||
default_auto_field = "django.db.models.AutoField"
|
default_auto_field = "django.db.models.AutoField"
|
||||||
name = "photologue"
|
name = "photologue"
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
from django.db import connection
|
||||||
|
|
||||||
|
def enable_sqlite_wal(sender, connection, **kwargs):
|
||||||
|
if connection.vendor == "sqlite":
|
||||||
|
cursor = connection.cursor()
|
||||||
|
cursor.execute("PRAGMA journal_mode=WAL;")
|
||||||
|
cursor.execute("PRAGMA synchronous=OFF;")
|
||||||
|
cursor.execute("PRAGMA journal_size_limit=67108864;")
|
||||||
|
cursor.execute("PRAGMA wal_autocheckpoint=1000;")
|
||||||
|
cursor.execute("PRAGMA cache_size=-65536;")
|
||||||
|
cursor.execute("PRAGMA temp_store=MEMORY;")
|
||||||
|
cursor.execute("PRAGMA mmap_size=268435456;")
|
||||||
|
|
||||||
|
connection_created.connect(enable_sqlite_wal)
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Photologue\n"
|
"Project-Id-Version: Photologue\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-03-05 15:30+0100\n"
|
"POT-Creation-Date: 2026-05-03 21:01+0000\n"
|
||||||
"PO-Revision-Date: 2017-12-03 14:47+0000\n"
|
"PO-Revision-Date: 2017-12-03 14:47+0000\n"
|
||||||
"Last-Translator: Richard Barran <richard@arbee-design.co.uk>\n"
|
"Last-Translator: Richard Barran <richard@arbee-design.co.uk>\n"
|
||||||
"Language-Team: French (http://www.transifex.com/richardbarran/django-"
|
"Language-Team: French (http://www.transifex.com/richardbarran/django-"
|
||||||
|
|
@ -21,24 +21,23 @@ msgstr ""
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
|
||||||
#: .\photologue\admin.py:26 .\photologue\models.py:168
|
#: photologue/admin.py:25 photologue/models.py:168 photologue/models.py:802
|
||||||
#: .\photologue\models.py:786
|
|
||||||
msgid "tags"
|
msgid "tags"
|
||||||
msgstr "balises"
|
msgstr "balises"
|
||||||
|
|
||||||
#: .\photologue\admin.py:63 .\photologue\forms.py:68
|
#: photologue/admin.py:62 photologue/forms.py:69
|
||||||
msgid "Gallery"
|
msgid "Gallery"
|
||||||
msgstr "Galerie"
|
msgstr "Galerie"
|
||||||
|
|
||||||
#: .\photologue\admin.py:67 .\photologue\models.py:529
|
#: photologue/admin.py:66 photologue/models.py:542
|
||||||
msgid "owner"
|
msgid "owner"
|
||||||
msgstr "propriétaire"
|
msgstr "propriétaire"
|
||||||
|
|
||||||
#: .\photologue\forms.py:70
|
#: photologue/forms.py:71
|
||||||
msgid "-- Create a new gallery --"
|
msgid "-- Create a new gallery --"
|
||||||
msgstr "-- Créer une nouvelle galerie --"
|
msgstr "-- Créer une nouvelle galerie --"
|
||||||
|
|
||||||
#: .\photologue\forms.py:72
|
#: photologue/forms.py:73
|
||||||
msgid ""
|
msgid ""
|
||||||
"Select a gallery to add these images to. Leave this empty to create a new "
|
"Select a gallery to add these images to. Leave this empty to create a new "
|
||||||
"gallery from the supplied title."
|
"gallery from the supplied title."
|
||||||
|
|
@ -46,117 +45,108 @@ msgstr ""
|
||||||
"Sélectionner une galerie à laquelle ajouter ces images. Laisser ce champ "
|
"Sélectionner une galerie à laquelle ajouter ces images. Laisser ce champ "
|
||||||
"vide pour créer une nouvelle galerie à partir du titre indiqué."
|
"vide pour créer une nouvelle galerie à partir du titre indiqué."
|
||||||
|
|
||||||
#: .\photologue\forms.py:77
|
#: photologue/forms.py:78
|
||||||
msgid "New gallery title"
|
msgid "New gallery title"
|
||||||
msgstr "Titre de la nouvelle galerie"
|
msgstr "Titre de la nouvelle galerie"
|
||||||
|
|
||||||
#: .\photologue\forms.py:82
|
#: photologue/forms.py:83
|
||||||
msgid "New gallery event start date"
|
msgid "New gallery event start date"
|
||||||
msgstr "Date de début de l'évènement de la nouvelle galerie"
|
msgstr "Date de début de l'évènement de la nouvelle galerie"
|
||||||
|
|
||||||
#: .\photologue\forms.py:87
|
#: photologue/forms.py:89
|
||||||
msgid "New gallery event end date"
|
msgid "New gallery event end date"
|
||||||
msgstr "Date de fin de l'évènement de la nouvelle galerie"
|
msgstr "Date de fin de l'évènement de la nouvelle galerie"
|
||||||
|
|
||||||
#: .\photologue\forms.py:93
|
#: photologue/forms.py:96
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "description"
|
#| msgid "description"
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr "description"
|
msgstr "description"
|
||||||
|
|
||||||
#: .\photologue\forms.py:98
|
#: photologue/forms.py:101
|
||||||
msgid "New gallery tags"
|
msgid "New gallery tags"
|
||||||
msgstr "Balises de la nouvelle galerie"
|
msgstr "Balises de la nouvelle galerie"
|
||||||
|
|
||||||
#: .\photologue\forms.py:101
|
#: photologue/forms.py:129 photologue/templates/photologue/upload.html:8
|
||||||
msgid ""
|
#: photologue/templates/photologue/upload.html:17
|
||||||
"Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: .\photologue\forms.py:120 .\photologue\templates\photologue\upload.html:8
|
|
||||||
#: .\photologue\templates\photologue\upload.html:15
|
|
||||||
msgid "Upload"
|
msgid "Upload"
|
||||||
msgstr "Télécharger"
|
msgstr "Télécharger"
|
||||||
|
|
||||||
#: .\photologue\forms.py:126
|
#: photologue/forms.py:141
|
||||||
msgid "A gallery with that title already exists."
|
|
||||||
msgstr "Une galerie portant ce nom existe déjà."
|
|
||||||
|
|
||||||
#: .\photologue\forms.py:138
|
|
||||||
msgid "Select an existing gallery, or enter a title for a new gallery."
|
msgid "Select an existing gallery, or enter a title for a new gallery."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Sélectionner une galerie existante ou entrer un titre pour une nouvelle "
|
"Sélectionner une galerie existante ou entrer un titre pour une nouvelle "
|
||||||
"galerie."
|
"galerie."
|
||||||
|
|
||||||
#: .\photologue\models.py:88
|
#: photologue/models.py:88
|
||||||
msgid "Very Low"
|
msgid "Very Low"
|
||||||
msgstr "Très Bas"
|
msgstr "Très Bas"
|
||||||
|
|
||||||
#: .\photologue\models.py:89
|
#: photologue/models.py:89
|
||||||
msgid "Low"
|
msgid "Low"
|
||||||
msgstr "Bas"
|
msgstr "Bas"
|
||||||
|
|
||||||
#: .\photologue\models.py:90
|
#: photologue/models.py:90
|
||||||
msgid "Medium-Low"
|
msgid "Medium-Low"
|
||||||
msgstr "Moyen-Bas"
|
msgstr "Moyen-Bas"
|
||||||
|
|
||||||
#: .\photologue\models.py:91
|
#: photologue/models.py:91
|
||||||
msgid "Medium"
|
msgid "Medium"
|
||||||
msgstr "Moyen"
|
msgstr "Moyen"
|
||||||
|
|
||||||
#: .\photologue\models.py:92
|
#: photologue/models.py:92
|
||||||
msgid "Medium-High"
|
msgid "Medium-High"
|
||||||
msgstr "Moyen-Haut"
|
msgstr "Moyen-Haut"
|
||||||
|
|
||||||
#: .\photologue\models.py:93
|
#: photologue/models.py:93
|
||||||
msgid "High"
|
msgid "High"
|
||||||
msgstr "Haut"
|
msgstr "Haut"
|
||||||
|
|
||||||
#: .\photologue\models.py:94
|
#: photologue/models.py:94
|
||||||
msgid "Very High"
|
msgid "Very High"
|
||||||
msgstr "Très Haut"
|
msgstr "Très Haut"
|
||||||
|
|
||||||
#: .\photologue\models.py:99
|
#: photologue/models.py:99
|
||||||
msgid "Top"
|
msgid "Top"
|
||||||
msgstr "Sommet"
|
msgstr "Sommet"
|
||||||
|
|
||||||
#: .\photologue\models.py:100
|
#: photologue/models.py:100
|
||||||
msgid "Right"
|
msgid "Right"
|
||||||
msgstr "Droite"
|
msgstr "Droite"
|
||||||
|
|
||||||
#: .\photologue\models.py:101
|
#: photologue/models.py:101
|
||||||
msgid "Bottom"
|
msgid "Bottom"
|
||||||
msgstr "Bas"
|
msgstr "Bas"
|
||||||
|
|
||||||
#: .\photologue\models.py:102
|
#: photologue/models.py:102
|
||||||
msgid "Left"
|
msgid "Left"
|
||||||
msgstr "Gauche"
|
msgstr "Gauche"
|
||||||
|
|
||||||
#: .\photologue\models.py:103
|
#: photologue/models.py:103
|
||||||
msgid "Center (Default)"
|
msgid "Center (Default)"
|
||||||
msgstr "Centré (par défaut)"
|
msgstr "Centré (par défaut)"
|
||||||
|
|
||||||
#: .\photologue\models.py:107
|
#: photologue/models.py:107
|
||||||
msgid "Flip left to right"
|
msgid "Flip left to right"
|
||||||
msgstr "Inversion de gauche à droite"
|
msgstr "Inversion de gauche à droite"
|
||||||
|
|
||||||
#: .\photologue\models.py:108
|
#: photologue/models.py:108
|
||||||
msgid "Flip top to bottom"
|
msgid "Flip top to bottom"
|
||||||
msgstr "Inversion de haut en bas"
|
msgstr "Inversion de haut en bas"
|
||||||
|
|
||||||
#: .\photologue\models.py:109
|
#: photologue/models.py:109
|
||||||
msgid "Rotate 90 degrees counter-clockwise"
|
msgid "Rotate 90 degrees counter-clockwise"
|
||||||
msgstr "Rotation de 90 degrés dans le sens anti-horloger"
|
msgstr "Rotation de 90 degrés dans le sens anti-horloger"
|
||||||
|
|
||||||
#: .\photologue\models.py:110
|
#: photologue/models.py:110
|
||||||
msgid "Rotate 90 degrees clockwise"
|
msgid "Rotate 90 degrees clockwise"
|
||||||
msgstr "Rotation de 90 degrés dans le sens horloger"
|
msgstr "Rotation de 90 degrés dans le sens horloger"
|
||||||
|
|
||||||
#: .\photologue\models.py:111
|
#: photologue/models.py:111
|
||||||
msgid "Rotate 180 degrees"
|
msgid "Rotate 180 degrees"
|
||||||
msgstr "Rotation de 180 degrés"
|
msgstr "Rotation de 180 degrés"
|
||||||
|
|
||||||
#: .\photologue\models.py:125
|
#: photologue/models.py:125
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Chain multiple filters using the following pattern \"FILTER_ONE->FILTER_TWO-"
|
"Chain multiple filters using the following pattern \"FILTER_ONE->FILTER_TWO-"
|
||||||
|
|
@ -167,116 +157,119 @@ msgstr ""
|
||||||
">FILTRE_DEUX->FILTRE_TROIS\". Les filtres d'image seront appliqués dans "
|
">FILTRE_DEUX->FILTRE_TROIS\". Les filtres d'image seront appliqués dans "
|
||||||
"l'ordre. Les filtres suivants sont disponibles: %s."
|
"l'ordre. Les filtres suivants sont disponibles: %s."
|
||||||
|
|
||||||
#: .\photologue\models.py:148 .\photologue\models.py:517
|
#: photologue/models.py:148 photologue/models.py:530
|
||||||
msgid "title"
|
msgid "title"
|
||||||
msgstr "titre"
|
msgstr "titre"
|
||||||
|
|
||||||
#: .\photologue\models.py:150
|
#: photologue/models.py:150
|
||||||
msgid "title slug"
|
msgid "title slug"
|
||||||
msgstr "version abrégée du titre"
|
msgstr "version abrégée du titre"
|
||||||
|
|
||||||
#: .\photologue\models.py:153 .\photologue\models.py:522
|
#: photologue/models.py:153 photologue/models.py:535 photologue/models.py:796
|
||||||
#: .\photologue\models.py:780
|
|
||||||
msgid "A \"slug\" is a unique URL-friendly title for an object."
|
msgid "A \"slug\" is a unique URL-friendly title for an object."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Un \"slug\" est un titre abrégé et unique, compatible avec les URL, pour un "
|
"Un \"slug\" est un titre abrégé et unique, compatible avec les URL, pour un "
|
||||||
"objet."
|
"objet."
|
||||||
|
|
||||||
#: .\photologue\models.py:157
|
#: photologue/models.py:157
|
||||||
msgid "start date"
|
msgid "start date"
|
||||||
msgstr "date de début"
|
msgstr "date de début"
|
||||||
|
|
||||||
#: .\photologue\models.py:162
|
#: photologue/models.py:162
|
||||||
msgid "end date"
|
msgid "end date"
|
||||||
msgstr "date de fin"
|
msgstr "date de fin"
|
||||||
|
|
||||||
#: .\photologue\models.py:164
|
#: photologue/models.py:164
|
||||||
msgid "description"
|
msgid "description"
|
||||||
msgstr "description"
|
msgstr "description"
|
||||||
|
|
||||||
#: .\photologue\models.py:174 .\photologue\models.py:548
|
#: photologue/models.py:174 photologue/models.py:561
|
||||||
msgid "photos"
|
msgid "photos"
|
||||||
msgstr "photos"
|
msgstr "photos"
|
||||||
|
|
||||||
#: .\photologue\models.py:181
|
#: photologue/models.py:178
|
||||||
|
msgid "public token"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: photologue/models.py:188
|
||||||
msgid "gallery"
|
msgid "gallery"
|
||||||
msgstr "galerie"
|
msgstr "galerie"
|
||||||
|
|
||||||
#: .\photologue\models.py:182
|
#: photologue/models.py:189
|
||||||
msgid "galleries"
|
msgid "galleries"
|
||||||
msgstr "galleries"
|
msgstr "galleries"
|
||||||
|
|
||||||
#: .\photologue\models.py:214
|
#: photologue/models.py:221
|
||||||
msgid "count"
|
msgid "count"
|
||||||
msgstr "nombre"
|
msgstr "nombre"
|
||||||
|
|
||||||
#: .\photologue\models.py:215
|
#: photologue/models.py:222
|
||||||
msgid "private count"
|
msgid "private count"
|
||||||
msgstr "nombre de photos privées"
|
msgstr "nombre de photos privées"
|
||||||
|
|
||||||
#: .\photologue\models.py:220
|
#: photologue/models.py:227
|
||||||
msgid "image"
|
msgid "image"
|
||||||
msgstr "image"
|
msgstr "image"
|
||||||
|
|
||||||
#: .\photologue\models.py:223
|
#: photologue/models.py:236
|
||||||
msgid "date taken"
|
msgid "date taken"
|
||||||
msgstr "date de prise de vue"
|
msgstr "date de prise de vue"
|
||||||
|
|
||||||
#: .\photologue\models.py:226
|
#: photologue/models.py:239
|
||||||
msgid "Date image was taken; is obtained from the image EXIF data."
|
msgid "Date image was taken; is obtained from the image EXIF data."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"La date à laquelle l'image a été prise ; obtenue à partir des données EXIF "
|
"La date à laquelle l'image a été prise ; obtenue à partir des données EXIF "
|
||||||
"de l'image."
|
"de l'image."
|
||||||
|
|
||||||
#: .\photologue\models.py:228
|
#: photologue/models.py:241
|
||||||
msgid "view count"
|
msgid "view count"
|
||||||
msgstr "nombre"
|
msgstr "nombre"
|
||||||
|
|
||||||
#: .\photologue\models.py:230
|
#: photologue/models.py:243
|
||||||
msgid "crop from"
|
msgid "crop from"
|
||||||
msgstr "découper à partir de"
|
msgstr "découper à partir de"
|
||||||
|
|
||||||
#: .\photologue\models.py:254
|
#: photologue/models.py:267
|
||||||
msgid "An \"admin_thumbnail\" photo size has not been defined."
|
msgid "An \"admin_thumbnail\" photo size has not been defined."
|
||||||
msgstr "Une taille de photo \"admin_thumbnail\" n'a pas encore été définie."
|
msgstr "Une taille de photo \"admin_thumbnail\" n'a pas encore été définie."
|
||||||
|
|
||||||
#: .\photologue\models.py:267
|
#: photologue/models.py:280
|
||||||
msgid "Thumbnail"
|
msgid "Thumbnail"
|
||||||
msgstr "Miniature"
|
msgstr "Miniature"
|
||||||
|
|
||||||
#: .\photologue\models.py:519 .\photologue\models.py:779
|
#: photologue/models.py:532 photologue/models.py:795
|
||||||
msgid "slug"
|
msgid "slug"
|
||||||
msgstr "libellé court"
|
msgstr "libellé court"
|
||||||
|
|
||||||
#: .\photologue\models.py:524
|
#: photologue/models.py:537
|
||||||
msgid "caption"
|
msgid "caption"
|
||||||
msgstr "légende"
|
msgstr "légende"
|
||||||
|
|
||||||
#: .\photologue\models.py:525
|
#: photologue/models.py:538
|
||||||
msgid "date added"
|
msgid "date added"
|
||||||
msgstr "date d'ajout"
|
msgstr "date d'ajout"
|
||||||
|
|
||||||
#: .\photologue\models.py:534
|
#: photologue/models.py:547
|
||||||
msgid "license"
|
msgid "license"
|
||||||
msgstr "licence"
|
msgstr "licence"
|
||||||
|
|
||||||
#: .\photologue\models.py:537
|
#: photologue/models.py:550
|
||||||
msgid "is public"
|
msgid "is public"
|
||||||
msgstr "est public"
|
msgstr "est public"
|
||||||
|
|
||||||
#: .\photologue\models.py:539
|
#: photologue/models.py:552
|
||||||
msgid "Public photographs will be displayed in the default views."
|
msgid "Public photographs will be displayed in the default views."
|
||||||
msgstr "Les photographies publique seront affichées dans les vues par défaut."
|
msgstr "Les photographies publique seront affichées dans les vues par défaut."
|
||||||
|
|
||||||
#: .\photologue\models.py:547
|
#: photologue/models.py:560
|
||||||
msgid "photo"
|
msgid "photo"
|
||||||
msgstr "photo"
|
msgstr "photo"
|
||||||
|
|
||||||
#: .\photologue\models.py:610 .\photologue\models.py:774
|
#: photologue/models.py:626 photologue/models.py:790
|
||||||
msgid "name"
|
msgid "name"
|
||||||
msgstr "nom"
|
msgstr "nom"
|
||||||
|
|
||||||
#: .\photologue\models.py:614
|
#: photologue/models.py:630
|
||||||
msgid ""
|
msgid ""
|
||||||
"Photo size name should contain only letters, numbers and underscores. "
|
"Photo size name should contain only letters, numbers and underscores. "
|
||||||
"Examples: \"thumbnail\", \"display\", \"small\", \"main_page_widget\"."
|
"Examples: \"thumbnail\", \"display\", \"small\", \"main_page_widget\"."
|
||||||
|
|
@ -285,41 +278,41 @@ msgstr ""
|
||||||
"chiffres et des caractères de soulignement. Exemples: \"miniature\", "
|
"chiffres et des caractères de soulignement. Exemples: \"miniature\", "
|
||||||
"\"affichage\", \"petit\", \"widget_page_principale\"."
|
"\"affichage\", \"petit\", \"widget_page_principale\"."
|
||||||
|
|
||||||
#: .\photologue\models.py:626
|
#: photologue/models.py:642
|
||||||
msgid "width"
|
msgid "width"
|
||||||
msgstr "largeur"
|
msgstr "largeur"
|
||||||
|
|
||||||
#: .\photologue\models.py:629
|
#: photologue/models.py:645
|
||||||
msgid ""
|
msgid ""
|
||||||
"If width is set to \"0\" the image will be scaled to the supplied height."
|
"If width is set to \"0\" the image will be scaled to the supplied height."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Si la largeur est réglée à \"0\" l l'image sera redimensionnée par rapport à "
|
"Si la largeur est réglée à \"0\" l l'image sera redimensionnée par rapport à "
|
||||||
"la hauteur fournie."
|
"la hauteur fournie."
|
||||||
|
|
||||||
#: .\photologue\models.py:633
|
#: photologue/models.py:649
|
||||||
msgid "height"
|
msgid "height"
|
||||||
msgstr "hauteur"
|
msgstr "hauteur"
|
||||||
|
|
||||||
#: .\photologue\models.py:636
|
#: photologue/models.py:652
|
||||||
msgid ""
|
msgid ""
|
||||||
"If height is set to \"0\" the image will be scaled to the supplied width"
|
"If height is set to \"0\" the image will be scaled to the supplied width"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Si la hauteur est réglée à \"0\" l l'image sera redimensionnée par rapport à "
|
"Si la hauteur est réglée à \"0\" l l'image sera redimensionnée par rapport à "
|
||||||
"la largeur fournie."
|
"la largeur fournie."
|
||||||
|
|
||||||
#: .\photologue\models.py:640
|
#: photologue/models.py:656
|
||||||
msgid "quality"
|
msgid "quality"
|
||||||
msgstr "qualité"
|
msgstr "qualité"
|
||||||
|
|
||||||
#: .\photologue\models.py:643
|
#: photologue/models.py:659
|
||||||
msgid "JPEG image quality."
|
msgid "JPEG image quality."
|
||||||
msgstr "Qualité JPEG de l'image."
|
msgstr "Qualité JPEG de l'image."
|
||||||
|
|
||||||
#: .\photologue\models.py:646
|
#: photologue/models.py:662
|
||||||
msgid "upscale images?"
|
msgid "upscale images?"
|
||||||
msgstr "agrandir les images ?"
|
msgstr "agrandir les images ?"
|
||||||
|
|
||||||
#: .\photologue\models.py:649
|
#: photologue/models.py:665
|
||||||
msgid ""
|
msgid ""
|
||||||
"If selected the image will be scaled up if necessary to fit the supplied "
|
"If selected the image will be scaled up if necessary to fit the supplied "
|
||||||
"dimensions. Cropped sizes will be upscaled regardless of this setting."
|
"dimensions. Cropped sizes will be upscaled regardless of this setting."
|
||||||
|
|
@ -328,11 +321,11 @@ msgstr ""
|
||||||
"dimensions fournies. Les dimensions ajustées seront agrandies sans prendre "
|
"dimensions fournies. Les dimensions ajustées seront agrandies sans prendre "
|
||||||
"en compte ce paramètre."
|
"en compte ce paramètre."
|
||||||
|
|
||||||
#: .\photologue\models.py:655
|
#: photologue/models.py:671
|
||||||
msgid "crop to fit?"
|
msgid "crop to fit?"
|
||||||
msgstr "découper pour adapter à la taille ?"
|
msgstr "découper pour adapter à la taille ?"
|
||||||
|
|
||||||
#: .\photologue\models.py:658
|
#: photologue/models.py:674
|
||||||
msgid ""
|
msgid ""
|
||||||
"If selected the image will be scaled and cropped to fit the supplied "
|
"If selected the image will be scaled and cropped to fit the supplied "
|
||||||
"dimensions."
|
"dimensions."
|
||||||
|
|
@ -340,21 +333,21 @@ msgstr ""
|
||||||
"Si sélectionné l'image sera redimensionnée et recadrée pour coïncider avec "
|
"Si sélectionné l'image sera redimensionnée et recadrée pour coïncider avec "
|
||||||
"les dimensions fournies."
|
"les dimensions fournies."
|
||||||
|
|
||||||
#: .\photologue\models.py:663
|
#: photologue/models.py:679
|
||||||
msgid "pre-cache?"
|
msgid "pre-cache?"
|
||||||
msgstr "mise en cache ?"
|
msgstr "mise en cache ?"
|
||||||
|
|
||||||
#: .\photologue\models.py:666
|
#: photologue/models.py:682
|
||||||
msgid "If selected this photo size will be pre-cached as photos are added."
|
msgid "If selected this photo size will be pre-cached as photos are added."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Si sélectionné cette taille de photo sera mise en cache au moment au les "
|
"Si sélectionné cette taille de photo sera mise en cache au moment au les "
|
||||||
"photos sont ajoutées."
|
"photos sont ajoutées."
|
||||||
|
|
||||||
#: .\photologue\models.py:670
|
#: photologue/models.py:686
|
||||||
msgid "increment view count?"
|
msgid "increment view count?"
|
||||||
msgstr "incrémenter le nombre d'affichages ?"
|
msgstr "incrémenter le nombre d'affichages ?"
|
||||||
|
|
||||||
#: .\photologue\models.py:673
|
#: photologue/models.py:689
|
||||||
msgid ""
|
msgid ""
|
||||||
"If selected the image's \"view_count\" will be incremented when this photo "
|
"If selected the image's \"view_count\" will be incremented when this photo "
|
||||||
"size is displayed."
|
"size is displayed."
|
||||||
|
|
@ -362,79 +355,95 @@ msgstr ""
|
||||||
"Si sélectionné le \"view_count\" (nombre d'affichage) de l'image sera "
|
"Si sélectionné le \"view_count\" (nombre d'affichage) de l'image sera "
|
||||||
"incrémenté quand cette taille de photo sera affichée."
|
"incrémenté quand cette taille de photo sera affichée."
|
||||||
|
|
||||||
#: .\photologue\models.py:680
|
#: photologue/models.py:696
|
||||||
msgid "photo size"
|
msgid "photo size"
|
||||||
msgstr "taille de la photo"
|
msgstr "taille de la photo"
|
||||||
|
|
||||||
#: .\photologue\models.py:681
|
#: photologue/models.py:697
|
||||||
msgid "photo sizes"
|
msgid "photo sizes"
|
||||||
msgstr "tailles des photos"
|
msgstr "tailles des photos"
|
||||||
|
|
||||||
#: .\photologue\models.py:699
|
#: photologue/models.py:715
|
||||||
msgid "Can only crop photos if both width and height dimensions are set."
|
msgid "Can only crop photos if both width and height dimensions are set."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"La hauteur et la largeur doivent être toutes les deux définies pour "
|
"La hauteur et la largeur doivent être toutes les deux définies pour "
|
||||||
"retailler des photos."
|
"retailler des photos."
|
||||||
|
|
||||||
#: .\photologue\models.py:785
|
#: photologue/models.py:801
|
||||||
msgid "tag"
|
msgid "tag"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: .\photologue\templates\photologue\gallery_archive.html:9
|
#: photologue/templates/photologue/gallery_archive.html:9
|
||||||
msgid "Latest photo galleries"
|
msgid "Latest photo galleries"
|
||||||
msgstr "Dernières galeries de photos"
|
msgstr "Dernières galeries de photos"
|
||||||
|
|
||||||
#: .\photologue\templates\photologue\gallery_archive.html:14
|
#: photologue/templates/photologue/gallery_archive.html:14
|
||||||
#: .\photologue\templates\photologue\gallery_archive_year.html:14
|
#: photologue/templates/photologue/gallery_archive_year.html:14
|
||||||
msgid "Filter by year"
|
msgid "Filter by year"
|
||||||
msgstr "Filtrer par année"
|
msgstr "Filtrer par année"
|
||||||
|
|
||||||
#: .\photologue\templates\photologue\gallery_archive.html:31
|
#: photologue/templates/photologue/gallery_archive.html:31
|
||||||
msgid "No galleries were found"
|
msgid "No galleries were found"
|
||||||
msgstr "Aucune galerie trouvée"
|
msgstr "Aucune galerie trouvée"
|
||||||
|
|
||||||
#: .\photologue\templates\photologue\gallery_archive_year.html:9
|
#: photologue/templates/photologue/gallery_archive_year.html:9
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Galleries for %(show_year)s"
|
msgid "Galleries for %(show_year)s"
|
||||||
msgstr "Galeries de %(show_year)s"
|
msgstr "Galeries de %(show_year)s"
|
||||||
|
|
||||||
#: .\photologue\templates\photologue\gallery_archive_year.html:31
|
#: photologue/templates/photologue/gallery_archive_year.html:31
|
||||||
msgid "No galleries were found."
|
msgid "No galleries were found."
|
||||||
msgstr "Aucune galerie trouvée."
|
msgstr "Aucune galerie trouvée."
|
||||||
|
|
||||||
#: .\photologue\templates\photologue\gallery_detail.html:42
|
#: photologue/templates/photologue/gallery_detail.html:48
|
||||||
msgid "to"
|
msgid "to"
|
||||||
msgstr "au"
|
msgstr "au"
|
||||||
|
|
||||||
#: .\photologue\templates\photologue\gallery_detail.html:59
|
#: photologue/templates/photologue/gallery_detail.html:53
|
||||||
|
msgid "Public link:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: photologue/templates/photologue/gallery_detail.html:55
|
||||||
|
msgid "Copy"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: photologue/templates/photologue/gallery_detail.html:56
|
||||||
|
msgid "Revoke"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: photologue/templates/photologue/gallery_detail.html:59
|
||||||
|
msgid "Generate public link"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: photologue/templates/photologue/gallery_detail.html:78
|
||||||
msgid "All pictures"
|
msgid "All pictures"
|
||||||
msgstr "Toutes les photos"
|
msgstr "Toutes les photos"
|
||||||
|
|
||||||
#: .\photologue\templates\photologue\gallery_detail.html:85
|
#: photologue/templates/photologue/gallery_detail.html:106
|
||||||
msgid "Download all gallery"
|
msgid "Download all gallery"
|
||||||
msgstr "Télécharger toute la galerie"
|
msgstr "Télécharger toute la galerie"
|
||||||
|
|
||||||
#: .\photologue\templates\photologue\photo_confirm_delete.html:9
|
#: photologue/templates/photologue/photo_confirm_delete.html:9
|
||||||
#: .\photologue\templates\photologue\photo_confirm_delete.html:14
|
#: photologue/templates/photologue/photo_confirm_delete.html:14
|
||||||
msgid "Delete confirmation"
|
msgid "Delete confirmation"
|
||||||
msgstr "Confirmation de la suppression"
|
msgstr "Confirmation de la suppression"
|
||||||
|
|
||||||
#: .\photologue\templates\photologue\photo_confirm_delete.html:17
|
#: photologue/templates/photologue/photo_confirm_delete.html:17
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Are you sure you want to delete <code>%(object)s</code>?"
|
msgid "Are you sure you want to delete <code>%(object)s</code>?"
|
||||||
msgstr "Es tu sure que tu veux supprimer <code>%(object)s</code> ?"
|
msgstr "Es tu sure que tu veux supprimer <code>%(object)s</code> ?"
|
||||||
|
|
||||||
#: .\photologue\templates\photologue\photo_confirm_delete.html:22
|
#: photologue/templates/photologue/photo_confirm_delete.html:22
|
||||||
#: .\photologue\templates\photologue\photo_confirm_report.html:22
|
#: photologue/templates/photologue/photo_confirm_report.html:22
|
||||||
msgid "Confirm"
|
msgid "Confirm"
|
||||||
msgstr "Confimer"
|
msgstr "Confimer"
|
||||||
|
|
||||||
#: .\photologue\templates\photologue\photo_confirm_report.html:9
|
#: photologue/templates/photologue/photo_confirm_report.html:9
|
||||||
#: .\photologue\templates\photologue\photo_confirm_report.html:14
|
#: photologue/templates/photologue/photo_confirm_report.html:14
|
||||||
msgid "Report confirmation"
|
msgid "Report confirmation"
|
||||||
msgstr "Raporter la confirmation"
|
msgstr "Raporter la confirmation"
|
||||||
|
|
||||||
#: .\photologue\templates\photologue\photo_confirm_report.html:17
|
#: photologue/templates/photologue/photo_confirm_report.html:17
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Are you sure you want to report <code>%(object)s</code>? This photo will no "
|
"Are you sure you want to report <code>%(object)s</code>? This photo will no "
|
||||||
|
|
@ -443,14 +452,17 @@ msgstr ""
|
||||||
"Es-tu sur de signaler <code>%(object)s</code>? Cette photo ne va plus être "
|
"Es-tu sur de signaler <code>%(object)s</code>? Cette photo ne va plus être "
|
||||||
"publique, et les administrateur vont être notifiés."
|
"publique, et les administrateur vont être notifiés."
|
||||||
|
|
||||||
#: .\photologue\templates\photologue\photo_detail.html:15
|
#: photologue/templates/photologue/photo_detail.html:15
|
||||||
msgid "Published"
|
msgid "Published"
|
||||||
msgstr "Publiée le"
|
msgstr "Publiée le"
|
||||||
|
|
||||||
#: .\photologue\templates\photologue\upload.html:20
|
#: photologue/templates/photologue/upload.html:22
|
||||||
msgid "Drag and drop photos here"
|
msgid "Drag and drop photos here"
|
||||||
msgstr "Glissez et déposez les photos ici"
|
msgstr "Glissez et déposez les photos ici"
|
||||||
|
|
||||||
#: .\photologue\templates\photologue\upload.html:24
|
#: photologue/templates/photologue/upload.html:26
|
||||||
msgid "Owner will be"
|
msgid "Owner will be"
|
||||||
msgstr "Le propriétaire sera"
|
msgstr "Le propriétaire sera"
|
||||||
|
|
||||||
|
#~ msgid "A gallery with that title already exists."
|
||||||
|
#~ msgstr "Une galerie portant ce nom existe déjà."
|
||||||
|
|
|
||||||
32
photologue/management/commands/create_default_admin.py
Normal file
32
photologue/management/commands/create_default_admin.py
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Create default admin user (admin@localhost / admin) if it does not exist"
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
User = get_user_model()
|
||||||
|
email = "admin@localhost"
|
||||||
|
username = "admin"
|
||||||
|
password = "admin"
|
||||||
|
|
||||||
|
if User.objects.filter(username=username).exists():
|
||||||
|
self.stdout.write("Default admin already exists, skipping.")
|
||||||
|
return
|
||||||
|
|
||||||
|
user = User.objects.create_superuser(username=username, email=email, password=password)
|
||||||
|
|
||||||
|
# Mark the email as verified via allauth
|
||||||
|
try:
|
||||||
|
from allauth.account.models import EmailAddress
|
||||||
|
EmailAddress.objects.create(
|
||||||
|
user=user,
|
||||||
|
email=email,
|
||||||
|
primary=True,
|
||||||
|
verified=True,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
self.stderr.write(f"Could not create allauth EmailAddress: {e}")
|
||||||
|
|
||||||
|
self.stdout.write(f"Default admin created: {username} / {password}")
|
||||||
45
photologue/migrations/0008_photo_dimensions.py
Normal file
45
photologue/migrations/0008_photo_dimensions.py
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import photologue.models
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def fill_dimensions(apps, schema_editor):
|
||||||
|
Photo = apps.get_model('photologue', 'Photo')
|
||||||
|
for photo in Photo.objects.filter(image_width__isnull=True).iterator():
|
||||||
|
try:
|
||||||
|
photo.image_width = photo.image.width
|
||||||
|
photo.image_height = photo.image.height
|
||||||
|
photo.save(update_fields=['image_width', 'image_height'])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('photologue', '0007_allow_duplicate_photo_titles'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='photo',
|
||||||
|
name='image_width',
|
||||||
|
field=models.PositiveIntegerField(editable=False, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='photo',
|
||||||
|
name='image_height',
|
||||||
|
field=models.PositiveIntegerField(editable=False, null=True),
|
||||||
|
),
|
||||||
|
migrations.RunPython(fill_dimensions, migrations.RunPython.noop),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='photo',
|
||||||
|
name='image',
|
||||||
|
field=models.ImageField(
|
||||||
|
max_length=100,
|
||||||
|
upload_to=photologue.models.get_storage_path,
|
||||||
|
verbose_name='image',
|
||||||
|
width_field='image_width',
|
||||||
|
height_field='image_height',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
31
photologue/migrations/0009_regen_thumbnails.py
Normal file
31
photologue/migrations/0009_regen_thumbnails.py
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def regen_thumbnails(apps, schema_editor):
|
||||||
|
Photo = apps.get_model("photologue", "Photo")
|
||||||
|
PhotoSize = apps.get_model("photologue", "PhotoSize")
|
||||||
|
|
||||||
|
try:
|
||||||
|
thumbnail = PhotoSize.objects.get(name="thumbnail")
|
||||||
|
except PhotoSize.DoesNotExist:
|
||||||
|
return
|
||||||
|
|
||||||
|
for photo in Photo.objects.all():
|
||||||
|
# Use the real model instance to access file methods
|
||||||
|
from photologue.models import Photo as RealPhoto
|
||||||
|
try:
|
||||||
|
real_photo = RealPhoto.objects.get(pk=photo.pk)
|
||||||
|
real_photo.remove_size(thumbnail)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("photologue", "0008_photo_dimensions"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(regen_thumbnails, migrations.RunPython.noop),
|
||||||
|
]
|
||||||
|
|
@ -486,7 +486,7 @@ class ImageModel(models.Model):
|
||||||
self._old_image.storage.delete(
|
self._old_image.storage.delete(
|
||||||
self._old_image.name
|
self._old_image.name
|
||||||
) # Delete (old) base image.
|
) # Delete (old) base image.
|
||||||
if self.date_taken is None or image_has_changed:
|
if (self.date_taken is None or image_has_changed) and self.image:
|
||||||
# Attempt to get the date the photo was taken from the EXIF data.
|
# Attempt to get the date the photo was taken from the EXIF data.
|
||||||
try:
|
try:
|
||||||
exif_date = self.exif(self.image.file).get(
|
exif_date = self.exif(self.image.file).get(
|
||||||
|
|
|
||||||
|
|
@ -34,20 +34,20 @@ lgContainer.addEventListener('lgAfterOpen', () => {
|
||||||
|
|
||||||
const downloadUrl = this.getAttribute('href');
|
const downloadUrl = this.getAttribute('href');
|
||||||
|
|
||||||
// Affichage de la modale stylisée
|
// // Affichage de la modale stylisée
|
||||||
Swal.fire({
|
// Swal.fire({
|
||||||
title: gettext('Download'),
|
// title: gettext('Download'),
|
||||||
text: gettext("This image is free to download, but permission from the photographer and the people in the photo is required before republishing it on another website. Furthermore, it is good practice to credit L[ENS] and the photographers in any republications."),
|
// text: gettext("This image is free to download, but permission from the photographer and the people in the photo is required before republishing it on another website. Furthermore, it is good practice to credit L[ENS] and the photographers in any republications."),
|
||||||
icon: 'info',
|
// icon: 'info',
|
||||||
showCancelButton: true,
|
// showCancelButton: true,
|
||||||
confirmButtonColor: '#3085d6',
|
// confirmButtonColor: '#3085d6',
|
||||||
cancelButtonColor: '#d33',
|
// cancelButtonColor: '#d33',
|
||||||
confirmButtonText: gettext('Download'),
|
// confirmButtonText: gettext('Download'),
|
||||||
cancelButtonText: gettext('Cancel'),
|
// cancelButtonText: gettext('Cancel'),
|
||||||
background: '#1a1a1a', // Optionnel : pour matcher le thème sombre de LightGallery
|
// background: '#1a1a1a', // Optionnel : pour matcher le thème sombre de LightGallery
|
||||||
color: '#fff'
|
// color: '#fff'
|
||||||
}).then((result) => {
|
// }).then((result) => {
|
||||||
if (result.isConfirmed) {
|
// if (result.isConfirmed) {
|
||||||
// Si validé, on déclenche le téléchargement
|
// Si validé, on déclenche le téléchargement
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = downloadUrl;
|
link.href = downloadUrl;
|
||||||
|
|
@ -55,8 +55,8 @@ lgContainer.addEventListener('lgAfterOpen', () => {
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
link.click();
|
link.click();
|
||||||
document.body.removeChild(link);
|
document.body.removeChild(link);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}, true); // Utilisation du mode capture pour intercepter avant le script interne
|
}, true); // Utilisation du mode capture pour intercepter avant le script interne
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
75
photologue/static/gallery_justified.js
Normal file
75
photologue/static/gallery_justified.js
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
// Justified photo grid layout (Google Photos style)
|
||||||
|
// Inspired by https://github.com/flickr/justified-layout
|
||||||
|
(function () {
|
||||||
|
const SPACING = 3;
|
||||||
|
const PADDING = 3;
|
||||||
|
|
||||||
|
function applyLayout() {
|
||||||
|
const container = document.getElementById('lightgallery');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const items = [...container.querySelectorAll('.photo-item')];
|
||||||
|
if (!items.length) return;
|
||||||
|
|
||||||
|
const containerWidth = container.offsetWidth - PADDING * 2;
|
||||||
|
const TARGET_HEIGHT = Math.min(220, Math.floor(containerWidth * 0.4));
|
||||||
|
const ratios = items.map(a => {
|
||||||
|
const w = parseInt(a.dataset.width) || 1;
|
||||||
|
const h = parseInt(a.dataset.height) || 1;
|
||||||
|
return w / h;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Group items into rows
|
||||||
|
const rows = [];
|
||||||
|
let rowStart = 0;
|
||||||
|
while (rowStart < ratios.length) {
|
||||||
|
let sum = 0;
|
||||||
|
let end = rowStart;
|
||||||
|
while (end < ratios.length) {
|
||||||
|
sum += ratios[end];
|
||||||
|
const rowWidth = sum * TARGET_HEIGHT + SPACING * (end - rowStart);
|
||||||
|
end++;
|
||||||
|
if (rowWidth >= containerWidth) break;
|
||||||
|
}
|
||||||
|
rows.push({ start: rowStart, end });
|
||||||
|
rowStart = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position each item
|
||||||
|
let top = PADDING;
|
||||||
|
rows.forEach(({ start, end }, rowIndex) => {
|
||||||
|
const isLastRow = rowIndex === rows.length - 1;
|
||||||
|
const count = end - start;
|
||||||
|
const sumRatios = ratios.slice(start, end).reduce((a, b) => a + b, 0);
|
||||||
|
const totalSpacing = SPACING * (count - 1);
|
||||||
|
|
||||||
|
// Don't stretch the last row if it's not full
|
||||||
|
const rowHeight = isLastRow && count < 3
|
||||||
|
? TARGET_HEIGHT
|
||||||
|
: (containerWidth - totalSpacing) / sumRatios;
|
||||||
|
|
||||||
|
let left = PADDING;
|
||||||
|
for (let i = start; i < end; i++) {
|
||||||
|
const width = Math.round(ratios[i] * rowHeight);
|
||||||
|
const height = Math.round(rowHeight);
|
||||||
|
const item = items[i];
|
||||||
|
item.style.top = top + 'px';
|
||||||
|
item.style.left = left + 'px';
|
||||||
|
item.style.width = width + 'px';
|
||||||
|
item.style.height = height + 'px';
|
||||||
|
left += width + SPACING;
|
||||||
|
}
|
||||||
|
top += Math.round(rowHeight) + SPACING;
|
||||||
|
});
|
||||||
|
|
||||||
|
container.style.height = (top - SPACING + PADDING) + 'px';
|
||||||
|
items.forEach(item => {
|
||||||
|
item.style.visibility = 'visible';
|
||||||
|
const img = item.querySelector('img[data-lazy]');
|
||||||
|
if (img) img.src = img.dataset.lazy;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', applyLayout);
|
||||||
|
window.addEventListener('resize', applyLayout);
|
||||||
|
})();
|
||||||
|
|
@ -13,6 +13,7 @@ class lgAdmin {
|
||||||
this.isStaff = document.querySelector('[name=is_staff]').value === "true";
|
this.isStaff = document.querySelector('[name=is_staff]').value === "true";
|
||||||
this.userId = document.querySelector('[name=user_id]').value;
|
this.userId = document.querySelector('[name=user_id]').value;
|
||||||
this.canResolveCensorship = document.querySelector('[name=can_resolve_censorship]').value === "true";
|
this.canResolveCensorship = document.querySelector('[name=can_resolve_censorship]').value === "true";
|
||||||
|
this.guestMode = document.querySelector('[name=guest_mode]').value === "true";
|
||||||
this.csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
|
this.csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
|
||||||
this.photoId = 0;
|
this.photoId = 0;
|
||||||
return this;
|
return this;
|
||||||
|
|
@ -33,7 +34,8 @@ class lgAdmin {
|
||||||
document.getElementById("lg-delete").addEventListener('click', this.onDelete.bind(this));
|
document.getElementById("lg-delete").addEventListener('click', this.onDelete.bind(this));
|
||||||
|
|
||||||
// Add button to report photo
|
// Add button to report photo
|
||||||
this.core.$toolbar.append(`<a href="#" id="lg-report" title="Notify abuse" class="lg-icon lg-bi-icon">${reportIcon}</a>`);
|
this.core.$toolbar.append(`<a href="#" id="lg-report" title="Request removal" class="lg-icon lg-bi-icon">${deleteIcon}</a>`);
|
||||||
|
document.getElementById("lg-report").style.display = 'none';
|
||||||
document.getElementById("lg-report").addEventListener('click', this.onReport.bind(this));
|
document.getElementById("lg-report").addEventListener('click', this.onReport.bind(this));
|
||||||
|
|
||||||
// Add button to restore a censored photo
|
// Add button to restore a censored photo
|
||||||
|
|
@ -53,6 +55,7 @@ class lgAdmin {
|
||||||
const ownerId = el ? el.dataset.ownerId : null;
|
const ownerId = el ? el.dataset.ownerId : null;
|
||||||
const canDelete = this.isStaff || (ownerId && ownerId === this.userId);
|
const canDelete = this.isStaff || (ownerId && ownerId === this.userId);
|
||||||
document.getElementById("lg-delete").style.display = canDelete ? 'block' : 'none';
|
document.getElementById("lg-delete").style.display = canDelete ? 'block' : 'none';
|
||||||
|
document.getElementById("lg-report").style.display = canDelete ? 'none' : 'block';
|
||||||
const isCensored = el ? el.dataset.isPublic === 'false' : false;
|
const isCensored = el ? el.dataset.isPublic === 'false' : false;
|
||||||
document.getElementById("lg-restore").style.display = (this.canResolveCensorship && isCensored) ? 'block' : 'none';
|
document.getElementById("lg-restore").style.display = (this.canResolveCensorship && isCensored) ? 'block' : 'none';
|
||||||
}
|
}
|
||||||
|
|
@ -135,7 +138,7 @@ class lgAdmin {
|
||||||
// Event called when user click on report button
|
// Event called when user click on report button
|
||||||
onReport(event) {
|
onReport(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if(confirm("Are you sure to report this photo?")) {
|
if(confirm("Are you sure to ask removal for this photo?")) {
|
||||||
// Build form request
|
// Build form request
|
||||||
const photoId = this.photoId;
|
const photoId = this.photoId;
|
||||||
const currentIndex = this.core.index;
|
const currentIndex = this.core.index;
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,13 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
<input type="hidden" name="is_staff" value="{{ request.user.is_staff|yesno:'true,false' }}">
|
<input type="hidden" name="is_staff" value="{{ request.user.is_staff|yesno:'true,false' }}">
|
||||||
<input type="hidden" name="user_id" value="{{ request.user.id }}">
|
<input type="hidden" name="user_id" value="{{ request.user.id }}">
|
||||||
<input type="hidden" name="can_resolve_censorship" value="{{ can_resolve_censorship|yesno:'true,false' }}">
|
<input type="hidden" name="can_resolve_censorship" value="{{ can_resolve_censorship|yesno:'true,false' }}">
|
||||||
|
<input type="hidden" name="guest_mode" value="{{ guest_mode|yesno:'true,false' }}">
|
||||||
<script src="{% static 'lightgallery/lightgallery.min.js' %}"></script>
|
<script src="{% static 'lightgallery/lightgallery.min.js' %}"></script>
|
||||||
<script src="{% static 'lightgallery/plugins/admin/lg-admin.js' %}"></script>
|
<script src="{% static 'lightgallery/plugins/admin/lg-admin.js' %}"></script>
|
||||||
<script src="{% static 'lightgallery/plugins/hash/lg-hash.min.js' %}"></script>
|
<script src="{% static 'lightgallery/plugins/hash/lg-hash.min.js' %}"></script>
|
||||||
<script src="{% static 'lightgallery/plugins/thumbnail/lg-thumbnail.min.js' %}"></script>
|
<script src="{% static 'lightgallery/plugins/thumbnail/lg-thumbnail.min.js' %}"></script>
|
||||||
<script src="{% static 'lightgallery/plugins/zoom/lg-zoom.min.js' %}"></script>
|
<script src="{% static 'lightgallery/plugins/zoom/lg-zoom.min.js' %}"></script>
|
||||||
|
<script src="{% static 'gallery_justified.js' %}"></script>
|
||||||
<script src="{% static 'gallery_detail.js' %}"></script>
|
<script src="{% static 'gallery_detail.js' %}"></script>
|
||||||
<script src="{% static 'sweetalert.js' %}"></script>
|
<script src="{% static 'sweetalert.js' %}"></script>
|
||||||
<script src="{% static 'copy-button.js' %}"></script>
|
<script src="{% static 'copy-button.js' %}"></script>
|
||||||
|
|
@ -87,10 +89,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="card-body row" id="lightgallery">
|
<div class="card-body p-0" id="lightgallery">
|
||||||
{% for photo in photos %}
|
{% for photo in photos %}
|
||||||
<a class="col-6 col-md-3 mb-2 text-center" href="{{ photo.get_absolute_url }}" data-src="{{ photo.get_display_url}}" data-download-url="{{ photo.image.url }}" data-slide-name="{{ photo.id }}" data-owner-id="{{ photo.owner.id }}" data-is-public="{{ photo.is_public|yesno:'true,false' }}">
|
<a class="photo-item" href="{{ photo.get_absolute_url }}" data-src="{{ photo.get_display_url}}" data-download-url="{{ photo.image.url }}" data-slide-name="{{ photo.id }}" data-owner-id="{{ photo.owner.id }}" data-is-public="{{ photo.is_public|yesno:'true,false' }}" data-width="{{ photo.image_width|default:1 }}" data-height="{{ photo.image_height|default:1 }}">
|
||||||
<img src="{{ photo.get_thumbnail_url }}" loading="lazy" class="img-thumbnail p-0{% if not photo.is_public %} border-danger border-5{% endif %}" alt="{{ photo.title }}{% if photo.date_taken %} - {{ photo.date_taken|date }} {{ photo.date_taken|time }}{% endif %}{% if photo.owner.get_full_name %} - {{ photo.owner.get_full_name }}{% else %} - {{ photo.owner.username }}{% endif %}{% if photo.license %} - {{ photo.license }}{% endif %}{% if not photo.is_public %} - !PRIVATE!{% endif %}">
|
<img src="{{ photo.get_thumbnail_url }}" data-lazy="{{ photo.get_thumbnail_url }}" class="{% if not photo.is_public %}photo-private{% endif %}" alt="{{ photo.title }}{% if photo.date_taken %} - {{ photo.date_taken|date }} {{ photo.date_taken|time }}{% endif %}{% if photo.owner.get_full_name %} - {{ photo.owner.get_full_name }}{% else %} - {{ photo.owner.username }}{% endif %}{% if photo.license %} - {{ photo.license }}{% endif %}{% if not photo.is_public %} - !PRIVATE!{% endif %}">
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -237,6 +237,7 @@ class GalleryPublicView(DetailView):
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
gallery = self.get_object()
|
gallery = self.get_object()
|
||||||
return redirect("photologue:pl-gallery", slug=gallery.slug)
|
return redirect("photologue:pl-gallery", slug=gallery.slug)
|
||||||
|
request.session['public_gallery_access'] = True
|
||||||
request.guest_mode = True
|
request.guest_mode = True
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,7 @@ ExifRead>=2.1.2
|
||||||
Pillow>=6.0.0
|
Pillow>=6.0.0
|
||||||
django-debug-toolbar>=3.2.0
|
django-debug-toolbar>=3.2.0
|
||||||
python-decouple>=3.6
|
python-decouple>=3.6
|
||||||
|
whitenoise>=6.0
|
||||||
|
psycopg2-binary>=2.9
|
||||||
|
requests>=2.25
|
||||||
|
gunicorn>=21.0
|
||||||
|
|
|
||||||
2
tox.ini
2
tox.ini
|
|
@ -27,7 +27,7 @@ deps =
|
||||||
pep8-naming
|
pep8-naming
|
||||||
pyflakes
|
pyflakes
|
||||||
commands =
|
commands =
|
||||||
flake8 allauth_note_kfet photo21 photologue
|
flake8 allauth_oauth photo21 photologue
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
ignore = W503, I100, I101
|
ignore = W503, I100, I101
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue