08da8c19401bd55fb14805500c284aaf403cd47e
[progfou.git] / wcs / wcs-extract
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 """
4 Outil d'export de données w.c.s.
5
6 Copyright : Agence universitaire de la Francophonie — www.auf.org
7 Licence : GNU General Public Licence, version 2
8 Auteur : Jean Christophe André
9 Date de création : 15 octobre 2009
10
11 Depends: wcs, python-simplejson, python-magic
12 """
13 import os
14 import os.path
15 import shutil
16 import logging
17 from time import gmtime, strftime
18 import simplejson as json
19 import magic
20 import mimetypes
21
22 from wcs import publisher
23 from wcs.formdef import FormDef
24 from wcs.fields import TitleField, CommentField, TextField, \
25                        StringField, ItemField, EmailField, \
26                        DateField, FileField, BoolField
27
28
29 def reduce_to_alnum(s, replacement_char='-'):
30     """réduction d'une chaîne de caractères à de l'alpha-numérique"""
31
32     avec_accent = u'çÇáàâÁÀÂéèêëÉÈÊËíìîïÍÌÎÏóòôöÓÒÔÖúùûüÚÙÛÜýỳyÿÝỲYŸ'
33     sans_accent = u'cCaaaAAAeeeeEEEEiiiiIIIIooooOOOOuuuuUUUUyyyyYYYY'
34     if type(s) is not unicode:
35         s = unicode(s, 'utf-8')
36         u  = False
37     r = ''
38     for c in s:
39         index = avec_accent.find(c)
40         if index >= 0:
41             r += sans_accent[index]
42         elif ('a' <= c.lower() <= 'z') or ('0' <= c <= '9'):
43             r += c
44         elif len(r) > 0 and r[-1] != replacement_char:
45             r += replacement_char
46         else: # r == '' or r[-1] == replacement_char
47             pass
48     r = r.strip(replacement_char)
49     if not u:
50         r = r.encode('utf-8')
51     return r
52
53
54 def extract_fields(formdef, output_directory):
55     """nommage des champs de façon unique"""
56     # TODO: devrait retourner un résultat, qui serait alors sauvé en dehors
57
58     # XXX: hack temporaire… :-/
59     global field_names
60
61     f = open(os.path.join(output_directory, 'field-names.txt'), 'w')
62
63     field_names = {}
64     field_names_duplicates = {}
65     for field in formdef.fields:
66         if isinstance(field, TitleField) or isinstance(field, CommentField):
67             continue
68         name = reduce_to_alnum(field.label,'_').lower()
69         if name in field_names.values(): # duplicat
70             field_names_duplicates[name] = field_names_duplicates.get(name, 1) + 1
71             name = '%s_%d' % (name, field_names_duplicates[name])
72         field_names.update({field.id: name})
73         print >>f, "%s:%s:%s" % (field.id, field_names[field.id], field.label)
74
75     f.close()
76
77     f = open(os.path.join(output_directory, 'field-names.json'), 'wb')
78     f.write(json.dumps(field_names, ensure_ascii=False))
79     f.close()
80
81
82 def extract_data(formdef, output_directory):
83     """extraction des données du formulaire"""
84     # TODO: devrait retourner un résultat, qui serait alors sauvé en dehors
85
86     # XXX: hack temporaire… :-/
87     global pub
88
89     # on charge la base des types MIME une fois pour toutes
90     #magicmime = magic.Magic(mime=True) => ce sera pour plus tard…
91     magicmime = magic.open(magic.MAGIC_MIME)
92     magicmime.load()
93
94     liste_dossiers = []
95     for object in formdef.data_class().select():
96         if object.user is None:
97             logging.warning("Dossier '%s' sans utilisateur associé ?!?"\
98                             " On ignore...", object.id)
99             continue
100
101         result = {
102             'num_dossier': object.id,
103             'wcs_status': object.status,
104             'wcs_workflow_status': object.get_workflow_status().name,
105             'wcs_user_email': object.user.email,
106             'wcs_user_display_name': object.user.display_name,
107            #'wcs_last_modified': strftime('%Y-%m-%d %H:%M:%S', gmtime(object.last_modified())),
108             'wcs_comments': [],
109         }
110
111         if object.evolution is not None:
112             for e in object.evolution:
113                 if e.comment is not None:
114                     who = pub.user_class.get(e.who).display_name
115                     time = strftime('%Y-%m-%d %H:%M:%S', e.time)
116                     comment = '%s -- %s %s' % (e.comment, who, time)
117                     result['wcs_comments'].append(comment)
118
119         qfiles = { }
120         for field in formdef.fields:
121             field_id = str(field.id)
122             if not field_id in object.data:
123                 continue
124             if isinstance(field, TitleField) or isinstance(field, CommentField):
125                 continue
126             field_name = field_names[field_id]
127             data = object.data.get(field_id)
128             if isinstance(field, StringField) or isinstance(field, TextField) \
129             or isinstance(field, EmailField) or isinstance(field, ItemField):
130                 result[field_name] = data
131             elif isinstance(field, BoolField):
132                 result[field_name] = (data == 'True')
133             elif isinstance(field, DateField):
134                 result[field_name] = '%04d-%02d-%02d' % (data.tm_year,
135                                                     data.tm_mon, data.tm_mday)
136             elif isinstance(field, FileField):
137                 if data is None:
138                     continue
139                 if '.' in data.orig_filename:
140                     extension = data.orig_filename.rpartition('.')[2].lower()
141                 else: # il n'y a pas d'extension dans le nom de fichier
142                     p = os.path.join(pub.app_dir, 'uploads', data.qfilename)
143                     try:
144                         #m = magicmime.from_file(p) => ce sera pour plus tard…
145                         m = magicmime.file(p).split()[0].strip(';')
146                         extension = mimetypes.guess_extension(m)
147                     except:
148                         logging.warning("Type de fichier inconnu pour '%s'.", p)
149                         extension = None
150                     if extension is not None:
151                         extension = extension[1:]
152                     else:
153                         extension = 'unknown'
154                 result[field_name] = "%s.%s" % (field_name, extension)
155                 qfiles[field_name] = data.qfilename
156             else:
157                 logging.error("Type de champ inconnu '%s' pour '%s'.",
158                                     field.__class__.__name__, field.label)
159                 raise RuntimeError
160
161         num_dossier = result['num_dossier']
162         nom = reduce_to_alnum(result['nom']).upper()
163         prenom = reduce_to_alnum(result['prenom']).upper()
164         adel = result['adresse_electronique'].replace('@','-').lower()
165
166         filename = "%04d-%s-%s-%s" % (num_dossier, nom, prenom, adel)
167         liste_dossiers.append(filename + '.json')
168
169         # copie des fichiers joints
170         for f in qfiles:
171             result[f] = filename + '_' + result[f]
172             src = os.path.join(pub.app_dir, 'uploads', qfiles[f])
173             dst = os.path.join(output_directory, 'data', result[f])
174             if not os.path.exists(dst) or os.path.getmtime(src) > os.path.getmtime(dst):
175                 shutil.copy2(src, dst)
176                 os.chmod(dst, 0644)
177
178         # génération du fichier JSON
179         jsonname = os.path.join(output_directory, 'data', filename + '.json')
180         f = open(jsonname, 'wb')
181         f.write(json.dumps(result, ensure_ascii=False))
182         f.close()
183
184         logging.info("Dossier '%s' : %s.",
185                                     filename, result['wcs_workflow_status'])
186
187     liste_dossiers.sort()
188     f = open(os.path.join(output_directory, 'liste-dossiers.json'), 'wb')
189     f.write(json.dumps(liste_dossiers, ensure_ascii=False))
190     f.close()
191
192
193 if __name__ == '__main__':
194     import sys
195
196     if len(sys.argv) != 4:
197         print >>sys.stderr, "Usage : %s <dossier-destination> <site> <formulaire>" % sys.argv[0]
198         sys.exit(1)
199
200     VHOST = sys.argv[2]
201     FORM_NAME = sys.argv[3]
202     OUTPUT_DIRECTORY = os.path.join(sys.argv[1], VHOST, FORM_NAME)
203
204     os.umask(0022)
205     # création du dossier d'extraction, au besoin
206     if not os.path.isdir(os.path.join(OUTPUT_DIRECTORY, 'data')):
207         os.makedirs(os.path.join(OUTPUT_DIRECTORY, 'data'), 0755)
208
209     logging.basicConfig(level=logging.DEBUG,
210         format='%(asctime)s %(levelname)s %(message)s',
211         filename=os.path.join(OUTPUT_DIRECTORY, 'last-run.log'),
212         filemode='w')
213
214     logging.info('Début.')
215
216     pub = publisher.WcsPublisher.create_publisher()
217     pub.app_dir = os.path.join(pub.app_dir, VHOST)
218
219     formdef = FormDef.get_by_urlname(FORM_NAME)
220
221     extract_fields(formdef, OUTPUT_DIRECTORY)
222
223     try:
224         extract_data(formdef, OUTPUT_DIRECTORY)
225     except:
226         logging.exception("Interruption du traitement pour cause d'erreur !")
227
228     logging.info('Fin.')
229