PageRenderTime 34ms CodeModel.GetById 7ms app.highlight 22ms RepoModel.GetById 0ms app.codeStats 1ms

/solace/scripts.py

https://bitbucket.org/charlenopires/solace
Python | 385 lines | 347 code | 21 blank | 17 comment | 4 complexity | 8ca3086f1fb483121cb9226ac599b22e MD5 | raw file
Possible License(s): BSD-3-Clause
  1# -*- coding: utf-8 -*-
  2"""
  3    solace.scripts
  4    ~~~~~~~~~~~~~~
  5
  6    Provides some setup.py commands.  The js-translation compiler is taken
  7    from Sphinx, the Python documentation tool.
  8
  9    :copyright: (c) 2009 by Plurk Inc.
 10                (c) 2009 by the Sphinx Team, see AUTHORS for more details.
 11    :license: BSD, see LICENSE for more details.
 12"""
 13# note on imports:  This module must not import anything from the
 14# solace package, so that the initial import happens in the commands.
 15import os
 16import sys
 17from datetime import datetime, timedelta
 18from distutils import log
 19from distutils.cmd import Command
 20from distutils.errors import DistutilsOptionError, DistutilsSetupError
 21from random import randrange, choice, random, shuffle
 22from jinja2.utils import generate_lorem_ipsum
 23
 24from babel.messages.pofile import read_po
 25from babel.messages.frontend import compile_catalog
 26from simplejson import dump as dump_json
 27
 28
 29class RunserverCommand(Command):
 30    description = 'runs the development server'
 31    user_options = [
 32        ('host=', 'h',
 33         'the host of the server, defaults to localhost'),
 34        ('port=', 'p',
 35         'the port of the server, defaults to 3000'),
 36        ('no-reloader', None,
 37         'disable the automatic reloader'),
 38        ('no-debugger', None,
 39         'disable the integrated debugger')
 40    ]
 41    boolean_options = ['no-reloader', 'no-debugger']
 42
 43    def initialize_options(self):
 44        self.host = 'localhost'
 45        self.port = 3000
 46        self.no_reloader = False
 47        self.no_debugger = False
 48
 49    def finalize_options(self):
 50        if not str(self.port).isdigit():
 51            raise DistutilsOptionError('port has to be numeric')
 52
 53    def run(self):
 54        from werkzeug import run_simple
 55        def wsgi_app(*a):
 56            from solace.application import application
 57            return application(*a)
 58
 59        # werkzeug restarts the interpreter with the same arguments
 60        # which would print "running runserver" a second time.  Because
 61        # of this we force distutils into quiet mode.
 62        import sys
 63        sys.argv.insert(1, '-q')
 64
 65        run_simple(self.host, self.port, wsgi_app,
 66                   use_reloader=not self.no_reloader,
 67                   use_debugger=not self.no_debugger)
 68
 69
 70class InitDatabaseCommand(Command):
 71    description = 'initializes the database'
 72    user_options = [
 73        ('drop-first', 'D',
 74         'drops existing tables first')
 75    ]
 76    boolean_options = ['drop-first']
 77
 78    def initialize_options(self):
 79        self.drop_first = False
 80
 81    def finalize_options(self):
 82        pass
 83
 84    def run(self):
 85        from solace import database
 86        if self.drop_first:
 87            database.drop_tables()
 88            print 'dropped existing tables'
 89        database.init()
 90        print 'created database tables'
 91
 92
 93class ResetDatabase(Command):
 94    description = 'like initdb, but creates an admin:default user'
 95    user_options = [
 96        ('username', 'u', 'the admin username'),
 97        ('email', 'e', 'the admin email'),
 98        ('password', 'p', 'the admin password')
 99    ]
100
101    def initialize_options(self):
102        self.username = 'admin'
103        self.email = None
104        self.password = 'default'
105
106    def finalize_options(self):
107        if self.email is None:
108            self.email = self.username + '@localhost'
109
110    def run(self):
111        from solace import database, models
112        database.drop_tables()
113        print 'dropped existing tables'
114        database.init()
115        print 'created database tables'
116        admin = models.User(self.username, self.email, self.password,
117                            is_admin=True)
118        database.session.commit()
119        print 'Created %s:%s (%s)' % (self.username, self.password,
120                                      self.email)
121
122
123class MakeTestData(Command):
124    description = 'adds tons of test data into the database'
125    user_options = [
126        ('data-set-size', 's', 'the size of the dataset '
127         '(small, medium, large)')
128    ]
129
130    USERNAMES = '''
131        asanuma bando chiba ekiguchi erizawa fukuyama inouye ise jo kanada
132        kaneko kasahara kasuse kazuyoshi koyama kumasaka matsushina
133        matsuzawa mazaki miwa momotami morri moto nakamoto nakazawa obinata
134        ohira okakura okano oshima raikatuji saigo sakoda santo sekigawa
135        shibukji sugita tadeshi takahashi takizawa taniguchi tankoshitsu
136        tenshin umehara yamakage yamana yamanouchi yamashita yamura
137        aebru aendra afui asanna callua clesil daev danu eadyel eane efae
138        ettannis fisil frudali glapao glofen grelnor halissa iorran oamira
139        oinnan ondar orirran oudin paenael
140    '''.split()
141    TAGS = '''
142        ajhar amuse animi apiin azoic bacon bala bani bazoo bear bloom bone
143        broke bungo burse caam cento clack clear clog coyly creem cush deity
144        durry ella evan firn grasp gype hance hanky havel hunks ingot javer
145        juno kroo larix lift luke malo marge mart mash nairy nomos noyau
146        papey parch parge parka pheal pint poche pooch puff quit ravin ream
147        remap rotal rowen ruach sadhu saggy saura savor scops seat sere
148        shone shorn sitao skair skep smush snoop soss sprig stalk stiff
149        stipa study swept tang tars taxis terry thirt ulex unkin unmix unsin
150        uprid vire wally wheat woven xylan
151    '''.split()
152    EPOCH = datetime(1930, 1, 1)
153
154    def initialize_options(self):
155        from solace import settings
156        self.data_set_size = 'small'
157        self.highest_date = None
158        self.locales = settings.LANGUAGE_SECTIONS[:]
159
160    def finalize_options(self):
161        if self.data_set_size not in ('small', 'medium', 'large'):
162            raise DistutilsOptionError('invalid value for data-set-size')
163
164    def get_date(self, last=None):
165        secs = randrange(10, 120)
166        d = (last or self.EPOCH) + timedelta(seconds=secs)
167        if self.highest_date is None or d > self.highest_date:
168            self.highest_date = d
169        return d
170
171    def create_users(self):
172        """Creates a bunch of test users."""
173        from solace.models import User
174        num = {'small': 15, 'medium': 30, 'large': 50}[self.data_set_size]
175        result = []
176        used = set()
177        for x in xrange(num):
178            while 1:
179                username = choice(self.USERNAMES)
180                if username not in used:
181                    used.add(username)
182                    break
183            result.append(User(username, '%s@example.com' % username,
184                               'default'))
185        print 'Generated %d users' % num
186        return result
187
188    def create_tags(self):
189        """Creates a bunch of tags."""
190        from solace.models import Tag
191        num = {'small': 10, 'medium': 20, 'large': 50}[self.data_set_size]
192        result = {}
193        tag_count = 0
194        for locale in self.locales:
195            c = result[locale] = []
196            used = set()
197            for x in xrange(randrange(num - 5, num + 5)):
198                while 1:
199                    tag = choice(self.TAGS)
200                    if tag not in used:
201                        used.add(tag)
202                        break
203                c.append(Tag(tag, locale).name)
204                tag_count += 1
205        print 'Generated %d tags' % tag_count
206        return result
207
208    def create_topics(self, tags, users):
209        """Generates a bunch of topics."""
210        from solace.models import Topic
211        last_date = None
212        topics = []
213        num, var = {'small': (50, 10), 'medium': (200, 20),
214                    'large': (1000, 200)}[self.data_set_size]
215        count = 0
216        for locale in self.locales:
217            for x in xrange(randrange(num - var, num + var)):
218                topic = Topic(locale, generate_lorem_ipsum(1, False, 3, 9),
219                              generate_lorem_ipsum(randrange(1, 5), False,
220                                                   40, 140), choice(users),
221                              date=self.get_date(last_date))
222                last_date = topic.last_change
223                these_tags = list(tags[locale])
224                shuffle(these_tags)
225                topic.bind_tags(these_tags[:randrange(2, 6)])
226                topics.append(topic)
227                count += 1
228        print 'Generated %d topics in %d locales' % (count, len(self.locales))
229        return topics
230
231    def answer_and_vote(self, topics, users):
232        from solace.models import Post
233        replies = {'small': 4, 'medium': 8, 'large': 12}[self.data_set_size]
234        posts = [x.question for x in topics]
235        last_date = topics[-1].last_change
236        for topic in topics:
237            for x in xrange(randrange(2, replies)):
238                post = Post(topic, choice(users),
239                            generate_lorem_ipsum(randrange(1, 3), False,
240                                                 20, 100),
241                            self.get_date(last_date))
242                posts.append(post)
243                last_date = post.created
244        print 'Generated %d posts' % len(posts)
245
246        votes = 0
247        for post in posts:
248            for x in xrange(randrange(replies * 4)):
249                post = choice(posts)
250                user = choice(users)
251                if user != post.author:
252                    if random() >= 0.05:
253                        user.upvote(post)
254                    else:
255                        user.downvote(post)
256                    votes += 1
257
258        print 'Casted %d votes' % votes
259
260        answered = 0
261        for topic in topics:
262            replies = list(topic.replies)
263            if replies:
264                replies.sort(key=lambda x: x.votes)
265                post = choice(replies[:4])
266                if post.votes > 0 and random() > 0.2:
267                    topic.accept_answer(post, choice(users))
268                    answered += 1
269
270        print 'Answered %d posts' % answered
271        return posts
272
273    def create_comments(self, posts, users):
274        """Creates comments for the posts."""
275        from solace.models import Comment
276        num = {'small': 3, 'medium': 6, 'large': 10}[self.data_set_size]
277        last_date = posts[-1].created
278        comments = 0
279        for post in posts:
280            for x in xrange(randrange(num)):
281                comment = Comment(post, choice(users),
282                                  generate_lorem_ipsum(1, False, 10, 40),
283                                  self.get_date(last_date))
284                last_date = comment.date
285                comments += 1
286        print 'Generated %d comments' % comments
287
288    def rebase_dates(self, topics):
289        """Rebase all dates so that they are most recent."""
290        print 'Rebasing dates...',
291        delta = datetime.utcnow() - self.highest_date
292        for topic in topics:
293            topic.last_change += delta
294            topic.date += delta
295            for post in topic.posts:
296                post.updated += delta
297                post.created += delta
298                for comment in post.comments:
299                    comment.date += delta
300            topic._update_hotness()
301        print 'done'
302
303    def run(self):
304        from solace.database import session
305        users = self.create_users()
306        tags = self.create_tags()
307        topics = self.create_topics(tags, users)
308        posts = self.answer_and_vote(topics, users)
309        self.create_comments(posts, users)
310        self.rebase_dates(topics)
311        session.commit()
312
313
314class CompileCatalogEx(compile_catalog):
315    """Extends the standard catalog compiler to one that also creates
316    .js files for the strings that are needed in JavaScript.
317    """
318
319    def run(self):
320        compile_catalog.run(self)
321
322        po_files = []
323        js_files = []
324
325        if not self.input_file:
326            if self.locale:
327                po_files.append((self.locale,
328                                 os.path.join(self.directory, self.locale,
329                                              'LC_MESSAGES',
330                                              self.domain + '.po')))
331                js_files.append(os.path.join(self.directory, self.locale,
332                                             'LC_MESSAGES',
333                                             self.domain + '.js'))
334            else:
335                for locale in os.listdir(self.directory):
336                    po_file = os.path.join(self.directory, locale,
337                                           'LC_MESSAGES',
338                                           self.domain + '.po')
339                    if os.path.exists(po_file):
340                        po_files.append((locale, po_file))
341                        js_files.append(os.path.join(self.directory, locale,
342                                                     'LC_MESSAGES',
343                                                     self.domain + '.js'))
344        else:
345            po_files.append((self.locale, self.input_file))
346            if self.output_file:
347                js_files.append(self.output_file)
348            else:
349                js_files.append(os.path.join(self.directory, self.locale,
350                                             'LC_MESSAGES',
351                                             self.domain + '.js'))
352
353        for js_file, (locale, po_file) in zip(js_files, po_files):
354            infile = open(po_file, 'r')
355            try:
356                catalog = read_po(infile, locale)
357            finally:
358                infile.close()
359
360            if catalog.fuzzy and not self.use_fuzzy:
361                continue
362
363            log.info('writing JavaScript strings in catalog %r to %r',
364                     po_file, js_file)
365
366            jscatalog = {}
367            for message in catalog:
368                if any(x[0].endswith('.js') for x in message.locations):
369                    msgid = message.id
370                    if isinstance(msgid, (list, tuple)):
371                        msgid = msgid[0]
372                    jscatalog[msgid] = message.string
373
374            outfile = open(js_file, 'wb')
375            try:
376                outfile.write('Solace.TRANSLATIONS.load(');
377                dump_json(dict(
378                    messages=jscatalog,
379                    plural_expr=catalog.plural_expr,
380                    locale=str(catalog.locale),
381                    domain=str(self.domain)
382                ), outfile)
383                outfile.write(');\n')
384            finally:
385                outfile.close()