...
 
Commits (3)
......@@ -123,3 +123,7 @@ USE_TZ = True
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
MEDIA_URL = '/photos/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'photos')
......@@ -15,8 +15,10 @@ Including another URLconf
"""
from django.conf.urls import url, include
from django.contrib import admin
from django.conf.urls.static import static
from .settings import MEDIA_URL, MEDIA_ROOT
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'', include('pose.urls')),
]
] + static(MEDIA_URL, document_root=MEDIA_ROOT)
from django import forms
class ImportCsvForm(forms.Form):
fname = forms.FileField(label="Fichier à importer")
fname = forms.FileField(label="Choisir un fichier")
jQuery(document).ready(function () {
$("#loading").hide();
});
function showLoading(){
$("#loading").show();
return true;
}
......@@ -67,3 +67,50 @@
color: black;
}
#footer h1, h2, h3, ul, li {
display: inline-block;
}
#footer li a {
background: #79aec8;
color: white;
border-radius: 4px;
padding: 0px 4px;
}
#footer {
padding: 0.5em;
border: 1px gray solid;
border-radius: 0.5em;
}
#loading {
background: rgba(0,0,0,0.5);
z-index: 100;
width: 100%;
height:100%;
display: none;
position: fixed;
top: 0px;
left: 0px;
}
#loading img{
position: relative;
width: 150px;
height: 150px;
display: block;
margin: 150px auto;
}
dd {
margin-left: 1em;
}
.vignette {
display: inline-block;
}
.vignette img {
width: 8em;
}
......@@ -7,12 +7,12 @@
<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 'portrait.js' %}"></script>
<script type="text/javascript" src="{% static 'importeCSV.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>
<form method="post" action="" enctype="multipart/form-data">
<form method="post" action="" enctype="multipart/form-data" onsubmit="return showLoading()">
<fieldset>
<legend>Fichier de type CSV</legend>
{% csrf_token %}
......@@ -24,11 +24,26 @@
</form>
<h1>Importation d'élèves</h1>
<dl>
<dt>Fichier</dt>
<dt>Fichier traité :</dt>
<dd>{{fname}}</dd>
<dt>Nombre d'ajouts effectués</dt>
<dd>{{written}}</dd>
{% if ok %}
<dt>Colonnes prises en considération :</dt>
<dd>{{okFields}}</dd>
<dt>Nombre d'ajouts dans la base de données :</dt>
<dd>{{created}}</dd>
<dt>Nombre de mises à jour dans la base de données :</dt>
<dd>{{updated}}</dd>
{% endif %}
</dl>
<div id="footer">
<h3>Liens :</h3>
<ul>
<li><a href="/">Retour à la page d'accueil</a></li>
</ul>
</div>
<div id="loading">
<img src="{% static 'loading.gif' %}" alt="loading"/>
</div>
</body>
</html>
<!--
......
{% 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 'importeCSV.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>
{{grrr}}
<div id="footer">
<h3>Liens :</h3>
<ul>
<li>Photos exportées dans le dossier <b>{{destdir}}</b></li>
<li><a href="/">Retour à la page d'accueil</a></li>
</ul>
</div>
<div id="loading">
<img src="{% static 'loading.gif' %}" alt="loading"/>
</div>
<div id="vignettes">
{% for v in vignettes %}
<div class="vignette">
<img src="{{v.url}}" alt="{{v.prenom}} {{v.nom}}"/> <br/>
{{v.prenom}} <b>{{v.nom}}</b>
</div>
{% endfor %}
</div>
</body>
</html>
<!--
Local Variables:
mode: nxml
End:
-->
......@@ -8,4 +8,5 @@ urlpatterns = [
url(r'^chercheNom$', views.chercheNom, name='chercheNom'),
url(r'^cherchePrenom$', views.cherchePrenom, name='cherchePrenom'),
url(r'^importeCSV$', views.importeCSV, name='importeCSV'),
url(r'^pourPronote$', views.pourPronote, name='pourPronote'),
]
......@@ -171,25 +171,7 @@ def cherchePrenom(request):
persons=Person.objects.filter(lastName=nom, firstName__icontains=prenom)
return [p.firstName for p in persons]
def find_encoding(f, encodings):
"""
finds the encoding of a text file
@param f a bytesIO object
@param encodings a list of encodings to try
@return the first encoding which allowed one to read the file, or None
"""
f.seek(0)
contents=f.read()
f.seek(0)
for e in encodings:
try:
contents.decode(e)
return e
except:
pass
return None
def getReader(csvio, fields, encoding):
def getReader(csvio, fields, encodings):
"""
makes a DictReader from the file f, with the given encoding.
This Dictrader must have one field with name matched by secondNamePattern
......@@ -197,26 +179,37 @@ def getReader(csvio, fields, encoding):
@param csvio a BytesIO stream
@param fields a dictionary:
Person's field => (compiled re pattern, found field name)
@param encoding an encoding able to decode csvio
@return a Dictreader, and the updated dictionary
@param encodings a sequence of encodings to try in order to decode the
file's content; the first successful one is taken
@return a Dictreader, and the updated dictionary, or None and the original dictionary
"""
reader=None
csvio.seek(0)
content=csvio.read().decode(encoding)
csvio=io.StringIO(content)
for delimiter in (";", ",", "\t",):
csvio.seek(0)
reader = csv.DictReader(csvio, delimiter=delimiter)
nbfound=0
for f in fields:
found=[fn for fn in reader.fieldnames if fields[f][0].match(fn)]
if found:
fields[f][1]=found[0]
nbfound+=1
else:
fields[f][1]=""
if nbfound>=2: # fields were made visible
return reader, fields
content=""
for e in encodings:
try:
csvio.seek(0)
content=csvio.read().decode(e)
break
except:
pass
if content:
try:
csvio=io.StringIO(content)
for delimiter in (";", ",", "\t",):
csvio.seek(0)
reader1 = csv.DictReader(csvio, delimiter=delimiter)
nbfound=0
for f in fields:
found=[fn for fn in reader1.fieldnames if fields[f][0].match(fn)]
if found:
fields[f][1]=found[0]
nbfound+=1
else:
fields[f][1]=""
if nbfound>=2: # fields were made visible
return reader1, fields
except:
pass
return reader, fields
def addToDb(row, fields):
......@@ -225,10 +218,12 @@ def addToDb(row, fields):
@param row a data row coming from a csv dict reader
@param fields a dictionary:
Person's field => (compiled re pattern, found field name)
@return 0 or 1 depending on the success
@return (update_number, create_number)
"""
update_number=0
create_number=0
if not fields["firstName"][1] or not fields["lastName"][1]:
return 0
return update_number, create_number
persons=Person.objects.filter(
firstName=row[fields["firstName"][1]],
lastName=row[fields["lastName"][1]],
......@@ -238,15 +233,17 @@ def addToDb(row, fields):
firstName=row[fields["firstName"][1]],
lastName=row[fields["lastName"][1]],
)
create_number+=1
else:
p=persons[0]
update_number+=1
for k in fields: # update other fields if any
if k in ("firstName","lastName"):
continue
if fields[k][1]:
setattr(p,k,row[fields[k][1]])
p.save()
return 1
return update_number, create_number
def importeCSV(request):
......@@ -258,33 +255,95 @@ def importeCSV(request):
form=ImportCsvForm(request)
if 'fname' not in request.FILES or request.FILES['fname'].content_type != "text/csv":
return render(request, "pose/importCSV.html", {
"fname": "Fichier non encore choisi",
"written": 0,
"fname": "... sélectionner un fichier de type CSV (champs séparés par des virgules) ...",
"ok": False,
"created": 0,
"updated": 0,
"form": form,
})
# fname is defined
bio=request.FILES['fname'].file
encodingList=[
"utf-8",
"latin1",
"dos",
"cp437",
]
# dictionary Person's field => [compiled re pattern, found field name]
fields={
"firstName": [re.compile(r"^pr[eé]nom$", re.I), ""],
"lastName": [re.compile(r"^#?nom$", re.I), ""],
"level": [re.compile(r"^niveau$", re.I), ""],
"className": [re.compile(r"^classe$", re.I), ""],
}
encoding=find_encoding(bio, encodingList)
written=0
reader, fields = getReader(bio, fields, encoding)
for r in reader:
written += addToDb(r, fields)
updates_creates=[]
reader, fields = getReader(
bio,
{
"firstName": [re.compile(r"^pr[eé]nom$", re.I), ""],
"lastName": [re.compile(r"^#?nom$", re.I), ""],
"level": [re.compile(r"^niveau$", re.I), ""],
"className": [re.compile(r"^classe$", re.I), ""],
},
["utf-8", "latin1", "dos", "cp437"],
)
okFields=""
ok=False
if reader:
ok=True
okFields=", ".join([fields[k][1] for k in fields if fields[k][1]])
for r in reader:
updates_creates.append(addToDb(r, fields))
return render(request, "pose/importCSV.html", {
"fname": request.FILES['fname'].name,
"written": written,
"ok": True,
"okFields": okFields,
"updated": sum([u for u,c in updates_creates]),
"created": sum([c for u,c in updates_creates]),
"form": form,
})
def unquote(s):
if len(s)<2: return s
elif s[0]=='"' and s[-1]=='"': return s[1:-1]
elif s[0]=="'" and s[-1]=="'": return s[1:-1]
else: return s
def protege(s):
return s.replace(" ","-").replace("'","-")
def sansAccent(s):
accent={'a':'àâ','e':"éèêë","i":"îï","o":"ô","u":"ù","c":"ç",'A':'ÀÂ',
'E':"ÉÈÊË","I":"ÎÏ","O":"Ô","U":"Ù","C":"Ç"}
r=""
for c in s:
ok=False
for d in accent.keys():
if c in accent[d]:
r+=d
ok=True
if not ok:
r+=c
return r
def pourPronote(request):
"""
Exports the photos into a directory, as expected by the software
Pronote.
"""
from tempfile import mkdtemp
from subprocess import call
persons=Person.objects.all()
destdir=mkdtemp(prefix='Pronote_')
vignettes=[]
for p in persons:
nom=p.lastName
prenom=p.firstName
photo=p.photo
if photo and nom and prenom:
path=os.path.join(BASE_DIR, "photos", os.path.basename(photo.path))
target="{prenom}.{nom}.jpg".format(
prenom=protege(sansAccent(prenom)).lower(),
nom=protege(sansAccent(nom)).lower(),
)
cmd="cp {p} {t}".format(
p=path, t=os.path.join(destdir, target))
call(cmd, shell=True)
vignettes.append({
"nom": nom,
"prenom": prenom,
"url": photo.url,
})
print("GRRRR", vignettes)
return render(request,"pose/pourPronote.html",{
"destdir": destdir+"/",
"vignettes" : vignettes,
})