handle utf in make_master
[redakcja.git] / apps / catalogue / migrations / 0003_from_hg.py
1 # encoding: utf-8
2 import datetime
3 from zlib import compress
4 import os
5 import os.path
6 import re
7 import urllib
8
9 from django.db import models
10 from mercurial import hg, ui
11 from south.db import db
12 from south.v2 import DataMigration
13
14 from django.conf import settings
15 from slughifi import slughifi
16
17 META_REGEX = re.compile(r'\s*<!--\s(.*?)-->', re.DOTALL | re.MULTILINE)
18 STAGE_TAGS_RE = re.compile(r'^#stage-finished: (.*)$', re.MULTILINE)
19 AUTHOR_RE = re.compile(r'\s*(.*?)\s*<(.*)>\s*')
20
21
22 def urlunquote(url):
23     """Unqotes URL
24
25     # >>> urlunquote('Za%C5%BC%C3%B3%C5%82%C4%87_g%C4%99%C5%9Bl%C4%85_ja%C5%BA%C5%84')
26     # u'Za\u017c\xf3\u0142\u0107_g\u0119\u015bl\u0105 ja\u017a\u0144'
27     """
28     return unicode(urllib.unquote(url), 'utf-8', 'ignore')
29
30
31 def split_name(name):
32     parts = name.split('__')
33     return parts
34
35
36 def file_to_title(fname):
37     """ Returns a title-like version of a filename. """
38     parts = (p.replace('_', ' ').title() for p in fname.split('__'))
39     return ' / '.join(parts)
40
41
42 def plain_text(text):
43     return re.sub(META_REGEX, '', text, 1)
44
45
46 def gallery(slug, text):
47     result = {}
48
49     m = re.match(META_REGEX, text)
50     if m:
51         for line in m.group(1).split('\n'):
52             try:
53                 k, v = line.split(':', 1)
54                 result[k.strip()] = v.strip()
55             except ValueError:
56                 continue
57
58     gallery = result.get('gallery', slughifi(slug))
59
60     if gallery.startswith('/'):
61         gallery = os.path.basename(gallery)
62
63     return gallery
64
65
66 def migrate_file_from_hg(orm, fname, entry):
67     fname = urlunquote(fname)
68     print fname
69     if fname.endswith('.xml'):
70         fname = fname[:-4]
71     title = file_to_title(fname)
72     fname = slughifi(fname)
73
74     # create all the needed objects
75     # what if it already exists?
76     book = orm.Book.objects.create(
77         title=title,
78         slug=fname)
79     chunk = orm.Chunk.objects.create(
80         book=book,
81         number=1,
82         slug='1')
83     try:
84         chunk.stage = orm.ChunkTag.objects.order_by('ordering')[0]
85     except IndexError:
86         chunk.stage = None
87
88     maxrev = entry.filerev()
89     gallery_link = None
90
91     # this will fail if directory exists
92     os.makedirs(os.path.join(settings.CATALOGUE_REPO_PATH, str(chunk.pk)))
93
94     for rev in xrange(maxrev + 1):
95         fctx = entry.filectx(rev)
96         data = fctx.data()
97         gallery_link = gallery(fname, data)
98         data = plain_text(data)
99
100         # get tags from description
101         description = fctx.description().decode("utf-8", 'replace')
102         tags = STAGE_TAGS_RE.findall(description)
103         tags = [orm.ChunkTag.objects.get(slug=slug.strip()) for slug in tags]
104
105         if tags:
106             max_ordering = max(tags, key=lambda x: x.ordering).ordering
107             try:
108                 chunk.stage = orm.ChunkTag.objects.filter(ordering__gt=max_ordering).order_by('ordering')[0]
109             except IndexError:
110                 chunk.stage = None
111
112         description = STAGE_TAGS_RE.sub('', description)
113
114         author = author_name = author_email = None
115         author_desc = fctx.user().decode("utf-8", 'replace')
116         m = AUTHOR_RE.match(author_desc)
117         if m:
118             try:
119                 author = orm['auth.User'].objects.get(username=m.group(1), email=m.group(2))
120             except orm['auth.User'].DoesNotExist:
121                 author_name = m.group(1)
122                 author_email = m.group(2)
123         else:
124             author_name = author_desc
125
126         head = orm.ChunkChange.objects.create(
127             tree=chunk,
128             revision=rev + 1,
129             created_at=datetime.datetime.fromtimestamp(fctx.date()[0]),
130             description=description,
131             author=author,
132             author_name=author_name,
133             author_email=author_email,
134             parent=chunk.head
135             )
136
137         path = "%d/%d" % (chunk.pk, head.pk)
138         abs_path = os.path.join(settings.CATALOGUE_REPO_PATH, path)
139         f = open(abs_path, 'wb')
140         f.write(compress(data))
141         f.close()
142         head.data = path
143
144         head.tags = tags
145         head.save()
146
147         chunk.head = head
148
149     chunk.save()
150     if gallery_link:
151         book.gallery = gallery_link
152         book.save()
153
154
155 class Migration(DataMigration):
156
157     def forwards(self, orm):
158         try:
159             hg_path = settings.WIKI_REPOSITORY_PATH
160         except:
161             print 'repository not configured, skipping'
162         else:
163             print 'migrate from', hg_path
164             repo = hg.repository(ui.ui(), hg_path)
165             tip = repo['tip']
166             for fname in tip:
167                 if fname.startswith('.'):
168                     continue
169                 migrate_file_from_hg(orm, fname, tip[fname])
170
171
172     def backwards(self, orm):
173         "Write your backwards methods here."
174         pass
175
176
177     models = {
178         'auth.group': {
179             'Meta': {'object_name': 'Group'},
180             'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
181             'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
182             'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
183         },
184         'auth.permission': {
185             'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
186             'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
187             'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
188             'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
189             'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
190         },
191         'auth.user': {
192             'Meta': {'object_name': 'User'},
193             'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
194             'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
195             'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
196             'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
197             'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
198             'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
199             'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
200             'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
201             'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
202             'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
203             'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
204             'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
205             'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
206         },
207         'catalogue.book': {
208             'Meta': {'ordering': "['parent_number', 'title']", 'object_name': 'Book'},
209             '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
210             '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
211             '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
212             '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
213             'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
214             'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
215             'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
216             'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
217             'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
218             'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
219         },
220         'catalogue.bookpublishrecord': {
221             'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'},
222             'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}),
223             'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
224             'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
225             'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
226         },
227         'catalogue.chunk': {
228             'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'},
229             '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
230             '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
231             '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
232             'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}),
233             'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_documents'", 'null': 'True', 'to': "orm['auth.User']"}),
234             'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}),
235             'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
236             'number': ('django.db.models.fields.IntegerField', [], {}),
237             'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
238             'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}),
239             'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
240             'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
241         },
242         'catalogue.chunkchange': {
243             'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'},
244             'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
245             'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
246             'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
247             'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
248             'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
249             'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
250             'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
251             'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
252             'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
253             'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
254             'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
255             'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}),
256             'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"})
257         },
258         'catalogue.chunkpublishrecord': {
259             'Meta': {'object_name': 'ChunkPublishRecord'},
260             'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}),
261             'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}),
262             'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
263         },
264         'catalogue.chunktag': {
265             'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'},
266             'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
267             'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
268             'ordering': ('django.db.models.fields.IntegerField', [], {}),
269             'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'})
270         },
271         'contenttypes.contenttype': {
272             'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
273             'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
274             'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
275             'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
276             'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
277         }
278     }
279
280     complete_apps = ['catalogue']