Commit 8d7d09d5 authored by Georges Khaznadar's avatar Georges Khaznadar

Merge branch 'upstream' for the "trombi" export

parents e06b48ab 290cbc58
nobody.jpg

1.69 KB

......@@ -62,6 +62,8 @@
<h3>Liens :</h3>
<ul>
<li><a href="/importeCSV">Importation depuis un fichier CSV</a></li>
<li><a href="/pourPronote">Export des photos pour Pronote</a></li>
<li><a href="/trombi">Export en trombinoscopes</a></li>
</ul>
</div>
<div id="dialog"></div>
......
{% load static %}
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<title>Export pour Pronote</title>
<script src="{% static 'jquery.js' %}" type="text/javascript"></script>
<link rel="stylesheet" href="{% static 'jquery-ui-themes/smoothness/jquery-ui.css' %}"/>
<script src="{% static 'jquery-ui/jquery-ui.js' %}" type="text/javascript"></script>
<script type="text/javascript" src="{% static 'pourPronote.js' %}"></script>
<link rel="stylesheet" href="{% static 'portrait.css' %}" type="text/css"/>
<link rel="stylesheet" type="text/css" href="{% static '/admin/css/base.css' %}" />
</head>
<body>
<h1>Exports de trombinoscopes à imprimer</h1>
<form method="post" action="">
<fieldset><legend>Options</legend>
{% csrf_token %}
<label for="laclasse">Sélection :</label>
<select id="laclasse" name="laclasse">
{% for c in lesclasses %}
<option>{{c}}</option>
{% endfor %}
</select>
</fieldset>
<input type="submit" value="Télécharger le trombinoscope"/>
</form>
</body>
</html>
<!--
Local Variables:
mode: nxml
End:
-->
......@@ -10,7 +10,66 @@ import zipfile
import xml.dom.minidom
import os
import hashlib
from photodb.settings import BASE_DIR
from copy import deepcopy
class PhotoWithTitle:
"""
Une simple classe pour une photo au format JPG et une phrase de titre
"""
def __init__(self, photo, title):
"""
Le contructeur
@param photo un chemin vers un fichier
@param title une phrase
"""
self._photo=photo
self._title=title
return
def __str__(self):
return "%s: %s" %(self._title, self._photo)
def __lt__(self, other):
return self._title.upper() < other._title.upper()
@property
def photo(self):
return self._photo
@property
def title(self):
return self._title
class Trombi:
"""
Classe pour un trombinoscope. C4est à dire un titre général et une
liste de PhotoWithTitle
"""
def __init__(self, title="", photos=[]):
"""
Le constructeur
@param title le titre général
@param photos une liste d'instances de PhotoWithTitle, ou [] par
défaut
"""
self._title=title
self._photos=photos
return
def __str__(self):
return "Trombi({}: [{}])".format(self._title, ", ".join([str(p) for p in self._photos]))
@property
def title(self):
return self._title
@property
def photos(self):
return self._photos
def hashImageFileName(f):
"""
prend un nom de fichier qui se termine par .jpg et renvoie un nom de fichier
......@@ -20,47 +79,82 @@ def hashImageFileName(f):
root=root.encode("utf-8")
return "Pictures/000000000000096000000"+hashlib.md5(root).hexdigest()[:18].upper()+ext
def pageGen(template="templates/modele0.odt", data=[], title="Joli titre"):
def pageGen(template=None, trombi=None):
"""
Crée un fichier temporaire au format ODT
@param template un fichier ODT comportant 36 places de tableau
@param data une liste contenant au plus 36 photos et phrases
@param title un titre pour la page
@param template un fichier ODT comportant des emplacements
structurés comme :
<table:table-cell><draw:image/><text:p/></table:table-cell>
et où le compte des éléments <draw:image/> représente bien le
nombre d'emplacements. Un tout premier élément <text:p/> est
prévu pour recevoir le titre.
@param trombi une instance de Trombi
@return un BytesIO contenant une page au format ODT
"""
if template is None:
template = os.path.join(BASE_DIR, "templates", "modele0.odt")
if trombi is None:
trombi=Trombi()
modele=zipfile.ZipFile(template)
manifest=modele.open("META-INF/manifest.xml")
contents=modele.open("content.xml")
zipIO=BytesIO()
result=zipfile.ZipFile(zipIO,"x")
for zinf in modele.infolist():
f=zinf.filename
if f!="content.xml":
if f not in ["META-INF/manifest.xml","content.xml"]:
result.writestr(f,modele.read(zinf))
page=xml.dom.minidom.parse(contents)
print(page.toprettyxml(indent=" "))
cells=page.getElementsByTagName("table:table-cell")
manif=xml.dom.minidom.parse(manifest)
nsuri=manif.documentElement.namespaceURI
# effacement des images initialement présentes dans manifest.xml
manif_images=[f for f in manif.getElementsByTagName("manifest:file-entry") if f.getAttribute("manifest:full-path").startswith("Pictures")]
for el in manif_images:
manif.documentElement.removeChild(el)
el.unlink()
# copie du tableau pour utilisation si plus de photos que possible
# en un seul tableau
table=page.getElementsByTagName("table:table")[0]
table0=deepcopy(table)
maxPhotos=len(page.getElementsByTagName("draw:image"))
cells=table.getElementsByTagName("table:table-cell")
title0=cells[0].getElementsByTagName("text:p")[0]
title0.firstChild.replaceWholeText(title)
title0.firstChild.replaceWholeText(trombi.title)
i=1
for photo, texte in data:
for pwt in trombi.photos:
# insertion de la photo
image=cells[i].getElementsByTagName("draw:image")[0]
target=hashImageFileName(photo)
target=hashImageFileName(pwt.photo)
image.setAttribute("xlink:href", target)
with open(photo,"rb") as infile:
file_entry=manif.createElement("manifest:file-entry")
file_entry.setAttribute("manifest:full-path", target)
file_entry.setAttribute("manifest:media-type", "image/jpeg")
manif.documentElement.appendChild(file_entry)
with open(pwt.photo,"rb") as infile:
result.writestr(target, infile.read())
# insertion du texte
t=cells[i].getElementsByTagName("text:s")[0].nextSibling
t.replaceWholeText(texte)
t.replaceWholeText(pwt.title)
i+=1
# limitation à 36 cases de tableau !
if i > 36:
break
# limitation aux cases du tableau !
if i > maxPhotos:
# on ajoute un tableau neuf au bout quand le précédent est plein
table1=deepcopy(table0)
theText=page.getElementsByTagName("office:text")[0]
theText.appendChild(table1)
cells=table1.getElementsByTagName("table:table-cell")
title1=cells[0].getElementsByTagName("text:p")[0]
title1.firstChild.replaceWholeText(trombi.title)
i=1
newcontents=StringIO()
page.writexml(newcontents)
newcontents.seek(0)
result.writestr("content.xml", newcontents.read())
newmanifest=StringIO()
manif.writexml(newmanifest)
newmanifest.seek(0)
result.writestr("META-INF/manifest.xml", newmanifest.read())
result.close()
zipIO.seek(0)
return zipIO
......@@ -74,7 +168,10 @@ if __name__=="__main__":
example="/home/georgesk/tex/classes/2017-2018/photodb/var-lib-photodb/photodb/photos"
_,_,fnames=next(os.walk(example))
fnames=[f for f in fnames if f.endswith('.jpg')]
data=[(os.path.join(example,f),textFrom(f)) for f in fnames[:36]]
zipIO=pageGen(data=data)
trombi=Trombi(
"Exemple",
[PhotoWithTitle(os.path.join(example,f),textFrom(f)) for f in fnames[:50]]
)
zipIO=pageGen(trombi=trombi)
with open("/tmp/montest.odt",'wb') as outfile:
outfile.write(zipIO.read())
......@@ -9,4 +9,5 @@ urlpatterns = [
url(r'^cherchePrenom$', views.cherchePrenom, name='cherchePrenom'),
url(r'^importeCSV$', views.importeCSV, name='importeCSV'),
url(r'^pourPronote$', views.pourPronote, name='pourPronote'),
url(r'^trombi$', views.trombi, name='trombi'),
]
......@@ -6,7 +6,7 @@ from .autoretouche import jpgPrefix, FaceImage
from .forms import ImportCsvForm
from photodb.settings import BASE_DIR, MEDIA_ROOT
import re, os, base64, uuid, csv, json, io
from .trombi import Trombi, PhotoWithTitle, pageGen
def json_response(F):
"""
......@@ -400,3 +400,43 @@ def pourPronote(request):
"currentexport": currentexport,
})
def trombi(request):
"""
Export de trombinoscopes sous forme de fichiers ODT ou HTML
autonomes
"""
tout="Toutes les photos"
rien="Photos sans classe"
if request.method=="POST":
laclasse=request.POST.get("laclasse")
if laclasse==tout:
persons=Person.objects.all()
elif laclasse == rien:
persons=Person.objects.filter(className__isnull=True)
else:
persons=Person.objects.filter(className=laclasse)
trombi=Trombi(
laclasse,
sorted(
[PhotoWithTitle(
os.path.join(MEDIA_ROOT, os.path.basename(p.photo.url)),
"%s %s" %(p.lastName, p.firstName))
for p in persons if p.photo] + \
[PhotoWithTitle(
os.path.join(BASE_DIR, "nobody.jpg"),
"%s %s" %(p.lastName, p.firstName))
for p in persons if not p.photo]
)
)
zipIO=pageGen(trombi=trombi)
return HttpResponse(zipIO.read(), content_type="application/vnd.oasis.opendocument.text")
else:
persons=Person.objects.all()
lesclasses=[tout] + \
sorted(list({p.className for p in persons}-{None})) + \
[rien]
print("GRRRR lesclasses=", lesclasses)
return render(request, "pose/trombi.html",{
"lesclasses": lesclasses,
})
Markdown is supported
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