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