Browse Source

适配复旦版本

fudan
欧醚 4 years ago
parent
commit
e8b7033d19
Signed by: OmmyZhang GPG Key ID: 757D312E7C9D13F7
9 changed files with 34 additions and 180 deletions
  1. +2
    -0
      .gitignore
  2. +8
    -57
      app.py
  3. BIN
     
  4. BIN
     
  5. BIN
     
  6. BIN
     
  7. BIN
     
  8. BIN
     
  9. +24
    -123
      templates/list.html

+ 2
- 0
.gitignore View File

@ -5,6 +5,8 @@
#config
config.py
config_fudan.py
# Byte-compiled / optimized / DLL files
__pycache__/

+ 8
- 57
app.py View File

@ -2,41 +2,30 @@ from flask import Flask, request, render_template, send_from_directory, abort, r
from flask_sqlalchemy import SQLAlchemy
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from mastodon import Mastodon
import re
import random
from datetime import datetime
from dateutil.tz import tzlocal
import html2text
from config import C
from config_fudan import C
app = Flask(__name__)
app.config.from_object('config.C')
app.config.from_object('config_fudan.C')
app.secret_key = C.session_key
th = Mastodon(
access_token = C.token,
api_base_url = 'https://' + C.domain
)
limiter = Limiter(
app,
key_func=get_remote_address,
default_limits=["50 / minute"],
)
h2t = html2text.HTML2Text()
h2t.ignore_links = True
db = SQLAlchemy(app)
class Candidate(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.String(4000))
private = db.Column(db.String(1000))
url = db.Column(db.String(50))
time = db.Column(db.DateTime)
toot = db.Column(db.BigInteger)
likeNum = db.Column(db.Integer, default=0)
# always increment 1 for the id of a new record
@ -87,7 +76,7 @@ def can_list():
} for name, ques, hint, ans in C.verify
]
return render_template('list.html', pagination=pag, vs=vs, showPrivate=(key==C.key), sort_by=sort_by, key=key)
return render_template('list.html', pagination=pag, vs=vs, showPrivate=(key==C.key), sort_by=sort_by, key=key, text1=C.text1, text2=C.text2)
@app.route('/ordinary/new', methods=['POST'])
@limiter.limit("5 / hour; 1 / 2 second")
@ -95,7 +84,6 @@ def new_one():
content = request.form.get('text')
private = request.form.get('privateText')
url = request.form.get('url')
for name, ques, hint, ans in C.verify:
if request.form.get(name) != ans:
@ -114,19 +102,11 @@ def new_one():
if not content or len(content)>4000: abort(422)
if private and len(private)>1000: abort(422)
if url and not re.match('https://(cloud\.tsinghua\.edu\.cn/f/[0-9a-z]+/(\?dl=1)?|closed\.social/safeShare/\d([a-zA-Z]+)?)', url): abort(422)
if not Candidate.query.filter_by(content=content).first():
toot = th.status_post(
f"有新的自荐报名(大家可以直接在此处评论):\n\n{content}",
visibility=C.visibility
)
c = Candidate(
content=content,
private=private,
url=url,
toot=toot.id,
time=datetime.now()
)
db.session.add(c)
@ -134,48 +114,19 @@ def new_one():
return redirect(".")
@limiter.limit("100 / hour; 2 / second")
@app.route('/ordinary/<int:toot>/comments')
def get_comments(toot):
c = Candidate.query.filter_by(toot=toot).first()
if not c:
abort(404)
context = th.status_context(toot)
replies = [
{
'disp': (t.account.display_name or t.account.acct),
'url': t.account.url,
'content': h2t.handle(t.content).replace(C.bot_name,'').strip(),
'time': str(t.created_at)
}
for t in context.descendants
]
d = list(filter(
lambda r: r['content'] == '删除' and r['url'].split('/@')[1] in C.admins,
replies
))
if d:
db.session.delete(c)
db.session.commit()
th.status_delete(toot)
return '该内容已被删除', 404
return {'replies': replies}
@limiter.limit("100 / hour")
@app.route('/ordinary/<int:toot>/like', methods=['POST'])
def like(toot):
c = Candidate.query.filter_by(toot=toot).first()
@app.route('/ordinary/<int:id>/like', methods=['POST'])
def like(id):
c = Candidate.query.get(id)
if not c:
abort(404)
uid = session['uid']
if not uid: abort(401)
if Like.query.filter_by(uid=uid, cid=c.id).first():
if Like.query.filter_by(uid=uid, cid=id).first():
return '点赞过了', 403
l = Like(uid=uid, cid=c.id)
l = Like(uid=uid, cid=id)
c.likeNum += 1
db.session.add(l)
db.session.commit()

BIN
View File


BIN
View File


BIN
View File


BIN
View File


BIN
View File


BIN
View File


+ 24
- 123
templates/list.html View File

@ -7,10 +7,10 @@
<link rel="icon" type="image/png" href="/img/ord/icon-128.png" />
<link href="https://fonts.yecdn.com/css2?family=Noto+Serif+SC:wght@300;700&display=swap" rel="stylesheet">
<link href="https://fonts.yecdn.com/css2?family=Noto+Sans+SC&display=swap" rel="stylesheet">
<meta property="og:title" content="华清大学特普通奖学金 报名页面" />
<meta property="og:description" content="华清大学特普通奖学金" />
<meta property="og:title" content="复旦大学特普通奖学金 报名页面" />
<meta property="og:description" content="复旦大学特普通奖学金" />
<link href="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
<title>华清大学特普通奖 报名页面</title>
<title>复旦大学特普通奖 报名页面</title>
<style>
body,
pre {
@ -80,12 +80,6 @@
color: white;
}
.judge .qbox {
background: white;
color: black;
padding: 5px 15px;
}
.twin.new .qbox input,
.twin.new .qbox textarea {
border-bottom: 2px solid;
@ -141,12 +135,13 @@
transition-duration: 0.5s;
}
.judge {
.poster {
position: absolute;
top: 0;
right: 0;
margin: 0 0 30px 20px;
width: 90%;
border: 2px black solid;
}
.twin {
overflow: hidden;
@ -187,10 +182,10 @@
</style>
</head>
<body style="background: #fff url('/img/ord/bg.jpg') repeat-x fixed center top / 650px">
<body style="background: #fff url('/img/ord_fudan/bg_sm.jpg') repeat-x fixed center top / 190px">
<div class="container" style="overflow: hidden;min-height: 100vh">
<div style='padding:15px'>
<img src="/img/ord/logo.png" width="200px" />
<img src="/img/ord_fudan/logo_sm.png" width="200px" />
</div>
<div class="part1">
@ -199,25 +194,14 @@
<form action="new" method="post">
<div class="form-group qbox">
<h1 style="margin: -14px -13px 20px">自荐提名</h1>
<textarea class="form-control" name="text" rows="5" maxlength="4000" placeholder="
一段自我陈述,
向大家展示普通的自己。" required="required"></textarea>
<hr />
<textarea class="form-control" name="privateText" rows="4" maxlength="1000" placeholder="不想公开,但想被评委们看到的内容。
如有意正式提名,请于此留下自己的联系方式。"></textarea>
<hr />
<div class="form-group row">
<label for="url" class="col-sm-3 col-form-label">补充材料</label>
<div class="col-sm-9">
<input type="url" class="form-control" id="url" name="url" placeholder="清华云盘或safeShare链接,https://开头" pattern="https://(cloud\.tsinghua\.edu\.cn/f/[0-9a-z]+/(\?dl=1)?|closed\.social/safeShare/\d([a-zA-Z]+)?)">
</div>
</div>
<textarea class="form-control" name="text" rows="5" maxlength="4000" placeholder="{{text1}}" required="required"></textarea>
<hr />
<textarea class="form-control" name="privateText" rows="4" maxlength="1000" placeholder="{{text2}}"></textarea>
<hr />
{% for v in vs %}
<div class="form-group row">
<label for="{{v.name}}" class="col-sm-8 col-form-label">{{v.ques}}</label>
<div class="col-sm-4">
<label for="{{v.name}}" class="col-sm-7 col-form-label">{{v.ques}}</label>
<div class="col-sm-5">
<input type="text" class="form-control" name="{{v.name}}" placeholder="{{v.hint}}" required="required">
</div>
</div>
@ -226,62 +210,12 @@
</div>
</form>
<div style="font-size:80%;">
<ul>
<li>如需附上补充材料,请使用清华云盘。为了避免泄露姓名推荐使用<a href="/safeShare" target="_blank">safeShare</a>。其他云盘是不被接受的。</li>
<li>需要回答几个简单的问题以初步验证学生身份,入围后的线上答辩环节将于<a href="https://thu.closed.social/">闭社</a>平台进行(需清华邮箱注册)以正式验证身份。</li>
<li>如果出错(例如验证问题答错了),请回退,多数浏览器都会恢复之前填写的内容。</li>
<li>获得五个赞并留下联系方式(如微信号、手机号或邮箱)者被视为正式提名,会有工作人员与之联系并发放奖品。</li>
<li>点击倒三角按钮展开评论,发布评论请前往<a href="https://thu.closed.social/">闭社</a></li>
<li>如需删除报名,请联系工作人员(微信:ordinary_thuer)。</li>
</ul>
</div>
</div>
<div class="judge twin-collapse twin behind">
<form action="judge" method="post">
<div class="form-group qbox">
<h1 style="text-align:right;margin:-12px -20px 20px">成为评委</h1>
<div style="font-size:80%">
<p>为了更好地选出十位普通人的代表,为了更好地展现大众的声音,我们希望招募更多的评委。</p>
<p>成为评委的条件:</p>
<ul>
<li>是华清大学在读学生</li>
<li>没有报名参选</li>
<li>愿意对所有报名者进行评分</li>
</ul>
<p>我们原则上不拒绝任何的评委报名。</p>
<p>目前在三个平台上建了三个评委群,分别是闭聊(Matrix),Telegram,微信。前两个消息自动同步,加入哪个都是等价的,微信群与另外两个群不连通。(注意Telegram墙内不可直接访问。闭聊由闭社提供,使用闭社帐号登陆,如使用其他服务器上的Matrix帐号加群需要验证华清身份。)</p>
<p>选择想加入的群,完成验证,进入评委群。如果不愿意加群,也可以私信主办方保持沟通和上传打分结果。</p>
</div>
<div style="text-align:center">
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="groupType" value="mx">
<label class="form-check-label" for="mx">闭聊</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="groupType" value="tg">
<label class="form-check-label" for="tg">Telegram</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="groupType" value="wx">
<label class="form-check-label" for="wx">微信</label>
</div>
</div>
<hr/>
{% for v in vs %}
<div class="form-group row">
<label for="{{v.name}}" class="col-sm-8 col-form-label">{{v.ques}}</label>
<div class="col-sm-4">
<input type="text" class="form-control" name="{{v.name}}" placeholder="{{v.hint}}" required="required">
</div>
</div>
{% endfor %}
<p>(火热开发中... 马上就可以点了)</p>
</div>
</form>
<div class="poster twin-collapse twin behind">
<a target="_blank">
<img src="/img/ord_fudan/poster_sm.jpg" width="100%">
</a>
</div>
<a href="##" onclick="showNew()" class="show-mask">
@ -327,7 +261,7 @@
{% endif %}
<div style="text-align:right;margin: 27px 0 -5px">
<time class="timeago" datetime="{{c.time}}"></time>
<a href="##" class="btn btn-link" id="like-{{c.toot}}" onClick="like('{{c.toot}}')" style="text-decoration: none;">
<a href="##" class="btn btn-link" id="like-{{c.id}}" onClick="like('{{c.id}}')" style="text-decoration: none;">
<svg viewBox="-20 0 552 512" height="16" class="{{c.liked}}">
<path stroke="#000" stroke-width="30" d="M474.644,74.27C449.391,45.616,414.358,29.836,376,29.836c-53.948,0-88.103,32.22-107.255,59.25
c-4.969,7.014-9.196,14.047-12.745,20.665c-3.549-6.618-7.775-13.651-12.745-20.665c-19.152-27.03-53.307-59.25-107.255-59.25
@ -341,16 +275,6 @@
{{c.likeNum}}
</span>
</a>
<a class="btn btn-link request-answer" data-toggle="collapse" href="#collapse-{{c.toot}}" role="button" aria-expanded="false" aria-controls="collapse-{{c.toot}}">
<svg fill="#000" viewBox="0 24 24 20" height="16">
<path d="m0 24 l12 18 l12 -18 z"></path>
</svg>
</a>
</div>
<div class="collapse" id="collapse-{{c.toot}}">
<div class="card card-body">
加载中...
</div>
</div>
</div>
{% endfor %}
@ -378,7 +302,7 @@
</div>
<div class="footer">
<p>
<a href="//closed.social" target="_blank">闭社</a>提供技术支持
<a href="//closed.social" target="_blank">闭社</a>提供技术支持,本报名系统开源于<a href="//git.closed.social/closed-social/ordinary/src/branch/fudan">碧茶</a>
</p>
<p> 🄯 2020 Copyleft: closed.social</p>
</div>
@ -410,45 +334,22 @@
}
$(document).ready(function(){
if(window.innerWidth > 1000)
showNew();
$('.timeago').timeago();
$('.twin').click((e) => {
showNew();
if($(e.currentTarget).hasClass('front'))
return;
let behind_box = $('.behind');
let front_box = $('.front');
behind_box.toggleClass('behind front');
front_box.toggleClass('front behind');
});
$('.collapse').on('show.bs.collapse', (e) => {
let self = e.target;
let toot = self.id.split('-')[1];
$.ajax({
type: 'GET',
url: toot + '/comments',
success: (result, status, xhr) => {
if (result.replies.length) {
$(self).empty();
result.replies.forEach((rp) => {
$(self).append(`<div class="card card-body"><p class="display_name"><a href="${rp.url}">${rp.disp}</a>:</p><pre class="inner">${rp.content}</pre><time class="timeago" datetime="${rp.time}"></time></div>`)
});
$(self).find('.timeago').timeago();
} else {
$(self).empty();
$(self).append(`<div class="card card-body">(暂无评论)</div>`)
}
},
error: (xhr, status, error) => {
console.log(error, status, xhr.status, xhr.responseText);
$(self).empty();
$(self).append(`<div class="card card-body"><p style="color:red">${xhr.status} ${xhr.responseText}</p></div>`);
if (xhr.status == 404)
setTimeout(() => {
$(self).parent().remove()
}, 3000);
}
});
setTimeout(() => {
$('.poster.front a').attr('href', '/img/ord_fudan/poster.jpg');
$('.poster.behind a').removeAttr('href');
}, 300);
});
});
</script>

Loading…
Cancel
Save