├── daimaduan ├── __init__.py ├── forms │ ├── __init__.py │ ├── bookmark.py │ ├── password.py │ ├── email.py │ ├── signin.py │ ├── userinfo.py │ ├── signup.py │ └── paste.py ├── tasks │ ├── __init__.py │ ├── server.py │ ├── celery.py │ ├── deployments.py │ └── seed_data.py ├── extensions │ ├── __init__.py │ └── assets.py ├── utils │ ├── __init__.py │ ├── pagination.py │ ├── decorators.py │ ├── oauth.py │ ├── filters.py │ └── email_confirmation.py ├── static │ ├── images │ │ └── favicon.ico │ ├── css │ │ ├── pastes.scss │ │ ├── colorful.min.css │ │ ├── app.scss │ │ └── embed.scss │ └── js │ │ ├── lexers.js │ │ ├── app.js │ │ └── pastes.js ├── templates │ ├── error.html │ ├── email │ │ ├── confirm.html │ │ └── active.html │ ├── macros │ │ ├── tags.html │ │ ├── users.html │ │ ├── pastes.html │ │ └── common.html │ ├── shared │ │ ├── jiathis.html │ │ ├── baidu_analytics.html │ │ ├── oauth.html │ │ ├── google_analytics.html │ │ ├── form_errors.html │ │ ├── footer.html │ │ ├── user_panel.html │ │ └── header.html │ ├── tags │ │ ├── index.html │ │ └── view.html │ ├── oauths │ │ └── callback.html │ ├── wtf.html │ ├── users │ │ ├── lost_password.html │ │ ├── reset_password.html │ │ ├── signin.html │ │ ├── manage.html │ │ ├── signup.html │ │ ├── messages.html │ │ ├── user.html │ │ └── likes.html │ ├── pastes │ │ ├── edit_comment.html │ │ ├── embed.html │ │ ├── form_code.html │ │ ├── create.html │ │ ├── edit.html │ │ ├── view.html │ │ └── paste.html │ ├── search.html │ ├── index.html │ ├── bookmarks │ │ ├── view.html │ │ ├── create.html │ │ └── index.html │ └── base.html ├── models │ ├── user_oauth.py │ ├── tag.py │ ├── syntax.py │ ├── message.py │ ├── bookmark.py │ ├── __init__.py │ └── base.py ├── views │ ├── tags.py │ ├── __init__.py │ ├── users.py │ ├── bookmarks.py │ ├── pastes.py │ └── sites.py ├── migrations │ ├── 20160318173601.py │ └── 20160204162201.py ├── default_settings.py └── bootstrap.py ├── .bowerrc ├── Dockerfile ├── Makefile ├── .travis.yml ├── tox.ini ├── fabfile.py ├── bower.json ├── docker-compose.yml ├── MANIFEST.in ├── manage.py ├── requirements.txt ├── README.md ├── setup.py ├── .gitignore ├── LICENSE └── scripts └── fetch_lumen.py /daimaduan/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /daimaduan/forms/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /daimaduan/tasks/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "daimaduan/static/lib/" 3 | } -------------------------------------------------------------------------------- /daimaduan/extensions/__init__.py: -------------------------------------------------------------------------------- 1 | from .assets import assets 2 | 3 | __all__ = ["assets"] 4 | -------------------------------------------------------------------------------- /daimaduan/utils/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logger = logging.getLogger('daimaduan') 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:2.7 2 | ADD requirements.txt /root/requirements.txt 3 | RUN pip install -r /root/requirements.txt 4 | -------------------------------------------------------------------------------- /daimaduan/static/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wujunze/daimaduan.com/master/daimaduan/static/images/favicon.ico -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | check: 2 | @tox -e pep8 3 | 4 | server: 5 | cd daimaduan && python runserver.py 6 | 7 | install: 8 | sudo pip install -r requirements.txt -------------------------------------------------------------------------------- /daimaduan/templates/error.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %}{{ title }}{% endblock %} 3 | {% block content %} 4 | {{ message }} 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /daimaduan/templates/email/confirm.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %}{{ title }}{% endblock %} 3 | {% block content %} 4 | {{ message }} 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /daimaduan/templates/macros/tags.html: -------------------------------------------------------------------------------- 1 | {% macro render_tag(tag) %} 2 | {{ tag.name }} 3 | {% endmacro %} 4 | -------------------------------------------------------------------------------- /daimaduan/templates/shared/jiathis.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | notifications: 3 | slack: gdgxian:pJNN3YhXXtlyA3GXwrih3Qpl 4 | python: 5 | - "2.7" 6 | install: 7 | - pip install -U pip 8 | - pip install tox flake8 9 | script: 10 | - tox -e pep8 11 | -------------------------------------------------------------------------------- /daimaduan/tasks/server.py: -------------------------------------------------------------------------------- 1 | from fabric.decorators import task 2 | 3 | from daimaduan.bootstrap import app 4 | 5 | 6 | @task 7 | def run(): 8 | """Start development server""" 9 | app.run(host="0.0.0.0", port=8080) 10 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27 3 | 4 | [testenv:pep8] 5 | commands = flake8 6 | 7 | [flake8] 8 | show-source = True 9 | exclude = .tox,dist,doc,*.egg,build 10 | ignore = E41,F401 11 | max-line-length = 160 12 | max-complexity = 10 13 | -------------------------------------------------------------------------------- /fabfile.py: -------------------------------------------------------------------------------- 1 | from daimaduan.tasks.server import run 2 | from daimaduan.tasks.seed_data import seed 3 | from daimaduan.tasks.deployments import bootstrap 4 | from daimaduan.tasks.deployments import pack 5 | from daimaduan.tasks.deployments import deploy 6 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "ignore": [ 4 | "**/.*", 5 | "node_modules", 6 | "bower_components", 7 | "test", 8 | "tests" 9 | ], 10 | "dependencies": { 11 | "vue": "~1.0.14", 12 | "underscore": "~1.8.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | mongo: 2 | image: mongo 3 | 4 | app: 5 | build: . 6 | volumes: 7 | - .:/app 8 | links: 9 | - mongo:mongo 10 | ports: 11 | - 8080:8080 12 | environment: 13 | - MONGODB_HOST=mongo 14 | working_dir: /app 15 | command: fab run_server 16 | -------------------------------------------------------------------------------- /daimaduan/forms/bookmark.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from flask_wtf import Form 3 | from wtforms import StringField, BooleanField 4 | 5 | 6 | class BookmarkForm(Form): 7 | title = StringField(u'名字') 8 | description = StringField(u'描述') 9 | is_private = BooleanField(u'我想要这个码单私有') 10 | -------------------------------------------------------------------------------- /daimaduan/templates/shared/baidu_analytics.html: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /daimaduan/models/user_oauth.py: -------------------------------------------------------------------------------- 1 | from daimaduan.bootstrap import db 2 | from daimaduan.models import BaseDocument 3 | 4 | 5 | class UserOauth(BaseDocument): 6 | user = db.ReferenceField('User') 7 | 8 | provider = db.StringField(required=True) 9 | openid = db.StringField(required=True) 10 | token = db.StringField(required=True) 11 | -------------------------------------------------------------------------------- /daimaduan/utils/pagination.py: -------------------------------------------------------------------------------- 1 | from flask import request 2 | 3 | 4 | def get_page(): 5 | """ Get current page 6 | 7 | Get current page from query string `page=x`, 8 | if page not given returns `1` instead. 9 | """ 10 | try: 11 | page = request.args.get('page', 1) 12 | return int(page) 13 | except ValueError: 14 | return 1 15 | -------------------------------------------------------------------------------- /daimaduan/templates/shared/oauth.html: -------------------------------------------------------------------------------- 1 |
2 | 更多选择 3 | 4 | Google 5 | 6 | 7 | Github 8 | 9 |
10 | -------------------------------------------------------------------------------- /daimaduan/forms/password.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from flask_wtf import Form 3 | from wtforms import PasswordField 4 | from wtforms.validators import InputRequired 5 | from wtforms.validators import EqualTo 6 | 7 | 8 | class PasswordForm(Form): 9 | password = PasswordField(u'密码', validators=[InputRequired()]) 10 | password_confirm = PasswordField(u'密码确认', validators=[EqualTo('password', message=u'两次密码不同')]) 11 | -------------------------------------------------------------------------------- /daimaduan/templates/tags/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {%- from 'macros/tags.html' import render_tag -%} 3 | {% block title %}所有标签{% endblock %} 4 | {% block content %} 5 |

6 | 7 | 热门标签 8 |

9 |
10 | {% for tag in tags %} 11 | {{ render_tag(tag) }} 12 | {% endfor %} 13 |
14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /daimaduan/models/tag.py: -------------------------------------------------------------------------------- 1 | from daimaduan.bootstrap import db 2 | from daimaduan.models import BaseDocument 3 | from daimaduan.models.base import Paste 4 | 5 | 6 | class Tag(BaseDocument): 7 | key = db.StringField(required=True, unique=True) 8 | name = db.StringField(required=True, unique=True) 9 | popularity = db.IntField(default=1) 10 | 11 | @property 12 | def pastes(self): 13 | return Paste.objects(tags=self) 14 | -------------------------------------------------------------------------------- /daimaduan/models/syntax.py: -------------------------------------------------------------------------------- 1 | from daimaduan.bootstrap import db 2 | from daimaduan.models import BaseDocument 3 | from daimaduan.models.base import Paste 4 | 5 | 6 | class Syntax(BaseDocument): 7 | key = db.StringField(required=True, unique=True) 8 | syntax = db.StringField(required=True) 9 | name = db.StringField(required=True, unique=True) 10 | 11 | @property 12 | def pastes(self): 13 | return Paste.objects(codes__syntax=self.key) 14 | -------------------------------------------------------------------------------- /daimaduan/templates/oauths/callback.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %}绑定账号{% endblock %} 3 | 4 | {% block content %} 5 |
6 |
绑定账号
7 |
8 | 尊敬的用户 {{ session['oauth_name'] }},您之前未使用 {{ session['oauth_provider'] }} 账号登录过, 9 | 请登录已有账号或者注册新账号以完成绑定。 10 |
11 |
12 | {% endblock %} -------------------------------------------------------------------------------- /daimaduan/templates/shared/google_analytics.html: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include requirements.txt 2 | include fabfile.py 3 | recursive-include daimaduan/templates * 4 | recursive-include daimaduan/static * 5 | recursive-include daimaduan/migrations * 6 | exclude daimaduan/custom_settings.py 7 | recursive-exclude daimaduan/static/.webassets-cache * 8 | recursive-exclude daimaduan/static/css * 9 | recursive-exclude daimaduan/static/js * 10 | recursive-exclude daimaduan/static/lib * 11 | include daimaduan/static/css/compiled.css 12 | include daimaduan/static/js/compiled.js 13 | -------------------------------------------------------------------------------- /daimaduan/templates/tags/view.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {%- from 'macros/pastes.html' import render_paste_item %} 4 | {%- from 'macros/common.html' import render_pagination %} 5 | 6 | {% block title %}{{ tag.name }}{% endblock %} 7 | {% block content %} 8 | 9 | 10 |
11 | {% for paste in pagination.items %} 12 | {{ render_paste_item(paste) }} 13 | {% endfor %} 14 | 15 |
16 | 17 | {{ render_pagination(pagination, url_for('tag_app.view', tag_name=tag.name)) }} 18 |
19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | from flask.ext.script import Manager 2 | from flask.ext.script import Server 3 | from flask.ext.assets import ManageAssets 4 | 5 | from daimaduan.bootstrap import app 6 | 7 | manager = Manager(app) 8 | 9 | # Keep the following line for issue https://github.com/miracle2k/flask-assets/issues/85 10 | app.jinja_env.assets_environment.environment = app.jinja_env.assets_environment 11 | manager.add_command("assets", ManageAssets(app.jinja_env.assets_environment)) 12 | manager.add_command("runserver", Server(host="0.0.0.0", port=8080)) 13 | 14 | if __name__ == "__main__": 15 | manager.run() 16 | -------------------------------------------------------------------------------- /daimaduan/templates/wtf.html: -------------------------------------------------------------------------------- 1 | {% macro render_field(field, label=true) -%} 2 |
3 | {% if field.type != 'HiddenField' and label %} 4 | 7 | {% endif %} 8 | 9 | {{ field(class_='form-control', **kwargs) }} 10 | 11 | {% if field.errors %} 12 | {% for e in field.errors %} 13 |

{{ e }}

14 | {% endfor %} 15 | {% endif %} 16 |
17 | {%- endmacro %} -------------------------------------------------------------------------------- /daimaduan/templates/users/lost_password.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% import 'wtf.html' as f %} 3 | {% block title %}重置密码{% endblock %} 4 | {% block full_content %} 5 |
6 | {{ form.csrf_token }} 7 |
8 |
重置密码
9 |
10 | {{ f.render_field(form.email, placeholder='Email', type='email') }} 11 |
12 | 15 |
16 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /daimaduan/templates/pastes/edit_comment.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' -%} 2 | {% from 'macros/common.html' import fa_icon -%} 3 | 4 | {% block title %}修改评论{% endblock %} 5 | 6 | {% block content %} 7 | 8 | 9 |
10 |
11 | 12 |
13 | 14 |
15 | 16 |
17 |
18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /daimaduan/utils/decorators.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from functools import wraps 3 | 4 | from flask import render_template 5 | from flask_login import current_user 6 | 7 | from daimaduan.forms.email import EmailForm 8 | 9 | 10 | def user_active_required(func): 11 | @wraps(func) 12 | def wrapper(*args, **kwargs): 13 | if current_user.user.is_email_confirmed: 14 | return func(*args, **kwargs) 15 | form = EmailForm() 16 | return render_template('email/active.html', 17 | title=u"邮箱需要激活", 18 | reactive=True, 19 | form=form) 20 | return wrapper 21 | -------------------------------------------------------------------------------- /daimaduan/forms/email.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from flask_wtf import Form 3 | from wtforms import StringField 4 | from wtforms.validators import Email 5 | from wtforms.validators import InputRequired 6 | 7 | from daimaduan.models.base import User 8 | 9 | 10 | class EmailForm(Form): 11 | email = StringField(u'email', validators=[InputRequired(), Email(message=u"邮件地址不正确")]) 12 | 13 | def validate(self): 14 | if not Form.validate(self): 15 | return False 16 | 17 | user = User.objects(email=self.email.data).first() 18 | 19 | if not user: 20 | self.email.errors.append(u'用户不存在') 21 | return False 22 | 23 | return True 24 | -------------------------------------------------------------------------------- /daimaduan/views/tags.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | from flask import render_template 3 | 4 | from daimaduan.models.tag import Tag 5 | from daimaduan.utils.pagination import get_page 6 | 7 | 8 | tag_app = Blueprint('tag_app', __name__, template_folder="templates") 9 | 10 | 11 | @tag_app.route('/', methods=['GET']) 12 | def view(tag_name): 13 | tag = Tag.objects.get_or_404(key=tag_name) 14 | page = get_page() 15 | 16 | pastes = tag.pastes(is_private=False).order_by('-updated_at') 17 | pagination = pastes.paginate(page, per_page=20) 18 | 19 | return render_template('tags/view.html', 20 | tag=tag, 21 | pagination=pagination) 22 | -------------------------------------------------------------------------------- /daimaduan/templates/pastes/embed.html: -------------------------------------------------------------------------------- 1 | {% assets "embed" -%} 2 | document.write(''); 3 | {% endassets -%} 4 | {% for code in paste.codes -%} 5 | document.write('
'); 6 | document.write('
'); 7 | document.write('{{ code.highlight_content | replace('\n', '\\n') | replace('\r', '') | safe }}'); 8 | document.write('
'); 9 | {% endfor -%} -------------------------------------------------------------------------------- /daimaduan/templates/shared/form_errors.html: -------------------------------------------------------------------------------- 1 | {% for field, errors in form.errors.iteritems() %} 2 |
3 | {% if field == "codes" %} 4 | {% for code in form[field] %} 5 |
  • 6 | {{code.content.label}} 7 | {% for error in errors %} 8 | {% for v in error.values() %} 9 | {{', '.join(v)}} 10 | {% endfor %} 11 | {% endfor %} 12 |
  • 13 | {% endfor %} 14 | {% else %} 15 |
  • 16 | {{ form[field].label }}{{ ', '.join(errors) }} 17 |
  • 18 | {% endif %} 19 |
    20 | {% endfor %} 21 | -------------------------------------------------------------------------------- /daimaduan/templates/users/reset_password.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% import 'wtf.html' as f %} 3 | {% block title %}重置密码{% endblock %} 4 | {% block full_content %} 5 |
    6 | {{ form.csrf_token }} 7 |
    8 |
    重置密码
    9 |
    10 | {{ f.render_field(form.password, placeholder='Password', type='password') }} 11 | {{ f.render_field(form.password_confirm, placeholder='Password', type='password') }} 12 |
    13 | 16 |
    17 |
    18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flask==0.10.1 2 | flask-mongoengine==0.7.5 3 | Flask-Assets==0.11 4 | flask-login==0.3.2 5 | Flask-Gravatar==0.4.2 6 | Flask-Script==2.0.5 7 | flask-log==0.1.0 8 | cssmin==0.2.0 9 | scss==0.8.73 10 | sass==2.3 11 | ecdsa==0.13 12 | Fabric==1.10.2 13 | flake8==2.4.1 14 | MarkupSafe==0.23 15 | mccabe==0.3.1 16 | mongoengine==0.10.0 17 | paramiko==1.15.2 18 | pep8==1.5.7 19 | pluggy==0.3.1 20 | py==1.4.30 21 | pycrypto==2.6.1 22 | pyflakes==0.8.1 23 | six==1.9.0 24 | tox==2.1.1 25 | virtualenv==13.1.2 26 | wheel==0.24.0 27 | requests==1.2.3 28 | rauth==0.6.2 29 | itsdangerous==0.24 30 | mailthon==0.1.1 31 | newrelic==2.60.0.46 32 | blinker==1.4 33 | Pygments==2.0.2 34 | celery==3.1.19 35 | markdown==2.6.5 36 | mongodb-migrations==0.1.1 37 | python-memcached==1.57 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | daimaduan.com 2 | ============= 3 | [![Build Status](https://travis-ci.org/DoubleCiti/daimaduan.com.svg?branch=master)](https://travis-ci.org/DoubleCiti/daimaduan.com) 4 | 5 | source code of daimaduan.com 6 | 7 | ## 项目依赖 8 | 9 | * Python 2.7 10 | * MongoDB 3.0+ 11 | * node.js 12 | 13 | ## 开发环境配置 14 | 15 | 在运行服务器程序之前, 需要先准备好本地的开发坏境 16 | 17 | ``` 18 | gem install sass 19 | npm install -g bower scss uglifyjs bower 20 | bower install 21 | ``` 22 | 23 | ## 本地运行开发服务器 24 | 25 | 1. 启动MongoDB 26 | 2. `cp daimaduan/default_settings.py daimaduan/custom_settings.py` 27 | 3. `python setup.py develop` 28 | 4. `fab run` 29 | 30 | ## message bus系统 31 | 32 | daimaduan使用`Celery`来管理异步的任务, 启动`woker`的命令为: 33 | 34 | ``` 35 | celery -A daimaduan.bootstrap:celery worker -l info 36 | ``` 37 | -------------------------------------------------------------------------------- /daimaduan/templates/email/active.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %}{{ title }}{% endblock %} 3 | {% block content %} 4 | {% if reactive == True %} 5 | 6 | 您的邮箱没有激活,暂时不能分享代码,请激活您的email: {{ current_user.user.email }} 7 | 8 |
    9 | {% else %} 10 | 11 | 恭喜您, 注册成功! 我们已经向您的注册邮箱发送了激活邮件, 请检查并激活您的账户. 12 | 13 |
    14 | {% endif %} 15 | 16 | 如果您没有收到激活邮件,请点击 17 |
    18 | {{ form.csrf_token }} 19 | 20 | 21 |
    22 |
    23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /daimaduan/models/message.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import datetime 3 | 4 | from daimaduan.bootstrap import db 5 | 6 | 7 | NEW_PASTE = u"您关注的用户 [{user_username}]({user_url}) 发布了新的代码集合 [{paste_title}]({paste_url})" 8 | NEW_COMMENT = u"用户 [{user_username}]({user_url}) 评论了您的代码集合 [{paste_title}]({paste_url})" 9 | WATCH = u"用户 [{user_username}]({user_url}) 关注了您" 10 | BOOKMARK = u"用户 [{user_username}]({user_url}) 收藏了您的代码集合 [{paste_title}]({paste_url}) 到收藏夹 [{bookmark_title}]({bookmark_url})" 11 | LIKE = u"用户 [{user_username}]({user_url}) 喜欢了您的代码集合 [{paste_title}]({paste_url})" 12 | 13 | 14 | class Message(db.Document): 15 | user = db.ReferenceField('User') 16 | who = db.ReferenceField('User') 17 | content = db.StringField() 18 | created_at = db.DateTimeField(default=datetime.datetime.now) 19 | -------------------------------------------------------------------------------- /daimaduan/templates/users/signin.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% import 'wtf.html' as f %} 3 | {% block title %}用户登录{% endblock %} 4 | {% block full_content %} 5 |
    6 | {{ form.csrf_token }} 7 |
    8 |
    用户登录
    9 |
    10 | {{ f.render_field(form.email, placeholder='Email', type='email') }} 11 | {{ f.render_field(form.password, placeholder='Password', type='password') }} 12 | 忘记密码 13 |
    14 | 18 |
    19 |
    20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /daimaduan/models/bookmark.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from daimaduan.bootstrap import db 3 | from daimaduan.models import BaseDocument 4 | from daimaduan.models.base import User 5 | 6 | 7 | class Bookmark(BaseDocument): 8 | user = db.ReferenceField(User) 9 | 10 | hash_id = db.StringField(unique=True) 11 | title = db.StringField(required=True) 12 | description = db.StringField() 13 | is_private = db.BooleanField(default=False) 14 | is_default = db.BooleanField(default=False) 15 | 16 | pastes = db.ListField(db.ReferenceField('Paste')) 17 | 18 | def save(self, *args, **kwargs): 19 | self.create_hash_id(self.user.salt, 'bookmark') 20 | if not self.title: 21 | self.title = u"收藏夹: %s" % self.hash_id 22 | super(Bookmark, self).save(*args, **kwargs) 23 | -------------------------------------------------------------------------------- /daimaduan/forms/signin.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from flask_wtf import Form 3 | from wtforms import StringField 4 | from wtforms import PasswordField 5 | from wtforms.validators import InputRequired 6 | from daimaduan.models.base import User 7 | 8 | 9 | class SigninForm(Form): 10 | email = StringField(u'email', validators=[InputRequired()]) 11 | password = PasswordField(u'password', validators=[InputRequired()]) 12 | 13 | def validate(self): 14 | if not Form.validate(self): 15 | return False 16 | 17 | user = User.objects(email=self.email.data).first() 18 | 19 | if user: 20 | if user.check_login(self.password.data): 21 | self.user = user 22 | return True 23 | 24 | self.password.errors.append(u'登录邮箱或者密码不正确') 25 | return False 26 | -------------------------------------------------------------------------------- /daimaduan/templates/shared/footer.html: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /daimaduan/tasks/celery.py: -------------------------------------------------------------------------------- 1 | from flask import current_app 2 | from mailthon import email 3 | from mailthon.middleware import TLS, Auth 4 | from mailthon.postman import Postman 5 | 6 | from daimaduan.bootstrap import celery 7 | 8 | 9 | SENDER = 'noreply.daimaduan@gmail.com' 10 | 11 | 12 | @celery.task 13 | def send_email(config, user_email, subject, content): 14 | envelope = email(sender=SENDER, 15 | receivers=[user_email], 16 | subject=subject, 17 | content=content) 18 | 19 | postman = Postman(host=config['host'], 20 | port=int(config['port']), 21 | middlewares=[TLS(force=True), 22 | Auth(username=config['username'], password=config['password'])]) 23 | 24 | postman.send(envelope) 25 | -------------------------------------------------------------------------------- /daimaduan/templates/macros/users.html: -------------------------------------------------------------------------------- 1 | {% macro render_user_avatar(user, size=36) %} 2 | 3 | {{ user.username }} 7 | 8 | {% endmacro %} 9 | 10 | {% macro render_user_panel(user) %} 11 | {% include 'shared/user_panel.html' %} 12 | {% endmacro %} 13 | 14 | {% macro render_user_watch(user, current_user) %} 15 | {% set is_following = current_user.is_following(user) -%} 16 | {% set display = is_following | ternary('取消关注TA', '关注TA') -%} 17 |
    18 | 19 |
    20 | {% endmacro %} 21 | -------------------------------------------------------------------------------- /daimaduan/templates/users/manage.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% import 'wtf.html' as f %} 3 | {% block title %}修改信息{% endblock %} 4 | {% block full_content %} 5 |
    6 | {{ form.csrf_token }} 7 |
    8 |
    修改个人信息
    9 |
    10 | {% if session['email'] %} 11 | {{ f.render_field(form.email, placeholder='Email', type='email', readonly='readonly') }} 12 | {% else %} 13 | {{ f.render_field(form.email, placeholder='Email', type='email') }} 14 | {% endif %} 15 | {{ f.render_field(form.username, placeholder='Username', type='text') }} 16 |
    17 | 20 |
    21 |
    22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /daimaduan/extensions/assets.py: -------------------------------------------------------------------------------- 1 | from flask_assets import Bundle 2 | from flask_assets import Environment 3 | 4 | assets = Environment() 5 | 6 | js = Bundle('lib/vue/dist/vue.js', 7 | 'lib/underscore/underscore.js', 8 | 'js/lexers.js', 9 | 'js/app.js', 10 | 'js/pastes.js', 11 | filters='uglifyjs', output='js/compiled.js') 12 | assets.register('js_all', js) 13 | 14 | scss = Bundle('css/app.scss', 15 | 'css/pastes.scss', 16 | 'css/embed.scss', 17 | filters='scss') 18 | 19 | css = Bundle('css/bootstrap.min.css', 20 | 'css/colorful.min.css', 21 | scss, 22 | filters='cssmin', output='css/compiled.css') 23 | 24 | assets.register('css_all', css) 25 | 26 | embed = Bundle('css/embed.scss', filters='scss,cssmin', output='css/embed.css') 27 | 28 | assets.register('embed', embed) 29 | -------------------------------------------------------------------------------- /daimaduan/templates/users/signup.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% import 'wtf.html' as f %} 3 | {% block title %}注册新用户{% endblock %} 4 | {% block full_content %} 5 |
    6 | {{ form.csrf_token }} 7 |
    8 |
    用户注册
    9 |
    10 | {{ f.render_field(form.email, placeholder='Email地址', type='email') }} 11 | {{ f.render_field(form.username, placeholder='用户名', type='text') }} 12 | {{ f.render_field(form.password, placeholder='密码', type='password') }} 13 | {{ f.render_field(form.password_confirm, placeholder='密码确认', type='password') }} 14 |
    15 | 20 |
    21 |
    22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /daimaduan/templates/users/messages.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {%- from 'macros/common.html' import render_pagination %} 3 | {% block title %}查看消息{% endblock %} 4 | {% block content %} 5 | 6 |
    7 | {% for message in pagination.items %} 8 |
    9 |
    10 | 11 | {{ message.who.username }} 12 | 13 |
    14 |
    15 |
    {{ message.content | markdown | safe }}
    16 | {{ message.created_at | time_passed }} 17 |
    18 |
    19 | {% endfor %} 20 | 21 | {{ render_pagination(pagination, url_for('site_app.messages')) }} 22 |
    23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /daimaduan/templates/search.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %}搜索代码段{% endblock %} 3 | 4 | {%- from 'macros/pastes.html' import render_paste_item %} 5 | {%- from 'macros/common.html' import render_pagination %} 6 | 7 | {% block content %} 8 | 9 | 10 |
    11 | {% for paste in pagination.items %} 12 | {{ render_paste_item(paste) }} 13 | {% endfor %} 14 | 15 |
    16 | 17 | {{ render_pagination(pagination, url_for('site_app.search_paste', q=keyword)) }} 18 |
    19 | {% endblock %} 20 | 21 | {% block sidebar %} 22 |
    23 |
    查询优化
    24 |
    25 |
      26 |
    • 在查询条件里加上'tag:TAG'可以只查询标签为TAG的代码集合
    • 27 |
    • 在查询条件里加上'user:USER'可以只查询该用户的代码集合
    • 28 |
    • 例如: "uwsgi tag:python user:david-xie"
    • 29 |
    30 |
    31 |
    32 | {% endblock %} 33 | -------------------------------------------------------------------------------- /daimaduan/templates/users/user.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {%- from 'macros/tags.html' import render_tag %} 4 | {%- from 'macros/pastes.html' import render_paste_item %} 5 | {%- from 'macros/common.html' import render_pagination %} 6 | {%- from 'macros/users.html' import render_user_panel with context %} 7 | 8 | {% block metatitle %} 9 | 10 | {% endblock %} 11 | 12 | {% block title %}{{ user.username }} 发布的代码段{% endblock %} 13 | 14 | {% block content %} 15 | 16 | 17 |
    18 | {% for paste in pagination.items %} 19 | {{ render_paste_item(paste) }} 20 | {% endfor %} 21 | 22 |
    23 | 24 | {{ render_pagination(pagination, url_for('user_app.view', username=user.username)) }} 25 |
    26 | {% endblock %} 27 | 28 | {% block sidebar %} 29 | {{ render_user_panel(user) }} 30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /daimaduan/views/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import time 3 | 4 | from flask import g 5 | from flask import render_template 6 | 7 | from daimaduan.bootstrap import app 8 | 9 | 10 | @app.before_request 11 | def before_request(): 12 | g.started_at = time.time() 13 | 14 | 15 | @app.errorhandler(401) 16 | def error_401(error): 17 | return render_template('error.html', 18 | title=u'请登录', 19 | message=u'请登录后再执行此操作!') 20 | 21 | 22 | @app.errorhandler(404) 23 | def error_404(error): 24 | return render_template('error.html', 25 | title=u"页面找不到", 26 | message=u"您所访问的页面不存在!") 27 | 28 | 29 | @app.errorhandler(500) 30 | def error_500(error): 31 | if hasattr(error, 'description'): 32 | message = error.description 33 | else: 34 | message = u"服务器开小差了, 晚点再来吧!" 35 | return render_template('error.html', 36 | title=u"服务器错误", 37 | message=message) 38 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import sys 3 | 4 | from setuptools import setup, find_packages 5 | 6 | # get requirements 7 | install_requires = [] 8 | dependency_links = [] 9 | with open('requirements.txt') as f: 10 | for line in f.read().splitlines(): 11 | if 'egg=' in line: 12 | dependency_links.append(line) 13 | else: 14 | install_requires.append(line) 15 | 16 | # get dev version from git 17 | cmd = subprocess.Popen("git log --oneline | head -1 | awk '{print $1}'", shell=True, stdout=subprocess.PIPE) 18 | out, err = cmd.communicate() 19 | if err: 20 | print err 21 | sys.exit(-1) 22 | 23 | 24 | setup( 25 | name='daimaduan.com', 26 | version='2.4+%s' % out.strip(), 27 | long_description=__doc__, 28 | url='https://github.com/DoubleCiti/daimaduan.com', 29 | author='David Xie', 30 | author_email='david.scriptfan@gmail.com', 31 | packages=find_packages(), 32 | include_package_data=True, 33 | zip_safe=False, 34 | install_requires=install_requires, 35 | dependency_links=dependency_links 36 | ) 37 | -------------------------------------------------------------------------------- /daimaduan/templates/users/likes.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {%- from 'macros/users.html' import render_user_panel with context %} 4 | {%- from 'macros/tags.html' import render_tag %} 5 | {%- from 'macros/pastes.html' import render_paste_item %} 6 | {%- from 'macros/common.html' import render_pagination %} 7 | 8 | {% block title %}{{ user.username }} 喜欢的代码段{% endblock %} 9 | 10 | {% block content %} 11 | 12 | 13 |
    14 | {% for paste in pagination.items %} 15 | {{ render_paste_item(paste) }} 16 | {% endfor %} 17 | 18 |
    19 | 20 | {{ render_pagination(pagination, url_for('user_app.view_likes', username=user.nickname)) }} 21 |
    22 | {% endblock %} 23 | 24 | {% block sidebar %} 25 | {{ render_user_panel(user) }} 26 | {#
    #} 27 | {#
    热门标签
    #} 28 | {#
    #} 29 | {# {% for tag in tags %}#} 30 | {# {{ render_tag(tag.name) }}#} 31 | {# {% endfor %}#} 32 | {#
    #} 33 | {#
    #} 34 | {% endblock %} 35 | -------------------------------------------------------------------------------- /daimaduan/forms/userinfo.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from flask import session 3 | from flask_wtf import Form 4 | from wtforms import StringField 5 | from wtforms import ValidationError 6 | from wtforms.validators import Email 7 | from wtforms.validators import InputRequired 8 | from wtforms.validators import Regexp 9 | 10 | from daimaduan.models.base import User 11 | 12 | 13 | class UserInfoForm(Form): 14 | username = StringField(u'昵称', validators=[ 15 | InputRequired(), Regexp(r'\S{3,12}', message=u'3到12个字符,不能包含空格')]) 16 | email = StringField(u'Email', validators=[InputRequired(), Email()]) 17 | 18 | def validate_username(self, field): 19 | user = User.objects(username=field.data).first() 20 | if user: 21 | raise ValidationError(u'用户名已被使用') 22 | 23 | def validate_email(self, field): 24 | if session['email']: 25 | if session['email'] != field.data: 26 | raise ValidationError(u'不能修改第三方登录的email地址') 27 | user = User.objects(email=field.data).first() 28 | if user: 29 | raise ValidationError(u'Email地址已被使用') 30 | -------------------------------------------------------------------------------- /daimaduan/forms/signup.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from flask_wtf import Form 3 | from wtforms import StringField 4 | from wtforms import PasswordField 5 | from wtforms import ValidationError 6 | from wtforms.validators import InputRequired 7 | from wtforms.validators import Email 8 | from wtforms.validators import Regexp 9 | from wtforms.validators import EqualTo 10 | from daimaduan.models.base import User 11 | 12 | 13 | class SignupForm(Form): 14 | username = StringField(u'昵称', validators=[ 15 | InputRequired(), Regexp(r'\S{3,12}', message=u'3到12个字符,不能包含空格')]) 16 | email = StringField(u'Email', validators=[InputRequired(), Email()]) 17 | password = PasswordField(u'密码', validators=[InputRequired()]) 18 | password_confirm = PasswordField(u'密码确认', validators=[EqualTo('password', message=u'两次密码不同')]) 19 | 20 | def validate_username(self, field): 21 | user = User.objects(username=field.data).first() 22 | if user is not None: 23 | raise ValidationError(u'用户名已被使用') 24 | 25 | def validate_email(self, field): 26 | user = User.objects(email=field.data).first() 27 | if user is not None: 28 | raise ValidationError(u'Email已被使用') 29 | -------------------------------------------------------------------------------- /daimaduan/utils/oauth.py: -------------------------------------------------------------------------------- 1 | from daimaduan.models.user_oauth import UserOauth 2 | 3 | 4 | def oauth_config(config, provider): 5 | return { 6 | 'name': config['OAUTH'][provider]['name'], 7 | 'base_url': config['OAUTH'][provider]['base_url'], 8 | 'authorize_url': config['OAUTH'][provider]['authorize_url'], 9 | 'access_token_url': config['OAUTH'][provider]['access_token_url'], 10 | 'client_id': config['OAUTH'][provider]['client_id'], 11 | 'client_secret': config['OAUTH'][provider]['client_secret'], 12 | # 'callback_url': config['OAUTH'][provider]['callback_url'], 13 | # 'scope': config['OAUTH'][provider]['scope'] 14 | } 15 | 16 | 17 | def user_bind_oauth(user, session): 18 | """Bind oauth info in session to given user, then clear the session""" 19 | oauth = UserOauth(user=user, 20 | provider=session['oauth_provider'], 21 | openid=session['oauth_openid'], 22 | token=session['oauth_token']) 23 | oauth.save() 24 | 25 | # Clear oauth info in session 26 | del session['oauth_provider'] 27 | del session['oauth_openid'] 28 | del session['oauth_name'] 29 | del session['oauth_token'] 30 | -------------------------------------------------------------------------------- /daimaduan/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {%- from 'macros/tags.html' import render_tag %} 4 | {%- from 'macros/pastes.html' import render_paste_item %} 5 | {%- from 'macros/common.html' import render_pagination %} 6 | 7 | {% block content %} 8 | 9 | 10 |
    11 | {% for paste in pagination.items %} 12 | {{ render_paste_item(paste) }} 13 | {% endfor %} 14 | 15 |
    16 | 17 | {{ render_pagination(pagination, url_for('site_app.index')) }} 18 |
    19 | {% endblock %} 20 | 21 | {% block sidebar %} 22 |
    23 |
    热门标签
    24 |
    25 | {% for tag in tags %} 26 | {{ render_tag(tag) }} 27 | {% endfor %} 28 |
    29 |
    30 | {##} 31 | {##} 32 | {##} 37 | {##} 40 | {% endblock %} 41 | -------------------------------------------------------------------------------- /daimaduan/static/css/pastes.scss: -------------------------------------------------------------------------------- 1 | .page-pastes-view { 2 | .page-header { 3 | margin-bottom: 10px; 4 | } 5 | .page-sub-header { 6 | margin-bottom: 20px; 7 | } 8 | } 9 | 10 | 11 | .panel-code { 12 | .title-wrapper { 13 | margin-right: 160px; 14 | } 15 | 16 | .syntax-wrapper { 17 | width: 100px; 18 | float: right; 19 | margin-right: 10px; 20 | } 21 | .trash-wrapper { 22 | width: 40px; 23 | float: right; 24 | } 25 | } 26 | 27 | #panel-paste-user { 28 | .panel-heading { 29 | border-bottom: 1px solid #dddddd; 30 | } 31 | .img-rounded { 32 | //Instead of the line below you could use @include border-radius($radius, $vertical-radius) 33 | border-radius: 50%; 34 | } 35 | } 36 | .action-unlike { 37 | &:link { 38 | color: #e5214a; 39 | } 40 | &:visited { 41 | color: #e5214a; 42 | } 43 | &:hover { 44 | color: #e5214a; 45 | } 46 | &:active { 47 | color: #e5214a; 48 | } 49 | } 50 | .view_pastes.func_show { 51 | .page-header { 52 | margin-bottom: 10px; 53 | padding-bottom: 5px; 54 | h4 { 55 | margin-bottom: 5px; 56 | } 57 | } 58 | .page-sub-header { 59 | margin-bottom: 20px; 60 | } 61 | } 62 | .action-like:hover { 63 | color: #e5214a; 64 | } 65 | 66 | td.linenos { 67 | width: 40px; 68 | text-align: right; 69 | } 70 | -------------------------------------------------------------------------------- /daimaduan/forms/paste.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from wtforms import Form 3 | from wtforms import StringField 4 | from wtforms import TextAreaField 5 | from wtforms import FieldList 6 | from wtforms import FormField 7 | from wtforms import SelectField 8 | from wtforms import BooleanField 9 | from wtforms.validators import InputRequired, ValidationError 10 | 11 | from daimaduan.models.syntax import Syntax 12 | 13 | 14 | class NonValidatingSelectField(SelectField): 15 | def pre_validate(self, form): 16 | pass 17 | 18 | 19 | class CodeForm(Form): 20 | title = StringField(u'片段描述') 21 | syntax = NonValidatingSelectField(u'语法') 22 | content = TextAreaField(u'代码片段', 23 | validators=[ 24 | InputRequired(message=u'不能为空!') 25 | ]) 26 | 27 | 28 | class PasteForm(Form): 29 | title = StringField(u'标题') 30 | is_private = BooleanField(u'我想要这段代码私有') 31 | codes = FieldList(FormField(CodeForm), min_entries=1, max_entries=7) 32 | tags = StringField(u'标签') 33 | 34 | def validate_tags(self, field): 35 | if len(field.data.split()) > 3: 36 | raise ValidationError(u'只能添加3个标签') 37 | 38 | 39 | class CommentForm(Form): 40 | content = StringField(u'评论') 41 | -------------------------------------------------------------------------------- /daimaduan/templates/bookmarks/view.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% from 'macros/users.html' import render_user_panel with context %} 4 | {% from 'macros/pastes.html' import render_paste_item with context %} 5 | {% from 'macros/common.html' import render_pagination %} 6 | {% from 'macros/common.html' import fa_icon %} 7 | 8 | {% block metatitle %} 9 | 10 | {% endblock %} 11 | 12 | {% block title %}{{ bookmark.title }}{% endblock %} 13 | {% block content %} 14 | 28 | 29 |
    30 | {% for paste in bookmark.pastes %} 31 | {{ render_paste_item(paste) }} 32 | {% endfor %} 33 |
    34 | {% endblock %} 35 | 36 | {% block sidebar %} 37 | {{ render_user_panel(bookmark.user) }} 38 | {% endblock %} 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | Procfile 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | 47 | # Translations 48 | *.mo 49 | *.pot 50 | 51 | # Django stuff: 52 | *.log 53 | 54 | # Sphinx documentation 55 | docs/_build/ 56 | 57 | # PyBuilder 58 | target/ 59 | sessions 60 | config.cfg 61 | .idea/ 62 | .venv/ 63 | .newrelic_key 64 | 65 | .DS_Store 66 | .venv 67 | 68 | # Configurations 69 | /daimaduan/custom_settings.py 70 | 71 | # Assets 72 | /daimaduan/static/lib 73 | /daimaduan/static/.webassets-cache 74 | /daimaduan/static/css/compiled.css 75 | /daimaduan/static/css/embed.css 76 | /daimaduan/static/js/compiled.js 77 | 78 | -------------------------------------------------------------------------------- /daimaduan/templates/shared/user_panel.html: -------------------------------------------------------------------------------- 1 | {% from 'macros/common.html' import panel_tag %} 2 | {% from 'macros/common.html' import fa_icon %} 3 | {% from 'macros/users.html' import render_user_avatar %} 4 | {% from 'macros/users.html' import render_user_watch %} 5 | 6 |
    9 |
    10 |
    11 | {{ render_user_avatar(user, size=80) }} 12 |
    13 | 14 |

    {{ user.username }}

    15 |
    16 | 17 | 36 |
    -------------------------------------------------------------------------------- /daimaduan/templates/pastes/form_code.html: -------------------------------------------------------------------------------- 1 | {% raw %} 2 |
    4 |
    5 |
    6 | 11 |
    12 |
    13 |
    14 | 20 |
    21 |
    22 | 23 |
    24 | 29 |
    30 |
    31 |
    32 |
    33 | 37 |
    38 |
    39 |
    40 | {% endraw %} -------------------------------------------------------------------------------- /daimaduan/static/js/lexers.js: -------------------------------------------------------------------------------- 1 | var lexers = [{"name": "Bash", "value": "bash"}, {"name": "C", "value": "c"}, {"name": "C#", "value": "csharp"}, {"name": "C++", "value": "cpp"}, {"name": "CSS", "value": "css"}, {"name": "Clojure", "value": "clojure"}, {"name": "CoffeeScript", "value": "coffee-script"}, {"name": "Dart", "value": "dart"}, {"name": "Diff", "value": "diff"}, {"name": "Docker", "value": "docker"}, {"name": "ERB", "value": "erb"}, {"name": "Erlang", "value": "erlang"}, {"name": "Go", "value": "go"}, {"name": "Gradle", "value": "gradle"}, {"name": "Groovy", "value": "groovy"}, {"name": "HTML", "value": "html"}, {"name": "INI", "value": "ini"}, {"name": "JSON", "value": "json"}, {"name": "Java", "value": "java"}, {"name": "JavaScript", "value": "js"}, {"name": "Kotlin", "value": "kotlin"}, {"name": "Lua", "value": "lua"}, {"name": "Makefile", "value": "make"}, {"name": "Nginx configuration file", "value": "nginx"}, {"name": "Objective-C", "value": "objective-c"}, {"name": "PHP", "value": "php"}, {"name": "Perl", "value": "perl"}, {"name": "Puppet", "value": "puppet"}, {"name": "Python", "value": "python"}, {"name": "RPMSpec", "value": "spec"}, {"name": "Ruby", "value": "rb"}, {"name": "SCSS", "value": "scss"}, {"name": "SQL", "value": "sql"}, {"name": "Sass", "value": "sass"}, {"name": "Scala", "value": "scala"}, {"name": "Swift", "value": "swift"}, {"name": "Text only", "value": "text"}, {"name": "VimL", "value": "vim"}, {"name": "XML", "value": "xml"}, {"name": "YAML", "value": "yaml"}, {"name": "reStructuredText", "value": "rst"}]; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, DoubleCiti 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of daimaduan.com nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /daimaduan/templates/bookmarks/create.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %}创建新的码单{% endblock %} 3 | 4 | {% block content %} 5 | {% include 'shared/form_errors.html' %} 6 |
    7 | {{ form.csrf_token }} 8 |
    9 |
    创建新的码单
    10 |
    11 |
    12 |
    13 | {{ form.title(class_="form-control", placeholder="描述一下这段代码集合(选填)", autofocus=true) }} 14 |
    15 |
    16 |
    17 |
    18 | 19 |
    20 |
    21 |
    22 | {{ form.description.label(class="control-label col-sm-2") }} 23 |
    {{ form.description(class="form-control", rows="10") }}
    24 |
    25 |
    26 |
    27 | 28 |
    29 |
    30 |
    31 |
    32 |
    33 | {% endblock %} 34 | {% block sidebar %} 35 |

    36 | 37 | 小贴士 38 |

    39 |

    40 |

  • 每一个代码集合可以有多个代码片段
  • 41 |
  • 添加一个代码片段只需要点击"添加一个片段"即可
  • 42 |
  • 每个代码片段可以有自己的描述和语法
  • 43 |
  • 勾选"私有"后可以将代码集合隐藏, 只有知道链接的人才可以访问到
  • 44 |

    45 | {% endblock %} 46 | -------------------------------------------------------------------------------- /daimaduan/migrations/20160318173601.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import hashlib 3 | 4 | import time 5 | 6 | from mongodb_migrations.base import BaseMigration 7 | 8 | 9 | class Migration(BaseMigration): 10 | def upgrade(self): 11 | """ 12 | Add default bookmark to every user 13 | :return: 14 | """ 15 | user_ids = [str(i['user']) for i in self.db.bookmark.find()] 16 | users = self.db.user.find() 17 | for user in users: 18 | if str(user['_id']) not in user_ids: 19 | self.db.bookmark.save({'user': user['_id'], 20 | 'title': u'%s 的收藏夹' % user['username'], 21 | 'description': '', 22 | 'hash_id': self.create_hash_id(user['salt'], 'bookmark'), 23 | 'created_at': user['created_at'], 24 | 'updated_at': user['created_at'], 25 | 'pastes': [], 26 | 'is_private': False, 27 | 'is_default': True}) 28 | 29 | def create_hash_id(self, salt, string): 30 | def generate_hash_id(): 31 | return hashlib.sha1('%s%s%s' % (salt, string, str(time.time()))).hexdigest()[:11] 32 | hash_id = generate_hash_id() 33 | while(self.db.bookmark.find_one({'hash_id': hash_id}) is not None): 34 | hash_id = generate_hash_id() 35 | 36 | return hash_id 37 | 38 | def downgrade(self): 39 | print "I'm in downgrade - migration2" 40 | -------------------------------------------------------------------------------- /daimaduan/templates/pastes/create.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' -%} 2 | {% from 'macros/common.html' import fa_icon -%} 3 | 4 | {% block title %}粘贴新代码{% endblock %} 5 | 6 | {% block content %} 7 | 14 | 15 | 16 | 17 |
    18 |
    19 | 20 |
    21 | 22 |
    23 | 24 |
    25 | 26 |
    27 | {% include "pastes/form_code.html" %} 28 | 34 |
    35 | 36 |
    37 | 38 |
    39 | 42 |
    43 | 44 |
    45 | {% endblock %} 46 | 47 | {% block sidebar %} 48 |

    49 | 50 | 小贴士 51 |

    52 |

    53 |

  • 每一个代码集合可以有多个代码片段
  • 54 |
  • 添加一个代码片段只需要点击"添加一个片段"即可
  • 55 |
  • 每个代码片段可以有自己的描述和语法
  • 56 |
  • 勾选"私有"后可以将代码集合隐藏, 只有知道链接的人才可以访问到
  • 57 |

    58 | {% endblock %} 59 | -------------------------------------------------------------------------------- /daimaduan/templates/pastes/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' -%} 2 | {% from 'macros/common.html' import fa_icon -%} 3 | 4 | {% block title %}修改代码段{% endblock %} 5 | 6 | {% block content %} 7 | 10 | 11 | 12 | 13 |
    14 | 15 | 16 |
    17 | 18 |
    19 | 20 |
    21 | 22 |
    23 | 24 |
    25 | {% include "pastes/form_code.html" %} 26 | 32 |
    33 | 34 |
    35 | 36 |
    37 | 40 |
    41 | 42 |
    43 | {% endblock %} 44 | 45 | {% block sidebar %} 46 |

    47 | 48 | 小贴士 49 |

    50 |

    51 |

  • 每一个代码集合可以有多个代码片段
  • 52 |
  • 添加一个代码片段只需要点击"添加一个片段"即可
  • 53 |
  • 每个代码片段可以有自己的描述和语法
  • 54 |
  • 勾选"私有"后可以将代码集合隐藏, 只有知道链接的人才可以访问到
  • 55 |

    56 | {% endblock %} 57 | -------------------------------------------------------------------------------- /scripts/fetch_lumen.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from urlparse import urljoin 4 | import urllib2 5 | from tempfile import NamedTemporaryFile 6 | import re 7 | import os 8 | from distutils.file_util import copy_file 9 | 10 | BASE_URL = 'https://cdnjs.cloudflare.com/ajax/libs/bootswatch' 11 | VERSION = '3.3.6' 12 | THEME = 'lumen' 13 | PAT_URL = r"url\(\'(?P.+?)\'\)" 14 | DIST_FILE = 'daimaduan/static/css/bootstrap.min.css' 15 | 16 | 17 | def has_gfw(line): 18 | return 'fonts.googleapis.com' in line 19 | 20 | 21 | def has_url(line): 22 | return 'url(' in line 23 | 24 | 25 | def theme_file_url(filename): 26 | return '%s/%s/%s/%s' % (BASE_URL, VERSION, THEME, filename) 27 | 28 | 29 | def expand_url(match): 30 | relative_url = match.group('URL') 31 | full_url = urljoin(theme_file_url('foo.bar'), relative_url) 32 | print 'Process %s' % relative_url 33 | print ' ==> %s' % full_url 34 | return "url('%s')" % full_url 35 | 36 | 37 | def expend_urls(line): 38 | return re.sub(PAT_URL, expand_url, line) 39 | 40 | 41 | def fetch_lumen(version='3.3.6'): 42 | url = theme_file_url('bootstrap.min.css') 43 | 44 | print 'Fetching %s' % url 45 | response = urllib2.urlopen(url) 46 | 47 | with NamedTemporaryFile() as f: 48 | for line in response: 49 | if has_gfw(line): 50 | print 'Dropped gfw content: %s' % line, 51 | elif has_url(line): 52 | f.write(expend_urls(line)) 53 | else: 54 | f.write(line) 55 | f.flush() 56 | 57 | print 'Updating site static %s' % DIST_FILE 58 | copy_file(f.name, DIST_FILE) 59 | 60 | 61 | if __name__ == '__main__': 62 | fetch_lumen() 63 | -------------------------------------------------------------------------------- /daimaduan/utils/filters.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import time 3 | from markdown import markdown 4 | 5 | 6 | def datetimeformat(value): 7 | """filter for Jinja2""" 8 | return value.strftime("%Y-%m-%d %H:%M:%S") 9 | 10 | 11 | MINUTE = 60 12 | HOUR = 60 * MINUTE 13 | DAY = 24 * HOUR 14 | MONTH = 30 * DAY 15 | YEAR = 12 * MONTH 16 | 17 | 18 | def time_passed(value): 19 | """filter for Jinjia2""" 20 | time_diff = int(time.time() - time.mktime(value.timetuple())) 21 | if time_diff < MINUTE: 22 | quantity = time_diff 23 | unit = u'秒' 24 | if time_diff >= MINUTE and time_diff < HOUR: 25 | quantity = time_diff / MINUTE 26 | unit = u'分钟' 27 | if time_diff >= HOUR and time_diff < DAY: 28 | quantity = time_diff / HOUR 29 | unit = u'小时' 30 | if time_diff >= DAY and time_diff < MONTH: 31 | quantity = time_diff / DAY 32 | unit = u'天' 33 | if time_diff >= MONTH and time_diff < YEAR: 34 | quantity = time_diff / MONTH 35 | unit = u'月' 36 | if time_diff >= YEAR: 37 | quantity = time_diff / YEAR 38 | unit = u'年' 39 | 40 | return u'%s %s前' % (quantity, unit) 41 | 42 | 43 | def ternary(value, x, y): 44 | """Ternary operator simulator 45 | 46 | This filter 47 | 48 | {{ is_worked | ternary('Yes', 'No') }} 49 | 50 | works as the following code in other language 51 | 52 | is_worked ? 'Yes' : 'No' 53 | """ 54 | 55 | if value: 56 | return x 57 | else: 58 | return y 59 | 60 | 61 | def md(value): 62 | """ 63 | Translate markdown syntax to html 64 | :param value: 65 | :return: 66 | """ 67 | return markdown(value) 68 | 69 | 70 | def time_used(value): 71 | t = int((time.time() - value) * 1000) 72 | return "%sms" % t 73 | -------------------------------------------------------------------------------- /daimaduan/default_settings.py: -------------------------------------------------------------------------------- 1 | DEBUG = True 2 | 3 | # Set it to 'INFO' in production 4 | FLASK_LOG_LEVEL = 'DEBUG' 5 | 6 | # Enable Flask-Assets debug mode. 7 | ASSETS_DEBUG = False 8 | 9 | MONGODB_SETTINGS = { 10 | 'host': '127.0.0.1', 11 | 'db': 'daimaduan' 12 | } 13 | 14 | SECRET_KEY = 'Youshouldnotknowthis' 15 | 16 | BROKER_URL = 'mongodb://127.0.0.1:27017/daimaduan_celery' 17 | 18 | USE_JINJA_CACHE = False 19 | MEMCACHED_URL = '127.0.0.1:11211' 20 | 21 | EMAIL = { 22 | 'salt': "won't tell you", 23 | 'host': 'smtp.gmail.com', 24 | 'port': 587, 25 | 'username': 'daimaduan', 26 | 'password': 'guessme' 27 | } 28 | 29 | DOMAIN = 'daimaduan.dev:8080' 30 | 31 | VERSION = '2.4' 32 | DEPLOYED_AT = '2016-04-09 16:02:13' 33 | 34 | OAUTH = { 35 | 'github': { 36 | 'name': 'github', 37 | 'authorize_url': 'https://github.com/login/oauth/authorize', 38 | 'access_token_url': 'https://github.com/login/oauth/access_token', 39 | 'base_url': 'https://api.github.com/', 40 | 'callback_url': 'http://daimaduan.dev:8080/oauth/github/callback', 41 | 'client_id': '1f8813d6e0535fbfff98', 42 | 'client_secret': '085d8ac899e236e12feaceb528c9de63aa601d39', 43 | 'scope': 'user:email' 44 | }, 45 | 'google': { 46 | 'name': 'google', 47 | 'authorize_url': 'https://accounts.google.com/o/oauth2/auth', 48 | 'access_token_url': 'https://accounts.google.com/o/oauth2/token', 49 | 'base_url': 'https://www.googleapis.com/oauth2/v1/', 50 | 'callback_url': 'http://daimaduan.dev:8080/oauth/google/callback', 51 | 'client_id': '572596272656-9urgn16qjoj36c439pecjcjmsogs76au.apps.googleusercontent.com', 52 | 'client_secret': '085d8ac899e236e12feaceb528c9de63aa601d39', 53 | 'scope': 'email profile openid' 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /daimaduan/static/js/app.js: -------------------------------------------------------------------------------- 1 | function signoutHandler() { 2 | $.ajax({ 3 | url: '/signout', 4 | method: 'DELETE', 5 | success: function(data) { 6 | if (data.status == 302) { 7 | document.location = data.location; 8 | } 9 | } 10 | }); 11 | } 12 | 13 | app.faIcon = function(icon, text) { 14 | var template = _.template(' <%= text %>'); 15 | return template({ icon: icon, text: text }); 16 | }; 17 | 18 | function initToggleFullCode() { 19 | $(document).on('click', '.full-code-toggle', function(event) { 20 | event.preventDefault(); 21 | $(this).prev().toggleClass('code_preview'); 22 | if ($(this).text() == '显示完整代码') 23 | $(this).text('隐藏代码'); 24 | else 25 | $(this).text('显示完整代码'); 26 | }); 27 | } 28 | 29 | function switchWatchAction(data) { 30 | data.text = data.watchedStatus ? "取消关注TA" : "关注TA"; 31 | data.class_name = data.watchedStatus ? "unwatch" : "watch"; 32 | 33 | var new_html = ''; 34 | 35 | var compiled = _.template(new_html); 36 | return compiled(data); 37 | } 38 | 39 | function toggleUserWatch() { 40 | var $action = $(this); 41 | var action = $action.is(".action-watch") ? "watch" : "unwatch"; 42 | var username = $(".panel-heading h4").text(); 43 | $.ajax({ 44 | url: "/user/" + username + "/" + action, 45 | type: 'POST', 46 | success: function(data) { 47 | $action.replaceWith(switchWatchAction(data)); 48 | } 49 | }); 50 | } 51 | 52 | $(document).ready(function() { 53 | initToggleFullCode(); 54 | $(document).on('click', '.action-signout', signoutHandler); 55 | $(document).on('click', '.action-watch, .action-unwatch', toggleUserWatch); 56 | }); 57 | -------------------------------------------------------------------------------- /daimaduan/templates/bookmarks/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% from 'macros/common.html' import render_pagination %} 4 | {% from 'macros/users.html' import render_user_avatar %} 5 | {% from 'macros/pastes.html' import render_private %} 6 | {% from 'macros/common.html' import fa_icon %} 7 | 8 | {% block metatitle %} 9 | 10 | {% endblock %} 11 | 12 | {% block title %}收藏夹{% endblock %} 13 | {% block content %} 14 | 19 |
    20 | 21 | {% for li in pagination.items %} 22 |
    23 |
    24 |
    25 | {{ render_user_avatar(li.user, size=38) }} 26 |
    27 |
    28 | 34 | 35 |
    36 | 37 | {{ li.title }} 38 | {{ render_private(li) }} 39 | 40 |
    41 | 42 |

    43 | 最后更新于{{ li.updated_at | time_passed }} 44 |

    45 | 46 |
    47 |
    48 |
    49 | {% endfor %} 50 | 51 |
    52 | 53 | {{ render_pagination(pagination, url_for('bookmark_app.index')) }} 54 |
    55 | {% endblock %} 56 | -------------------------------------------------------------------------------- /daimaduan/models/__init__.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import hashlib 3 | import time 4 | 5 | from flask_login import UserMixin 6 | 7 | from daimaduan.bootstrap import db 8 | 9 | 10 | class BaseDocument(db.Document): 11 | meta = {'abstract': True, 12 | 'strict': False} 13 | 14 | # Increase specific counter by `count` 15 | def increase_counter(self, field, count=1): 16 | counter_field = '%s_count' % field 17 | counter_value = getattr(self, counter_field, 0) + count 18 | if counter_value <= 0: 19 | counter_value = 0 20 | 21 | attributes = dict([(counter_field, counter_value)]) 22 | self.update(**attributes) 23 | 24 | @classmethod 25 | def find_or_create_by(cls, **attributes): 26 | record = cls.objects(**attributes).first() 27 | if record is None: 28 | record = cls(**attributes).save() 29 | 30 | return record 31 | 32 | @classmethod 33 | def find_and_delete_by(cls, **attributes): 34 | record = cls.objects(**attributes).first() 35 | if record: 36 | record.delete() 37 | 38 | def create_hash_id(self, salt, string): 39 | if not self.hash_id: 40 | def generate_hash_id(): 41 | return hashlib.sha1('%s%s%s' % (salt, string, str(time.time()))).hexdigest()[:11] 42 | self.hash_id = generate_hash_id() 43 | while(self.__class__.objects(hash_id=self.hash_id).first() is not None): 44 | self.hash_id = generate_hash_id() 45 | 46 | created_at = db.DateTimeField(default=datetime.datetime.now) 47 | updated_at = db.DateTimeField(default=datetime.datetime.now) 48 | 49 | 50 | class LoginManagerUser(UserMixin): 51 | def __init__(self, user): 52 | self.user = user 53 | self.id = user.id 54 | self.username = user.username 55 | self.email = user.email 56 | -------------------------------------------------------------------------------- /daimaduan/static/css/colorful.min.css: -------------------------------------------------------------------------------- 1 | .highlight .hll{background-color:#ffc}.highlight{background:#fff}.highlight .c{color:#888}.highlight .err{color:red;background-color:#FAA}.highlight .s,.highlight .s2,.highlight .sb,.highlight .se,.highlight .sh{background-color:#fff0f0}.highlight .k{color:#080;font-weight:700}.highlight .o{color:#333}.highlight .cm{color:#888}.highlight .cp{color:#579}.highlight .c1{color:#888}.highlight .cs{color:#c00;font-weight:700}.highlight .gd{color:#A00000}.highlight .ge{font-style:italic}.highlight .gr{color:red}.highlight .gh{color:navy;font-weight:700}.highlight .gi{color:#00A000}.highlight .go{color:#888}.highlight .gp{color:#c65d09;font-weight:700}.highlight .gs{font-weight:700}.highlight .gu{color:purple;font-weight:700}.highlight .gt{color:#04D}.highlight .kc,.highlight .kd,.highlight .kn{color:#080;font-weight:700}.highlight .kp{color:#038;font-weight:700}.highlight .kr{color:#080;font-weight:700}.highlight .kt{color:#339;font-weight:700}.highlight .m{color:#60E;font-weight:700}.highlight .na{color:#00C}.highlight .nb{color:#007020}.highlight .nc{color:#B06;font-weight:700}.highlight .no{color:#036;font-weight:700}.highlight .nd{color:#555;font-weight:700}.highlight .ni{color:#800;font-weight:700}.highlight .ne{color:red;font-weight:700}.highlight .nf{color:#06B;font-weight:700}.highlight .nl{color:#970;font-weight:700}.highlight .nn{color:#0e84b5;font-weight:700}.highlight .nt{color:#070}.highlight .nv{color:#963}.highlight .ow{color:#000;font-weight:700}.highlight .w{color:#bbb}.highlight .mb,.highlight .mf{color:#60E;font-weight:700}.highlight .mh{color:#058;font-weight:700}.highlight .mi{color:#00D;font-weight:700}.highlight .mo{color:#40E;font-weight:700}.highlight .sc{color:#04D}.highlight .sd{color:#D42}.highlight .se{color:#666;font-weight:700}.highlight .si{background-color:#eee}.highlight .sx{color:#D20;background-color:#fff0f0}.highlight .sr{color:#000;background-color:#fff0ff}.highlight .s1{background-color:#fff0f0}.highlight .ss{color:#A60}.highlight .bp{color:#007020}.highlight .vc{color:#369}.highlight .vg{color:#d70;font-weight:700}.highlight .vi{color:#33B}.highlight .il{color:#00D;font-weight:700} -------------------------------------------------------------------------------- /daimaduan/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% block metatitle %}{% endblock %} 7 | 8 | {% block metakeywords %}{% endblock %} 9 | {% block title %}最新代码{% endblock %} - 代码段 10 | 11 | {% assets "css_all" %} 12 | 13 | {% endassets %} 14 | 15 | 16 | 17 | 28 | 29 | 30 | 31 | {% include 'shared/header.html' %} 32 | 33 |
    34 |
    {% block full_content %}{% endblock %}
    35 |
    {% block content %}{% endblock %}
    36 |
    {% block sidebar %}{% endblock %}
    37 |
    38 | 39 | {% include 'shared/footer.html' %} 40 | 41 | 42 | 43 | {% assets "js_all" %} 44 | 45 | {% endassets %} 46 | 47 | {% if config['GOOGLE_ANALYTICS'] %}{% include 'shared/google_analytics.html' %}{% endif %} 48 | {% if config['BAIDU_ANALYTICS'] %}{% include 'shared/baidu_analytics.html' %}{% endif %} 49 | 50 | 51 | -------------------------------------------------------------------------------- /daimaduan/templates/pastes/view.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% from 'macros/users.html' import render_user_panel with context %} 3 | {% from 'macros/tags.html' import render_tag %} 4 | {% from 'macros/pastes.html' import render_code %} 5 | {% from 'macros/pastes.html' import render_private %} 6 | {% from 'macros/pastes.html' import like_paste_button %} 7 | {% from 'macros/pastes.html' import edit_paste_button %} 8 | {% from 'macros/pastes.html' import del_paste_button %} 9 | {% from 'macros/pastes.html' import add_to_bookmark_form %} 10 | {% from 'macros/pastes.html' import embed_paste_button with context %} 11 | {% from 'macros/common.html' import render_comments with context %} 12 | 13 | {% block metatitle %} 14 | 15 | {% endblock %} 16 | {% block metakeywords %} 17 | 18 | {% endblock %} 19 | 20 | {% block title %}{{ paste.title }}{% endblock %} 21 | 22 | {% block content %} 23 |
    24 | 34 | 35 |
    36 |
    37 | {% if paste.is_user_owned(current_user.user) %} 38 | {{ edit_paste_button(paste) }} 39 | {{ del_paste_button(paste) }} 40 | {% endif %} 41 | {% if current_user.is_authenticated %} 42 | {{ like_paste_button(current_user.user, paste) }} 43 |
    44 | {{ add_to_bookmark_form(paste, paste_lists) }} 45 |
    46 | {% endif %} 47 | 48 |
    49 | {{ embed_paste_button(paste) }} 50 |
    51 | 52 |
    53 |
    54 |
    55 | 56 | {% for code in paste.codes %} 57 | {{ render_code(code) }} 58 | {% endfor %} 59 | 60 | {{ render_comments(paste) }} 61 |
    62 | {% include 'shared/jiathis.html' %} 63 | {% endblock %} 64 | 65 | {% block sidebar %} 66 | {{ render_user_panel(paste.user) }} 67 | {% endblock %} 68 | -------------------------------------------------------------------------------- /daimaduan/bootstrap.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import os 3 | 4 | import memcache 5 | from celery import Celery 6 | from flask import Flask 7 | from flask_gravatar import Gravatar 8 | from flask_login import LoginManager 9 | from flask_mongoengine import MongoEngine 10 | from flask.ext.log import Logging 11 | from jinja2 import MemcachedBytecodeCache 12 | 13 | from daimaduan.extensions import assets 14 | from daimaduan.utils.filters import datetimeformat 15 | from daimaduan.utils.filters import md 16 | from daimaduan.utils.filters import ternary 17 | from daimaduan.utils.filters import time_passed 18 | from daimaduan.utils.filters import time_used 19 | 20 | 21 | # set default CONFIG to config.cfg 22 | if not os.environ.get('CONFIG', None): 23 | os.environ['CONFIG'] = 'custom_settings.py' 24 | 25 | 26 | db = MongoEngine() 27 | login_manager = LoginManager() 28 | flask_log = Logging() 29 | 30 | app = Flask(__name__) 31 | app.config.from_object('daimaduan.default_settings') 32 | app.config.from_envvar('CONFIG') 33 | 34 | db.init_app(app) 35 | flask_log.init_app(app) 36 | celery = Celery(__name__) 37 | celery.conf.add_defaults(app.config) 38 | 39 | from daimaduan.views.sites import site_app 40 | from daimaduan.views.users import user_app 41 | from daimaduan.views.pastes import paste_app 42 | from daimaduan.views.tags import tag_app 43 | from daimaduan.views.bookmarks import bookmark_app 44 | 45 | app.register_blueprint(site_app) 46 | app.register_blueprint(user_app, url_prefix='/user') 47 | app.register_blueprint(paste_app, url_prefix='/paste') 48 | app.register_blueprint(tag_app, url_prefix='/tag') 49 | app.register_blueprint(bookmark_app, url_prefix='/bookmark') 50 | 51 | app.jinja_env.filters['time_passed'] = time_passed 52 | app.jinja_env.filters['time_used'] = time_used 53 | app.jinja_env.filters['ternary'] = ternary 54 | app.jinja_env.filters['datetimeformat'] = datetimeformat 55 | app.jinja_env.filters['markdown'] = md 56 | if app.config['USE_JINJA_CACHE']: 57 | app.jinja_env.bytecode_cache = MemcachedBytecodeCache(memcache.Client([app.config['MEMCACHED_URL']])) 58 | 59 | login_manager.init_app(app) 60 | assets.init_app(app) 61 | 62 | gravatar = Gravatar(app, 63 | size=100, 64 | rating='g', 65 | default='retro', 66 | force_default=False, 67 | force_lower=False, 68 | use_ssl=True, 69 | base_url='https://cn.gravatar.com/') 70 | -------------------------------------------------------------------------------- /daimaduan/templates/shared/header.html: -------------------------------------------------------------------------------- 1 | 52 | -------------------------------------------------------------------------------- /daimaduan/utils/email_confirmation.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from itsdangerous import URLSafeTimedSerializer 3 | 4 | from daimaduan.models.base import User 5 | from daimaduan.tasks.celery import send_email 6 | 7 | 8 | CONFIRMATION_SUBJECT = u'请激活您在代码段注册的邮箱地址' 9 | CONFIRMATION_CONTENT = u"

    你好 %s,

    \ 10 |

    非常感谢注册代码段. 请点击链接来激活您的邮箱地址.

    \ 11 |

    或者您可以拷贝下面这个地址到您的浏览器中访问来激活邮箱地址 https://%s/confirm/%s

    \ 12 |
    \ 13 |

    此致!

    \ 14 |

    代码段团队

    " 15 | RESET_PASSWORD_SUBJECT = u'您请求重置密码' 16 | RESET_PASSWORD_CONTENT = u"

    你好 %s,

    \ 17 |

    您已请求重置代码段密码. 请点击链接来重置您的密码.

    \ 18 |

    或者您可以拷贝下面这个地址到您的浏览器中访问来重置您的密码 https://%s/reset_password/%s

    \ 19 |
    \ 20 |

    此致!

    \ 21 |

    代码段团队

    " 22 | 23 | 24 | def get_email_config(config): 25 | return dict(host=config['EMAIL']['host'], 26 | port=config['EMAIL']['port'], 27 | username=config['EMAIL']['username'], 28 | password=config['EMAIL']['password']) 29 | 30 | 31 | def generate_confirmation_token(config, email): 32 | """generate confirmation token using user's email via itsdangerous""" 33 | serializer = URLSafeTimedSerializer(config['SECRET_KEY']) 34 | return serializer.dumps(email, salt=config['EMAIL']['salt']) 35 | 36 | 37 | def validate_token(config, token, expire_time=3600): 38 | """from token and expire_time to confirm user's email""" 39 | serializer = URLSafeTimedSerializer(config['SECRET_KEY']) 40 | try: 41 | confirmed_email = serializer.loads(token, max_age=expire_time, salt=config['EMAIL']['salt']) 42 | except Exception: 43 | return False 44 | return confirmed_email 45 | 46 | 47 | def send_confirm_email(config, user_email): 48 | token = generate_confirmation_token(config, user_email) 49 | user = User.objects(email=user_email).first() 50 | content = CONFIRMATION_CONTENT % (user.username, config['DOMAIN'], token, config['DOMAIN'], token) 51 | send_email.delay(get_email_config(config), user_email, CONFIRMATION_SUBJECT, content) 52 | 53 | 54 | def send_reset_password_email(config, user_email): 55 | token = generate_confirmation_token(config, user_email) 56 | user = User.objects(email=user_email).first() 57 | content = RESET_PASSWORD_CONTENT % (user.username, config['DOMAIN'], token, config['DOMAIN'], token) 58 | send_email.delay(get_email_config(config), user_email, RESET_PASSWORD_SUBJECT, content) 59 | -------------------------------------------------------------------------------- /daimaduan/templates/pastes/paste.html: -------------------------------------------------------------------------------- 1 | {% from 'macros/users.html' import render_user_avatar -%} 2 | {% from 'macros/tags.html' import render_tag -%} 3 | {% from 'macros/pastes.html' import render_source_code -%} 4 | {% from 'macros/pastes.html' import render_source_code_preview -%} 5 | {% from 'macros/pastes.html' import render_private -%} 6 | 7 |
    8 |
    9 |
    10 | {{ render_user_avatar(paste.user, size=38) }} 11 |
    12 |
    13 |
    14 | 15 | 16 | {{ paste.codes | length }}段代码 17 | 18 | 19 | 20 | {{ paste.likes_count }}次喜欢 21 | 22 | 23 | 24 | 25 | {{ paste.comments_count }}条评论 26 | 27 | 28 | {% if bookmark_hash_id %} 29 |
    32 | 33 | 34 | 38 |
    39 | {% endif %} 40 |
    41 | 42 |
    43 | 44 | {{ paste.title | truncate(30) }} 45 | {{ render_private(paste) }} 46 | 47 |
    48 | 49 |

    50 | 最后更新于{{ paste.updated_at | time_passed }} 51 |

    52 | 53 |
    54 | 55 |

    56 | {% for tag in paste.tags %} 57 | {{ render_tag(tag) }} 58 | {% endfor %} 59 |

    60 | 61 |
    62 | 63 | {% if paste.codes[0].content.count('\n') > 10 %} 64 | {{ render_source_code_preview(paste.codes[0].highlight_content) }} 65 | {% else %} 66 | {{ render_source_code(paste.codes[0].highlight_content) }} 67 | {% endif %} 68 |
    69 |
    70 | -------------------------------------------------------------------------------- /daimaduan/static/css/app.scss: -------------------------------------------------------------------------------- 1 | //@extend-elements 2 | //original selectors 3 | //.bubble:after, .bubble:before 4 | %extend_1 { 5 | right: 100%; 6 | top: 50%; 7 | border: solid transparent; 8 | content: " "; 9 | height: 0; 10 | width: 0; 11 | position: absolute; 12 | pointer-events: none; 13 | } 14 | a { 15 | &:hover { 16 | text-decoration: none; 17 | outline: none; 18 | } 19 | &:active { 20 | text-decoration: none; 21 | outline: none; 22 | } 23 | &:focus { 24 | text-decoration: none; 25 | outline: none; 26 | } 27 | } 28 | body { 29 | min-width: 320px; 30 | } 31 | #form-signin { 32 | margin: 30px auto; 33 | width: 600px; 34 | } 35 | #form-signup { 36 | margin: 30px auto; 37 | width: 600px; 38 | } 39 | pre { 40 | overflow: auto; 41 | //Instead of the line below you could use @include word-break($value) 42 | word-break: normal; 43 | word-wrap: normal; 44 | &.panel-body { 45 | border: 0; 46 | margin: 0; 47 | } 48 | code { 49 | white-space: pre; 50 | } 51 | } 52 | .name { 53 | padding-right: 30px; 54 | font-size: 24px; 55 | } 56 | .inline { 57 | display: inline; 58 | } 59 | .tag { 60 | margin-bottom: 3px; 61 | } 62 | .page-header { 63 | margin-top: 0; 64 | } 65 | .input-xs { 66 | height: 26px; 67 | padding: 4px 10px; 68 | font-size: 12px; 69 | line-height: 1.5; 70 | //Instead of the line below you could use @include border-radius($radius, $vertical-radius) 71 | border-radius: 2px; 72 | } 73 | .bubble { 74 | position: relative; 75 | border: 1px solid #dddddd; 76 | padding: 1px 5px; 77 | //Instead of the line below you could use @include border-radius($radius, $vertical-radius) 78 | border-radius: 3px; 79 | margin-left: 5px; 80 | &:after { 81 | @extend %extend_1; 82 | border-color: rgba(255, 255, 255, 0); 83 | border-right-color: white; 84 | border-width: 3px 5px 3px 0; 85 | margin-top: -3px; 86 | margin-right: -1px; 87 | } 88 | &:before { 89 | @extend %extend_1; 90 | border-color: rgba(221, 221, 221, 0); 91 | border-right-color: #dddddd; 92 | border-width: 4px 5px 4px 0; 93 | margin-top: -4px; 94 | } 95 | } 96 | .paste { 97 | margin-bottom: 30px; 98 | } 99 | .avatar-wrapper { 100 | margin-top: 10px; 101 | } 102 | .input-group-embed { 103 | max-width: 120px; 104 | float: left; 105 | } 106 | @media(min-width: 480px) { 107 | .input-group-embed { 108 | max-width: 200px; 109 | } 110 | .paste-actions .action span { 111 | display: inline; 112 | } 113 | } 114 | @media(min-width: 768px) { 115 | .input-group-embed { 116 | max-width: 250px; 117 | } 118 | } 119 | @media(min-width: 992px) { 120 | } 121 | @media(min-width: 1200px) { 122 | } 123 | .container .jumbotron { 124 | padding: 20px; 125 | } 126 | .paste-metas a { 127 | display: inline-block; 128 | margin-left: 15px; 129 | text-decoration: none; 130 | &:hover { 131 | color: #555; 132 | } 133 | .fa { 134 | margin-right: 3px; 135 | } 136 | } 137 | .paste-actions .action { 138 | margin-left: 5px; 139 | &:first-child { 140 | margin-left: 0; 141 | } 142 | } 143 | .code .panel-body pre { 144 | border: 0; 145 | background-color: #FFF; 146 | } 147 | 148 | .code .panel-body { 149 | padding: 0; 150 | } 151 | 152 | .highlighttable { 153 | width: 100%; 154 | } 155 | 156 | .highlighttable pre { 157 | border: 0; 158 | border-radius: 0; 159 | } 160 | 161 | .code_preview { 162 | max-height: 192px; 163 | overflow: hidden; 164 | } 165 | .footer { 166 | margin-bottom: 100px; 167 | } 168 | -------------------------------------------------------------------------------- /daimaduan/static/js/pastes.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | function initPaste() { 3 | $(document).on('click', '.action-del', function(event) { 4 | if (!confirm("确认删除吗?")) { 5 | event.preventDefault(); 6 | return false; 7 | } 8 | }) 9 | } 10 | 11 | function renderLikePasteAction(data) { 12 | data.text = data.liked ? '取消喜欢' : '喜欢'; 13 | data.class_name = data.liked ? 'unlike' : 'like'; 14 | 15 | var template = '' 16 | + '' 19 | + ' <%= text %>' 20 | + ' <%= paste_likes %>' 21 | + '' 22 | var compiled = _.template(template); 23 | return compiled(data); 24 | } 25 | 26 | function togglePasteLike() { 27 | var $action = $(this); 28 | var pasteId = $action.data('id'); 29 | var action = $action.is('.action-like') ? 'like' : 'unlike'; 30 | var url = '/paste/' + pasteId + '/' + action; 31 | 32 | $.post(url).then(function(data) { 33 | // Update paste likes count 34 | $action.replaceWith(renderLikePasteAction(data)); 35 | 36 | // Update user paste likes count 37 | var $user = $('[data-user="' + app.current_user.id + '"]'); 38 | $user.find('.paste-likes-count').html(data.user_like); 39 | }); 40 | } 41 | 42 | function selectEmbedCode() { 43 | $('.input-group-embed :text').select(); 44 | } 45 | 46 | function initPasteEditor() { 47 | if ($('#form-paste').size() == 0) return; 48 | 49 | var newCode = { title: "", syntax: "text", content: "" }; 50 | var codes = new Vue({ 51 | el: "#form-paste", 52 | data: { 53 | lexers: lexers, 54 | paste: app.paste, 55 | errors: {} 56 | }, 57 | computed: { 58 | codeRemovable: function() { 59 | return this.paste.codes.length > 1; 60 | }, 61 | codeIncreasable: function() { 62 | return this.paste.codes.length < 7; 63 | } 64 | }, 65 | methods: { 66 | codeHasError: function(index, field) { 67 | try { 68 | var error = this.errors.codes[index][field]; 69 | return error ? 'has-error' : ''; 70 | } catch(e) { 71 | return ''; 72 | } 73 | }, 74 | addCode: function() { 75 | this.paste.codes.push(_.clone(newCode)); 76 | }, 77 | removeCode: function(code) { 78 | this.paste.codes.$remove(code); 79 | }, 80 | submitPaste: function() { 81 | var self = this; 82 | var url = app.rootUrl; 83 | 84 | $.ajax({ 85 | url: document.location.href, 86 | method: 'POST', 87 | dataType: 'json', 88 | data: $('#form-paste').serialize(), 89 | success: function(data) { 90 | if (data.success) { 91 | document.location = '/paste/' + data.hash_id; 92 | } else { 93 | self.errors = data.errors; 94 | } 95 | } 96 | }); 97 | } 98 | } 99 | }); 100 | } 101 | 102 | $(document).ready(function() { 103 | initPaste(); 104 | initPasteEditor(); 105 | 106 | $('.input-group-embed').on('click', selectEmbedCode); 107 | $('.input-group-embed :text').on('focus', selectEmbedCode); 108 | $(document).on('click', '.action-like, .action-unlike', togglePasteLike); 109 | }); 110 | })(); 111 | -------------------------------------------------------------------------------- /daimaduan/templates/macros/pastes.html: -------------------------------------------------------------------------------- 1 | {% from 'macros/tags.html' import render_tag %} 2 | {% from 'macros/common.html' import fa_icon %} 3 | 4 | 5 | {% macro render_paste_item(paste) %} 6 | {% include 'pastes/paste.html' %} 7 | {% endmacro %} 8 | 9 | 10 | {% macro render_private(paste) %} 11 | {% if paste.is_private -%} 12 | 13 | {% endif -%} 14 | {% endmacro %} 15 | 16 | 17 | {% macro render_code(code) %} 18 |
    19 |
    20 | {{ render_tag(code.syntax) }} 21 | {{ code.title }} 22 |
    23 |
    24 | {{ render_source_code(code.highlight_content) }} 25 |
    26 |
    27 | {% endmacro %} 28 | 29 | 30 | {% macro render_source_code_preview(source) %} 31 |
    {{ source | safe }}
    32 | 显示完整代码 33 | {% endmacro %} 34 | 35 | 36 | {% macro render_source_code(source) %} 37 | {{ source | safe }} 38 | {% endmacro %} 39 | 40 | 41 | {% macro edit_paste_button(paste) %} 42 | 46 | {{ fa_icon('pencil') }} 编辑 47 | 48 | {% endmacro %} 49 | 50 | 51 | {% macro del_paste_button(paste) %} 52 |
    55 | 59 |
    60 | {% endmacro %} 61 | 62 | 63 | {% macro like_paste_button(user, paste) %} 64 | {% set liked = paste in user.likes -%} 65 | {% set text = liked | ternary('取消喜欢', '喜欢') -%} 66 | 70 | {{ fa_icon('heart') }} {{ text }} 71 | {{ paste.likes_count }} 72 | 73 | {% endmacro %} 74 | 75 | 76 | {% macro embed_paste_button(paste) %} 77 |
    78 | 79 | 83 | 84 | 86 |
    87 | {% endmacro %} 88 | 89 | {% macro add_to_bookmark_form(paste, paste_lists) %} 90 | {% if paste_lists %} 91 |
    92 | 93 |
    94 | 99 |
    100 | 101 |
    102 | {% endif %} 103 | {% endmacro %} -------------------------------------------------------------------------------- /daimaduan/templates/macros/common.html: -------------------------------------------------------------------------------- 1 | {% macro render_comments(paste) -%} 2 |
    3 |

    {{ paste.comments_count }} 条评论

    4 |
    5 | {% for comment in paste.comments.items %} 6 |
    7 |
    8 | 9 | {{ comment.user.username }} 10 | 11 |
    12 |
    13 | {% if current_user.is_authenticated and comment.is_user_owned(current_user.user) %} 14 | 24 | {% endif %} 25 |
    26 | {{ comment.user.username }} 27 | 发表于 {{ comment.created_at | time_passed }} 28 |
    29 |

    {{ comment.content }}

    30 |
    31 |
    32 | {% endfor %} 33 | {{ render_pagination(paste.comments, url_for('paste_app.view_paste', hash_id=paste.hash_id)) }} 34 | {% if current_user.is_authenticated %} 35 |
    36 |
    37 | 38 |
    39 |
    40 |
    41 |
    42 | 43 |
    44 |
    45 | 46 |
    47 |
    48 |
    49 |
    50 | {% endif %} 51 |
    52 | {%- endmacro %} 53 | 54 | {# 快速添加 fa 图标的标签 #} 55 | {% macro fa_icon(icon, text='', right=False) -%} 56 | {% if right %} 57 | {{ text }} 58 | {% else %} 59 | {{ text }} 60 | {% endif %} 61 | {% endmacro %} 62 | 63 | {% macro panel_tag(title, class='', id='', with_body=True) -%} 64 |
    66 |
    {{ title }}
    67 | {% if with_body %}
    {% endif %} 68 | {{ caller() }} 69 | {% if with_body %}
    {% endif %} 70 |
    71 | {%- endmacro %} 72 | 73 | {%- macro render_pagination(pagination, endpoint) %} 74 | 93 | {%- endmacro %} 94 | -------------------------------------------------------------------------------- /daimaduan/views/users.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from flask import Blueprint, jsonify 3 | from flask import redirect 4 | from flask import render_template 5 | from flask import request 6 | from flask import session 7 | from flask import url_for 8 | from flask_login import current_user 9 | from flask_login import login_required 10 | from flask_login import login_user 11 | 12 | from daimaduan.forms.userinfo import UserInfoForm 13 | from daimaduan.models import LoginManagerUser 14 | from daimaduan.models.base import User, Paste 15 | from daimaduan.models.bookmark import Bookmark 16 | from daimaduan.models.message import WATCH 17 | from daimaduan.models.message import Message 18 | from daimaduan.models.tag import Tag 19 | from daimaduan.utils.pagination import get_page 20 | 21 | user_app = Blueprint("user_app", __name__, template_folder="templates") 22 | 23 | 24 | @user_app.route('/manage', methods=['POST']) 25 | def manage(): 26 | form = UserInfoForm(request.form) 27 | if form.validate(): 28 | if current_user.is_authenticated: 29 | current_user.user.username = form.username.data 30 | return redirect('/') 31 | else: 32 | user = User(email=form.email.data, username=form.username.data, 33 | is_email_confirmed=True) 34 | user.save() 35 | bookmark = Bookmark(user=user, 36 | title=u"%s 的收藏夹" % user.username, 37 | is_default=True) 38 | bookmark.save() 39 | user_mixin = LoginManagerUser(user) 40 | login_user(user_mixin) 41 | if 'email' in session: 42 | del(session['email']) 43 | return redirect('/') 44 | return render_template('users/manage.html', 45 | form=form) 46 | 47 | 48 | @user_app.route('/', methods=['GET']) 49 | def view(username): 50 | page = get_page() 51 | user = User.objects.get_or_404(username=username) 52 | 53 | pastes = user.pastes.order_by('-updated_at') 54 | if not (current_user.is_authenticated and current_user.user == user): 55 | pastes = pastes(is_private=False) 56 | 57 | pagination = pastes.paginate(page, per_page=20) 58 | 59 | return render_template('users/user.html', 60 | user=user, 61 | pagination=pagination, 62 | tags=Tag.objects().order_by('-popularity')[:10]) 63 | 64 | 65 | @user_app.route('//likes', methods=['GET']) 66 | def view_likes(username): 67 | user = User.objects.get_or_404(username=username) 68 | 69 | page = get_page() 70 | likes = Paste.objects(id__in=[str(i.id) for i in user.likes]).order_by('-updated_at') 71 | pagination = likes.paginate(page, per_page=20) 72 | 73 | return render_template('users/likes.html', 74 | user=user, 75 | pagination=pagination) 76 | 77 | 78 | @user_app.route('//watch', methods=['POST']) 79 | @login_required 80 | def watch_user(username): 81 | following_user = User.objects(username=username).first_or_404() 82 | 83 | if not current_user.user.is_following(following_user): 84 | current_user.user.followings.append(following_user) 85 | current_user.user.save() 86 | 87 | content = WATCH.format(user_username=current_user.user.username, 88 | user_url=url_for('user_app.view', username=current_user.user.username)) 89 | message = Message(user=following_user, 90 | who=current_user.user, 91 | content=content) 92 | message.save() 93 | 94 | return jsonify(watchedStatus=current_user.user.is_following(following_user)) 95 | 96 | 97 | @user_app.route('//unwatch', methods=['POST']) 98 | @login_required 99 | def unwatch_user(username): 100 | following_user = User.objects(username=username).first_or_404() 101 | 102 | if current_user.user.is_following(following_user): 103 | current_user.user.followings.remove(following_user) 104 | current_user.user.save() 105 | 106 | return jsonify(watchedStatus=current_user.user.is_following(following_user)) 107 | -------------------------------------------------------------------------------- /daimaduan/models/base.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import hashlib 3 | import time 4 | 5 | from daimaduan.utils.pagination import get_page 6 | from pygments import highlight 7 | from pygments.formatters import HtmlFormatter 8 | from pygments.lexers import get_lexer_by_name 9 | 10 | from daimaduan.bootstrap import db 11 | from daimaduan.models import BaseDocument 12 | from daimaduan.models.user_oauth import UserOauth 13 | 14 | 15 | class User(BaseDocument): 16 | username = db.StringField(required=True) 17 | email = db.StringField(required=True) 18 | password = db.StringField() 19 | salt = db.StringField() 20 | is_email_confirmed = db.BooleanField(default=False) 21 | email_confirmed_on = db.DateTimeField(default=None) 22 | 23 | oauths = db.ListField(db.ReferenceField(UserOauth)) 24 | 25 | likes = db.ListField(db.ReferenceField('Paste')) 26 | followings = db.ListField(db.ReferenceField('User')) 27 | 28 | @property 29 | def pastes(self): 30 | return Paste.objects(user=self) 31 | 32 | @property 33 | def pastes_count(self): 34 | return len(self.pastes) 35 | 36 | @property 37 | def private_pastes_count(self): 38 | return len(self.pastes(is_private=True)) 39 | 40 | @property 41 | def public_pastes_count(self): 42 | return len(self.pastes) - len(self.pastes(is_private=True)) 43 | 44 | def save(self, *args, **kwargs): 45 | if not self.salt: 46 | self.salt = hashlib.sha1(str(time.time())).hexdigest() 47 | self.password = self.generate_password(self.password) 48 | super(User, self).save(*args, **kwargs) 49 | 50 | def owns_record(self, record): 51 | return record.user == self 52 | 53 | def generate_password(self, string): 54 | return hashlib.sha1('%s%s' % (string, self.salt)).hexdigest() 55 | 56 | def check_login(self, password): 57 | return self.generate_password(password) == self.password 58 | 59 | @classmethod 60 | def find_by_oauth(cls, provider, openid): 61 | """Find user that has oauth info with given provider and openid""" 62 | oauth = UserOauth.objects(provider=provider, openid=openid).first() 63 | 64 | if oauth and oauth.user: 65 | return oauth.user 66 | 67 | def is_following(self, user): 68 | return user in self.followings 69 | 70 | 71 | class Code(db.EmbeddedDocument): 72 | title = db.StringField() 73 | content = db.StringField(required=True) 74 | syntax = db.ReferenceField('Syntax') 75 | 76 | def content_head(self, n=10): 77 | lines = self.content.splitlines()[:n] 78 | return '\n'.join(lines) 79 | 80 | @property 81 | def highlight_content(self): 82 | lexer = get_lexer_by_name(self.syntax.syntax, stripall=True) 83 | formatter = HtmlFormatter(linenos=True, cssclass='highlight') 84 | return highlight(self.content, lexer, formatter) 85 | 86 | 87 | class Paste(BaseDocument): 88 | user = db.ReferenceField(User) 89 | 90 | title = db.StringField() 91 | hash_id = db.StringField(unique=True) 92 | is_private = db.BooleanField(default=False) 93 | codes = db.ListField(db.EmbeddedDocumentField(Code)) 94 | tags = db.ListField(db.ReferenceField('Tag')) 95 | 96 | views = db.IntField(default=0) 97 | 98 | @property 99 | def comments(self): 100 | page = get_page() 101 | return Comment.objects(paste=self).order_by('-created_at').paginate(page=page, per_page=20) 102 | 103 | @property 104 | def comments_count(self): 105 | return Comment.objects(paste=self).count() 106 | 107 | def save(self, *args, **kwargs): 108 | self.create_hash_id(self.user.salt, 'paste') 109 | if not self.title: 110 | self.title = u'代码集合: %s' % self.hash_id 111 | super(Paste, self).save(*args, **kwargs) 112 | 113 | def increase_views(self): 114 | self.views = self.views + 1 115 | self.save() 116 | 117 | def is_user_owned(self, user): 118 | return self.user == user 119 | 120 | @property 121 | def likes_count(self): 122 | return User.objects(likes=self).count() 123 | 124 | 125 | class Comment(BaseDocument): 126 | hash_id = db.StringField(unique=True) 127 | user = db.ReferenceField(User) 128 | paste = db.ReferenceField(Paste) 129 | content = db.StringField() 130 | 131 | def save(self, *args, **kwargs): 132 | self.create_hash_id(self.user.salt, 'comment') 133 | super(Comment, self).save(*args, **kwargs) 134 | 135 | def is_user_owned(self, user): 136 | return self.user == user 137 | -------------------------------------------------------------------------------- /daimaduan/tasks/deployments.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import urllib2 3 | from datetime import datetime 4 | 5 | from fabric.context_managers import cd 6 | from fabric.decorators import task 7 | from fabric.operations import local 8 | from fabric.operations import put 9 | from fabric.operations import run 10 | from fabric.operations import sudo 11 | from fabric.state import env 12 | 13 | 14 | env.colorize_errors = True 15 | # the servers where the commands are executed 16 | env.hosts = ['daimaduan.com'] 17 | # the user to use for the remote commands 18 | env.user = 'daimaduan' 19 | # application data 20 | APP_NAME = 'daimaduan' 21 | TEST_WEBSITE = { 22 | 'preview': 'https://preview.daimaduan.com', 23 | 'production': 'https://daimaduan.com' 24 | } 25 | NEWRELIC_APPLICATION_ID = { 26 | 'preview': '13327736', 27 | 'production': '13331097' 28 | } 29 | 30 | 31 | @task 32 | def pack(): 33 | """Create a new source distribution as tarball""" 34 | local('bower install') 35 | local('python setup.py sdist --formats=gztar', capture=False) 36 | 37 | 38 | @task 39 | def bootstrap(app_env): 40 | """Create bootstrap environment for daimaduan""" 41 | if not app_env: 42 | print "fab pack deploy:" 43 | sys.exit(1) 44 | 45 | app_path = '/var/www/%s/%s' % (APP_NAME, app_env) 46 | log_path = '/var/log/%s/%s' % (APP_NAME, app_env) 47 | sudo('mkdir -p %s' % app_path) 48 | sudo('chown %s:%s %s' % (env.user, env.user, app_path)) 49 | sudo('mkdir -p %s' % log_path) 50 | sudo('chown %s:%s %s' % (env.user, env.user, log_path)) 51 | with cd(app_path): 52 | run('virtualenv --distribute venv') 53 | 54 | 55 | @task 56 | def deploy(app_env): 57 | """Deploy it to server""" 58 | if not app_env: 59 | print "fab pack deploy:" 60 | sys.exit(1) 61 | 62 | with open('.newrelic_key') as f: 63 | newrelic_key = f.read().strip() 64 | if not newrelic_key: 65 | print "cannot find newrelic_key in .newrelic_key file" 66 | sys.exit(1) 67 | 68 | app_path = '/var/www/%s/%s' % (APP_NAME, app_env) 69 | out = run('ls -t %s | grep -v current | grep daimaduan.com' % app_path) 70 | versions = [i.strip() for i in out.split("\n")] 71 | # figure out the release name and version 72 | dist = local('python setup.py --fullname', capture=True).strip() 73 | version = local('python setup.py --version', capture=True).strip() 74 | # upload the source tarball to the temporary folder on the server 75 | put('dist/%s.tar.gz' % dist, '%s/%s.tar.gz' % (app_path, dist)) 76 | with cd(app_path): 77 | run('tar xzf %s.tar.gz' % dist) 78 | with cd('%s/%s' % (app_path, dist)): 79 | run('%s/venv/bin/python setup.py develop > /dev/null 2>&1' % app_path) 80 | run('rm -f %s/current' % app_path) 81 | run('ln -s %s/%s/daimaduan %s/current' % (app_path, dist, app_path)) 82 | run('cp %s/shared/custom_settings.py %s/current' % (app_path, app_path)) 83 | run('sed "s/VERSION.*/VERSION = \'%s\'/" -i %s/current/custom_settings.py' % (version, app_path)) 84 | run('sed "s/DEPLOYED_AT.*/DEPLOYED_AT = \'%s\'/" -i %s/current/custom_settings.py' % ( 85 | datetime.now().strftime('%Y-%m-%d %H:%M:%S'), app_path)) 86 | run('cp %s/shared/deploy.py %s/current' % (app_path, app_path)) 87 | 88 | # touching uwsgi ini file will reload this app 89 | sudo('touch /etc/uwsgi.d/daimaduan_%s.ini' % app_env) 90 | 91 | run('rm -f %s/%s.tar.gz' % (app_path, dist)) 92 | 93 | # after deploying, we need to test if deployment succeed 94 | is_deploy_succeed = True 95 | try: 96 | resp = urllib2.urlopen(TEST_WEBSITE[app_env]) 97 | except Exception: 98 | is_deploy_succeed = False 99 | else: 100 | if resp.code != 200: 101 | is_deploy_succeed = False 102 | if versions: 103 | print "Deploy failed, switch back to previous version" 104 | run('rm -f %s/current' % app_path) 105 | run('ln -s %s/%s/daimaduan %s/current' % (app_path, versions[0], app_path)) 106 | sudo('touch /etc/uwsgi.d/daimaduan_%s.ini' % app_env) 107 | sys.exit(1) 108 | 109 | # clean old versions 110 | if is_deploy_succeed: 111 | versions.insert(0, dist) 112 | else: 113 | versions.append(dist) 114 | if len(versions) > 4: 115 | versions = ["%s/%s" % (app_path, i) for i in versions] 116 | versions_to_delete = " ".join(versions[3:]) 117 | run('rm -rf %s' % versions_to_delete) 118 | 119 | # send deployments notification to newrelic 120 | version = local('python setup.py --version', capture=True).strip() 121 | local('curl -H "x-api-key:%s" -d "deployment[application_id]=%s" ' 122 | '-d "deployment[revision]=%s" -d "deployment[user]=David Xie" ' 123 | 'https://api.newrelic.com/deployments.xml' % (newrelic_key, NEWRELIC_APPLICATION_ID[app_env], version)) 124 | -------------------------------------------------------------------------------- /daimaduan/views/bookmarks.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from flask import abort, jsonify 3 | from flask import Blueprint 4 | from flask import render_template 5 | from flask import request 6 | from flask import redirect 7 | from flask import url_for 8 | from flask.ext.login import current_user, login_required 9 | 10 | from daimaduan.forms.bookmark import BookmarkForm 11 | from daimaduan.models.base import Paste 12 | from daimaduan.models.bookmark import Bookmark 13 | from daimaduan.models.message import BOOKMARK 14 | from daimaduan.models.message import Message 15 | from daimaduan.utils.pagination import get_page 16 | 17 | 18 | bookmark_app = Blueprint("bookmark_app", __name__, template_folder="templates") 19 | 20 | 21 | @bookmark_app.route('/', methods=['GET']) 22 | def index(): 23 | page = get_page() 24 | 25 | pagination = Bookmark.objects(is_private=False).order_by('-updated_at').paginate(page, per_page=20) 26 | 27 | return render_template('bookmarks/index.html', 28 | pagination=pagination) 29 | 30 | 31 | @bookmark_app.route('/mine', methods=['GET']) 32 | @login_required 33 | def my_bookmark(): 34 | page = get_page() 35 | 36 | pagination = Bookmark.objects(user=current_user.user).order_by('-updated_at').paginate(page, per_page=20) 37 | 38 | return render_template('bookmarks/index.html', 39 | pagination=pagination) 40 | 41 | 42 | @bookmark_app.route('/create', methods=['GET', 'POST']) 43 | @login_required 44 | def create(): 45 | form = BookmarkForm() 46 | if request.method == 'GET': 47 | return render_template('bookmarks/create.html', 48 | form=form) 49 | else: 50 | if form.validate_on_submit(): 51 | bookmark = Bookmark(title=form.title.data, 52 | is_private=form.is_private.data, 53 | description=form.description.data, 54 | user=current_user.user) 55 | bookmark.save() 56 | 57 | return redirect(url_for('bookmark_app.view', hash_id=bookmark.hash_id)) 58 | return render_template('bookmarks/create.html', 59 | form=form) 60 | 61 | 62 | @bookmark_app.route('/add_paste', methods=['POST']) 63 | @login_required 64 | def add_paste(): 65 | if 'paste_hash_id' not in request.form or 'bookmark_id' not in request.form: 66 | abort(404) 67 | 68 | bookmark = Bookmark.objects(hash_id=request.form['bookmark_id']).get_or_404() 69 | paste = Paste.objects(hash_id=request.form['paste_hash_id']).get_or_404() 70 | if paste in bookmark.pastes: 71 | return render_template('error.html', 72 | title=u'该代码集合已经在收藏夹中', 73 | message=u'该代码集合已经在收藏夹中') 74 | if len(bookmark.pastes) >= 10: 75 | return render_template('error.html', 76 | title=u'一个收藏夹最多只能有10个代码集合', 77 | message=u'一个收藏夹最多只能有10个代码集合') 78 | if paste not in bookmark.pastes: 79 | bookmark.pastes.append(paste) 80 | bookmark.save() 81 | 82 | if bookmark.user != paste.user and not bookmark.is_private: 83 | content = BOOKMARK.format(user_username=current_user.user.username, 84 | user_url=url_for('user_app.view', username=current_user.user.username), 85 | paste_title=paste.title, 86 | paste_url=url_for('paste_app.view_paste', hash_id=paste.hash_id), 87 | bookmark_title=bookmark.title, 88 | bookmark_url=url_for('bookmark_app.view', hash_id=bookmark.hash_id)) 89 | 90 | message = Message(user=paste.user, 91 | who=bookmark.user, 92 | content=content) 93 | message.save() 94 | 95 | return redirect(url_for('bookmark_app.view', hash_id=bookmark.hash_id)) 96 | 97 | 98 | @bookmark_app.route('/remove_paste', methods=['POST']) 99 | @login_required 100 | def remove_paste(): 101 | if 'paste_hash_id' not in request.form or 'bookmark_id' not in request.form: 102 | abort(404) 103 | 104 | bookmark = Bookmark.objects(hash_id=request.form['bookmark_id']).get_or_404() 105 | paste = Paste.objects(hash_id=request.form['paste_hash_id']).get_or_404() 106 | if paste not in bookmark.pastes: 107 | return render_template('error.html', 108 | title=u'该代码集合已经收藏夹中移除', 109 | message=u'该代码集合已经在收藏夹中移除') 110 | 111 | bookmark.pastes.remove(paste) 112 | bookmark.save() 113 | 114 | return redirect(url_for('bookmark_app.view', hash_id=bookmark.hash_id)) 115 | 116 | 117 | @bookmark_app.route('//delete', methods=['POST']) 118 | @login_required 119 | def delete(hash_id): 120 | bookmark = Bookmark.objects(hash_id=hash_id).get_or_404() 121 | if bookmark.user != current_user.user: 122 | abort(401) 123 | 124 | if bookmark.is_default: 125 | abort(500, u'不能删除默认收藏夹') 126 | 127 | bookmark.delete() 128 | 129 | return redirect(url_for('bookmark_app.index')) 130 | 131 | 132 | @bookmark_app.route('/', methods=['GET']) 133 | def view(hash_id): 134 | bookmark = Bookmark.objects(hash_id=hash_id).get_or_404() 135 | 136 | bookmark_hash_id = None 137 | if current_user.is_authenticated and current_user.user == bookmark.user: 138 | bookmark_hash_id = bookmark.hash_id 139 | 140 | return render_template('bookmarks/view.html', 141 | bookmark=bookmark, 142 | bookmark_hash_id=bookmark_hash_id) 143 | -------------------------------------------------------------------------------- /daimaduan/static/css/embed.scss: -------------------------------------------------------------------------------- 1 | .daimaduan-code { 2 | border: 1px solid #dddddd; 3 | // // Instead of the line below you could use @include border-radius($radius, $vertical-radius) 4 | border-radius: 3px; 5 | margin-bottom: 1em; 6 | } 7 | .daimaduan-code-meta { 8 | background: #f7f7f7; 9 | padding: 8px; 10 | border-top: 1px solid #eeeeee; 11 | font-size: 12px; 12 | a { 13 | &:link { 14 | color: #4848F0; 15 | text-decoration: none; 16 | } 17 | &:visited { 18 | color: #4848F0; 19 | text-decoration: none; 20 | } 21 | &:hover { 22 | color: #4848F0; 23 | text-decoration: none; 24 | } 25 | &:active { 26 | color: #4848F0; 27 | text-decoration: none; 28 | } 29 | } 30 | } 31 | .daimaduan-code-block { 32 | overflow-x: auto; 33 | overflow-y: hidden; 34 | .linenos { 35 | background: white; 36 | padding: 5px 8px; 37 | border-right: 1px solid #eeeeee; 38 | width: 1%; 39 | } 40 | .code { 41 | padding: 5px 8px; 42 | } 43 | pre { 44 | margin: 0; 45 | } 46 | } 47 | .daimaduan-code-name { 48 | white-space: nowrap; 49 | overflow: hidden; 50 | text-overflow: ellipsis; 51 | margin-right: 120px; 52 | } 53 | .daimaduan-copyright { 54 | float: right; 55 | } 56 | .daimaduan-code .hll { background-color: #ffffcc } 57 | .daimaduan-code .c { color: #808080 } /* Comment */ 58 | .daimaduan-code .err { color: #F00000; background-color: #F0A0A0 } /* Error */ 59 | .daimaduan-code .k { color: #008000; font-weight: bold } /* Keyword */ 60 | .daimaduan-code .o { color: #303030 } /* Operator */ 61 | .daimaduan-code .cm { color: #808080 } /* Comment.Multiline */ 62 | .daimaduan-code .cp { color: #507090 } /* Comment.Preproc */ 63 | .daimaduan-code .c1 { color: #808080 } /* Comment.Single */ 64 | .daimaduan-code .cs { color: #cc0000; font-weight: bold } /* Comment.Special */ 65 | .daimaduan-code .gd { color: #A00000 } /* Generic.Deleted */ 66 | .daimaduan-code .ge { font-style: italic } /* Generic.Emph */ 67 | .daimaduan-code .gr { color: #FF0000 } /* Generic.Error */ 68 | .daimaduan-code .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 69 | .daimaduan-code .gi { color: #00A000 } /* Generic.Inserted */ 70 | .daimaduan-code .go { color: #808080 } /* Generic.Output */ 71 | .daimaduan-code .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 72 | .daimaduan-code .gs { font-weight: bold } /* Generic.Strong */ 73 | .daimaduan-code .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 74 | .daimaduan-code .gt { color: #0040D0 } /* Generic.Traceback */ 75 | .daimaduan-code .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ 76 | .daimaduan-code .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ 77 | .daimaduan-code .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ 78 | .daimaduan-code .kp { color: #003080; font-weight: bold } /* Keyword.Pseudo */ 79 | .daimaduan-code .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ 80 | .daimaduan-code .kt { color: #303090; font-weight: bold } /* Keyword.Type */ 81 | .daimaduan-code .m { color: #6000E0; font-weight: bold } /* Literal.Number */ 82 | .daimaduan-code .s { background-color: #fff0f0 } /* Literal.String */ 83 | .daimaduan-code .na { color: #0000C0 } /* Name.Attribute */ 84 | .daimaduan-code .nb { color: #007020 } /* Name.Builtin */ 85 | .daimaduan-code .nc { color: #B00060; font-weight: bold } /* Name.Class */ 86 | .daimaduan-code .no { color: #003060; font-weight: bold } /* Name.Constant */ 87 | .daimaduan-code .nd { color: #505050; font-weight: bold } /* Name.Decorator */ 88 | .daimaduan-code .ni { color: #800000; font-weight: bold } /* Name.Entity */ 89 | .daimaduan-code .ne { color: #F00000; font-weight: bold } /* Name.Exception */ 90 | .daimaduan-code .nf { color: #0060B0; font-weight: bold } /* Name.Function */ 91 | .daimaduan-code .nl { color: #907000; font-weight: bold } /* Name.Label */ 92 | .daimaduan-code .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 93 | .daimaduan-code .nt { color: #007000 } /* Name.Tag */ 94 | .daimaduan-code .nv { color: #906030 } /* Name.Variable */ 95 | .daimaduan-code .ow { color: #000000; font-weight: bold } /* Operator.Word */ 96 | .daimaduan-code .w { color: #bbbbbb } /* Text.Whitespace */ 97 | .daimaduan-code .mf { color: #6000E0; font-weight: bold } /* Literal.Number.Float */ 98 | .daimaduan-code .mh { color: #005080; font-weight: bold } /* Literal.Number.Hex */ 99 | .daimaduan-code .mi { color: #0000D0; font-weight: bold } /* Literal.Number.Integer */ 100 | .daimaduan-code .mo { color: #4000E0; font-weight: bold } /* Literal.Number.Oct */ 101 | .daimaduan-code .sb { background-color: #fff0f0 } /* Literal.String.Backtick */ 102 | .daimaduan-code .sc { color: #0040D0 } /* Literal.String.Char */ 103 | .daimaduan-code .sd { color: #D04020 } /* Literal.String.Doc */ 104 | .daimaduan-code .s2 { background-color: #fff0f0 } /* Literal.String.Double */ 105 | .daimaduan-code .se { color: #606060; font-weight: bold; background-color: #fff0f0 } /* Literal.String.Escape */ 106 | .daimaduan-code .sh { background-color: #fff0f0 } /* Literal.String.Heredoc */ 107 | .daimaduan-code .si { background-color: #e0e0e0 } /* Literal.String.Interpol */ 108 | .daimaduan-code .sx { color: #D02000; background-color: #fff0f0 } /* Literal.String.Other */ 109 | .daimaduan-code .sr { color: #000000; background-color: #fff0ff } /* Literal.String.Regex */ 110 | .daimaduan-code .s1 { background-color: #fff0f0 } /* Literal.String.Single */ 111 | .daimaduan-code .ss { color: #A06000 } /* Literal.String.Symbol */ 112 | .daimaduan-code .bp { color: #007020 } /* Name.Builtin.Pseudo */ 113 | .daimaduan-code .vc { color: #306090 } /* Name.Variable.Class */ 114 | .daimaduan-code .vg { color: #d07000; font-weight: bold } /* Name.Variable.Global */ 115 | .daimaduan-code .vi { color: #3030B0 } /* Name.Variable.Instance */ 116 | .daimaduan-code .il { color: #0000D0; font-weight: bold } /* Literal.Number.Integer.Long */ 117 | -------------------------------------------------------------------------------- /daimaduan/migrations/20160204162201.py: -------------------------------------------------------------------------------- 1 | import re 2 | from mongodb_migrations.base import BaseMigration 3 | 4 | 5 | ALL_SUPPORTTED_SYNTAX = ( 6 | ('bash', 'bash', 'Bash'), 7 | ('java', 'java', 'Java'), 8 | ('cpp', 'cpp', 'C++'), 9 | ('xml', 'xml', 'XML'), 10 | ('spec', 'spec', 'RPMSpec'), 11 | ('sql', 'sql', 'SQL'), 12 | ('json', 'json', 'JSON'), 13 | ('sass', 'sass', 'Sass'), 14 | ('coffee-script', 'coffee-script', 'CoffeeScript'), 15 | ('vim', 'vim', 'VimL'), 16 | ('scala', 'scala', 'Scala'), 17 | ('css', 'css', 'CSS'), 18 | ('less', 'css', 'LESS'), 19 | ('c', 'c', 'C'), 20 | ('yaml', 'yaml', 'YAML'), 21 | ('text', 'text', 'Text only'), 22 | ('dart', 'dart', 'Dart'), 23 | ('diff', 'diff', 'Diff'), 24 | ('swift', 'swift', 'Swift'), 25 | ('js', 'js', 'JavaScript'), 26 | ('puppet', 'puppet', 'Puppet'), 27 | ('groovy', 'groovy', 'Groovy'), 28 | ('gradle', 'groovy', 'Gradle'), 29 | ('python', 'python', 'Python'), 30 | ('scss', 'scss', 'SCSS'), 31 | ('lua', 'lua', 'Lua'), 32 | ('go', 'go', 'Go'), 33 | ('perl', 'perl', 'Perl'), 34 | ('ini', 'ini', 'INI'), 35 | ('nginx', 'nginx', 'Nginx configuration file'), 36 | ('php', 'php', 'PHP'), 37 | ('erb', 'erb', 'ERB'), 38 | ('html', 'html', 'HTML'), 39 | ('rst', 'rst', 'reStructuredText'), 40 | ('make', 'make', 'Makefile'), 41 | ('rb', 'rb', 'Ruby'), 42 | ('docker', 'docker', 'Docker'), 43 | ('kotlin', 'kotlin', 'Kotlin'), 44 | ('erlang', 'erlang', 'Erlang'), 45 | ('clojure', 'clojure', 'Clojure'), 46 | ('csharp', 'csharp', 'C#'), 47 | ('objective-c', 'objective-c', 'Objective-C') 48 | ) 49 | 50 | OLD_SYNTAX_NEW_SYNTAX = { 51 | 'less': 'LESS', 52 | 'access-log': 'Text only', 53 | 'vim-script': 'VimL', 54 | 'gradle': 'Gradle', 55 | 'nginx': 'Nginx configuration file', 56 | 'text': 'Text only' 57 | } 58 | 59 | 60 | class Migration(BaseMigration): 61 | def upgrade(self): 62 | """ 63 | 1. get all codes 64 | 2. set codes into pastes 65 | 3. add key to tag 66 | :return: 67 | """ 68 | new_syntax_keys = [syntax[0] for syntax in ALL_SUPPORTTED_SYNTAX] 69 | 70 | self.db.syntax.drop() 71 | for syntax in ALL_SUPPORTTED_SYNTAX: 72 | self.db.syntax.save({'key': syntax[0], 'syntax': syntax[1], 'name': syntax[2]}) 73 | 74 | codes = {} 75 | for code in self.db.code.find(): 76 | codes[str(code['_id'])] = code 77 | self.db.code.drop() 78 | 79 | self.db.tag.drop() 80 | 81 | user_likes = {} 82 | for like in self.db.like.find(): 83 | paste = self.db.paste.find_one({'_id': like['Paste']['_ref'].id}) 84 | user_id = str(like['user']) 85 | if user_id not in user_likes: 86 | user_likes[user_id] = [] 87 | user_likes[user_id].append(paste['hash_id']) 88 | 89 | new_pastes = self.get_new_pastes(codes, new_syntax_keys) 90 | 91 | self.db.paste.drop() 92 | for paste in new_pastes: 93 | self.db.paste.save(paste) 94 | 95 | self.update_user_likes(user_likes) 96 | 97 | self.db.like.drop() 98 | self.db.rate.drop() 99 | 100 | def update_user_likes(self, user_likes): 101 | for user in self.db.user.find(): 102 | user['likes'] = [] 103 | user_id = str(user['_id']) 104 | if user_id in user_likes.keys(): 105 | for paste_id in user_likes[user_id]: 106 | paste = self.db.paste.find_one({'hash_id': paste_id}) 107 | user['likes'].append(paste['_id']) 108 | for i in ['favourites', 'paste_likes_count', 'pastes_count', 'watched_users', 'oauths']: 109 | if i in user: 110 | del (user[i]) 111 | self.db.user.save(user) 112 | 113 | def get_new_pastes(self, codes, new_syntax_keys): 114 | new_pastes = [] 115 | for paste in self.db.paste.find(): 116 | new_paste = {} 117 | new_paste['created_at'] = paste['created_at'] 118 | new_paste['updated_at'] = paste['updated_at'] 119 | new_paste['title'] = paste['title'] 120 | new_paste['user'] = paste['user'] 121 | new_paste['hash_id'] = paste['hash_id'] 122 | new_paste['is_private'] = paste['is_private'] 123 | new_paste['views'] = paste['views'] 124 | new_paste['codes'] = [] 125 | code_ids = [str(code) for code in paste['codes']] 126 | tags = [] 127 | for code_id in code_ids: 128 | code = codes[code_id] 129 | old_syntax = code['tag'] 130 | if old_syntax in new_syntax_keys: 131 | syntax = self.db.syntax.find_one({'key': re.compile(old_syntax, re.IGNORECASE)}) 132 | else: 133 | if old_syntax in OLD_SYNTAX_NEW_SYNTAX.keys(): 134 | syntax = self.db.syntax.find_one( 135 | {'name': re.compile(OLD_SYNTAX_NEW_SYNTAX[old_syntax], re.IGNORECASE)}) 136 | else: 137 | re_string = old_syntax.replace('+', '\+') 138 | syntax = self.db.syntax.find_one({'name': re.compile(re_string, re.IGNORECASE)}) 139 | tag = self.db.tag.find_one({'key': syntax['key']}) 140 | if tag: 141 | tags.append(tag['_id']) 142 | if not tag: 143 | tag = self.db.tag.save({'key': syntax['key'], 'name': syntax['name']}) 144 | tags.append(tag) 145 | new_paste['codes'].append({'title': code['title'], 146 | 'content': code['content'], 147 | 'syntax': syntax['_id']}) 148 | new_paste['tags'] = tags 149 | new_pastes.append(new_paste) 150 | return new_pastes 151 | 152 | def downgrade(self): 153 | print "I'm in downgrade - migration2" 154 | -------------------------------------------------------------------------------- /daimaduan/views/pastes.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from flask import abort, jsonify, url_for 3 | from flask import current_app, request 4 | from flask import make_response 5 | from flask import redirect 6 | from flask import render_template, Blueprint 7 | from flask_login import current_user 8 | from flask_login import login_required 9 | 10 | from daimaduan.forms.paste import PasteForm 11 | from daimaduan.forms.paste import CommentForm 12 | from daimaduan.models.base import Paste 13 | from daimaduan.models.base import Code 14 | from daimaduan.models.base import User 15 | from daimaduan.models.base import Comment 16 | from daimaduan.models.message import NEW_PASTE 17 | from daimaduan.models.message import NEW_COMMENT 18 | from daimaduan.models.message import LIKE 19 | from daimaduan.models.message import Message 20 | from daimaduan.models.bookmark import Bookmark 21 | from daimaduan.models.syntax import Syntax 22 | from daimaduan.models.tag import Tag 23 | from daimaduan.utils.decorators import user_active_required 24 | from daimaduan.utils import logger 25 | 26 | 27 | paste_app = Blueprint("paste_app", __name__, template_folder="templates") 28 | 29 | 30 | def save_paste_and_codes(form, paste=None): 31 | if not paste: 32 | paste = Paste(user=current_user.user) 33 | 34 | paste.title = form.title.data 35 | paste.is_private = form.is_private.data 36 | paste.codes = [] 37 | tags = {} 38 | 39 | for tag in form.tags.data.split(): 40 | tags[tag.lower()] = tag.lower() 41 | for i, c in enumerate(form.codes): 42 | syntax = Syntax.objects(key=c.syntax.data).get_or_404() 43 | if not c.title.data: 44 | c.title.data = '代码片段%s' % (i + 1) 45 | code = Code(title=c.title.data, 46 | content=c.content.data, 47 | syntax=syntax) 48 | tags[syntax.key] = syntax.name 49 | paste.codes.append(code) 50 | for key in tags: 51 | syntax = Syntax.objects(name__iexact=tags[key]).first() 52 | tag = Tag.objects(key__iexact=key).first() 53 | if not tag and syntax: 54 | tag = Tag(key=syntax.key, name=syntax.name) 55 | tag.save() 56 | elif not tag and not syntax: 57 | tag = Tag(key=key, name=tags[key]) 58 | else: 59 | tag.popularity += 1 60 | tag.save() 61 | if tag not in paste.tags: 62 | paste.tags.append(tag) 63 | paste.save() 64 | return paste 65 | 66 | 67 | @paste_app.route('/create', methods=['GET', 'POST']) 68 | @login_required 69 | @user_active_required 70 | def create_paste(): 71 | if request.method == 'GET': 72 | # missing csrf 73 | form = PasteForm(data={'codes': [{'title': '', 'content': ''}]}) 74 | return render_template('pastes/create.html', form=form) 75 | else: 76 | form = PasteForm(request.form) 77 | if form.validate(): 78 | user = current_user.user 79 | paste = save_paste_and_codes(form) 80 | if not paste.is_private: 81 | followers = User.objects(followings=user) 82 | content = NEW_PASTE.format(user_username=user.username, 83 | user_url=url_for('user_app.view', username=user.username), 84 | paste_title=paste.title, 85 | paste_url=url_for('paste_app.view_paste', hash_id=paste.hash_id)) 86 | for follower in followers: 87 | message = Message(user=follower, 88 | who=user, 89 | content=content) 90 | message.save() 91 | return jsonify(success=True, hash_id=paste.hash_id) 92 | else: 93 | errors = form.errors 94 | errors['codes'] = [code.errors for code in form.codes] 95 | logger.info('Failed saving paste for reason: %s', errors) 96 | return jsonify(success=False, errors=errors) 97 | 98 | 99 | @paste_app.route('//edit', methods=['GET', 'POST']) 100 | @login_required 101 | @user_active_required 102 | def edit_paste(hash_id): 103 | paste = Paste.objects.get_or_404(hash_id=hash_id) 104 | 105 | if not paste.is_user_owned(current_user.user): 106 | abort(404) 107 | 108 | if request.method == 'GET': 109 | tags = [] 110 | syntaxes = [code.syntax.name for code in paste.codes] 111 | for tag in paste.tags: 112 | if tag.name not in syntaxes: 113 | tags.append(tag.name) 114 | data = {'hash_id': paste.hash_id, 115 | 'title': paste.title, 116 | 'is_private': paste.is_private, 117 | 'tags': ' '.join(tags), 118 | 'codes': [{'title': code.title, 'content': code.content, 'syntax': code.syntax.key} for code in paste.codes]} 119 | return render_template('pastes/edit.html', 120 | paste=paste, 121 | data=data) 122 | else: 123 | form = PasteForm(request.form) 124 | if form.validate(): 125 | paste = save_paste_and_codes(form, paste=paste) 126 | return jsonify(success=True, hash_id=paste.hash_id) 127 | else: 128 | errors = form.errors 129 | errors['codes'] = [code.errors for code in form.codes] 130 | logger.info('Failed saving paste for reason: %s', errors) 131 | return jsonify(success=False, errors=errors) 132 | 133 | 134 | @paste_app.route('/', methods=['GET']) 135 | def view_paste(hash_id): 136 | paste = Paste.objects.get_or_404(hash_id=hash_id) 137 | paste.increase_views() 138 | 139 | paste_lists = [] 140 | if current_user.is_authenticated: 141 | paste_lists = Bookmark.objects(user=current_user.user) 142 | 143 | return render_template('pastes/view.html', 144 | paste=paste, 145 | paste_lists=paste_lists) 146 | 147 | 148 | @paste_app.route('//comments', methods=['POST']) 149 | def comments(hash_id): 150 | paste = Paste.objects.get_or_404(hash_id=hash_id) 151 | 152 | form = CommentForm(request.form) 153 | if form.validate(): 154 | comment = Comment(user=current_user.user, 155 | paste=paste, 156 | content=form.content.data) 157 | comment.save() 158 | if comment.user != paste.user: 159 | content = NEW_COMMENT.format(user_username=current_user.user.username, 160 | user_url=url_for('user_app.view', username=current_user.user.username), 161 | paste_title=paste.title, 162 | paste_url=url_for('paste_app.view_paste', hash_id=paste.hash_id)) 163 | 164 | message = Message(user=paste.user, 165 | who=current_user.user, 166 | content=content) 167 | message.save() 168 | 169 | return redirect(url_for('paste_app.view_paste', hash_id=hash_id)) 170 | 171 | 172 | @paste_app.route('//comments/', methods=['GET', 'POST']) 173 | def edit_comment(paste_id, hash_id): 174 | comment = Comment.objects.get_or_404(hash_id=hash_id) 175 | form = CommentForm(request.form) 176 | if request.method == 'POST': 177 | if form.validate(): 178 | comment.content = form.content.data 179 | comment.save() 180 | return redirect(url_for('paste_app.view_paste', hash_id=paste_id)) 181 | 182 | return render_template('pastes/edit_comment.html', 183 | paste_id=paste_id, 184 | comment=comment) 185 | 186 | 187 | @paste_app.route('//comments//delete', methods=['GET']) 188 | def delete_comment(paste_id, hash_id): 189 | comment = Comment.objects.get_or_404(hash_id=hash_id) 190 | comment.delete() 191 | return redirect(url_for('paste_app.view_paste', hash_id=paste_id)) 192 | 193 | 194 | @paste_app.route('//like', methods=['POST']) 195 | @login_required 196 | def like(hash_id): 197 | paste = Paste.objects.get_or_404(hash_id=hash_id) 198 | user = current_user.user 199 | is_user_liked = paste in user.likes 200 | if not is_user_liked: 201 | user.likes.append(paste) 202 | user.save() 203 | if user != paste.user: 204 | content = LIKE.format(user_username=user.username, 205 | user_url=url_for('user_app.view', username=user.username), 206 | paste_title=paste.title, 207 | paste_url=url_for('paste_app.view_paste', hash_id=paste.hash_id)) 208 | message = Message(user=paste.user, 209 | who=user, 210 | content=content) 211 | message.save() 212 | return jsonify(dict(paste_id=hash_id, 213 | user_like=len(user.likes), 214 | paste_likes=len(user.likes), 215 | liked=True)) 216 | 217 | 218 | @paste_app.route('//unlike', methods=['POST']) 219 | @login_required 220 | def unlike(hash_id): 221 | paste = Paste.objects.get_or_404(hash_id=hash_id) 222 | user = current_user.user 223 | is_user_liked = paste in user.likes 224 | if is_user_liked: 225 | user.likes.remove(paste) 226 | user.save() 227 | return jsonify(dict(paste_id=hash_id, 228 | user_like=len(user.likes), 229 | paste_likes=len(user.likes), 230 | liked=True)) 231 | 232 | 233 | @paste_app.route('//delete', methods=['POST']) 234 | @login_required 235 | def delete(hash_id): 236 | paste = Paste.objects.get_or_404(hash_id=hash_id) 237 | 238 | if current_user.user.owns_record(paste): 239 | paste.delete() 240 | return redirect('/') 241 | else: 242 | abort(403) 243 | 244 | 245 | @paste_app.route('//embed.js', methods=['GET']) 246 | def embed_js(hash_id): 247 | paste = Paste.objects.get_or_404(hash_id=hash_id) 248 | 249 | resp = make_response(render_template('pastes/embed.html', paste=paste, domain=current_app.config['DOMAIN']), 200) 250 | resp.headers['Content-Type'] = 'text/javascript; charset=utf-8' 251 | return resp 252 | -------------------------------------------------------------------------------- /daimaduan/views/sites.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import datetime 3 | import json 4 | import re 5 | 6 | from flask import abort 7 | from flask import Blueprint, flash, current_app, session, jsonify 8 | from flask import redirect 9 | from flask import render_template 10 | from flask import request 11 | from flask import url_for 12 | from flask.ext.login import login_required 13 | from flask_login import current_user 14 | from flask_login import login_user 15 | from flask_login import logout_user 16 | from rauth import OAuth2Service 17 | 18 | from daimaduan.bootstrap import login_manager 19 | from daimaduan.forms.email import EmailForm 20 | from daimaduan.forms.password import PasswordForm 21 | from daimaduan.forms.signin import SigninForm 22 | from daimaduan.forms.signup import SignupForm 23 | from daimaduan.forms.userinfo import UserInfoForm 24 | from daimaduan.models import LoginManagerUser 25 | from daimaduan.models.base import Code 26 | from daimaduan.models.base import Paste 27 | from daimaduan.models.base import User 28 | from daimaduan.models.bookmark import Bookmark 29 | from daimaduan.models.tag import Tag 30 | from daimaduan.models.message import Message 31 | from daimaduan.models.user_oauth import UserOauth 32 | from daimaduan.utils.email_confirmation import send_confirm_email 33 | from daimaduan.utils.email_confirmation import send_reset_password_email 34 | from daimaduan.utils.email_confirmation import validate_token 35 | from daimaduan.utils.oauth import oauth_config 36 | from daimaduan.utils.pagination import get_page 37 | 38 | 39 | def get_oauth_services(): 40 | oauth_services = {} 41 | oauth_services['google'] = OAuth2Service(**oauth_config(current_app.config, 'google')) 42 | oauth_services['github'] = OAuth2Service(**oauth_config(current_app.config, 'github')) 43 | return oauth_services 44 | 45 | 46 | @login_manager.user_loader 47 | def load_user(user_id): 48 | user = User.objects.get_or_404(id=user_id) 49 | return LoginManagerUser(user) 50 | 51 | 52 | site_app = Blueprint("site_app", __name__, template_folder="templates") 53 | 54 | 55 | @site_app.route('/', methods=['GET']) 56 | def index(): 57 | page = get_page() 58 | pagination = Paste.objects(is_private=False).order_by('-updated_at').paginate(page=page, per_page=20) 59 | 60 | return render_template('index.html', 61 | pagination=pagination, 62 | tags=Tag.objects().order_by('-popularity')[:10]) 63 | 64 | 65 | @site_app.route('/tags', methods=['GET']) 66 | def tags(): 67 | return render_template('tags/index.html', 68 | tags=Tag.objects().order_by('-popularity')) 69 | 70 | 71 | @site_app.route('/status.json', methods=['GET']) 72 | def status(): 73 | return jsonify(version=current_app.config['VERSION'], 74 | pastes=Paste.objects().count(), 75 | bookmarks=Bookmark.objects().count(), 76 | tags=Tag.objects().count(), 77 | users=User.objects().count()) 78 | 79 | 80 | @site_app.route('/signin', methods=['GET', 'POST']) 81 | def signin(): 82 | form = SigninForm() 83 | if request.method == 'GET': 84 | return render_template('users/signin.html', 85 | form=SigninForm()) 86 | else: 87 | if form.validate_on_submit(): 88 | user = User.objects.get_or_404(email=form.email.data) 89 | user_mixin = LoginManagerUser(user) 90 | login_user(user_mixin) 91 | 92 | flash('Logged in successfully.') 93 | 94 | return redirect(url_for('site_app.index')) 95 | return render_template('users/signin.html', 96 | form=form) 97 | 98 | 99 | @site_app.route('/signout', methods=['DELETE']) 100 | def signout_delete(): 101 | logout_user() 102 | return jsonify(status=302, location="/") 103 | 104 | 105 | @site_app.route('/signup', methods=['GET', 'POST']) 106 | def signup(): 107 | form = SignupForm() 108 | if request.method == 'GET': 109 | return render_template('users/signup.html', 110 | form=form) 111 | else: 112 | if form.validate_on_submit(): 113 | user = User() 114 | form.populate_obj(user) 115 | user.save() 116 | bookmark = Bookmark(user=user, 117 | title=u"%s 的收藏夹" % user.username, 118 | is_default=True) 119 | bookmark.save() 120 | user_mixin = LoginManagerUser(user) 121 | login_user(user_mixin) 122 | send_confirm_email(current_app.config, user.email) 123 | return redirect(url_for('site_app.index')) 124 | return render_template('users/signup.html', 125 | form=form) 126 | 127 | 128 | @site_app.route('/oauth/', methods=['GET']) 129 | def oauth_signin(provider): 130 | oauth_service = get_oauth_services()[provider] 131 | 132 | url = oauth_service.get_authorize_url(scope=current_app.config['OAUTH'][provider]['scope'], 133 | response_type='code', 134 | redirect_uri=current_app.config['OAUTH'][provider]['callback_url']) 135 | return redirect(url) 136 | 137 | 138 | @site_app.route('/oauth//callback', methods=['GET']) 139 | def oauth_callback(provider): 140 | current_app.logger.info("Oauth callback for %s" % provider) 141 | redirect_uri = current_app.config['OAUTH'][provider]['callback_url'] 142 | oauth_service = get_oauth_services()[provider] 143 | 144 | data = dict(code=request.args.get('code'), 145 | grant_type='authorization_code', 146 | redirect_uri=redirect_uri) 147 | 148 | if provider == 'google': 149 | oauth_session = oauth_service.get_auth_session(data=data, decoder=json.loads) 150 | user_info = oauth_session.get('userinfo').json() 151 | email = session['email'] = user_info['email'] 152 | username = user_info['given_name'] 153 | elif provider == 'github': 154 | oauth_session = oauth_service.get_auth_session(data=data) 155 | user_info = oauth_session.get('user').json() 156 | email = session['email'] = user_info['email'] 157 | username = user_info['login'] 158 | 159 | access_token = oauth_session.access_token 160 | user_info['id'] = str(user_info['id']) 161 | 162 | current_app.logger.info("%s oauth access token is: %s" % (provider, access_token)) 163 | current_app.logger.info("%s oauth user info is %s" % (provider, user_info)) 164 | 165 | user = User.find_by_oauth(provider, user_info['id']) 166 | if user: 167 | # TODO: 直接登录时更新 token. 168 | user_mixin = LoginManagerUser(user) 169 | login_user(user_mixin) 170 | return redirect('/') 171 | else: 172 | user = User.objects(email=email).first() 173 | if user: 174 | user_oauth = UserOauth(provider=provider, openid=user_info['id'], token=access_token) 175 | user_oauth.save() 176 | user_mixin = LoginManagerUser(user) 177 | login_user(user_mixin) 178 | return redirect('/') 179 | else: 180 | return render_template('users/manage.html', 181 | form=UserInfoForm(email=email, username=username)) 182 | 183 | 184 | @site_app.route('/lost_password', methods=['GET', 'POST']) 185 | def lost_password_get(): 186 | if request.method == 'GET': 187 | return render_template('users/lost_password.html', 188 | form=EmailForm()) 189 | else: 190 | form = EmailForm(request.form) 191 | if form.validate(): 192 | user = User.objects(email=form.email.data).first() 193 | send_reset_password_email(current_app.config, user.email) 194 | return redirect('/reset_password_email_sent') 195 | return render_template('users/lost_password.html', 196 | form=form) 197 | 198 | 199 | @site_app.route('/reset_password_email_sent', methods=['GET']) 200 | def reset_password_email_sent(): 201 | return render_template('error.html', 202 | title=u"重置密码的邮件已经发出", 203 | message=u"重置密码的邮件已经发出, 请查收邮件并重置密码") 204 | 205 | 206 | @site_app.route('/reset_password/', methods=['GET', 'POST']) 207 | def reset_password(token): 208 | if request.method == 'GET': 209 | email = validate_token(current_app.config, token) 210 | if email: 211 | user = User.objects(email=email).first() 212 | if user: 213 | return render_template('users/reset_password.html', 214 | form=PasswordForm(), 215 | token=token) 216 | abort(404) 217 | else: 218 | email = validate_token(current_app.config, token) 219 | if email: 220 | user = User.objects(email=email).first() 221 | if user: 222 | form = PasswordForm(request.form) 223 | if form.validate_on_submit(): 224 | user.password = user.generate_password(form.password.data) 225 | user.save() 226 | return redirect('/reset_password_success') 227 | return render_template('users/reset_password.html', 228 | form=PasswordForm(), 229 | token=token) 230 | abort(404) 231 | 232 | 233 | @site_app.route('/reset_password_success', methods=['GET']) 234 | def reset_password_success(): 235 | return render_template('error.html', 236 | title=u"重置密码成功", 237 | message=u"您的密码已经重置, 请重新登录") 238 | 239 | 240 | @site_app.route('/confirm/', methods=['GET']) 241 | def confirm_email(token): 242 | email = validate_token(current_app.config, token) 243 | if email: 244 | user = User.objects(email=email).first_or_404() 245 | if (current_user.is_authenticated and user == current_user.user) or not current_user.is_authenticated: 246 | if user.is_email_confirmed: 247 | return render_template('email/confirm.html', title=u"Email已经激活过了", message=u"对不起,您的email已经激活过了。") 248 | else: 249 | user.is_email_confirmed = True 250 | user.email_confirmed_on = datetime.datetime.now() 251 | user.save() 252 | return render_template('email/confirm.html', title=u'Email已经激活', message=u'您的email已经激活,请点击登录查看最新代码段。') 253 | return render_template('email/confirm.html', 254 | title=u'Email验证链接错误', 255 | message=u'对不起,您的验证链接无效或者已经过期。') 256 | 257 | 258 | @site_app.route('/active_email', methods=['GET']) 259 | def active_email(): 260 | return render_template('email/active.html', 261 | email=current_user.user.email, 262 | title=u'注册成功') 263 | 264 | 265 | @site_app.route('/success_sendmail', methods=['GET']) 266 | def sendmail_success(): 267 | return render_template('email/confirm.html', 268 | title=u"激活邮件发送成功", 269 | message=u"激活邮件发送成功, 请检查并激活您的账户。") 270 | 271 | 272 | @site_app.route('/sendmail', methods=['POST']) 273 | def send_email(): 274 | form = EmailForm() 275 | if form.validate_on_submit(): 276 | user = User.objects(email=form.email.data).first() 277 | send_confirm_email(current_app.config, user.email) 278 | return redirect('/success_sendmail') 279 | return render_template('sendmail.html', 280 | form=form) 281 | 282 | 283 | def get_pastes_from_search(query_string, p=1): 284 | def get_string_by_keyword(keyword, query_string): 285 | string = '' 286 | result = re.search('\s*%s:([a-zA-Z+-_#]+)\s*' % keyword, query_string) 287 | if result: 288 | if len(result.groups()) == 1: 289 | string = result.groups()[0] 290 | return string, query_string.replace('%s:%s' % (keyword, string), '') 291 | 292 | tag, query_string = get_string_by_keyword('tag', query_string) 293 | user, query_string = get_string_by_keyword('user', query_string) 294 | keyword = query_string.strip() 295 | 296 | criteria = {'title__contains': keyword, 'is_private': False} 297 | if tag: 298 | criteria['tags'] = tag 299 | if user: 300 | user_object = User.objects(username=user).first() 301 | if user_object: 302 | criteria['user'] = user_object 303 | 304 | return keyword, Paste.objects(**criteria).order_by('-updated_at').paginate(p, per_page=2) 305 | 306 | 307 | @site_app.route('/search', methods=['GET']) 308 | def search_paste(): 309 | page = get_page() 310 | q = request.args['q'] 311 | keyword, pagination = get_pastes_from_search(q, p=page) 312 | return render_template('search.html', 313 | query_string=q, 314 | keyword=keyword, 315 | pagination=pagination) 316 | 317 | 318 | @site_app.route('/messages', methods=['GET']) 319 | @login_required 320 | def messages(): 321 | page = get_page() 322 | pagination = Message.objects(user=current_user.user).order_by('-created_at').paginate(page, per_page=20) 323 | return render_template('users/messages.html', 324 | pagination=pagination) 325 | -------------------------------------------------------------------------------- /daimaduan/tasks/seed_data.py: -------------------------------------------------------------------------------- 1 | import json 2 | from operator import itemgetter 3 | 4 | from fabric.decorators import task 5 | 6 | from daimaduan.models.syntax import Syntax 7 | 8 | 9 | DIST_FILE = 'daimaduan/static/js/lexers.js' 10 | 11 | ALL_SUPPORTED_SYNTAX = ( 12 | # ('brainfuck', 'brainfuck', 'Brainfuck'), 13 | # ('cirru', 'cirru', 'Cirru'), 14 | # ('nimrod', 'nimrod', 'Nimrod'), 15 | ('bash', 'bash', 'Bash'), 16 | # ('golo', 'golo', 'Golo'), 17 | # ('css+django', 'css+django', 'CSS+Django/Jinja'), 18 | # ('cfm', 'cfm', 'Coldfusion HTML'), 19 | # ('lagda', 'lagda', 'Literate Agda'), 20 | # ('xtend', 'xtend', 'Xtend'), 21 | # ('powershell', 'powershell', 'PowerShell'), 22 | # ('limbo', 'limbo', 'Limbo'), 23 | # ('jags', 'jags', 'JAGS'), 24 | # ('kal', 'kal', 'Kal'), 25 | # ('dylan-console', 'dylan-console', 'Dylan session'), 26 | ('java', 'java', 'Java'), 27 | # ('nemerle', 'nemerle', 'Nemerle'), 28 | # ('css+erb', 'css+erb', 'CSS+Ruby'), 29 | # ('mysql', 'mysql', 'MySQL'), 30 | ('cpp', 'cpp', 'C++'), 31 | # ('cbmbas', 'cbmbas', 'CBM BASIC V2'), 32 | # ('xml+smarty', 'xml+smarty', 'XML+Smarty'), 33 | # ('ahk', 'ahk', 'autohotkey'), 34 | # ('openedge', 'openedge', 'OpenEdge ABL'), 35 | # ('cmake', 'cmake', 'CMake'), 36 | # ('sp', 'sp', 'SourcePawn'), 37 | # ('mako', 'mako', 'Mako'), 38 | # ('css+mozpreproc', 'css+mozpreproc', 'CSS+mozpreproc'), 39 | # ('js+myghty', 'js+myghty', 'JavaScript+Myghty'), 40 | # ('rsl', 'rsl', 'RSL'), 41 | # ('scaml', 'scaml', 'Scaml'), 42 | # ('maql', 'maql', 'MAQL'), 43 | # ('as', 'as', 'ActionScript'), 44 | # ('squidconf', 'squidconf', 'SquidConf'), 45 | # ('fan', 'fan', 'Fantom'), 46 | # ('bbcode', 'bbcode', 'BBCode'), 47 | # ('css+myghty', 'css+myghty', 'CSS+Myghty'), 48 | # ('mupad', 'mupad', 'MuPAD'), 49 | # ('xml+erb', 'xml+erb', 'XML+Ruby'), 50 | # ('kconfig', 'kconfig', 'Kconfig'), 51 | # ('pytb', 'pytb', 'Python Traceback'), 52 | # ('cfs', 'cfs', 'cfstatement'), 53 | # ('ada', 'ada', 'Ada'), 54 | # ('xul+mozpreproc', 'xul+mozpreproc', 'XUL+mozpreproc'), 55 | # ('css+mako', 'css+mako', 'CSS+Mako'), 56 | # ('cucumber', 'cucumber', 'Gherkin'), 57 | # ('io', 'io', 'Io'), 58 | # ('urbiscript', 'urbiscript', 'UrbiScript'), 59 | # ('plpgsql', 'plpgsql', 'PL/pgSQL'), 60 | # ('inform6', 'inform6', 'Inform 6'), 61 | # ('mozpercentpreproc', 'mozpercentpreproc', 'mozpercentpreproc'), 62 | # ('bro', 'bro', 'Bro'), 63 | # ('antlr-objc', 'antlr-objc', 'ANTLR With ObjectiveC Target'), 64 | ('xml', 'xml', 'XML'), 65 | # ('nit', 'nit', 'Nit'), 66 | # ('genshitext', 'genshitext', 'Genshi Text'), 67 | # ('pike', 'pike', 'Pike'), 68 | # ('objective-j', 'objective-j', 'Objective-J'), 69 | # ('pycon', 'pycon', 'Python console session'), 70 | # ('ragel-c', 'ragel-c', 'Ragel in C Host'), 71 | # ('idl', 'idl', 'IDL'), 72 | # ('aspx-cs', 'aspx-cs', 'aspx-cs'), 73 | # ('ragel-ruby', 'ragel-ruby', 'Ragel in Ruby Host'), 74 | # ('html+genshi', 'html+genshi', 'HTML+Genshi'), 75 | ('spec', 'spec', 'RPMSpec'), 76 | # ('css+smarty', 'css+smarty', 'CSS+Smarty'), 77 | # ('antlr-csharp', 'antlr-csharp', 'ANTLR With C# Target'), 78 | # ('llvm', 'llvm', 'LLVM'), 79 | # ('py3tb', 'py3tb', 'Python 3.0 Traceback'), 80 | # ('ts', 'ts', 'TypeScript'), 81 | # ('minid', 'minid', 'MiniD'), 82 | ('sql', 'sql', 'SQL'), 83 | ('json', 'json', 'JSON'), 84 | # ('nasm', 'nasm', 'NASM'), 85 | # ('idris', 'idris', 'Idris'), 86 | # ('autoit', 'autoit', 'AutoIt'), 87 | ('sass', 'sass', 'Sass'), 88 | # ('aspx-vb', 'aspx-vb', 'aspx-vb'), 89 | # ('ceylon', 'ceylon', 'Ceylon'), 90 | # ('html+evoque', 'html+evoque', 'HTML+Evoque'), 91 | # ('numpy', 'numpy', 'NumPy'), 92 | ('coffee-script', 'coffee-script', 'CoffeeScript'), 93 | # ('xml+mako', 'xml+mako', 'XML+Mako'), 94 | # ('css+php', 'css+php', 'CSS+PHP'), 95 | ('vim', 'vim', 'VimL'), 96 | # ('css+genshitext', 'css+genshitext', 'CSS+Genshi Text'), 97 | # ('fancy', 'fancy', 'Fancy'), 98 | # ('ragel', 'ragel', 'Ragel'), 99 | # ('ssp', 'ssp', 'Scalate Server Page'), 100 | # ('at', 'at', 'AmbientTalk'), 101 | # ('xml+evoque', 'xml+evoque', 'XML+Evoque'), 102 | # ('redcode', 'redcode', 'Redcode'), 103 | # ('robotframework', 'robotframework', 'RobotFramework'), 104 | ('scala', 'scala', 'Scala'), 105 | # ('lighty', 'lighty', 'Lighttpd configuration file'), 106 | # ('rql', 'rql', 'RQL'), 107 | # ('chapel', 'chapel', 'Chapel'), 108 | # ('html+velocity', 'html+velocity', 'HTML+Velocity'), 109 | # ('rbcon', 'rbcon', 'Ruby irb session'), 110 | ('css', 'css', 'CSS'), 111 | # ('ragel-d', 'ragel-d', 'Ragel in D Host'), 112 | # ('asy', 'asy', 'Asymptote'), 113 | # ('xml+php', 'xml+php', 'XML+PHP'), 114 | # ('gnuplot', 'gnuplot', 'Gnuplot'), 115 | # ('pot', 'pot', 'Gettext Catalog'), 116 | # ('matlab', 'matlab', 'Matlab'), 117 | ('c', 'c', 'C'), 118 | # ('eiffel', 'eiffel', 'Eiffel'), 119 | # ('genshi', 'genshi', 'Genshi'), 120 | # ('vgl', 'vgl', 'VGL'), 121 | # ('velocity', 'velocity', 'Velocity'), 122 | # ('koka', 'koka', 'Koka'), 123 | # ('alloy', 'alloy', 'Alloy'), 124 | # ('irc', 'irc', 'IRC logs'), 125 | # ('swig', 'swig', 'SWIG'), 126 | # ('prolog', 'prolog', 'Prolog'), 127 | # ('xml+lasso', 'xml+lasso', 'XML+Lasso'), 128 | # ('smalltalk', 'smalltalk', 'Smalltalk'), 129 | ('yaml', 'yaml', 'YAML'), 130 | # ('antlr-as', 'antlr-as', 'ANTLR With ActionScript Target'), 131 | # ('cypher', 'cypher', 'Cypher'), 132 | # ('xslt', 'xslt', 'XSLT'), 133 | # ('splus', 'splus', 'S'), 134 | # ('dylan-lid', 'dylan-lid', 'DylanLID'), 135 | # ('ec', 'ec', 'eC'), 136 | # ('perl6', 'perl6', 'Perl6'), 137 | # ('logos', 'logos', 'Logos'), 138 | # ('racket', 'racket', 'Racket'), 139 | ('text', 'text', 'Text only'), 140 | ('dart', 'dart', 'Dart'), 141 | # ('ragel-cpp', 'ragel-cpp', 'Ragel in CPP Host'), 142 | # ('scilab', 'scilab', 'Scilab'), 143 | # ('jsp', 'jsp', 'Java Server Page'), 144 | # ('abap', 'abap', 'ABAP'), 145 | # ('rust', 'rust', 'Rust'), 146 | ('diff', 'diff', 'Diff'), 147 | # ('liquid', 'liquid', 'liquid'), 148 | # ('matlabsession', 'matlabsession', 'Matlab session'), 149 | # ('slim', 'slim', 'Slim'), 150 | # ('html+php', 'html+php', 'HTML+PHP'), 151 | # ('objective-c++', 'objective-c++', 'Objective-C++'), 152 | # ('postscript', 'postscript', 'PostScript'), 153 | # ('verilog', 'verilog', 'verilog'), 154 | # ('js+erb', 'js+erb', 'JavaScript+Ruby'), 155 | # ('cobolfree', 'cobolfree', 'COBOLFree'), 156 | # ('basemake', 'basemake', 'Base Makefile'), 157 | # ('ioke', 'ioke', 'Ioke'), 158 | # ('pypylog', 'pypylog', 'PyPy Log'), 159 | # ('python3', 'python3', 'Python 3'), 160 | ('swift', 'swift', 'Swift'), 161 | # ('antlr', 'antlr', 'ANTLR'), 162 | # ('psql', 'psql', 'PostgreSQL console (psql)'), 163 | # ('js+django', 'js+django', 'JavaScript+Django/Jinja'), 164 | # ('lsl', 'lsl', 'LSL'), 165 | # ('mathematica', 'mathematica', 'Mathematica'), 166 | # ('erl', 'erl', 'Erlang erl session'), 167 | # ('modelica', 'modelica', 'Modelica'), 168 | # ('antlr-python', 'antlr-python', 'ANTLR With Python Target'), 169 | # ('treetop', 'treetop', 'Treetop'), 170 | # ('tcl', 'tcl', 'Tcl'), 171 | # ('fsharp', 'fsharp', 'FSharp'), 172 | # ('newlisp', 'newlisp', 'NewLisp'), 173 | # ('css+lasso', 'css+lasso', 'CSS+Lasso'), 174 | # ('todotxt', 'todotxt', 'Todotxt'), 175 | # ('shell-session', 'shell-session', 'Shell Session'), 176 | # ('newspeak', 'newspeak', 'Newspeak'), 177 | # ('console', 'console', 'Bash Session'), 178 | # ('gst', 'gst', 'Gosu Template'), 179 | # ('tads3', 'tads3', 'TADS 3'), 180 | # ('rd', 'rd', 'Rd'), 181 | # ('resource', 'resource', 'ResourceBundle'), 182 | ('js', 'js', 'JavaScript'), 183 | # ('common-lisp', 'common-lisp', 'Common Lisp'), 184 | # ('apl', 'apl', 'APL'), 185 | # ('gap', 'gap', 'GAP'), 186 | # ('factor', 'factor', 'Factor'), 187 | # ('awk', 'awk', 'Awk'), 188 | # ('systemverilog', 'systemverilog', 'systemverilog'), 189 | # ('js+mako', 'js+mako', 'JavaScript+Mako'), 190 | # ('iex', 'iex', 'Elixir iex session'), 191 | # ('html+cheetah', 'html+cheetah', 'HTML+Cheetah'), 192 | # ('i6t', 'i6t', 'Inform 6 template'), 193 | # ('julia', 'julia', 'Julia'), 194 | # ('smarty', 'smarty', 'Smarty'), 195 | # ('protobuf', 'protobuf', 'Protocol Buffer'), 196 | # ('tea', 'tea', 'Tea'), 197 | # ('jasmin', 'jasmin', 'Jasmin'), 198 | # ('apacheconf', 'apacheconf', 'ApacheConf'), 199 | # ('js+genshitext', 'js+genshitext', 'JavaScript+Genshi Text'), 200 | # ('scheme', 'scheme', 'Scheme'), 201 | ('puppet', 'puppet', 'Puppet'), 202 | # ('octave', 'octave', 'Octave'), 203 | # ('live-script', 'live-script', 'LiveScript'), 204 | # ('monkey', 'monkey', 'Monkey'), 205 | # ('red', 'red', 'Red'), 206 | # ('cfc', 'cfc', 'Coldfusion CFC'), 207 | # ('d-objdump', 'd-objdump', 'd-objdump'), 208 | # ('haxeml', 'haxeml', 'Hxml'), 209 | ('groovy', 'groovy', 'Groovy'), 210 | ('gradle', 'groovy', 'Gradle'), 211 | # ('jsonld', 'jsonld', 'JSON-LD'), 212 | # ('pig', 'pig', 'Pig'), 213 | # ('cuda', 'cuda', 'CUDA'), 214 | # ('handlebars', 'handlebars', 'Handlebars'), 215 | # ('http', 'http', 'HTTP'), 216 | ('python', 'python', 'Python'), 217 | # ('boo', 'boo', 'Boo'), 218 | # ('logtalk', 'logtalk', 'Logtalk'), 219 | # ('vb.net', 'vb.net', 'VB.net'), 220 | # ('d', 'd', 'D'), 221 | # ('blitzbasic', 'blitzbasic', 'BlitzBasic'), 222 | ('scss', 'scss', 'SCSS'), 223 | # ('haml', 'haml', 'Haml'), 224 | # ('foxpro', 'foxpro', 'FoxPro'), 225 | # ('control', 'control', 'Debian Control file'), 226 | # ('jade', 'jade', 'Jade'), 227 | # ('c-objdump', 'c-objdump', 'c-objdump'), 228 | # ('xml+velocity', 'xml+velocity', 'XML+Velocity'), 229 | # ('js+cheetah', 'js+cheetah', 'JavaScript+Cheetah'), 230 | # ('cobol', 'cobol', 'COBOL'), 231 | # ('objdump', 'objdump', 'objdump'), 232 | # ('ca65', 'ca65', 'ca65 assembler'), 233 | # ('sparql', 'sparql', 'SPARQL'), 234 | # ('lasso', 'lasso', 'Lasso'), 235 | # ('ragel-java', 'ragel-java', 'Ragel in Java Host'), 236 | # ('vala', 'vala', 'Vala'), 237 | # ('haskell', 'haskell', 'Haskell'), 238 | ('lua', 'lua', 'Lua'), 239 | # ('aspectj', 'aspectj', 'AspectJ'), 240 | # ('groff', 'groff', 'Groff'), 241 | # ('js+lasso', 'js+lasso', 'JavaScript+Lasso'), 242 | # ('glsl', 'glsl', 'GLSL'), 243 | # ('gas', 'gas', 'GAS'), 244 | # ('mxml', 'mxml', 'MXML'), 245 | # ('xml+cheetah', 'xml+cheetah', 'XML+Cheetah'), 246 | ('go', 'go', 'Go'), 247 | # ('pan', 'pan', 'Pan'), 248 | # ('mql', 'mql', 'MQL'), 249 | # ('felix', 'felix', 'Felix'), 250 | # ('properties', 'properties', 'Properties'), 251 | # ('igor', 'igor', 'Igor'), 252 | # ('blitzmax', 'blitzmax', 'BlitzMax'), 253 | ('perl', 'perl', 'Perl'), 254 | # ('stan', 'stan', 'Stan'), 255 | ('ini', 'ini', 'INI'), 256 | # ('rhtml', 'rhtml', 'RHTML'), 257 | # ('coq', 'coq', 'Coq'), 258 | # ('tcsh', 'tcsh', 'Tcsh'), 259 | # ('dpatch', 'dpatch', 'Darcs Patch'), 260 | # ('twig', 'twig', 'Twig'), 261 | ('nginx', 'nginx', 'Nginx configuration file'), 262 | # ('agda', 'agda', 'Agda'), 263 | # ('applescript', 'applescript', 'AppleScript'), 264 | # ('html+smarty', 'html+smarty', 'HTML+Smarty'), 265 | # ('inform7', 'inform7', 'Inform 7'), 266 | # ('lhs', 'lhs', 'Literate Haskell'), 267 | ('php', 'php', 'PHP'), 268 | # ('mscgen', 'mscgen', 'Mscgen'), 269 | # ('ooc', 'ooc', 'Ooc'), 270 | # ('sourceslist', 'sourceslist', 'Debian Sourcelist'), 271 | # ('delphi', 'delphi', 'Delphi'), 272 | # ('modula2', 'modula2', 'Modula-2'), 273 | # ('postgresql', 'postgresql', 'PostgreSQL SQL dialect'), 274 | # ('rexx', 'rexx', 'Rexx'), 275 | # ('html+django', 'html+django', 'HTML+Django/Jinja'), 276 | # ('hx', 'hx', 'Haxe'), 277 | # ('django', 'django', 'Django/Jinja'), 278 | # ('dtd', 'dtd', 'DTD'), 279 | # ('nixos', 'nixos', 'Nix'), 280 | # ('vhdl', 'vhdl', 'vhdl'), 281 | # ('mask', 'mask', 'Mask'), 282 | # ('zephir', 'zephir', 'Zephir'), 283 | # ('pawn', 'pawn', 'Pawn'), 284 | # ('js+smarty', 'js+smarty', 'JavaScript+Smarty'), 285 | # ('html+twig', 'html+twig', 'HTML+Twig'), 286 | # ('fortran', 'fortran', 'Fortran'), 287 | # ('cryptol', 'cryptol', 'Cryptol'), 288 | # ('rebol', 'rebol', 'REBOL'), 289 | ('erb', 'erb', 'ERB'), 290 | # ('befunge', 'befunge', 'Befunge'), 291 | # ('moon', 'moon', 'MoonScript'), 292 | # ('dylan', 'dylan', 'Dylan'), 293 | # ('trac-wiki', 'trac-wiki', 'MoinMoin/Trac Wiki markup'), 294 | # ('croc', 'croc', 'Croc'), 295 | ('html', 'html', 'HTML'), 296 | ('rst', 'rst', 'reStructuredText'), 297 | # ('nsis', 'nsis', 'NSIS'), 298 | # ('elixir', 'elixir', 'Elixir'), 299 | # ('isabelle', 'isabelle', 'Isabelle'), 300 | # ('html+myghty', 'html+myghty', 'HTML+Myghty'), 301 | ('make', 'make', 'Makefile'), 302 | # ('sqlite3', 'sqlite3', 'sqlite3con'), 303 | # ('ocaml', 'ocaml', 'OCaml'), 304 | # ('clay', 'clay', 'Clay'), 305 | # ('jlcon', 'jlcon', 'Julia console'), 306 | ('rb', 'rb', 'Ruby'), 307 | # ('pov', 'pov', 'POVRay'), 308 | # ('dg', 'dg', 'dg'), 309 | # ('evoque', 'evoque', 'Evoque'), 310 | ('docker', 'docker', 'Docker'), 311 | # ('registry', 'registry', 'reg'), 312 | # ('html+mako', 'html+mako', 'HTML+Mako'), 313 | # ('cfengine3', 'cfengine3', 'CFEngine3'), 314 | # ('mason', 'mason', 'Mason'), 315 | # ('lean', 'lean', 'Lean'), 316 | # ('lcry', 'lcry', 'Literate Cryptol'), 317 | # ('as3', 'as3', 'ActionScript 3'), 318 | ('kotlin', 'kotlin', 'Kotlin'), 319 | # ('antlr-java', 'antlr-java', 'ANTLR With Java Target'), 320 | # ('bugs', 'bugs', 'BUGS'), 321 | # ('javascript+mozpreproc', 'javascript+mozpreproc', 'Javascript+mozpreproc'), 322 | # ('yaml+jinja', 'yaml+jinja', 'YAML+Jinja'), 323 | # ('cpp-objdump', 'cpp-objdump', 'cpp-objdump'), 324 | # ('bat', 'bat', 'Batchfile'), 325 | # ('hybris', 'hybris', 'Hybris'), 326 | # ('opa', 'opa', 'Opa'), 327 | # ('hylang', 'hylang', 'Hy'), 328 | # ('cython', 'cython', 'Cython'), 329 | ('erlang', 'erlang', 'Erlang'), 330 | # ('vctreestatus', 'vctreestatus', 'VCTreeStatus'), 331 | ('clojure', 'clojure', 'Clojure'), 332 | # ('antlr-perl', 'antlr-perl', 'ANTLR With Perl Target'), 333 | # ('mozhashpreproc', 'mozhashpreproc', 'mozhashpreproc'), 334 | # ('myghty', 'myghty', 'Myghty'), 335 | # ('clojurescript', 'clojurescript', 'ClojureScript'), 336 | # ('qml', 'qml', 'QML'), 337 | # ('moocode', 'moocode', 'MOOCode'), 338 | # ('rconsole', 'rconsole', 'RConsole'), 339 | # ('raw', 'raw', 'Raw token data'), 340 | # ('html+lasso', 'html+lasso', 'HTML+Lasso'), 341 | ('csharp', 'csharp', 'C#'), 342 | # ('tex', 'tex', 'TeX'), 343 | # ('chai', 'chai', 'ChaiScript'), 344 | # ('cheetah', 'cheetah', 'Cheetah'), 345 | # ('smali', 'smali', 'Smali'), 346 | # ('qbasic', 'qbasic', 'QBasic'), 347 | # ('gooddata-cl', 'gooddata-cl', 'GoodData-CL'), 348 | # ('html+handlebars', 'html+handlebars', 'HTML+Handlebars'), 349 | ('objective-c', 'objective-c', 'Objective-C'), 350 | # ('lidr', 'lidr', 'Literate Idris'), 351 | # ('ragel-em', 'ragel-em', 'Embedded Ragel'), 352 | # ('objdump-nasm', 'objdump-nasm', 'objdump-nasm'), 353 | # ('antlr-cpp', 'antlr-cpp', 'ANTLR With CPP Target'), 354 | # ('ebnf', 'ebnf', 'EBNF'), 355 | # ('gosu', 'gosu', 'Gosu'), 356 | # ('snobol', 'snobol', 'Snobol'), 357 | # ('js+php', 'js+php', 'JavaScript+PHP'), 358 | # ('xquery', 'xquery', 'XQuery'), 359 | # ('nesc', 'nesc', 'nesC'), 360 | # ('ecl', 'ecl', 'ECL'), 361 | # ('ragel-objc', 'ragel-objc', 'Ragel in Objective C Host'), 362 | # ('xml+django', 'xml+django', 'XML+Django/Jinja'), 363 | # ('sml', 'sml', 'Standard ML'), 364 | # ('antlr-ruby', 'antlr-ruby', 'ANTLR With Ruby Target'), 365 | # ('duel', 'duel', 'Duel'), 366 | # ('xml+myghty', 'xml+myghty', 'XML+Myghty') 367 | ) 368 | 369 | 370 | def lexer_parser(lexer): 371 | return {"name": lexer[2], "value": lexer[0]} 372 | 373 | 374 | def get_lexers(): 375 | lexers = map(lexer_parser, ALL_SUPPORTED_SYNTAX) 376 | return sorted(lexers, key=itemgetter("name")) 377 | 378 | 379 | def lexer_dumps(lexer): 380 | return json.dumps(lexer) 381 | 382 | 383 | @task 384 | def seed(): 385 | """Seed syntax data in MongoDB""" 386 | def find_or_create_syntax(key, syntax, name): 387 | syn = Syntax.objects(key=key).first() 388 | if syn is None: 389 | print "Seeding syntax: %s, %s, %s" % (key, syntax, name) 390 | Syntax(key=key, syntax=syntax, name=name).save() 391 | 392 | for key, syntax, name in ALL_SUPPORTED_SYNTAX: 393 | find_or_create_syntax(key, syntax, name) 394 | 395 | print "Generating %s" % DIST_FILE 396 | with open(DIST_FILE, "w") as f: 397 | lexers = get_lexers() 398 | lexers_code = ", ".join(map(lexer_dumps, lexers)) 399 | f.write("var lexers = [%s];" % lexers_code) 400 | print " %d lexers created." % len(lexers) 401 | --------------------------------------------------------------------------------