Commit 81ad3892 authored by ynerant's avatar ynerant
Browse files

Merge branch 'beta' into 'master'

Fix note pictures, better ansible

See merge request !113
parents 2f6c7ed1 8aac738c
Pipeline #8663 passed with stages
in 10 minutes and 32 seconds
This diff is collapsed.
#!/usr/bin/env ansible-playbook
---
- hosts: bde-note.adh.crans.org
- hosts: all
vars_prompt:
- name: DB_PASSWORD
prompt: "Password of the database"
prompt: "Password of the database (leave it blank to skip database init)"
private: yes
vars:
mirror: deb.debian.org
note:
server_name: note.crans.org
roles:
- 1-apt-basic
- 2-nk20
......
---
note:
server_name: note-beta.crans.org
git_branch: beta
cron_enabled: false
---
note:
server_name: note.crans.org
git_branch: master
cron_enabled: true
---
note:
server_name: note-dev.crans.org
git_branch: beta
cron_enabled: false
[server]
[dev]
bde3-virt.adh.crans.org
bde-nk20-beta.adh.crans.org
[prod]
bde-note.adh.crans.org
[all:vars]
......
......@@ -11,7 +11,7 @@
git:
repo: https://gitlab.crans.org/bde/nk20.git
dest: /var/www/note_kfet
version: master
version: "{{ note.git_branch }}"
force: true
- name: Use default env vars (should be updated!)
......@@ -30,6 +30,7 @@
group: www-data
- name: Setup cron jobs
when: "note.cron_enabled"
template:
src: note.cron.j2
dest: /etc/cron.d/note
......
# {{ ansible_managed }}
# Les cronjobs dont a besoin la Note Kfet
# m h dom mon dow user command
# Envoyer les mails en attente
* * * * * root cd /var/www/note_kfet && env/bin/python manage.py send_mail >> /var/www/note_kfet/cron_mail.log
* * * * * root cd /var/www/note_kfet && env/bin/python manage.py retry_deferred >> /var/www/note_kfet/cron_mail_deferred.log
00 0 * * * root cd /var/www/note_kfet && env/bin/python manage.py purge_mail_log 7 >> /var/www/note_kfet/cron_mail_purge.log
# Faire une sauvegarde de la base de données
00 2 * * * root cd /var/www/note_kfet && apps/scripts/shell/backup_db
# Vérifier la cohérence de la base et mailer en cas de problème
00 4 * * * root cd /var/www/note_kfet && env/bin/python manage.py check_consistency --sum-all --check-all --mail
# Mettre à jour le wiki (modification sans (dé)validation, activités passées)
#30 5 * * * root cd /var/www/note_kfet && env/bin/python manage.py refresh_activities --raw --comment refresh
# Spammer les gens en négatif
00 5 * * 2 root cd /var/www/note_kfet && env/bin/python manage.py send_mail_to_negative_balances --spam
# Envoyer le rapport mensuel aux trésoriers et respos info
00 8 6 * * root cd /var/www/note_kfet && env/bin/python manage.py send_mail_to_negative_balances --report
# Envoyer les rapports aux gens
55 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py send_reports
# Envoyer les rapports aux gens
00 9 * * * root cd /var/www/note_kfet && env/bin/python manage.py refresh_highlighted_buttons
../../../../note.cron
\ No newline at end of file
---
- name: Install basic APT packages
apt:
update_cache: true
name:
- certbot
- python3-certbot-nginx
register: pkg_result
retries: 3
until: pkg_result is succeeded
- name: Create /etc/letsencrypt/conf.d
file:
path: /etc/letsencrypt/conf.d
state: directory
- name: Add Certbot configuration
template:
src: "letsencrypt/conf.d/nk20.ini.j2"
dest: "/etc/letsencrypt/conf.d/nk20.ini"
mode: 0644
{{ ansible_managed | comment }}
# To generate the certificate, please use the following command
# certbot --config /etc/letsencrypt/conf.d/nk20.ini certonly
# Use a 4096 bit RSA key instead of 2048
rsa-key-size = 4096
# Always use the staging/testing server
# server = https://acme-staging.api.letsencrypt.org/directory
# Uncomment and update to register with the specified e-mail address
email = notekfet2020@lists.crans.org
# Uncomment to use a text interface instead of ncurses
text = True
# Use DNS-01 challenge
authenticator = nginx
---
- name: Install NGINX
apt:
name: nginx
register: pkg_result
retries: 3
until: pkg_result is succeeded
- name: Copy conf of Nginx
template:
src: "nginx_note.conf"
dest: /etc/nginx/sites-available/nginx_note.conf
mode: 0644
owner: www-data
group: www-data
- name: Enable Nginx site
file:
src: /etc/nginx/sites-available/nginx_note.conf
dest: /etc/nginx/sites-enabled/nginx_note.conf
owner: www-data
group: www-data
state: link
- name: Disable default Nginx site
file:
dest: /etc/nginx/sites-enabled/default
state: absent
- name: Copy conf of UWSGI
file:
src: /var/www/note_kfet/uwsgi_note.ini
dest: /etc/uwsgi/apps-enabled/uwsgi_note.ini
state: link
- name: Reload Nginx
systemd:
name: nginx
state: reloaded
- name: Restart UWSGI
systemd:
name: uwsgi
state: restarted
# the upstream component nginx needs to connect to
upstream note{
server unix:///var/www/note_kfet/note_kfet.sock; # file socket
}
# Redirect HTTP to nk20 HTTPS
server {
listen 80 default_server;
listen [::]:80 default_server;
location / {
return 301 https://{{ note.server_name }}$request_uri;
}
}
# Redirect all HTTPS to nk20 HTTPS
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
location / {
return 301 https://{{ note.server_name }}$request_uri;
}
ssl_certificate /etc/letsencrypt/live/{{ note.server_name }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ note.server_name }}/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
# configuration of the server
server {
listen 443 ssl;
listen [::]:443 ssl;
# the port your site will be served on
# the domain name it will serve for
server_name {{ note.server_name }}; # substitute your machine's IP address or FQDN
charset utf-8;
# max upload size
client_max_body_size 75M; # adjust to taste
# Django media
location /media {
alias /var/www/note_kfet/media; # your Django project's media files - amend as required
}
location /static {
alias /var/www/note_kfet/static; # your Django project's static files - amend as required
}
# Finally, send all non-media requests to the Django server.
location / {
uwsgi_pass note;
include /etc/nginx/uwsgi_params;
}
ssl_certificate /etc/letsencrypt/live/{{ note.server_name }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ note.server_name }}/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
......@@ -10,17 +10,15 @@
retries: 3
until: pkg_result is succeeded
- name: Install Psycopg2
pip:
name: psycopg2-binary
- name: Create role note
when: "DB_PASSWORD|bool" # If the password is not defined, skip the installation
postgresql_user:
name: note
password: "{{ DB_PASSWORD }}"
become_user: postgres
- name: Create NK20 database
when: "DB_PASSWORD|bool"
postgresql_db:
name: note_db
owner: note
......
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import io
from PIL import Image
from django import forms
from django.conf import settings
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.models import User
from django.forms import CheckboxSelectMultiple
......@@ -77,6 +81,38 @@ class ImageForm(forms.Form):
width = forms.FloatField(widget=forms.HiddenInput())
height = forms.FloatField(widget=forms.HiddenInput())
def clean(self):
"""Load image and crop"""
cleaned_data = super().clean()
# Image size is limited by Django DATA_UPLOAD_MAX_MEMORY_SIZE
image = cleaned_data.get('image')
if image:
# Let Pillow detect and load image
try:
im = Image.open(image)
except OSError:
# Rare case in which Django consider the upload file as an image
# but Pil is unable to load it
raise forms.ValidationError(_('This image cannot be loaded.'))
# Crop image
x = cleaned_data.get('x', 0)
y = cleaned_data.get('y', 0)
w = cleaned_data.get('width', 200)
h = cleaned_data.get('height', 200)
im = im.crop((x, y, x + w, y + h))
im = im.resize(
(settings.PIC_WIDTH, settings.PIC_RATIO * settings.PIC_WIDTH),
Image.ANTIALIAS,
)
# Save
image.file = io.BytesIO()
im.save(image.file, "PNG")
return cleaned_data
class ClubForm(forms.ModelForm):
def clean(self):
......
......@@ -32,8 +32,8 @@ SPDX-License-Identifier: GPL-3.0-or-later
<span class="glyphicon glyphicon-zoom-out"></span>
</button>
</div>
<button type="button" class="btn btn-default" data-dismiss="modal">Nevermind</button>
<button type="button" class="btn btn-primary js-crop-and-upload">Crop and upload</button>
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Nevermind" %}</button>
<button type="button" class="btn btn-primary js-crop-and-upload">{% trans "Crop and upload" %}</button>
</div>
</div>
</div>
......@@ -55,12 +55,18 @@ SPDX-License-Identifier: GPL-3.0-or-later
/* SCRIPT TO OPEN THE MODAL WITH THE PREVIEW */
$("#id_image").change(function (e) {
if (this.files && this.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
$("#modal-image").attr("src", e.target.result);
$("#modalCrop").modal("show");
// Check the image size
if (this.files[0].size > 2*1024*1024) {
alert("Ce fichier est trop volumineux.")
} else {
// Read the selected image file
var reader = new FileReader();
reader.onload = function (e) {
$("#modal-image").attr("src", e.target.result);
$("#modalCrop").modal("show");
}
reader.readAsDataURL(this.files[0]);
}
reader.readAsDataURL(this.files[0]);
}
});
......@@ -104,4 +110,4 @@ SPDX-License-Identifier: GPL-3.0-or-later
});
});
</script>
{% endblock %}
\ No newline at end of file
{% endblock %}
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import io
from datetime import timedelta, date
from PIL import Image
from django.conf import settings
from django.contrib.auth import logout
from django.contrib.auth.mixins import LoginRequiredMixin
......@@ -263,6 +261,7 @@ class PictureUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, Det
return context
def get_success_url(self):
"""Redirect to profile page after upload"""
return reverse_lazy('member:user_detail', kwargs={'pk': self.object.id})
def post(self, request, *args, **kwargs):
......@@ -271,26 +270,9 @@ class PictureUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, Det
return self.form_valid(form) if form.is_valid() else self.form_invalid(form)
def form_valid(self, form):
"""Save image to note"""
image_field = form.cleaned_data['image']
x = form.cleaned_data['x']
y = form.cleaned_data['y']
w = form.cleaned_data['width']
h = form.cleaned_data['height']
# image crop and resize
image_file = io.BytesIO(image_field.read())
# ext = image_field.name.split('.')[-1].lower()
# TODO: support GIF format
image = Image.open(image_file)
image = image.crop((x, y, x + w, y + h))
image_clean = image.resize((settings.PIC_WIDTH,
settings.PIC_RATIO * settings.PIC_WIDTH),
Image.ANTIALIAS)
image_file = io.BytesIO()
image_clean.save(image_file, "PNG")
image_field.file = image_file
# renaming
filename = "{}_pic.png".format(self.object.note.pk)
image_field.name = filename
image_field.name = "{}_pic.png".format(self.object.note.pk)
self.object.note.display_image = image_field
self.object.note.save()
return super().form_valid(form)
......
......@@ -127,7 +127,12 @@ class ConsumerSerializer(serializers.ModelSerializer):
# If the user has no right to see the note, then we only display the note identifier
return NotePolymorphicSerializer().to_representation(obj.note)\
if PermissionBackend.check_perm(get_current_authenticated_user(), "note.view_note", obj.note)\
else dict(id=obj.note.id, name=str(obj.note))
else dict(
id=obj.note.id,
name=str(obj.note),
is_active=obj.note.is_active,
display_image=obj.note.display_image.url,
)
def get_email_confirmed(self, obj):
if isinstance(obj.note, NoteUser):
......
......@@ -2639,7 +2639,7 @@
"note",
"noteclub"
],
"query": "{\"pk\": [\"club\", \"pk\"]}",
"query": "{\"club\": [\"club\"]}",
"type": "change",
"mask": 1,
"field": "display_image",
......@@ -2778,6 +2778,8 @@
62,
127,
133,
135,
136,
141,
142,
150,
......
This diff is collapsed.
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment