PageRenderTime 88ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/solace/scripts.py

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