Compare commits
13 commits
737f073287
...
5d0ea49318
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d0ea49318 | ||
|
|
5d8adc409e | ||
|
|
d8f114d931 | ||
|
|
429c44d14a | ||
|
|
0e1b1f5a35 | ||
|
|
75296c903a | ||
|
|
712d9a843e | ||
|
|
9b5fe1627d | ||
|
|
ddced4e2bc | ||
|
|
8458182990 | ||
|
|
92e1336f80 | ||
|
|
aa348d2b04 | ||
|
|
931c264a44 |
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
|
||||
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)
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
```bash
|
||||
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.**
|
||||
Change directory to where you want the project to be.
|
||||
In production, we usually use `/var/www/photos/` as the `root` user.
|
||||
3. **Configuration.**
|
||||
|
||||
```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 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 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 production, we use PostgreSQL which require a bit of setup:
|
||||
|
||||
```bash
|
||||
sudo apt install postgresql postgresql-contrib
|
||||
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;
|
||||
```
|
||||
|
||||
5. **Initialization.**,
|
||||
In production, please use `www-data` user.
|
||||
5. **Initialization.**
|
||||
|
||||
```
|
||||
./manage.py collectstatic
|
||||
|
|
@ -69,19 +126,14 @@ run and to maintain.
|
|||
# Only when creating a new database
|
||||
./manage.py loaddata initial
|
||||
./manage.py createsuperuser
|
||||
# change DEBUG to True in photo21/settings.py
|
||||
```
|
||||
|
||||
6. **Maintenance Mode.**,
|
||||
In production to toggle the server mainteance mode
|
||||
6. **Maintenance Mode.**
|
||||
In production to toggle the server maintenance mode
|
||||
|
||||
```./maintenance_tool.sh```
|
||||
|
||||
|
||||
6. *Enjoy \o/*
|
||||
|
||||
In production, the NGINX site should now work.
|
||||
In development, you can launch the development server using:
|
||||
7. *Enjoy \o/*
|
||||
|
||||
```bash
|
||||
(env)$ ./manage.py runserver
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
# 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
|
||||
|
||||
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
|
||||
|
||||
|
||||
class NoteKfetAccount(ProviderAccount):
|
||||
class OAuthAccount(ProviderAccount):
|
||||
def to_str(self):
|
||||
return self.account.extra_data.get("username")
|
||||
|
||||
|
||||
class NoteKfetProvider(OAuth2Provider):
|
||||
id = "notekfet"
|
||||
name = "Note Kfet"
|
||||
account_class = NoteKfetAccount
|
||||
class OAuthProvider(OAuth2Provider):
|
||||
id = "oauth"
|
||||
name = "OAuth"
|
||||
account_class = OAuthAccount
|
||||
|
||||
def extract_uid(self, data):
|
||||
return str(data["username"])
|
||||
|
|
@ -39,4 +39,4 @@ class NoteKfetProvider(OAuth2Provider):
|
|||
return ret
|
||||
|
||||
|
||||
provider_classes = [NoteKfetProvider]
|
||||
provider_classes = [OAuthProvider]
|
||||
|
|
@ -4,6 +4,6 @@
|
|||
|
||||
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,
|
||||
)
|
||||
|
||||
from .provider import NoteKfetProvider
|
||||
from .provider import OAuthProvider
|
||||
|
||||
|
||||
class NoteKfetOAuth2Adapter(OAuth2Adapter):
|
||||
provider_id = NoteKfetProvider.id
|
||||
class OAuthAdapter(OAuth2Adapter):
|
||||
provider_id = OAuthProvider.id
|
||||
|
||||
def complete_login(self, request, app, token, **kwargs):
|
||||
headers = {
|
||||
|
|
@ -31,7 +31,7 @@ class NoteKfetOAuth2Adapter(OAuth2Adapter):
|
|||
|
||||
@property
|
||||
def domain(self):
|
||||
return self.settings.get("DOMAIN", "note.crans.org")
|
||||
return self.settings.get("DOMAIN", "")
|
||||
|
||||
@property
|
||||
def access_token_url(self):
|
||||
|
|
@ -46,5 +46,5 @@ class NoteKfetOAuth2Adapter(OAuth2Adapter):
|
|||
return f"https://{self.domain}/api/me/"
|
||||
|
||||
|
||||
oauth2_login = OAuth2LoginView.adapter_view(NoteKfetOAuth2Adapter)
|
||||
oauth2_callback = OAuth2CallbackView.adapter_view(NoteKfetOAuth2Adapter)
|
||||
oauth2_login = OAuth2LoginView.adapter_view(OAuthAdapter)
|
||||
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,
|
||||
"quality": 70,
|
||||
"upscale": false,
|
||||
"crop": true,
|
||||
"crop": false,
|
||||
"pre_cache": true,
|
||||
"increment_count": false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,7 @@ class CustomSignupForm(SignupForm):
|
|||
|
||||
# Add description on email field
|
||||
self.fields["email"].help_text = _(
|
||||
"Please enter a valid email address ending with `@crans.org` or "
|
||||
"`@ens-paris-saclay.fr`."
|
||||
"Please enter a valid email address ending with `@ens-rennes.fr`"
|
||||
)
|
||||
|
||||
def clean_email(self):
|
||||
|
|
@ -22,10 +21,8 @@ class CustomSignupForm(SignupForm):
|
|||
Check that the email address ends with a trusted domain.
|
||||
"""
|
||||
email = super().clean_email()
|
||||
if not email.endswith("@crans.org") and not email.endswith(
|
||||
"@ens-paris-saclay.fr"
|
||||
):
|
||||
if not email.endswith("@ens-rennes.fr"):
|
||||
raise forms.ValidationError(
|
||||
_("Must end with `@crans.org` or `@ens-paris-saclay.fr`.")
|
||||
_("Must end with `@ens-rennes.fr`.")
|
||||
)
|
||||
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
|
||||
# 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"
|
||||
"Project-Id-Version: photo21\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"
|
||||
"Language-Team: French\n"
|
||||
"Language: fr\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 ""
|
||||
"Veuillez entrer une adresse email valide finissant par `@crans.org` ou `@ens-"
|
||||
"paris-saclay.fr`."
|
||||
#: photo21/forms.py:16
|
||||
msgid "Please enter a valid email address ending with `@ens-rennes.fr`"
|
||||
msgstr "Veuillez entrer une adresse e-mail valide finissant par `@ens-rennes.fr`."
|
||||
|
||||
#: .\photo21\forms.py:29
|
||||
msgid "Must end with `@crans.org` or `@ens-paris-saclay.fr`."
|
||||
msgstr "Doit finir par `@crans.org` ou `@ens-paris-saclay.fr`."
|
||||
#: photo21/forms.py:26
|
||||
msgid "Must end with `@ens-rennes.fr`."
|
||||
msgstr "Doit finir par `@ens-rennes.fr`."
|
||||
|
||||
#: .\photo21\settings.py:171
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: .\photo21\settings.py:172
|
||||
#: photo21/settings.py:201
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
msgstr "Anglais"
|
||||
|
||||
#: .\photo21\settings.py:173
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: .\photo21\settings.py:174
|
||||
#: photo21/settings.py:202
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
msgstr "Français"
|
||||
|
||||
#: .\photo21\templates\400.html:12
|
||||
#: photo21/templates/400.html:12
|
||||
msgid "Bad request"
|
||||
msgstr ""
|
||||
msgstr "Requête incorrecte"
|
||||
|
||||
#: .\photo21\templates\400.html:16
|
||||
#: 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 ""
|
||||
"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"
|
||||
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."
|
||||
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:"
|
||||
msgstr ""
|
||||
msgstr "Message d'exception :"
|
||||
|
||||
#: .\photo21\templates\404.html:12
|
||||
#: photo21/templates/404.html:12
|
||||
msgid "Page not found"
|
||||
msgstr ""
|
||||
msgstr "Page introuvable"
|
||||
|
||||
#: .\photo21\templates\404.html:16
|
||||
#: photo21/templates/404.html:16
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The requested path <code>%(request_path)s</code> was not found on the server."
|
||||
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"
|
||||
msgstr ""
|
||||
msgstr "Erreur serveur"
|
||||
|
||||
#: .\photo21\templates\500.html:16
|
||||
#: 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 ""
|
||||
"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:16
|
||||
#: .\photo21\templates\socialaccount\connections.html:16
|
||||
#: photo21/templates/account/email.html:8
|
||||
#: photo21/templates/account/email.html:16
|
||||
#: photo21/templates/socialaccount/connections.html:16
|
||||
msgid "E-mail Addresses"
|
||||
msgstr ""
|
||||
msgstr "Adresses e-mail"
|
||||
|
||||
#: .\photo21\templates\account\email.html:11 .\photo21\templates\base.html:63
|
||||
#: .\photo21\templates\socialaccount\connections.html:11
|
||||
#: photo21/templates/account/email.html:11 photo21/templates/base.html:66
|
||||
#: photo21/templates/socialaccount/connections.html:11
|
||||
msgid "Account"
|
||||
msgstr "Compte"
|
||||
|
||||
#: .\photo21\templates\account\email.html:19
|
||||
#: .\photo21\templates\socialaccount\connections.html:19
|
||||
#: photo21/templates/account/email.html:19
|
||||
#: photo21/templates/socialaccount/connections.html:19
|
||||
msgid "Social connections"
|
||||
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:"
|
||||
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"
|
||||
msgstr ""
|
||||
msgstr "Vérifié"
|
||||
|
||||
#: .\photo21\templates\account\email.html:38
|
||||
#: photo21/templates/account/email.html:38
|
||||
msgid "Unverified"
|
||||
msgstr ""
|
||||
msgstr "Non vérifié"
|
||||
|
||||
#: .\photo21\templates\account\email.html:40
|
||||
#: photo21/templates/account/email.html:40
|
||||
msgid "Primary"
|
||||
msgstr ""
|
||||
msgstr "Principal"
|
||||
|
||||
#: .\photo21\templates\account\email.html:46
|
||||
#: photo21/templates/account/email.html:46
|
||||
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"
|
||||
msgstr ""
|
||||
msgstr "Renvoyer la vérification"
|
||||
|
||||
#: .\photo21\templates\account\email.html:48
|
||||
#: .\photo21\templates\socialaccount\connections.html:47
|
||||
#: photo21/templates/account/email.html:48
|
||||
#: photo21/templates/socialaccount/connections.html:47
|
||||
msgid "Remove"
|
||||
msgstr ""
|
||||
msgstr "Supprimer"
|
||||
|
||||
#: .\photo21\templates\account\email.html:53
|
||||
#: photo21/templates/account/email.html:53
|
||||
msgid "Warning:"
|
||||
msgstr ""
|
||||
msgstr "Avertissement :"
|
||||
|
||||
#: .\photo21\templates\account\email.html:53
|
||||
#: 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 ""
|
||||
"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"
|
||||
msgstr "Ajouter une Adresse E-mail"
|
||||
|
||||
#: .\photo21\templates\account\email.html:62
|
||||
#: photo21/templates/account/email.html:62
|
||||
msgid "Add E-mail"
|
||||
msgstr "Ajouter un E-mail"
|
||||
|
||||
#: .\photo21\templates\account\login.html:8
|
||||
#: .\photo21\templates\account\login.html:36
|
||||
#: photo21/templates/account/login.html:8
|
||||
#: photo21/templates/account/login.html:36
|
||||
msgid "Sign In"
|
||||
msgstr "Se Connecter"
|
||||
|
||||
#: .\photo21\templates\account\login.html:19
|
||||
#: photo21/templates/account/login.html:19
|
||||
#, python-format
|
||||
msgid ""
|
||||
"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 "
|
||||
"et identifiez-vous ci-dessous :"
|
||||
|
||||
#: .\photo21\templates\account\login.html:26
|
||||
#: photo21/templates/account/login.html:26
|
||||
#, python-format
|
||||
msgid ""
|
||||
"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 "
|
||||
"href=\"%(signup_url)s\">inscrire</a>."
|
||||
|
||||
#: .\photo21\templates\account\login.html:39
|
||||
#: photo21/templates/account/login.html:39
|
||||
msgid "Forgot Password?"
|
||||
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"
|
||||
msgstr "En cas de problème, contactez les administrateurs à"
|
||||
|
||||
#: .\photo21\templates\account\logout.html:8
|
||||
#: .\photo21\templates\account\logout.html:13
|
||||
#: .\photo21\templates\account\logout.html:22
|
||||
#: photo21/templates/account/logout.html:8
|
||||
#: photo21/templates/account/logout.html:13
|
||||
#: photo21/templates/account/logout.html:22
|
||||
msgid "Sign Out"
|
||||
msgstr "Déconnexion"
|
||||
|
||||
#: .\photo21\templates\account\logout.html:16
|
||||
#: photo21/templates/account/logout.html:16
|
||||
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"
|
||||
msgstr ""
|
||||
msgstr "Inscription"
|
||||
|
||||
#: .\photo21\templates\account\signup.html:13
|
||||
#: .\photo21\templates\account\signup.html:24
|
||||
#: photo21/templates/account/signup.html:13
|
||||
#: photo21/templates/account/signup.html:24
|
||||
msgid "Sign Up"
|
||||
msgstr ""
|
||||
msgstr "S'inscrire"
|
||||
|
||||
#: .\photo21\templates\account\signup.html:16
|
||||
#: photo21/templates/account/signup.html:16
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Already have an account? Then please <a href=\"%(login_url)s\">sign in</a>."
|
||||
msgstr ""
|
||||
"Vous avez déjà un compte ? Veuillez alors <a href=\"%(login_url)s\">vous "
|
||||
"connecter</a>."
|
||||
|
||||
#: .\photo21\templates\base.html:16
|
||||
msgid "The ENS Paris-Saclay pictures server."
|
||||
msgstr ""
|
||||
#: photo21/templates/base.html:16
|
||||
msgid "The ENS Rennes pictures server."
|
||||
msgstr "Le serveur photos de l'ENS Rennes."
|
||||
|
||||
#: .\photo21\templates\base.html:41
|
||||
#: photo21/templates/base.html:43
|
||||
msgid "Galleries"
|
||||
msgstr "Galeries"
|
||||
|
||||
#: .\photo21\templates\base.html:46
|
||||
#: photo21/templates/base.html:48
|
||||
msgid "Upload"
|
||||
msgstr "Téléversement"
|
||||
msgstr "Upload"
|
||||
|
||||
#: .\photo21\templates\base.html:51
|
||||
#: photo21/templates/base.html:53
|
||||
msgid "Manage"
|
||||
msgstr "Gestion"
|
||||
msgstr "Manage"
|
||||
|
||||
#: .\photo21\templates\base.html:72
|
||||
#: photo21/templates/base.html:75
|
||||
msgid "Log out"
|
||||
msgstr ""
|
||||
msgstr "Déconnection"
|
||||
|
||||
#: .\photo21\templates\base.html:82
|
||||
#: photo21/templates/base.html:85
|
||||
msgid "Log in"
|
||||
msgstr ""
|
||||
msgstr "Connection"
|
||||
|
||||
#: .\photo21\templates\base.html:91
|
||||
#: photo21/templates/base.html:94
|
||||
msgid "Sign up"
|
||||
msgstr "Inscription"
|
||||
|
||||
#: .\photo21\templates\base.html:116
|
||||
#: photo21/templates/base.html:119
|
||||
msgid "Connected as"
|
||||
msgstr "Connecté en tant que"
|
||||
|
||||
#: .\photo21\templates\base.html:118
|
||||
#: photo21/templates/base.html:121
|
||||
msgid "Source code"
|
||||
msgstr "Code source"
|
||||
|
||||
#: .\photo21\templates\index.html:8
|
||||
#: photo21/templates/index.html:8
|
||||
msgid "Home"
|
||||
msgstr "Accueil"
|
||||
|
||||
#: .\photo21\templates\index.html:11
|
||||
#: photo21/templates/index.html:11
|
||||
msgid "Welcome to the pictures server!"
|
||||
msgstr "Bienvenue sur le serveur photos !"
|
||||
|
||||
#: .\photo21\templates\index.html:13
|
||||
#: 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."
|
||||
"life of ENS Rennes or involving its students."
|
||||
msgstr ""
|
||||
"Ce site à pour objectif de recenser les photos et films pris dans la vie "
|
||||
"associative de l'ENS Paris-Saclay ou impliquant ses usager·ères."
|
||||
"Ce site a pour objectif de recenser les photos et films pris dans la vie "
|
||||
"associative de l'ENS Rennes ou impliquant ses étudiant·es."
|
||||
|
||||
#: .\photo21\templates\index.html:20
|
||||
#: photo21/templates/index.html:20
|
||||
#, python-format
|
||||
msgid ""
|
||||
"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 "
|
||||
"toute republication sur un autre site.</b>"
|
||||
|
||||
#: .\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 ""
|
||||
"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
|
||||
#: photo21/templates/index.html:30
|
||||
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 "
|
||||
"href=\"mailto:sinfonie@ens-rennes.fr?subject=[Photographe] Demande de droits "
|
||||
"photographe\" class=\"btn btn-secondary btn-sm\">Become a photograph</a>"
|
||||
msgstr ""
|
||||
"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-"
|
||||
"secondary btn-sm\">Devenir photographe</a>"
|
||||
|
||||
#: .\photo21\templates\index.html:43
|
||||
#: photo21/templates/index.html:38
|
||||
msgid "Last galleries"
|
||||
msgstr "Galeries récentes"
|
||||
|
||||
#: .\photo21\templates\index.html:52
|
||||
#: photo21/templates/index.html:47
|
||||
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 ""
|
||||
"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:"
|
||||
"<!-- This project if a fork of <a href=\"https://gitlab.crans.org/bde/"
|
||||
"photo21/\">Photo21</a>. --> <!-- 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 <a "
|
||||
"href=\"https://sinfonie.org/\">Sinfonie</a> at the ENS Rennes. <!-- Current "
|
||||
"active administrators are: --> <!--"
|
||||
msgstr ""
|
||||
"Comme nous accordons une grande importance au respect de la vie privée, nous "
|
||||
"ne vendons pas les données de ce site, contrairement à de nombreuses "
|
||||
"plateformes en ligne gratuites. Le serveur dédié qui fait fonctionner ce "
|
||||
"site est gentiment hébergé par le <a href=\"https://www.crans.org/\">Crans</"
|
||||
"a> au sous-sol de l'ENS Paris-Saclay. <b>Il n'est pas géré par le Crans.</b> "
|
||||
"Les administrateur·trices sont :"
|
||||
"<!-- Ce projet est un fork de <a href=\"https://gitlab.crans.org/bde/"
|
||||
"photo21/\">Photo21</a>. --> <!-- Comme nous accordons une grande importance "
|
||||
"au respect de la vie privée, nous ne vendons pas les données de ce site, --> "
|
||||
"<!-- contrairement à de nombreuses plateformes en ligne gratuites. --> "
|
||||
"Le serveur qui fait fonctionner ce site est gentiment hébergé par "
|
||||
"<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"
|
||||
msgstr "Ils peuvent être contactés à"
|
||||
|
||||
#: .\photo21\templates\socialaccount\connections.html:8
|
||||
#: photo21/templates/socialaccount/connections.html:8
|
||||
msgid "Account Connections"
|
||||
msgstr ""
|
||||
msgstr "Connexions de compte"
|
||||
|
||||
#: .\photo21\templates\socialaccount\connections.html:25
|
||||
#: photo21/templates/socialaccount/connections.html:25
|
||||
msgid ""
|
||||
"You can sign in to your account using any of the following third party "
|
||||
"accounts:"
|
||||
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 ""
|
||||
"You currently have no social network accounts connected to this account."
|
||||
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"
|
||||
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"
|
||||
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
|
||||
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
|
||||
SECURE_HSTS_SECONDS = 31536000
|
||||
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
|
||||
|
|
@ -53,6 +56,15 @@ SECURE_HSTS_PRELOAD = True
|
|||
|
||||
# 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 = [
|
||||
"django.contrib.admin",
|
||||
"django.contrib.admindocs",
|
||||
|
|
@ -66,17 +78,20 @@ INSTALLED_APPS = [
|
|||
"allauth",
|
||||
"allauth.account",
|
||||
"allauth.socialaccount",
|
||||
"allauth_note_kfet",
|
||||
"crispy_forms",
|
||||
"photologue",
|
||||
"photo21",
|
||||
]
|
||||
|
||||
if OAUTH_ENABLED:
|
||||
INSTALLED_APPS += ["allauth_oauth"]
|
||||
|
||||
if DEBUG:
|
||||
INSTALLED_APPS += ["debug_toolbar",] # For debug and optimisations
|
||||
|
||||
MIDDLEWARE = [
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"whitenoise.middleware.WhiteNoiseMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
|
|
@ -121,12 +136,31 @@ WSGI_APPLICATION = "photo21.wsgi.application"
|
|||
# Database
|
||||
# https://docs.djangoproject.com/en/2.2/ref/settings/#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": {
|
||||
"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 = {
|
||||
"default": {
|
||||
|
|
@ -164,9 +198,7 @@ USE_TZ = True
|
|||
|
||||
# Limit available languages to this subset
|
||||
LANGUAGES = [
|
||||
("de", _("German")),
|
||||
("en", _("English")),
|
||||
("es", _("Spanish")),
|
||||
("fr", _("French")),
|
||||
]
|
||||
|
||||
|
|
@ -189,9 +221,20 @@ STATIC_ROOT = os.path.join(BASE_DIR, "static/")
|
|||
MEDIA_ROOT = os.path.join(BASE_DIR, "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")]
|
||||
|
||||
FIXTURE_DIRS = [os.path.join(BASE_DIR, "photo21/fixtures")]
|
||||
|
||||
# Do not send email during debug
|
||||
# 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")
|
||||
DEFAULT_FROM_EMAIL = f"Serveur photos <{SERVER_EMAIL}>"
|
||||
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
|
||||
LOGIN_REDIRECT_URL = "/"
|
||||
|
|
@ -221,14 +269,21 @@ MESSAGE_TAGS = {
|
|||
# Allauth configuration ## For the django =< 5.0
|
||||
ACCOUNT_EMAIL_REQUIRED = True
|
||||
# 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_LOGIN_METHODS = {'username', 'email'}
|
||||
ACCOUNT_FORMS = {"signup": "photo21.forms.CustomSignupForm"}
|
||||
|
||||
if OAUTH_ENABLED:
|
||||
SOCIALACCOUNT_ONLY = OAUTH_ONLY
|
||||
SOCIALACCOUNT_PROVIDERS = {
|
||||
"notekfet": {
|
||||
# Fetch user profile
|
||||
"SCOPE": ["1_1"],
|
||||
"oauth": {
|
||||
"SCOPE": OAUTH_SCOPE,
|
||||
"DOMAIN": OAUTH_SERVER_URL,
|
||||
"APP": {
|
||||
"client_id": OAUTH_CLIENT_ID,
|
||||
"secret": OAUTH_CLIENT_SECRET,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,9 @@
|
|||
// Copyright (C) 2022 Amicale des élèves de l'ENS Paris-Saclay
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// On language selection, submit form
|
||||
const langSelect = document.getElementsByName("language")[0];
|
||||
if (langSelect) {
|
||||
langSelect.addEventListener("change", (e) => {
|
||||
e.target.form.submit();
|
||||
document.querySelectorAll('[data-lang]').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
document.getElementById('lang-input').value = btn.dataset.lang;
|
||||
document.getElementById('lang-form').submit();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,29 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||
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 */
|
||||
.lang-select {
|
||||
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>
|
||||
</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 %}
|
||||
|
|
|
|||
|
|
@ -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="Referrer-Policy" content="no-referrer">
|
||||
<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>
|
||||
<link rel="stylesheet" href="{% static "bootstrap5/css/bootstrap.min.css" %}">
|
||||
<link rel="stylesheet" href="{% static "layout.css" %}">
|
||||
|
|
@ -56,6 +56,21 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||
{% endif %}
|
||||
</ul>
|
||||
<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 %}
|
||||
<li class="nav-item">
|
||||
{% url 'account_email' as url %}
|
||||
|
|
@ -112,26 +127,18 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||
{% endblock %}
|
||||
</main>
|
||||
<footer>
|
||||
<form action="{% url 'set_language' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<p class="small text-center text-muted mt-1">
|
||||
{% if request.user.is_authenticated %}
|
||||
{% trans "Connected as" %} <code>{{ request.user.username }}</code> ·
|
||||
{% endif %}
|
||||
<a class="text-reset" href="https://gitlab.crans.org/bde/photo21/">{% 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>
|
||||
<a class="text-reset" href="https://git.sinfonie.org/sinfonie/photo26">{% trans "Source code" %}</a>
|
||||
</p>
|
||||
</form>
|
||||
</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 "bootstrap5/js/bootstrap.bundle.min.js" %}"></script>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
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 %}
|
||||
</p>
|
||||
<p>
|
||||
|
|
@ -25,21 +25,16 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||
{% endblocktrans %}
|
||||
</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 %}
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
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 %}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<h3>{% trans "Last galleries" %}</h3>
|
||||
<div class="row mb-2">
|
||||
{% 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>
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
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:
|
||||
{% endblocktrans %}
|
||||
{% for user in superusers %} <code>{{ user.username }}</code>{% endfor %}.
|
||||
{% trans "They should be contacted at" %}
|
||||
<a href="mailto:photos@crans.org">photos@crans.org</a>.
|
||||
<!-- This project if a fork of <a href="https://gitlab.crans.org/bde/photo21/">Photo21</a>. -->
|
||||
<!-- 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
|
||||
<a href="https://sinfonie.org/">Sinfonie</a> at the ENS Rennes.
|
||||
<!-- Current active administrators are: -->
|
||||
<!-- {% endblocktrans %} -->
|
||||
<!-- {% for user in superusers %} <code>{{ user.username }}</code>{% endfor %}. -->
|
||||
<!-- {% trans "They should be contacted at" %} -->
|
||||
<!-- <a href="mailto:photos@crans.org">photos@crans.org</a>. -->
|
||||
</p>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -2,21 +2,29 @@
|
|||
# Copyright (C) 2021-2022 Amicale des élèves de l'ENS Paris-Saclay
|
||||
# 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.mixins import LoginRequiredMixin
|
||||
from django.http import HttpResponse
|
||||
from django.http import FileResponse, Http404
|
||||
from django.views.generic import ListView, View
|
||||
from photologue.models import Gallery
|
||||
|
||||
|
||||
|
||||
class MediaAccess(LoginRequiredMixin, View):
|
||||
class MediaAccess(View):
|
||||
def get(self, request, path):
|
||||
response = HttpResponse()
|
||||
# Content-type will be detected by nginx
|
||||
del response["Content-Type"]
|
||||
response["X-Accel-Redirect"] = "/protected/media/" + path
|
||||
response["Cache-Control"] = 'max-age=2678400'
|
||||
if not request.user.is_authenticated and not request.session.get('public_gallery_access'):
|
||||
from django.contrib.auth.views import redirect_to_login
|
||||
return redirect_to_login(request.get_full_path())
|
||||
media_root = os.path.realpath(settings.MEDIA_ROOT)
|
||||
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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin import EmptyFieldListFilter
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .models import Gallery, Photo, Tag
|
||||
|
|
@ -10,7 +11,7 @@ from .models import Gallery, Photo, Tag
|
|||
|
||||
class GalleryAdmin(admin.ModelAdmin):
|
||||
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"
|
||||
prepopulated_fields = {"slug": ("title",)}
|
||||
model = Gallery
|
||||
|
|
|
|||
|
|
@ -3,8 +3,25 @@
|
|||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.db.backends.signals import connection_created
|
||||
|
||||
|
||||
class PhotologueConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.AutoField"
|
||||
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 ""
|
||||
"Project-Id-Version: Photologue\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"
|
||||
"Last-Translator: Richard Barran <richard@arbee-design.co.uk>\n"
|
||||
"Language-Team: French (http://www.transifex.com/richardbarran/django-"
|
||||
|
|
@ -21,24 +21,23 @@ msgstr ""
|
|||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: .\photologue\admin.py:26 .\photologue\models.py:168
|
||||
#: .\photologue\models.py:786
|
||||
#: photologue/admin.py:25 photologue/models.py:168 photologue/models.py:802
|
||||
msgid "tags"
|
||||
msgstr "balises"
|
||||
|
||||
#: .\photologue\admin.py:63 .\photologue\forms.py:68
|
||||
#: photologue/admin.py:62 photologue/forms.py:69
|
||||
msgid "Gallery"
|
||||
msgstr "Galerie"
|
||||
|
||||
#: .\photologue\admin.py:67 .\photologue\models.py:529
|
||||
#: photologue/admin.py:66 photologue/models.py:542
|
||||
msgid "owner"
|
||||
msgstr "propriétaire"
|
||||
|
||||
#: .\photologue\forms.py:70
|
||||
#: photologue/forms.py:71
|
||||
msgid "-- Create a new gallery --"
|
||||
msgstr "-- Créer une nouvelle galerie --"
|
||||
|
||||
#: .\photologue\forms.py:72
|
||||
#: photologue/forms.py:73
|
||||
msgid ""
|
||||
"Select a gallery to add these images to. Leave this empty to create a new "
|
||||
"gallery from the supplied title."
|
||||
|
|
@ -46,117 +45,108 @@ msgstr ""
|
|||
"Sélectionner une galerie à laquelle ajouter ces images. Laisser ce champ "
|
||||
"vide pour créer une nouvelle galerie à partir du titre indiqué."
|
||||
|
||||
#: .\photologue\forms.py:77
|
||||
#: photologue/forms.py:78
|
||||
msgid "New gallery title"
|
||||
msgstr "Titre de la nouvelle galerie"
|
||||
|
||||
#: .\photologue\forms.py:82
|
||||
#: photologue/forms.py:83
|
||||
msgid "New gallery event start date"
|
||||
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"
|
||||
msgstr "Date de fin de l'évènement de la nouvelle galerie"
|
||||
|
||||
#: .\photologue\forms.py:93
|
||||
#: photologue/forms.py:96
|
||||
#, fuzzy
|
||||
#| msgid "description"
|
||||
msgid "Description"
|
||||
msgstr "description"
|
||||
|
||||
#: .\photologue\forms.py:98
|
||||
#: photologue/forms.py:101
|
||||
msgid "New gallery tags"
|
||||
msgstr "Balises de la nouvelle galerie"
|
||||
|
||||
#: .\photologue\forms.py:101
|
||||
msgid ""
|
||||
"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
|
||||
#: photologue/forms.py:129 photologue/templates/photologue/upload.html:8
|
||||
#: photologue/templates/photologue/upload.html:17
|
||||
msgid "Upload"
|
||||
msgstr "Télécharger"
|
||||
|
||||
#: .\photologue\forms.py:126
|
||||
msgid "A gallery with that title already exists."
|
||||
msgstr "Une galerie portant ce nom existe déjà."
|
||||
|
||||
#: .\photologue\forms.py:138
|
||||
#: photologue/forms.py:141
|
||||
msgid "Select an existing gallery, or enter a title for a new gallery."
|
||||
msgstr ""
|
||||
"Sélectionner une galerie existante ou entrer un titre pour une nouvelle "
|
||||
"galerie."
|
||||
|
||||
#: .\photologue\models.py:88
|
||||
#: photologue/models.py:88
|
||||
msgid "Very Low"
|
||||
msgstr "Très Bas"
|
||||
|
||||
#: .\photologue\models.py:89
|
||||
#: photologue/models.py:89
|
||||
msgid "Low"
|
||||
msgstr "Bas"
|
||||
|
||||
#: .\photologue\models.py:90
|
||||
#: photologue/models.py:90
|
||||
msgid "Medium-Low"
|
||||
msgstr "Moyen-Bas"
|
||||
|
||||
#: .\photologue\models.py:91
|
||||
#: photologue/models.py:91
|
||||
msgid "Medium"
|
||||
msgstr "Moyen"
|
||||
|
||||
#: .\photologue\models.py:92
|
||||
#: photologue/models.py:92
|
||||
msgid "Medium-High"
|
||||
msgstr "Moyen-Haut"
|
||||
|
||||
#: .\photologue\models.py:93
|
||||
#: photologue/models.py:93
|
||||
msgid "High"
|
||||
msgstr "Haut"
|
||||
|
||||
#: .\photologue\models.py:94
|
||||
#: photologue/models.py:94
|
||||
msgid "Very High"
|
||||
msgstr "Très Haut"
|
||||
|
||||
#: .\photologue\models.py:99
|
||||
#: photologue/models.py:99
|
||||
msgid "Top"
|
||||
msgstr "Sommet"
|
||||
|
||||
#: .\photologue\models.py:100
|
||||
#: photologue/models.py:100
|
||||
msgid "Right"
|
||||
msgstr "Droite"
|
||||
|
||||
#: .\photologue\models.py:101
|
||||
#: photologue/models.py:101
|
||||
msgid "Bottom"
|
||||
msgstr "Bas"
|
||||
|
||||
#: .\photologue\models.py:102
|
||||
#: photologue/models.py:102
|
||||
msgid "Left"
|
||||
msgstr "Gauche"
|
||||
|
||||
#: .\photologue\models.py:103
|
||||
#: photologue/models.py:103
|
||||
msgid "Center (Default)"
|
||||
msgstr "Centré (par défaut)"
|
||||
|
||||
#: .\photologue\models.py:107
|
||||
#: photologue/models.py:107
|
||||
msgid "Flip left to right"
|
||||
msgstr "Inversion de gauche à droite"
|
||||
|
||||
#: .\photologue\models.py:108
|
||||
#: photologue/models.py:108
|
||||
msgid "Flip top to bottom"
|
||||
msgstr "Inversion de haut en bas"
|
||||
|
||||
#: .\photologue\models.py:109
|
||||
#: photologue/models.py:109
|
||||
msgid "Rotate 90 degrees counter-clockwise"
|
||||
msgstr "Rotation de 90 degrés dans le sens anti-horloger"
|
||||
|
||||
#: .\photologue\models.py:110
|
||||
#: photologue/models.py:110
|
||||
msgid "Rotate 90 degrees clockwise"
|
||||
msgstr "Rotation de 90 degrés dans le sens horloger"
|
||||
|
||||
#: .\photologue\models.py:111
|
||||
#: photologue/models.py:111
|
||||
msgid "Rotate 180 degrees"
|
||||
msgstr "Rotation de 180 degrés"
|
||||
|
||||
#: .\photologue\models.py:125
|
||||
#: photologue/models.py:125
|
||||
#, python-format
|
||||
msgid ""
|
||||
"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 "
|
||||
"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"
|
||||
msgstr "titre"
|
||||
|
||||
#: .\photologue\models.py:150
|
||||
#: photologue/models.py:150
|
||||
msgid "title slug"
|
||||
msgstr "version abrégée du titre"
|
||||
|
||||
#: .\photologue\models.py:153 .\photologue\models.py:522
|
||||
#: .\photologue\models.py:780
|
||||
#: photologue/models.py:153 photologue/models.py:535 photologue/models.py:796
|
||||
msgid "A \"slug\" is a unique URL-friendly title for an object."
|
||||
msgstr ""
|
||||
"Un \"slug\" est un titre abrégé et unique, compatible avec les URL, pour un "
|
||||
"objet."
|
||||
|
||||
#: .\photologue\models.py:157
|
||||
#: photologue/models.py:157
|
||||
msgid "start date"
|
||||
msgstr "date de début"
|
||||
|
||||
#: .\photologue\models.py:162
|
||||
#: photologue/models.py:162
|
||||
msgid "end date"
|
||||
msgstr "date de fin"
|
||||
|
||||
#: .\photologue\models.py:164
|
||||
#: photologue/models.py:164
|
||||
msgid "description"
|
||||
msgstr "description"
|
||||
|
||||
#: .\photologue\models.py:174 .\photologue\models.py:548
|
||||
#: photologue/models.py:174 photologue/models.py:561
|
||||
msgid "photos"
|
||||
msgstr "photos"
|
||||
|
||||
#: .\photologue\models.py:181
|
||||
#: photologue/models.py:178
|
||||
msgid "public token"
|
||||
msgstr ""
|
||||
|
||||
#: photologue/models.py:188
|
||||
msgid "gallery"
|
||||
msgstr "galerie"
|
||||
|
||||
#: .\photologue\models.py:182
|
||||
#: photologue/models.py:189
|
||||
msgid "galleries"
|
||||
msgstr "galleries"
|
||||
|
||||
#: .\photologue\models.py:214
|
||||
#: photologue/models.py:221
|
||||
msgid "count"
|
||||
msgstr "nombre"
|
||||
|
||||
#: .\photologue\models.py:215
|
||||
#: photologue/models.py:222
|
||||
msgid "private count"
|
||||
msgstr "nombre de photos privées"
|
||||
|
||||
#: .\photologue\models.py:220
|
||||
#: photologue/models.py:227
|
||||
msgid "image"
|
||||
msgstr "image"
|
||||
|
||||
#: .\photologue\models.py:223
|
||||
#: photologue/models.py:236
|
||||
msgid "date taken"
|
||||
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."
|
||||
msgstr ""
|
||||
"La date à laquelle l'image a été prise ; obtenue à partir des données EXIF "
|
||||
"de l'image."
|
||||
|
||||
#: .\photologue\models.py:228
|
||||
#: photologue/models.py:241
|
||||
msgid "view count"
|
||||
msgstr "nombre"
|
||||
|
||||
#: .\photologue\models.py:230
|
||||
#: photologue/models.py:243
|
||||
msgid "crop from"
|
||||
msgstr "découper à partir de"
|
||||
|
||||
#: .\photologue\models.py:254
|
||||
#: photologue/models.py:267
|
||||
msgid "An \"admin_thumbnail\" photo size has not been defined."
|
||||
msgstr "Une taille de photo \"admin_thumbnail\" n'a pas encore été définie."
|
||||
|
||||
#: .\photologue\models.py:267
|
||||
#: photologue/models.py:280
|
||||
msgid "Thumbnail"
|
||||
msgstr "Miniature"
|
||||
|
||||
#: .\photologue\models.py:519 .\photologue\models.py:779
|
||||
#: photologue/models.py:532 photologue/models.py:795
|
||||
msgid "slug"
|
||||
msgstr "libellé court"
|
||||
|
||||
#: .\photologue\models.py:524
|
||||
#: photologue/models.py:537
|
||||
msgid "caption"
|
||||
msgstr "légende"
|
||||
|
||||
#: .\photologue\models.py:525
|
||||
#: photologue/models.py:538
|
||||
msgid "date added"
|
||||
msgstr "date d'ajout"
|
||||
|
||||
#: .\photologue\models.py:534
|
||||
#: photologue/models.py:547
|
||||
msgid "license"
|
||||
msgstr "licence"
|
||||
|
||||
#: .\photologue\models.py:537
|
||||
#: photologue/models.py:550
|
||||
msgid "is public"
|
||||
msgstr "est public"
|
||||
|
||||
#: .\photologue\models.py:539
|
||||
#: photologue/models.py:552
|
||||
msgid "Public photographs will be displayed in the default views."
|
||||
msgstr "Les photographies publique seront affichées dans les vues par défaut."
|
||||
|
||||
#: .\photologue\models.py:547
|
||||
#: photologue/models.py:560
|
||||
msgid "photo"
|
||||
msgstr "photo"
|
||||
|
||||
#: .\photologue\models.py:610 .\photologue\models.py:774
|
||||
#: photologue/models.py:626 photologue/models.py:790
|
||||
msgid "name"
|
||||
msgstr "nom"
|
||||
|
||||
#: .\photologue\models.py:614
|
||||
#: photologue/models.py:630
|
||||
msgid ""
|
||||
"Photo size name should contain only letters, numbers and underscores. "
|
||||
"Examples: \"thumbnail\", \"display\", \"small\", \"main_page_widget\"."
|
||||
|
|
@ -285,41 +278,41 @@ msgstr ""
|
|||
"chiffres et des caractères de soulignement. Exemples: \"miniature\", "
|
||||
"\"affichage\", \"petit\", \"widget_page_principale\"."
|
||||
|
||||
#: .\photologue\models.py:626
|
||||
#: photologue/models.py:642
|
||||
msgid "width"
|
||||
msgstr "largeur"
|
||||
|
||||
#: .\photologue\models.py:629
|
||||
#: photologue/models.py:645
|
||||
msgid ""
|
||||
"If width is set to \"0\" the image will be scaled to the supplied height."
|
||||
msgstr ""
|
||||
"Si la largeur est réglée à \"0\" l l'image sera redimensionnée par rapport à "
|
||||
"la hauteur fournie."
|
||||
|
||||
#: .\photologue\models.py:633
|
||||
#: photologue/models.py:649
|
||||
msgid "height"
|
||||
msgstr "hauteur"
|
||||
|
||||
#: .\photologue\models.py:636
|
||||
#: photologue/models.py:652
|
||||
msgid ""
|
||||
"If height is set to \"0\" the image will be scaled to the supplied width"
|
||||
msgstr ""
|
||||
"Si la hauteur est réglée à \"0\" l l'image sera redimensionnée par rapport à "
|
||||
"la largeur fournie."
|
||||
|
||||
#: .\photologue\models.py:640
|
||||
#: photologue/models.py:656
|
||||
msgid "quality"
|
||||
msgstr "qualité"
|
||||
|
||||
#: .\photologue\models.py:643
|
||||
#: photologue/models.py:659
|
||||
msgid "JPEG image quality."
|
||||
msgstr "Qualité JPEG de l'image."
|
||||
|
||||
#: .\photologue\models.py:646
|
||||
#: photologue/models.py:662
|
||||
msgid "upscale images?"
|
||||
msgstr "agrandir les images ?"
|
||||
|
||||
#: .\photologue\models.py:649
|
||||
#: photologue/models.py:665
|
||||
msgid ""
|
||||
"If selected the image will be scaled up if necessary to fit the supplied "
|
||||
"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 "
|
||||
"en compte ce paramètre."
|
||||
|
||||
#: .\photologue\models.py:655
|
||||
#: photologue/models.py:671
|
||||
msgid "crop to fit?"
|
||||
msgstr "découper pour adapter à la taille ?"
|
||||
|
||||
#: .\photologue\models.py:658
|
||||
#: photologue/models.py:674
|
||||
msgid ""
|
||||
"If selected the image will be scaled and cropped to fit the supplied "
|
||||
"dimensions."
|
||||
|
|
@ -340,21 +333,21 @@ msgstr ""
|
|||
"Si sélectionné l'image sera redimensionnée et recadrée pour coïncider avec "
|
||||
"les dimensions fournies."
|
||||
|
||||
#: .\photologue\models.py:663
|
||||
#: photologue/models.py:679
|
||||
msgid "pre-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."
|
||||
msgstr ""
|
||||
"Si sélectionné cette taille de photo sera mise en cache au moment au les "
|
||||
"photos sont ajoutées."
|
||||
|
||||
#: .\photologue\models.py:670
|
||||
#: photologue/models.py:686
|
||||
msgid "increment view count?"
|
||||
msgstr "incrémenter le nombre d'affichages ?"
|
||||
|
||||
#: .\photologue\models.py:673
|
||||
#: photologue/models.py:689
|
||||
msgid ""
|
||||
"If selected the image's \"view_count\" will be incremented when this photo "
|
||||
"size is displayed."
|
||||
|
|
@ -362,79 +355,95 @@ msgstr ""
|
|||
"Si sélectionné le \"view_count\" (nombre d'affichage) de l'image sera "
|
||||
"incrémenté quand cette taille de photo sera affichée."
|
||||
|
||||
#: .\photologue\models.py:680
|
||||
#: photologue/models.py:696
|
||||
msgid "photo size"
|
||||
msgstr "taille de la photo"
|
||||
|
||||
#: .\photologue\models.py:681
|
||||
#: photologue/models.py:697
|
||||
msgid "photo sizes"
|
||||
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."
|
||||
msgstr ""
|
||||
"La hauteur et la largeur doivent être toutes les deux définies pour "
|
||||
"retailler des photos."
|
||||
|
||||
#: .\photologue\models.py:785
|
||||
#: photologue/models.py:801
|
||||
msgid "tag"
|
||||
msgstr ""
|
||||
|
||||
#: .\photologue\templates\photologue\gallery_archive.html:9
|
||||
#: photologue/templates/photologue/gallery_archive.html:9
|
||||
msgid "Latest photo galleries"
|
||||
msgstr "Dernières galeries de photos"
|
||||
|
||||
#: .\photologue\templates\photologue\gallery_archive.html:14
|
||||
#: .\photologue\templates\photologue\gallery_archive_year.html:14
|
||||
#: photologue/templates/photologue/gallery_archive.html:14
|
||||
#: photologue/templates/photologue/gallery_archive_year.html:14
|
||||
msgid "Filter by year"
|
||||
msgstr "Filtrer par année"
|
||||
|
||||
#: .\photologue\templates\photologue\gallery_archive.html:31
|
||||
#: photologue/templates/photologue/gallery_archive.html:31
|
||||
msgid "No galleries were found"
|
||||
msgstr "Aucune galerie trouvée"
|
||||
|
||||
#: .\photologue\templates\photologue\gallery_archive_year.html:9
|
||||
#: photologue/templates/photologue/gallery_archive_year.html:9
|
||||
#, python-format
|
||||
msgid "Galleries for %(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."
|
||||
msgstr "Aucune galerie trouvée."
|
||||
|
||||
#: .\photologue\templates\photologue\gallery_detail.html:42
|
||||
#: photologue/templates/photologue/gallery_detail.html:48
|
||||
msgid "to"
|
||||
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"
|
||||
msgstr "Toutes les photos"
|
||||
|
||||
#: .\photologue\templates\photologue\gallery_detail.html:85
|
||||
#: photologue/templates/photologue/gallery_detail.html:106
|
||||
msgid "Download all gallery"
|
||||
msgstr "Télécharger toute la galerie"
|
||||
|
||||
#: .\photologue\templates\photologue\photo_confirm_delete.html:9
|
||||
#: .\photologue\templates\photologue\photo_confirm_delete.html:14
|
||||
#: photologue/templates/photologue/photo_confirm_delete.html:9
|
||||
#: photologue/templates/photologue/photo_confirm_delete.html:14
|
||||
msgid "Delete confirmation"
|
||||
msgstr "Confirmation de la suppression"
|
||||
|
||||
#: .\photologue\templates\photologue\photo_confirm_delete.html:17
|
||||
#: photologue/templates/photologue/photo_confirm_delete.html:17
|
||||
#, python-format
|
||||
msgid "Are you sure you want to delete <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_report.html:22
|
||||
#: photologue/templates/photologue/photo_confirm_delete.html:22
|
||||
#: photologue/templates/photologue/photo_confirm_report.html:22
|
||||
msgid "Confirm"
|
||||
msgstr "Confimer"
|
||||
|
||||
#: .\photologue\templates\photologue\photo_confirm_report.html:9
|
||||
#: .\photologue\templates\photologue\photo_confirm_report.html:14
|
||||
#: photologue/templates/photologue/photo_confirm_report.html:9
|
||||
#: photologue/templates/photologue/photo_confirm_report.html:14
|
||||
msgid "Report confirmation"
|
||||
msgstr "Raporter la confirmation"
|
||||
|
||||
#: .\photologue\templates\photologue\photo_confirm_report.html:17
|
||||
#: photologue/templates/photologue/photo_confirm_report.html:17
|
||||
#, python-format
|
||||
msgid ""
|
||||
"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 "
|
||||
"publique, et les administrateur vont être notifiés."
|
||||
|
||||
#: .\photologue\templates\photologue\photo_detail.html:15
|
||||
#: photologue/templates/photologue/photo_detail.html:15
|
||||
msgid "Published"
|
||||
msgstr "Publiée le"
|
||||
|
||||
#: .\photologue\templates\photologue\upload.html:20
|
||||
#: photologue/templates/photologue/upload.html:22
|
||||
msgid "Drag and drop photos here"
|
||||
msgstr "Glissez et déposez les photos ici"
|
||||
|
||||
#: .\photologue\templates\photologue\upload.html:24
|
||||
#: photologue/templates/photologue/upload.html:26
|
||||
msgid "Owner will be"
|
||||
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.name
|
||||
) # 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.
|
||||
try:
|
||||
exif_date = self.exif(self.image.file).get(
|
||||
|
|
|
|||
|
|
@ -34,20 +34,20 @@ lgContainer.addEventListener('lgAfterOpen', () => {
|
|||
|
||||
const downloadUrl = this.getAttribute('href');
|
||||
|
||||
// Affichage de la modale stylisée
|
||||
Swal.fire({
|
||||
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."),
|
||||
icon: 'info',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: gettext('Download'),
|
||||
cancelButtonText: gettext('Cancel'),
|
||||
background: '#1a1a1a', // Optionnel : pour matcher le thème sombre de LightGallery
|
||||
color: '#fff'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
// // Affichage de la modale stylisée
|
||||
// Swal.fire({
|
||||
// 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."),
|
||||
// icon: 'info',
|
||||
// showCancelButton: true,
|
||||
// confirmButtonColor: '#3085d6',
|
||||
// cancelButtonColor: '#d33',
|
||||
// confirmButtonText: gettext('Download'),
|
||||
// cancelButtonText: gettext('Cancel'),
|
||||
// background: '#1a1a1a', // Optionnel : pour matcher le thème sombre de LightGallery
|
||||
// color: '#fff'
|
||||
// }).then((result) => {
|
||||
// if (result.isConfirmed) {
|
||||
// Si validé, on déclenche le téléchargement
|
||||
const link = document.createElement('a');
|
||||
link.href = downloadUrl;
|
||||
|
|
@ -55,8 +55,8 @@ lgContainer.addEventListener('lgAfterOpen', () => {
|
|||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
});
|
||||
// }
|
||||
// });
|
||||
}, 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.userId = document.querySelector('[name=user_id]').value;
|
||||
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.photoId = 0;
|
||||
return this;
|
||||
|
|
@ -33,7 +34,8 @@ class lgAdmin {
|
|||
document.getElementById("lg-delete").addEventListener('click', this.onDelete.bind(this));
|
||||
|
||||
// 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));
|
||||
|
||||
// Add button to restore a censored photo
|
||||
|
|
@ -53,6 +55,7 @@ class lgAdmin {
|
|||
const ownerId = el ? el.dataset.ownerId : null;
|
||||
const canDelete = this.isStaff || (ownerId && ownerId === this.userId);
|
||||
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;
|
||||
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
|
||||
onReport(event) {
|
||||
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
|
||||
const photoId = this.photoId;
|
||||
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="user_id" value="{{ request.user.id }}">
|
||||
<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/plugins/admin/lg-admin.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/zoom/lg-zoom.min.js' %}"></script>
|
||||
<script src="{% static 'gallery_justified.js' %}"></script>
|
||||
<script src="{% static 'gallery_detail.js' %}"></script>
|
||||
<script src="{% static 'sweetalert.js' %}"></script>
|
||||
<script src="{% static 'copy-button.js' %}"></script>
|
||||
|
|
@ -87,10 +89,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card-body row" id="lightgallery">
|
||||
<div class="card-body p-0" id="lightgallery">
|
||||
{% 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' }}">
|
||||
<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 %}">
|
||||
<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 }}" 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>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -237,6 +237,7 @@ class GalleryPublicView(DetailView):
|
|||
if request.user.is_authenticated:
|
||||
gallery = self.get_object()
|
||||
return redirect("photologue:pl-gallery", slug=gallery.slug)
|
||||
request.session['public_gallery_access'] = True
|
||||
request.guest_mode = True
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,3 +6,7 @@ ExifRead>=2.1.2
|
|||
Pillow>=6.0.0
|
||||
django-debug-toolbar>=3.2.0
|
||||
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
|
||||
pyflakes
|
||||
commands =
|
||||
flake8 allauth_note_kfet photo21 photologue
|
||||
flake8 allauth_oauth photo21 photologue
|
||||
|
||||
[flake8]
|
||||
ignore = W503, I100, I101
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue