├── 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 |
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 |
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 |
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 |
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 | [](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 |
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 | {{ display }}
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 |
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 |
19 |
20 |
22 | {{ fa_icon('trash') }} 删除这个收藏夹
23 |
24 |
25 |
26 | {% endif %}
27 |
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 |
9 |
10 |
11 |
12 |
13 |
14 |
17 | {{ lexer.name }}
19 |
20 |
21 |
22 |
23 |
24 |
29 |
30 |
31 |
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 |
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 |
32 | {{ fa_icon("plus", text="增加一个片断") }}
33 |
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 |
30 | {{ fa_icon("plus", text="增加一个片断") }}
31 |
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 = '<%= text %> ';
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 |
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 |
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 |
2 |
3 |
12 |
13 |
14 |
19 |
20 |
21 |
22 |
23 |
24 |
分享我的代码
25 |
26 |
49 |
50 |
51 |
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 |
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 |
57 | {{ fa_icon('trash') }} 删除
58 |
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 |
80 | {{ fa_icon('code') }}
81 | 引用
82 |
83 |
84 |
86 |
87 | {% endmacro %}
88 |
89 | {% macro add_to_bookmark_form(paste, paste_lists) %}
90 | {% if paste_lists %}
91 |
92 |
93 |
94 |
95 | {% for paste_list in paste_lists %}
96 | {{ paste_list.title }}
97 | {% endfor %}
98 |
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 |
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 |
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 |
75 |
92 |
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 |
--------------------------------------------------------------------------------