]> git.openstreetmap.org Git - osqa.git/blob - forum_modules/exporter/exporter.py
a52bbb6a77ae08b97f9567ca0a32b7ee169bdde4
[osqa.git] / forum_modules / exporter / exporter.py
1 import os, tarfile, datetime, logging
2
3 from django.core.cache import cache
4 from django.utils.translation import ugettext as _
5 from forum.models import *
6 from forum.settings import APP_URL
7 from forum.templatetags.extra_tags import diff_date
8 import xml.etree.ElementTree
9 from xml.etree import ElementTree as ET
10 from xml.etree.ElementTree import Comment, _encode, ProcessingInstruction, QName, fixtag, _escape_attrib, _escape_cdata
11
12 CACHE_KEY = "%s_exporter_state" % APP_URL
13 EXPORT_STEPS = []
14
15 TMP_FOLDER = os.path.join(os.path.dirname(__file__), 'tmp')
16 LAST_BACKUP = os.path.join(TMP_FOLDER, 'backup.tar.gz')
17
18 def Etree_pretty__write(self, file, node, encoding, namespaces,
19                         level=0, identator="    "):
20     tag = node.tag
21     if tag is Comment:
22         file.write(level * identator + "<!-- %s -->" % _escape_cdata(node.text, encoding))
23     elif tag is ProcessingInstruction:
24         file.write("<?%s?>" % _escape_cdata(node.text, encoding))
25     else:
26         items = node.items()
27         xmlns_items = [] # new namespaces in this scope
28         try:
29             if isinstance(tag, QName) or tag[:1] == "{":
30                 tag, xmlns = fixtag(tag, namespaces)
31                 if xmlns: xmlns_items.append(xmlns)
32         except TypeError:
33             raise #_raise_serialization_error(tag)
34         file.write("\n" + level * identator + "<" + _encode(tag, encoding))
35         if items or xmlns_items:
36             items.sort() # lexical order
37             for k, v in items:
38                 try:
39                     if isinstance(k, QName) or k[:1] == "{":
40                         k, xmlns = fixtag(k, namespaces)
41                         if xmlns: xmlns_items.append(xmlns)
42                 except TypeError:
43                     raise #_raise_serialization_error(k)
44                 try:
45                     if isinstance(v, QName):
46                         v, xmlns = fixtag(v, namespaces)
47                         if xmlns: xmlns_items.append(xmlns)
48                 except TypeError:
49                     raise #_raise_serialization_error(v)
50                 file.write(" %s=\"%s\"" % (_encode(k, encoding),
51                                             _escape_attrib(v, encoding)))
52             for k, v in xmlns_items:
53                 file.write(" %s=\"%s\"" % (_encode(k, encoding),
54                                             _escape_attrib(v, encoding)))
55         if node.text or len(node):
56             file.write(">")
57             if node.text:
58                 file.write(_escape_cdata(node.text.replace("\n", (level + 1) * identator + "\n"), encoding))
59             for n in node:
60                 self._write(file, n, encoding, namespaces, level + 1, identator)
61             if node.text and len(node.text) < 125:
62                 file.write("</" + _encode(tag, encoding) + ">")
63             else:
64                 file.write("\n" + level * identator + "</" + _encode(tag, encoding) + ">")
65         else:
66             file.write(" />")
67         for k, v in xmlns_items:
68             del namespaces[v]
69     if node.tail:
70         file.write(_escape_cdata(node.tail.replace("\n", level * identator + "\n"), encoding))
71
72 def _add_tag(el, name, content = None):
73     tag = ET.SubElement(el, name)
74     if content:
75         tag.text = content
76     return tag
77
78 def ET_Element_add_tag(el, name, content = None, **attrs):
79     tag = ET.SubElement(el, name)
80
81     if content:
82         tag.text = unicode(content)
83
84     for k, v in attrs.items():
85         tag.set(k, unicode(v))
86
87     return tag
88
89 def make_extra(el, extra):
90     if not extra:
91         return
92
93     if isinstance(extra, dict):
94         for k, v in extra.items():
95             make_extra(el.add(k), v)
96     else:
97         el.text = unicode(extra)
98
99 def write_to_file(root, tmp, filename):
100     tree = ET.ElementTree(root)
101     tree.write(os.path.join(tmp, filename), encoding='UTF-8')
102
103 def create_targz(tmp, files):
104     if os.path.exists(LAST_BACKUP):
105         os.remove(LAST_BACKUP)
106         
107     t = tarfile.open(name=LAST_BACKUP, mode = 'w:gz')
108
109     for f in files:
110         t.add(os.path.join(tmp, f), arcname=f)
111
112     t.close()
113
114
115 def export(options):
116     original__write = xml.etree.ElementTree.ElementTree._write
117     xml.etree.ElementTree.ElementTree._write = Etree_pretty__write
118     xml.etree.ElementTree._ElementInterface.add = ET_Element_add_tag
119
120     start_time = datetime.datetime.now()
121     tmp = TMP_FOLDER
122     anon_data = options.get('anon_data', False)
123
124     steps = [s for s in EXPORT_STEPS if not (anon_data and s['fn'].is_user_data())]
125
126     state = dict([(s['id'], {
127         'status': _('Queued'), 'count': s['fn'].count(start_time), 'parsed': 0
128     }) for s in steps] + [
129         ('overall', {
130             'status': _('Starting'), 'count': sum([s['fn'].count(start_time) for s in steps]), 'parsed': 0
131         })
132     ])
133
134     full_state = dict(running=True, state=state, time_started="")
135
136     def set_state():
137         full_state['time_started'] = diff_date(start_time)
138         cache.set(CACHE_KEY, full_state)
139
140     set_state()
141
142     def ping_state(name):
143         state[name]['parsed'] += 1
144         state['overall']['parsed'] += 1
145         set_state()
146
147     def run(fn, name):
148         def ping():
149             ping_state(name)
150
151         state['overall']['status'] = _('Exporting %s') % s['name']
152         state[name]['status'] = _('Exporting')
153
154         root, fname = fn(ping, start_time, anon_data)
155
156         state[name]['status'] = _('Writing temp file')
157         state['overall']['status'] = _('Writing %s temp file') % s['name']
158
159         set_state()
160
161         write_to_file(root, tmp, fname)
162         state[name]['status'] = _('Done')
163
164         set_state()
165
166         return fname
167
168     try:
169         dump_files = []
170
171         for s in steps:
172             dump_files.append(run(s['fn'], s['id']))
173
174         state['overall']['status'] = _('Compressing files')
175         set_state()
176
177         create_targz(tmp, dump_files)
178         full_state['running'] = False
179         full_state['errors'] = False
180         state['overall']['status'] = _('Done')
181
182         set_state()
183     except Exception, e:
184         full_state['running'] = False
185         full_state['errors'] = "%s: %s" % (e.__class__.__name__, unicode(e))
186         set_state()
187         
188         import traceback
189         logging.error("Error executing xml backup: \n %s" % (traceback.format_exc()))
190     finally:
191         xml.etree.ElementTree.ElementTree._write = original__write
192         del xml.etree.ElementTree._ElementInterface.add
193
194 def exporter_step(queryset, root_tag_name, el_tag_name, name, date_lock=None, user_data=False):
195
196     def decorator(fn):
197         def qs(lock):
198             if date_lock:
199                 return queryset.filter(**{"%s__lte" % date_lock: lock})
200             return queryset
201
202         def decorated(ping, lock, anon_data):
203             root = ET.Element(root_tag_name)
204
205             for item in qs(lock).select_related():
206                 el = root.add(el_tag_name)
207                 fn(item, el, anon_data)
208                 ping()
209
210             return root, "%s.xml" % root_tag_name
211
212         def count(lock):
213             return qs(lock).count()
214
215         def is_user_data():
216             return user_data
217
218         decorated.count = count
219         decorated.is_user_data = is_user_data
220
221         EXPORT_STEPS.append(dict(id=root_tag_name, name=name, fn=decorated))
222
223         return decorated
224
225     return decorator
226
227 @exporter_step(Tag.objects.all(), 'tags', 'tag', _('Tags'))
228 def export_tags(t, el, anon_data):
229     el.add('name', t.name)
230     if not anon_data:
231         el.add('author', t.created_by.id)
232     el.add('used', t.used_count)
233
234
235 @exporter_step(User.objects.all(), 'users', 'user', _('Users'), 'date_joined', True)
236 def export_users(u, el, anon_data):
237     el.add('id', u.id)
238     el.add('username', u.username)
239     el.add('email', u.email, validated=u.email_isvalid and 'true' or 'false')
240     el.add('joindate', u.date_joined)
241
242     el.add('firstname', u.first_name)
243     el.add('lastname', u.last_name)
244     el.add('bio', u.about)
245     el.add('location', u.location)
246     el.add('website', u.website)
247     el.add('birthdate', u.date_of_birth)
248
249     roles = el.add('roles')
250
251     if u.is_superuser:
252         roles.add('role', 'superuser')
253
254     if u.is_staff:
255         roles.add('role', 'moderator')
256
257     el.add('reputation', u.reputation)
258
259 @exporter_step(Node.objects.all(), 'nodes', 'node', _('Nodes'), 'added_at')
260 def export_nodes(n, el, anon_data):
261     el.add('id', n.id)
262     el.add('type', n.node_type)
263
264     if not anon_data:
265         el.add('author', n.author.id)
266     el.add('date', n.added_at)
267     el.add('parent', n.parent and n.parent.id or "")
268
269     el.add('title', n.title)
270     el.add('body', n.body)
271
272     tags = el.add('tags')
273
274     for t in n.tagname_list():
275         tags.add('tag', t)
276
277     revs = el.add('revisions', active=n.active_revision and n.active_revision or n.revisions.order_by('revision')[0])
278
279     for r in n.revisions.order_by('revision'):
280         rev = _add_tag(revs, 'revision')
281         rev.add('number', r.revision)
282         rev.add('summary', r.summary)
283         if not anon_data:
284             rev.add('author', r.author.id)
285         rev.add('date', r.revised_at)
286
287         rev.add('title', r.title)
288         rev.add('body', r.body)
289         rev.add('tags', ", ".join(r.tagname_list()))
290
291     el.add('extraRef', n.extra_ref and n.extra_ref.id or "")
292     make_extra(el.add('exraData'), n.extra)
293
294
295 @exporter_step(Action.objects.all(), 'actions', 'action', _('Actions'), 'action_date')
296 def export_actions(a, el, anon_data):
297     el.add('id', a.id)
298     el.add('type', a.action_type)
299     el.add('date', a.action_date)
300
301     if not anon_data:
302         el.add('user', a.user.id)
303         el.add('realUser', a.real_user and a.real_user.id or "")
304         el.add('ip', a.ip)
305     el.add('node', a.node and a.node.id or "")
306
307     make_extra(el.add('extraData'), a.extra)
308
309     canceled = el.add('canceled', state=a.canceled and 'true' or 'false')
310
311     if a.canceled:
312         if not anon_data:
313             canceled.add('user', a.canceled_by.id)
314             canceled.add('ip', a.canceled_ip)
315
316         canceled.add('date', a.canceled_at)        
317
318     if not anon_data:
319         reputes = el.add('reputes')
320
321         for r in a.reputes.all():
322             repute = reputes.add('repute', byCanceled=r.by_canceled and 'true' or 'false')
323             repute.add('user', r.user.id)
324             repute.add('value', r.value)
325
326
327 @exporter_step(NodeState.objects.all(), 'states', 'state', _('Node states'), 'action__action_date')
328 def export_states(s, el, anon_data):
329     el.add('type', s.state_type)
330     el.add('node', s.node.id)
331     el.add('trigger', s.action.id)
332
333
334 @exporter_step(Badge.objects.all(), 'badges', 'badge', _('Badges'), user_data=True)
335 def export_badges(b, el, anon_data):
336     el.add('type', ["", 'gold', 'silver', 'bronze'][b.type])
337     el.add('name', b.cls)
338     el.add('count', b.awarded_count)
339
340
341 @exporter_step(Award.objects.all(), 'awards', 'award', _('Awards'), 'awarded_at', True)
342 def export_awards(a, el, anon_data):
343     el.add('badge', a.badge.cls)
344     el.add('user', a.user)
345     el.add('node', a.node and a.node.id or "")
346     el.add('trigger', a.trigger and a.trigger.id or "")
347     el.add('action', a.action.id)
348
349
350
351
352
353
354
355
356         
357
358
359
360
361
362
363
364