wcs : gestion des champs de type date non remplis.
[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, TableField
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         if field.varname:
69             name = field.varname
70         else:
71             name = reduce_to_alnum(field.label,'_').lower()
72         if name in field_names.values(): # duplicat
73             field_names_duplicates[name] = field_names_duplicates.get(name, 1) + 1
74             name = '%s_%d' % (name, field_names_duplicates[name])
75         field_names.update({field.id: name})
76         print >>f, "%s:%s:%s" % (field.id, field_names[field.id], field.label)
77
78     f.close()
79
80     f = open(os.path.join(output_directory, 'field-names.json'), 'wb')
81     f.write(json.dumps(field_names, ensure_ascii=False))
82     f.close()
83
84
85 def extract_data(formdef, output_directory):
86     """extraction des données du formulaire"""
87     # TODO: devrait retourner un résultat, qui serait alors sauvé en dehors
88
89     # XXX: hack temporaire… :-/
90     global pub
91
92     # on charge la base des types MIME une fois pour toutes
93     #magicmime = magic.Magic(mime=True) => ce sera pour plus tard…
94     magicmime = magic.open(magic.MAGIC_MIME)
95     magicmime.load()
96
97     liste_dossiers = []
98     for object in formdef.data_class().select():
99         if object.user is None:
100             logging.warning("Dossier '%s' sans utilisateur associé ?!?"\
101                             " On ignore...", object.id)
102             continue
103
104         result = {
105             'num_dossier': object.id,
106             'wcs_status': object.status,
107             'wcs_workflow_status': (object.status.startswith('wf-') and \
108                                 object.get_workflow_status().name or None),
109             'wcs_user_email': object.user.email,
110             'wcs_user_display_name': object.user.display_name,
111            #'wcs_last_modified': strftime('%Y-%m-%d %H:%M:%S', gmtime(object.last_modified())),
112             'wcs_comments': [],
113         }
114
115         if object.evolution is not None:
116             for e in object.evolution:
117                 if e.comment is not None:
118                     who = pub.user_class.get(e.who).display_name
119                     time = strftime('%Y-%m-%d %H:%M:%S', e.time)
120                     comment = '%s -- %s %s' % (e.comment, who, time)
121                     result['wcs_comments'].append(comment)
122
123         qfiles = { }
124         for field in formdef.fields:
125             field_id = str(field.id)
126             if not field_id in object.data:
127                 continue
128             if isinstance(field, TitleField) or isinstance(field, CommentField):
129                 continue
130             field_name = field_names[field_id]
131             data = object.data.get(field_id)
132             if data is None:
133                 result[field_name] = None
134                 continue
135             if isinstance(field, StringField) or isinstance(field, TextField) \
136             or isinstance(field, EmailField) or isinstance(field, ItemField):
137                 result[field_name] = data
138             elif isinstance(field, ItemsField) or isinstance(field, TableField):
139                 result[field_name] = data # liste => peux-être joindre sur ';'
140             elif isinstance(field, BoolField):
141                 result[field_name] = (data == 'True')
142             elif isinstance(field, DateField):
143                 if isinstance(data, time.struct_time):
144                     result[field_name] = '%04d-%02d-%02d' % (data.tm_year,
145                                                     data.tm_mon, data.tm_mday)
146                 else:
147                     result[field_name] = data
148             elif isinstance(field, FileField):
149                 if '.' in data.orig_filename:
150                     extension = data.orig_filename.rpartition('.')[2].lower()
151                 else: # il n'y a pas d'extension dans le nom de fichier
152                     p = os.path.join(pub.app_dir, 'uploads', data.qfilename)
153                     try:
154                         #m = magicmime.from_file(p) => ce sera pour plus tard…
155                         m = magicmime.file(p).split()[0].strip(';')
156                         extension = mimetypes.guess_extension(m)
157                     except:
158                         logging.warning("Type de fichier inconnu pour '%s'.", p)
159                         extension = None
160                     if extension is not None:
161                         extension = extension[1:]
162                     else:
163                         extension = 'unknown'
164                 result[field_name] = "%s.%s" % (field_name, extension)
165                 qfiles[field_name] = data.qfilename
166             else:
167                 logging.warning("Type de champ inconnu '%s' pour '%s' (%s).",
168                             field.__class__.__name__, field_name, field.label)
169
170         num_dossier = result['num_dossier']
171         nom = reduce_to_alnum(result.get('nom','sans-nom')).upper()
172         prenom = reduce_to_alnum(result.get('prenom','sans-prenom')).upper()
173         adel = result.get('adresse_electronique','sans-adel').replace('@','-').lower()
174
175         filename = "%04d-%s-%s-%s" % (num_dossier, nom, prenom, adel)
176         liste_dossiers.append(filename + '.json')
177
178         # copie des fichiers joints
179         for f in qfiles:
180             result[f] = filename + '_' + result[f]
181             src = os.path.join(pub.app_dir, 'uploads', qfiles[f])
182             dst = os.path.join(output_directory, 'data', result[f])
183             if not os.path.exists(dst) or os.path.getmtime(src) > os.path.getmtime(dst):
184                 shutil.copy2(src, dst)
185                 os.chmod(dst, 0644)
186
187         # génération du fichier JSON
188         jsonname = os.path.join(output_directory, 'data', filename + '.json')
189         f = open(jsonname, 'wb')
190         f.write(json.dumps(result, ensure_ascii=False).encode('utf-8'))
191         f.close()
192
193         logging.info("Dossier '%s' : %s.",
194                                     filename, result['wcs_workflow_status'])
195
196     liste_dossiers.sort()
197     f = open(os.path.join(output_directory, 'liste-dossiers.json'), 'wb')
198     f.write(json.dumps(liste_dossiers, ensure_ascii=False))
199     f.close()
200
201
202 if __name__ == '__main__':
203     import sys
204
205     if len(sys.argv) != 4:
206         print >>sys.stderr, "Usage : %s <dossier-destination> <site> <formulaire>" % sys.argv[0]
207         sys.exit(1)
208
209     VHOST = sys.argv[2]
210     FORM_NAME = sys.argv[3]
211     OUTPUT_DIRECTORY = os.path.join(sys.argv[1], VHOST, FORM_NAME)
212
213     os.umask(0022)
214     # création du dossier d'extraction, au besoin
215     if not os.path.isdir(os.path.join(OUTPUT_DIRECTORY, 'data')):
216         os.makedirs(os.path.join(OUTPUT_DIRECTORY, 'data'), 0755)
217
218     logging.basicConfig(level=logging.DEBUG,
219         format='%(asctime)s %(levelname)s %(message)s',
220         filename=os.path.join(OUTPUT_DIRECTORY, 'last-run.log'),
221         filemode='w')
222
223     logging.info('Début.')
224
225     pub = publisher.WcsPublisher.create_publisher()
226     pub.app_dir = os.path.join(pub.app_dir, VHOST)
227
228     formdef = FormDef.get_by_urlname(FORM_NAME)
229
230     extract_fields(formdef, OUTPUT_DIRECTORY)
231
232     try:
233         extract_data(formdef, OUTPUT_DIRECTORY)
234     except:
235         logging.exception("Interruption du traitement pour cause d'erreur !")
236
237     logging.info('Fin.')
238