OSDN Git Service

delete action updated
[pybbs/pybbs.git] / index.py
1
2 import os.path
3 import shutil,re
4 import tornado.escape
5 import tornado.web
6 import tornado.httpserver
7 import tornado.ioloop
8 import tornado.options
9 from tornado.options import define,options
10 from tinydb import TinyDB,Query,where
11 from tinydb.operations import delete
12 from datetime import datetime
13 import json
14
15 define('port',default=8000,help='run on the given port',type=int)
16
17 class BaseHandler(tornado.web.RequestHandler):
18     def get_current_user(self):
19         user = self.get_secure_cookie('admin_user')
20         return tornado.escape.utf8(user)
21     
22     def set_current_user(self,username):
23         self.set_secure_cookie('admin_user',username)
24         
25     def clear_current_user(self):
26         self.clear_cookie('admin_user')
27
28 class IndexHandler(BaseHandler):
29     def get(self,dbname,page='0'):
30         params = self.application.db.get(where('kinds') == 'conf')
31         if params['mentenance'] == True:
32             self.render('mentenance.htm',title=params['title'],db=dbname)
33             return
34         if self.application.collection(dbname) == False:
35             if self.current_user == b'admin':
36                 self.application.db.table(dbname)
37             else:
38                 raise tornado.web.HTTPError(404)
39                 return
40         key = self.get_argument('key','')
41         if key:
42             table = self.application.db.table(dbname)
43             rec = table.get(where('number') == int(key))
44             if rec:
45                 self.render('article.htm',record=rec)
46                 return
47             else:
48                 raise tornado.web.HTTPError(404)
49                 return
50         i = params['count']      
51         rule = tornado.escape.url_unescape(self.get_cookie('aikotoba',''))
52         na = tornado.escape.url_unescape(self.get_cookie("username",u"誰かさん"))
53         pos = self.application.gpos(dbname,page)
54         table = self.application.db.table(dbname)
55         start = (pos-1)*i
56         if start < 0:
57             start = len(table)-i
58             if start < 0:
59                 start = 0
60         rec = sorted(table.all(),key=lambda x: x['number'])[start:start+i]
61         if len(table) >= 10*i:
62             self.render('modules/full.htm',position=pos,records=rec,data=params,db=dbname)
63             return
64         self.render('modules/index.htm',position=pos,records=rec,data=params,username=na,db=dbname,aikotoba=rule)
65         
66 class LoginHandler(BaseHandler):
67     def get(self):
68         self.render('login.htm')
69         
70     def post(self):
71         pw = self.application.db.get(where('kinds') == 'conf')
72         if self.get_argument('password') == pw['password']:
73             self.set_current_user('admin')
74         dbname = self.get_argument('record')
75         self.redirect('/'+dbname+'/admin/0/')
76         
77 class LogoutHandler(BaseHandler):
78     def get(self):
79         self.clear_current_user()
80         self.redirect('/login')
81         
82 class NaviHandler(tornado.web.RequestHandler):
83     def get(self):
84         self.render('top.htm',coll=sorted(self.name()),full=self.full)
85         
86     def name(self):
87         for x in self.application.db.tables():
88             if x != '_default':
89                 yield x
90                 
91     def full(self,dbname):
92         if dbname in self.application.db.tables():
93             i = 10*self.application.db.get(where('kinds') == 'conf')['count']
94             table = self.application.db.table(dbname)
95             if len(table) >= i:
96                 return True
97         return False
98
99 class TitleHandler(NaviHandler):
100     def get(self):
101         rec = sorted(self.title(),key=lambda x: x['date2'])
102         self.render('title.htm',coll=rec,full=self.full)  
103         
104     def title(self):
105         for x in self.name():
106             item = {}
107             item['name'] = x
108             table = self.application.db.table(x)
109             i = len(table)
110             item['count'] = i            
111             if table.contains(where('number') == 1) == True:
112                 s = table.get(where('number') == 1)['title']
113             else:
114                 s = ''
115             item['title'] = s   
116             if i == 0:
117                 item['date'] = ''
118                 item['date2'] = 0
119             else:
120                 rec = sorted(table.all(),key=lambda k: k['number'])
121                 s = rec[i-1]['date']
122                 item['date'] = s
123                 i = datetime.strptime(s,'%Y/%m/%d %H:%M')
124                 year = datetime.now().year-i.year
125                 if year == 0:
126                     j = 800
127                 elif year == 1:
128                     j = 400
129                 else:
130                     j = 0
131                 item['date2'] = j+31*(i.month-1)+i.day
132             yield item
133         
134 class RegistHandler(tornado.web.RequestHandler):
135     def post(self,dbname):
136         if self.application.collection(dbname) == False:
137             raise tornado.web.HTTPError(404)
138             return
139         self.database = dbname
140         rec = self.application.db.get(where('kinds') == 'conf')
141         words = rec['bad_words']
142         out = rec['out_words']
143         na = self.get_argument('name')
144         sub = self.get_argument('title')
145         com = self.get_argument('comment',None,False)
146         text = ''
147         i = 0
148         url = []
149         error = ''
150         for word in out:
151             if word in com:
152                 error = error + u'禁止ワード.'
153                 break
154         for line in com.splitlines(True):
155             if error != '':
156                 break
157             for word in words:
158                 if word in line.lower():
159                     error = error + u'タグ違反.('+word+')'       
160             i += len(line)   
161             obj = re.finditer('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', line)
162             for x in obj:
163                 if x.group() not in url:
164                     url.append(x.group())
165             if re.match(' ',line):
166                 line = line.replace(' ','&nbsp;',1)
167             text = text+'<p>'+self.link(line)+'<br></p>'
168         s = ''
169         for x in url:
170             s = s+'<tr><td><a class=livepreview target=_blank href={0}>{0}</a></td></tr>'.format(x)
171         if s:
172             text = text+'<table><tr><td>検出URL:</td></tr>'+s+'</table>'
173         pw = self.get_argument('password')
174         if i == 0:
175             error = error + u'本文がありません.'
176         elif i > 1000:
177             error = error +u'文字数が1,000をこえました.'
178         article = self.application.db.table(dbname)
179         if len(article) == 0:
180             no = 1
181         else:
182             item = sorted(article.all(),key=lambda x: x['number'])[len(article)-1]
183             no = item['number']+1
184         if error == '':
185             if not na:
186                 na = u'誰かさん'
187             s = datetime.now()
188             reg = {'number':no,'name':na,'title':sub,'comment':text,'raw':com,'password':pw,'date':s.strftime('%Y/%m/%d %H:%M')}
189             article.insert(reg)
190             self.set_cookie('username',tornado.escape.url_escape(na))
191             self.redirect('/'+dbname+'#article')
192         else:
193             self.render('regist.htm',content=error)
194     
195     def link(self,command):
196         i = 0
197         text = ''
198         obj = re.finditer('>>[0-9]+',command)
199         for x in obj:
200             s = '<a class=minpreview data-preview-url=/{0}?key={1} href=/{0}#{1}>>>{1}</a>'.format(self.database,x.group()[2:])
201             text = text+command[i:x.start()]+s
202             i = x.end()
203         else:
204             text = text+command[i:]
205         return text
206     
207 class AdminHandler(BaseHandler):
208     @tornado.web.authenticated               
209     def get(self,dbname,page='0'):
210         if dbname == '':
211             dbname = self.get_argument('record','')
212         if self.application.collection(dbname) == False:
213             raise tornado.web.HTTPError(404)
214             return
215         table = self.application.db.table(dbname) 
216         rec = sorted(table.all(),key=lambda x: x['number'])                   
217         mente = self.application.db.get(where('kinds') == 'conf')
218         if mente['mentenance'] == True:
219             check = 'checked=checked'
220         else:
221             check = ''
222         pos = self.application.gpos(dbname,page)
223         i = mente['count']
224         start = (pos-1)*i
225         if start < 0:
226             start = len(table)-i
227             if start < 0:
228                 start = 0
229         self.render('modules/admin.htm',position=pos,records=rec[start:start+i],mente=check,password=mente['password'],db=dbname)
230
231 class AdminConfHandler(BaseHandler):
232     @tornado.web.authenticated
233     def post(self,dbname,func):
234         if func == 'set':
235             param = self.application.db.get(where('kinds') == 'conf')['mentenance']
236             if self.get_argument('mente','') == 'on':
237                 mente = True
238                 if param != mente:
239                     self.store()
240             else:
241                 mente = False  
242                 if param != mente:
243                     self.restore()
244             word = self.get_argument('pass','')
245             if word == '':
246                 self.render('regist.htm',content='パスワードを設定してください')
247                 return
248             else:
249                 self.application.db.update({'mentenance':mente,'password':word},where('kinds') == 'conf')  
250         elif func == 'del':
251             table = self.application.db.table(dbname)
252             for x in self.get_arguments('item'):
253                 table.remove(where('number') == int(x))
254         self.redirect('/'+dbname+'/admin/0/')
255         
256     def store(self):
257         self.application.db.close()
258         shutil.copy(st.json,st.bak)
259         self.application.db = TinyDB(st.json)
260         
261     def restore(self):
262         database = self.application.db
263         bak = TinyDB(st.bak)
264         for x in database.tables():
265             if self.application.collection(x) == True:
266                 database.purge_table(x)
267                 if x in bak.tables():
268                     table = database.table(x)
269                     table.insert_multiple(bak.table(x).all())
270           
271 class UserHandler(tornado.web.RequestHandler):
272     def post(self,dbname):
273         number = self.get_argument('number')
274         if number.isdigit() == True:
275             num = int(number)
276             pas = self.get_argument('password')
277             self.table = self.application.db.table(dbname)
278             qwr = Query()
279             obj = self.table.get(qwr.number == num)
280             if obj and(obj['password'] == pas):
281                 self.table.update({'title':u'削除されました','name':'','comment':u'<i><b>投稿者により削除されました</i></b>'},qwr.number == num)
282                 self.redirect('/'+dbname+self.page(num)+'#'+number)
283             else:
284                 self.redirect('/'+dbname)
285                 
286     def page(self,number):
287         if self.table != None:
288             rec = self.table.count(where('number') <= number)
289             conf = self.application.db.get(where('kinds') == 'conf')
290             if len(self.table)-rec >= conf['count']:
291                 return '/'+str(1+rec//conf['count'])+'/'
292             else:
293                 return ''
294       
295 class SearchHandler(tornado.web.RequestHandler):       
296     def post(self,dbname):
297         self.word = tornado.escape.url_unescape(self.get_argument('word1'))
298         self.radiobox = self.get_argument('filter')
299         self.set_cookie('search',tornado.escape.url_escape(self.word))         
300         rec = sorted(self.search(dbname),key=lambda x: x['number'])
301         self.render('modules/search.htm',records=rec,word1=self.word,db=dbname)
302     
303     def get(self,dbname):
304         if self.application.collection(dbname) == False:
305             raise tornado.web.HTTPError(404)
306             return
307         word = self.get_cookie('search','')
308         word = tornado.escape.url_unescape(word)
309         self.render('modules/search.htm',records=[],word1=word,db=dbname)
310         
311     def search(self,dbname):
312         table = self.application.db.table(dbname)    
313         element = self.word.split()
314         if len(element) == 0:
315             element = ['']
316         while len(element) < 3:
317             element.append(element[0])
318         if self.radiobox == 'comment':
319             query = (Query().raw.search(element[0])) | (Query().raw.search(element[1])) | (Query().raw.search(element[2]))
320         else:
321             query = (Query().name == element[0]) | (Query().name == element[1]) | (Query().name == element[2])
322         if self.radiobox == 'comment':    
323             for x in table.search(query):
324                 com = ''
325                 for text in x['raw'].splitlines(True):                  
326                     for word in self.word.split():                        
327                         if text.find(word) > -1:
328                             com = com +'<p style=background-color:yellow>'+text+'<br></p>'  
329                             break                          
330                     else:
331                         com = com+'<p>'+text+'<br></p>'
332                 x['comment'] = com
333                 yield x       
334         else:
335             for x in table.search(query):
336                 yield x
337                                         
338 class FooterModule(tornado.web.UIModule):
339     def render(self,number,url,link):
340         return self.render_string('modules/footer.htm',index=number,url=url,link=link)
341     
342 class HeadlineApi(tornado.web.RequestHandler):
343     def get(self):
344         response = {}
345         for x in self.application.db.tables():
346             if x != '_default':
347                 response[x] = self.get_data(x)           
348         self.write(json.dumps(response,ensure_ascii=False))
349     
350     def get_data(self,dbname):
351         table = self.application.db.table(dbname)
352         i = len(table)
353         if i == 0:
354             return {}
355         else:
356             rec = sorted(table.all(),key=lambda x: x['number'])[i-1]
357             return {'number':rec['number'],'title':rec['title'],'name':rec['name'],'comment':rec['raw'][0:19]}
358         
359 class ArticleApi(tornado.web.RequestHandler):
360     def get(self,dbname,number):
361         if self.application.collection(dbname) == True:
362             table = self.application.db.table(dbname)
363             response = table.get(where('number') == int(number))
364             if response == None:
365                 response = {}
366             else:
367                 del response['comment']
368             self.write(json.dumps(response,ensure_ascii=False))
369         else:
370             tornado.web.HTTPError(404)
371     
372     def post(self,dbname):
373         name = self.get_argument('name',u'誰かさん')
374         title = self.get_argument('title',u'タイトルなし')
375         comment = self.get_argument('comment')
376         table = self.application.db.table(dbname)
377         table.insert({'name':name,'title':title,'comment':comment})
378         
379 class Application(tornado.web.Application):    
380     def __init__(self):
381         self.db = TinyDB(st.json)
382         handlers = [(r'/',NaviHandler),(r'/login',LoginHandler),(r'/logout',LogoutHandler),(r'/title',TitleHandler),
383                     (r'/headline/api',HeadlineApi),(r'/read/api/([a-zA-Z0-9_]+)/([0-9]+)',ArticleApi),(r'/write/api/([a-zA-Z0-9_]+)',ArticleApi),
384                     (r'/([a-zA-Z0-9_]+)',IndexHandler),(r'/([a-zA-Z0-9_]+)/([0-9]+)/',IndexHandler),
385                     (r'/([a-zA-Z0-9_]+)/admin/([0-9]+)/',AdminHandler),(r'/([a-zA-Z0-9_]+)/admin/([a-z]+)/',AdminConfHandler),(r'/([a-zA-Z0-9_]+)/userdel',UserHandler),
386                     (r'/([a-zA-Z0-9_]+)/search',SearchHandler),(r'/([a-zA-Z0-9_]+)/regist',RegistHandler)]
387         settings = {'template_path':os.path.join(os.path.dirname(__file__),'pybbs'),
388                         'static_path':os.path.join(os.path.dirname(__file__),'static'),
389                         'ui_modules':{'Footer':FooterModule},
390                         'cookie_secret':'bZJc2sWbQLKos6GkHn/VB9oXwQt8SOROkRvJ5/xJ89E=',
391                         'xsrf_cookies':True,
392                         'debug':True,
393                         'login_url':'/login'
394                         }
395         tornado.web.Application.__init__(self,handlers,**settings)
396  
397     def gpos(self,dbname,page):
398         params = self.db.get(where('kinds') == 'conf')
399         pos = int(page)
400         if pos <= 0:
401             pos = 0
402         elif (pos-1)*params['count'] >= len(self.db.table(dbname)):
403             pos = 0
404         return pos
405     
406     def collection(self,name):
407         if name in self.db.tables():
408             return True
409         else:
410             return False
411
412 class static():
413     json = 'static/db/db.json'
414     bak = 'static/db/bak.json'
415
416 st = static()
417 if __name__ == '__main__':
418     tornado.options.parse_command_line()
419     http_server = tornado.httpserver.HTTPServer(Application())
420     http_server.listen(options.port)
421     tornado.ioloop.IOLoop.instance().start()