wcs : on crée aussi le dossier data/ et on ignore les dossiers invalides
[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                     #m = magicmime.from_file(p) => ce sera pour plus tard…
140                     m = magicmime.file(p).split()[0].strip(';')
141                     extension = mimetypes.guess_extension(m)
142                     if extension is not None:
143                         extension = extension[1:]
144                     else:
145                         extension = 'unknown'
146                 result[field_name] = "%s.%s" % (field_name, extension)
147                 qfiles[field_name] = data.qfilename
148             else:
149                 logging.error("Type de champ inconnu '%s' pour '%s'.",
150                                     field.__class__.__name__, field.label)
151                 raise RuntimeError
152
153         num_dossier = result['num_dossier']
154         nom = reduce_to_alnum(result['nom']).upper()
155         prenom = reduce_to_alnum(result['prenom']).upper()
156         adel = result['adresse_electronique'].replace('@','-').lower()
157
158         filename = "%04d-%s-%s-%s" % (num_dossier, nom, prenom, adel)
159         liste_dossiers.append(filename + '.json')
160
161         # copie des fichiers joints
162         for f in qfiles:
163             result[f] = filename + '_' + result[f]
164             src = os.path.join(pub.app_dir, 'uploads', qfiles[f])
165             dst = os.path.join(output_directory, 'data', result[f])
166             if not os.path.exists(dst) or os.path.getmtime(src) > os.path.getmtime(dst):
167                 shutil.copy2(src, dst)
168                 os.chmod(dst, 0644)
169
170         # génération du fichier JSON
171         jsonname = os.path.join(output_directory, 'data', filename + '.json')
172         f = open(jsonname, 'wb')
173         f.write(json.dumps(result, ensure_ascii=False, sort_keys=True))
174         f.close()
175
176         logging.info("Dossier '%s' : %s.",
177                                     filename, result['wcs_workflow_status'])
178
179     f = open(os.path.join(output_directory, 'liste-dossiers.json'), 'wb')
180     f.write(json.dumps(liste_dossiers, ensure_ascii=False, sort_keys=True))
181     f.close()
182
183
184 if __name__ == '__main__':
185     import sys
186
187     if len(sys.argv) != 4:
188         print >>sys.stderr, "Usage : %s <dossier-destination> <site> <formulaire>" % sys.argv[0]
189         sys.exit(1)
190
191     VHOST = sys.argv[2]
192     FORM_NAME = sys.argv[3]
193     OUTPUT_DIRECTORY = os.path.join(sys.argv[1], VHOST, FORM_NAME)
194
195     os.umask(0022)
196     # création du dossier d'extraction, au besoin
197     if not os.path.isdir(os.path.join(OUTPUT_DIRECTORY, 'data')):
198         os.makedirs(os.path.join(OUTPUT_DIRECTORY, 'data'), 0755)
199
200     logging.basicConfig(level=logging.DEBUG,
201         format='%(asctime)s %(levelname)s %(message)s',
202         filename=os.path.join(OUTPUT_DIRECTORY, 'last-run.log'),
203         filemode='w')
204
205     logging.info('Début.')
206
207     pub = publisher.WcsPublisher.create_publisher()
208     pub.app_dir = os.path.join(pub.app_dir, VHOST)
209
210     formdef = FormDef.get_by_urlname(FORM_NAME)
211
212     extract_fields(formdef, OUTPUT_DIRECTORY)
213
214     extract_data(formdef, OUTPUT_DIRECTORY)
215
216     logging.info('Fin.')
217