wcs-extract : gestion des exceptions sur la détection du type de fichier
[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, sort_keys=True))
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
93     liste_dossiers = []
94     for object in formdef.data_class().select():
95         if object.user is None:
96             logging.warning("Dossier '%s' sans utilisateur associé ?!?"\
97                             " On ignore...", object.id)
98             continue
99
100         result = {
101             'num_dossier': object.id,
102             'wcs_status': object.status,
103             'wcs_workflow_status': object.get_workflow_status().name,
104             'wcs_user_email': object.user.email,
105             'wcs_user_display_name': object.user.display_name,
106            #'wcs_last_modified': strftime('%Y-%m-%d %H:%M:%S', gmtime(object.last_modified())),
107             'wcs_comments': [],
108         }
109
110         if object.evolution is not None:
111             for e in object.evolution:
112                 if e.comment is not None:
113                     who = pub.user_class.get(e.who).display_name
114                     time = strftime('%Y-%m-%d %H:%M:%S', e.time)
115                     comment = '%s -- %s %s' % (e.comment, who, time)
116                     result['wcs_comments'].append(comment)
117
118         qfiles = { }
119         for field in formdef.fields:
120             field_id = str(field.id)
121             if not field_id in object.data:
122                 continue
123             if isinstance(field, TitleField) or isinstance(field, CommentField):
124                 continue
125             field_name = field_names[field_id]
126             data = object.data.get(field_id)
127             if isinstance(field, StringField) or isinstance(field, TextField) \
128             or isinstance(field, EmailField) or isinstance(field, ItemField):
129                 result[field_name] = data
130             elif isinstance(field, BoolField):
131                 result[field_name] = (data == 'True')
132             elif isinstance(field, DateField):
133                 result[field_name] = strftime('%Y-%m-%d', data)
134             elif isinstance(field, FileField):
135                 if '.' in data.orig_filename:
136                     extension = data.orig_filename.rpartition('.')[2].lower()
137                 else: # il n'y a pas d'extension dans le nom de fichier
138                     p = os.path.join(pub.app_dir, 'uploads', data.qfilename)
139                     try:
140                         #m = magicmime.from_file(p) => ce sera pour plus tard…
141                         m = magicmime.file(p).split()[0].strip(';')
142                         extension = mimetypes.guess_extension(m)
143                     except:
144                         logging.warning("Type de fichier inconnu pour '%s'.", p)
145                         extension = None
146                     if extension is not None:
147                         extension = extension[1:]
148                     else:
149                         extension = 'unknown'
150                 result[field_name] = "%s.%s" % (field_name, extension)
151                 qfiles[field_name] = data.qfilename
152             else:
153                 logging.error("Type de champ inconnu '%s' pour '%s'.",
154                                     field.__class__.__name__, field.label)
155                 raise RuntimeError
156
157         num_dossier = result['num_dossier']
158         nom = reduce_to_alnum(result['nom']).upper()
159         prenom = reduce_to_alnum(result['prenom']).upper()
160         adel = result['adresse_electronique'].replace('@','-').lower()
161
162         filename = "%04d-%s-%s-%s" % (num_dossier, nom, prenom, adel)
163         liste_dossiers.append(filename + '.json')
164
165         # copie des fichiers joints
166         for f in qfiles:
167             result[f] = filename + '_' + result[f]
168             src = os.path.join(pub.app_dir, 'uploads', qfiles[f])
169             dst = os.path.join(output_directory, 'data', result[f])
170             if not os.path.exists(dst) or os.path.getmtime(src) > os.path.getmtime(dst):
171                 shutil.copy2(src, dst)
172                 os.chmod(dst, 0644)
173
174         # génération du fichier JSON
175         jsonname = os.path.join(output_directory, 'data', filename + '.json')
176         f = open(jsonname, 'wb')
177         f.write(json.dumps(result, ensure_ascii=False, sort_keys=True))
178         f.close()
179
180         logging.info("Dossier '%s' : %s.",
181                                     filename, result['wcs_workflow_status'])
182
183     f = open(os.path.join(output_directory, 'liste-dossiers.json'), 'wb')
184     f.write(json.dumps(liste_dossiers, ensure_ascii=False, sort_keys=True))
185     f.close()
186
187
188 if __name__ == '__main__':
189     import sys
190
191     if len(sys.argv) != 4:
192         print >>sys.stderr, "Usage : %s <dossier-destination> <site> <formulaire>" % sys.argv[0]
193         sys.exit(1)
194
195     VHOST = sys.argv[2]
196     FORM_NAME = sys.argv[3]
197     OUTPUT_DIRECTORY = os.path.join(sys.argv[1], VHOST, FORM_NAME)
198
199     os.umask(0022)
200     # création du dossier d'extraction, au besoin
201     if not os.path.isdir(os.path.join(OUTPUT_DIRECTORY, 'data')):
202         os.makedirs(os.path.join(OUTPUT_DIRECTORY, 'data'), 0755)
203
204     logging.basicConfig(level=logging.DEBUG,
205         format='%(asctime)s %(levelname)s %(message)s',
206         filename=os.path.join(OUTPUT_DIRECTORY, 'last-run.log'),
207         filemode='w')
208
209     logging.info('Début.')
210
211     pub = publisher.WcsPublisher.create_publisher()
212     pub.app_dir = os.path.join(pub.app_dir, VHOST)
213
214     formdef = FormDef.get_by_urlname(FORM_NAME)
215
216     extract_fields(formdef, OUTPUT_DIRECTORY)
217
218     extract_data(formdef, OUTPUT_DIRECTORY)
219
220     logging.info('Fin.')
221