1147ba24d116aff50ebc033411accb00818b315d
[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, ItemsField, 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 data is None:
129                 result[field_name] = None
130                 continue
131             if isinstance(field, StringField) or isinstance(field, TextField) \
132             or isinstance(field, EmailField) or isinstance(field, ItemField):
133                 result[field_name] = data
134             elif isinstance(field, ItemsField):
135                 result[field_name] = data # liste => peux-être joindre sur ';'
136             elif isinstance(field, BoolField):
137                 result[field_name] = (data == 'True')
138             elif isinstance(field, DateField):
139                 result[field_name] = '%04d-%02d-%02d' % (data.tm_year,
140                                                     data.tm_mon, data.tm_mday)
141             elif isinstance(field, FileField):
142                 if '.' in data.orig_filename:
143                     extension = data.orig_filename.rpartition('.')[2].lower()
144                 else: # il n'y a pas d'extension dans le nom de fichier
145                     p = os.path.join(pub.app_dir, 'uploads', data.qfilename)
146                     try:
147                         #m = magicmime.from_file(p) => ce sera pour plus tard…
148                         m = magicmime.file(p).split()[0].strip(';')
149                         extension = mimetypes.guess_extension(m)
150                     except:
151                         logging.warning("Type de fichier inconnu pour '%s'.", p)
152                         extension = None
153                     if extension is not None:
154                         extension = extension[1:]
155                     else:
156                         extension = 'unknown'
157                 result[field_name] = "%s.%s" % (field_name, extension)
158                 qfiles[field_name] = data.qfilename
159             else:
160                 logging.error("Type de champ inconnu '%s' pour '%s'.",
161                                     field.__class__.__name__, field.label)
162                 raise RuntimeError
163
164         num_dossier = result['num_dossier']
165         nom = reduce_to_alnum(result['nom']).upper()
166         prenom = reduce_to_alnum(result['prenom']).upper()
167         adel = result['adresse_electronique'].replace('@','-').lower()
168
169         filename = "%04d-%s-%s-%s" % (num_dossier, nom, prenom, adel)
170         liste_dossiers.append(filename + '.json')
171
172         # copie des fichiers joints
173         for f in qfiles:
174             result[f] = filename + '_' + result[f]
175             src = os.path.join(pub.app_dir, 'uploads', qfiles[f])
176             dst = os.path.join(output_directory, 'data', result[f])
177             if not os.path.exists(dst) or os.path.getmtime(src) > os.path.getmtime(dst):
178                 shutil.copy2(src, dst)
179                 os.chmod(dst, 0644)
180
181         # génération du fichier JSON
182         jsonname = os.path.join(output_directory, 'data', filename + '.json')
183         f = open(jsonname, 'wb')
184         f.write(json.dumps(result, ensure_ascii=False))
185         f.close()
186
187         logging.info("Dossier '%s' : %s.",
188                                     filename, result['wcs_workflow_status'])
189
190     liste_dossiers.sort()
191     f = open(os.path.join(output_directory, 'liste-dossiers.json'), 'wb')
192     f.write(json.dumps(liste_dossiers, ensure_ascii=False))
193     f.close()
194
195
196 if __name__ == '__main__':
197     import sys
198
199     if len(sys.argv) != 4:
200         print >>sys.stderr, "Usage : %s <dossier-destination> <site> <formulaire>" % sys.argv[0]
201         sys.exit(1)
202
203     VHOST = sys.argv[2]
204     FORM_NAME = sys.argv[3]
205     OUTPUT_DIRECTORY = os.path.join(sys.argv[1], VHOST, FORM_NAME)
206
207     os.umask(0022)
208     # création du dossier d'extraction, au besoin
209     if not os.path.isdir(os.path.join(OUTPUT_DIRECTORY, 'data')):
210         os.makedirs(os.path.join(OUTPUT_DIRECTORY, 'data'), 0755)
211
212     logging.basicConfig(level=logging.DEBUG,
213         format='%(asctime)s %(levelname)s %(message)s',
214         filename=os.path.join(OUTPUT_DIRECTORY, 'last-run.log'),
215         filemode='w')
216
217     logging.info('Début.')
218
219     pub = publisher.WcsPublisher.create_publisher()
220     pub.app_dir = os.path.join(pub.app_dir, VHOST)
221
222     formdef = FormDef.get_by_urlname(FORM_NAME)
223
224     extract_fields(formdef, OUTPUT_DIRECTORY)
225
226     try:
227         extract_data(formdef, OUTPUT_DIRECTORY)
228     except:
229         logging.exception("Interruption du traitement pour cause d'erreur !")
230
231     logging.info('Fin.')
232