├── tabcrawler ├── tabcrawler │ ├── __init__.py │ ├── spiders │ │ ├── __init__.py │ │ └── sosospider.py │ ├── items.py │ └── settings.py └── scrapy.cfg ├── guitarfan ├── scrapy │ ├── __init__.py │ ├── items.py │ ├── spiders │ │ └── __init__.py │ └── settings.py ├── templates │ ├── admin │ │ ├── index.html │ │ ├── dashboard │ │ │ └── base.html │ │ ├── tabs │ │ │ ├── base.html │ │ │ ├── data_import.html │ │ │ ├── tag_management.html │ │ │ └── artist_management.html │ │ ├── macro.html │ │ ├── login.html │ │ └── base.html │ ├── site │ │ ├── videos.html │ │ ├── coming.html │ │ ├── popup_base.html │ │ ├── tabview.html │ │ └── index.html │ └── error │ │ ├── 404.html │ │ └── 500.html ├── extensions │ ├── __init__.py │ ├── flasksqlalchemy.py │ ├── flaskcache.py │ ├── flaskprincipal.py │ └── flasklogin.py ├── controlers │ ├── admin │ │ ├── __init__.py │ │ ├── forms │ │ │ ├── __init__.py │ │ │ ├── tag.py │ │ │ ├── tab.py │ │ │ ├── artist.py │ │ │ └── administrator.py │ │ ├── tag.py │ │ ├── tabfile.py │ │ ├── data.py │ │ └── administrator.py │ ├── api │ │ └── __init__.py │ ├── site │ │ ├── __init__.py │ │ ├── videos.py │ │ ├── courses.py │ │ ├── tabview.py │ │ ├── index.py │ │ └── tabs.py │ ├── error.py │ └── __init__.py ├── utilities │ ├── __init__.py │ ├── filters.py │ ├── oshelper.py │ ├── validator.py │ ├── pinyin.py │ └── qqFileUploader.py ├── static │ ├── favicon.ico │ ├── images │ │ ├── 404.jpg │ │ ├── 500.jpg │ │ ├── logo.png │ │ ├── email.png │ │ ├── nophoto.png │ │ ├── landing1.jpg │ │ ├── landing10.jpg │ │ ├── landing11.jpg │ │ ├── landing12.jpg │ │ ├── landing13.jpg │ │ ├── landing14.jpg │ │ ├── landing15.jpg │ │ ├── landing16.jpg │ │ ├── landing17.jpg │ │ ├── landing18.jpg │ │ ├── landing19.jpg │ │ ├── landing2.jpg │ │ ├── landing3.jpg │ │ ├── landing4.jpg │ │ ├── landing5.jpg │ │ ├── landing6.jpg │ │ ├── landing7.jpg │ │ ├── landing8.jpg │ │ ├── landing9.jpg │ │ ├── loading-1.gif │ │ ├── loading-2.gif │ │ ├── coming-soon.png │ │ └── Responsive-showcase-presentation.png │ ├── browser │ │ ├── chrome.gif │ │ ├── close.gif │ │ ├── msie.gif │ │ ├── opera.gif │ │ ├── safari.gif │ │ ├── firefox.gif │ │ └── detection.css │ ├── fancybox │ │ ├── blank.gif │ │ ├── fancybox_sprite.png │ │ ├── fancybox_loading.gif │ │ ├── fancybox_overlay.png │ │ ├── fancybox_loading@2x.gif │ │ ├── fancybox_sprite@2x.png │ │ ├── helpers │ │ │ ├── fancybox_buttons.png │ │ │ ├── jquery.fancybox-thumbs.css │ │ │ ├── jquery.fancybox-buttons.css │ │ │ ├── jquery.fancybox-buttons.js │ │ │ ├── jquery.fancybox-thumbs.js │ │ │ └── jquery.fancybox-media.js │ │ └── jquery.fancybox.css │ ├── FineUploader │ │ ├── edit.gif │ │ ├── loading.gif │ │ ├── processing.gif │ │ ├── iframe.xss.response-3.7.1.js │ │ ├── fineuploader-3.7.1.min.css │ │ └── fineuploader-3.7.1.css │ ├── select2 │ │ ├── img │ │ │ ├── select2.png │ │ │ ├── select2x2.png │ │ │ └── select2-spinner.gif │ │ └── css │ │ │ └── select2-bootstrap.css │ ├── dataTables │ │ ├── images │ │ │ ├── favicon.ico │ │ │ ├── sort_asc.png │ │ │ ├── sort_both.png │ │ │ ├── sort_desc.png │ │ │ ├── Sorting icons.psd │ │ │ ├── back_disabled.png │ │ │ ├── back_enabled.png │ │ │ ├── forward_disabled.png │ │ │ ├── forward_enabled.png │ │ │ ├── back_enabled_hover.png │ │ │ ├── sort_asc_disabled.png │ │ │ ├── sort_desc_disabled.png │ │ │ └── forward_enabled_hover.png │ │ └── css │ │ │ ├── jquery.dataTables.css │ │ │ └── jquery.dataTables_themeroller.css │ ├── FontAwesome │ │ ├── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.ttf │ │ │ └── fontawesome-webfont.woff │ │ ├── less │ │ │ ├── core.less │ │ │ ├── font-awesome.less │ │ │ ├── path.less │ │ │ └── mixins.less │ │ └── scss │ │ │ ├── _core.scss │ │ │ ├── font-awesome.scss │ │ │ ├── _path.scss │ │ │ └── _mixins.scss │ ├── bootflat │ │ ├── img │ │ │ └── check_flat │ │ │ │ └── default.png │ │ ├── js │ │ │ ├── html5shiv.js │ │ │ └── respond.min.js │ │ └── css │ │ │ └── bootflat-square.css │ ├── bootstrap │ │ └── img │ │ │ ├── glyphicons-halflings.png │ │ │ └── glyphicons-halflings-white.png │ ├── bootstrap3 │ │ └── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ └── glyphicons-halflings-regular.woff │ ├── google-code-prettify │ │ └── prettify.css │ ├── jqcloud │ │ ├── jqcloud.css │ │ └── jqcloud-1.0.4.min.js │ └── js │ │ ├── jquery.highlight.js │ │ ├── jquery.tagcloud.js │ │ └── admin.js ├── models │ ├── __init__.py │ ├── tag.py │ ├── administrator.py │ ├── tabfile.py │ ├── enums.py │ ├── artist.py │ └── tab.py └── __init__.py ├── requirements.txt ├── .gitignore ├── run.py ├── LICENSE ├── README.md ├── settings.py └── changetabfilename.py /tabcrawler/tabcrawler/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tabcrawler/tabcrawler/spiders/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /guitarfan/scrapy/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'jinzemin' 2 | -------------------------------------------------------------------------------- /guitarfan/templates/admin/index.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/base.html" %} -------------------------------------------------------------------------------- /guitarfan/extensions/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- -------------------------------------------------------------------------------- /guitarfan/controlers/admin/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- -------------------------------------------------------------------------------- /guitarfan/controlers/api/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- -------------------------------------------------------------------------------- /guitarfan/controlers/site/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- -------------------------------------------------------------------------------- /guitarfan/utilities/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | -------------------------------------------------------------------------------- /guitarfan/utilities/filters.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | -------------------------------------------------------------------------------- /guitarfan/controlers/admin/forms/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | -------------------------------------------------------------------------------- /guitarfan/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/favicon.ico -------------------------------------------------------------------------------- /guitarfan/static/images/404.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/404.jpg -------------------------------------------------------------------------------- /guitarfan/static/images/500.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/500.jpg -------------------------------------------------------------------------------- /guitarfan/static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/logo.png -------------------------------------------------------------------------------- /guitarfan/static/browser/chrome.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/browser/chrome.gif -------------------------------------------------------------------------------- /guitarfan/static/browser/close.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/browser/close.gif -------------------------------------------------------------------------------- /guitarfan/static/browser/msie.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/browser/msie.gif -------------------------------------------------------------------------------- /guitarfan/static/browser/opera.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/browser/opera.gif -------------------------------------------------------------------------------- /guitarfan/static/browser/safari.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/browser/safari.gif -------------------------------------------------------------------------------- /guitarfan/static/fancybox/blank.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/fancybox/blank.gif -------------------------------------------------------------------------------- /guitarfan/static/images/email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/email.png -------------------------------------------------------------------------------- /guitarfan/static/images/nophoto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/nophoto.png -------------------------------------------------------------------------------- /guitarfan/static/browser/firefox.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/browser/firefox.gif -------------------------------------------------------------------------------- /guitarfan/static/images/landing1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/landing1.jpg -------------------------------------------------------------------------------- /guitarfan/static/images/landing10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/landing10.jpg -------------------------------------------------------------------------------- /guitarfan/static/images/landing11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/landing11.jpg -------------------------------------------------------------------------------- /guitarfan/static/images/landing12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/landing12.jpg -------------------------------------------------------------------------------- /guitarfan/static/images/landing13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/landing13.jpg -------------------------------------------------------------------------------- /guitarfan/static/images/landing14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/landing14.jpg -------------------------------------------------------------------------------- /guitarfan/static/images/landing15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/landing15.jpg -------------------------------------------------------------------------------- /guitarfan/static/images/landing16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/landing16.jpg -------------------------------------------------------------------------------- /guitarfan/static/images/landing17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/landing17.jpg -------------------------------------------------------------------------------- /guitarfan/static/images/landing18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/landing18.jpg -------------------------------------------------------------------------------- /guitarfan/static/images/landing19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/landing19.jpg -------------------------------------------------------------------------------- /guitarfan/static/images/landing2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/landing2.jpg -------------------------------------------------------------------------------- /guitarfan/static/images/landing3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/landing3.jpg -------------------------------------------------------------------------------- /guitarfan/static/images/landing4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/landing4.jpg -------------------------------------------------------------------------------- /guitarfan/static/images/landing5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/landing5.jpg -------------------------------------------------------------------------------- /guitarfan/static/images/landing6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/landing6.jpg -------------------------------------------------------------------------------- /guitarfan/static/images/landing7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/landing7.jpg -------------------------------------------------------------------------------- /guitarfan/static/images/landing8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/landing8.jpg -------------------------------------------------------------------------------- /guitarfan/static/images/landing9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/landing9.jpg -------------------------------------------------------------------------------- /guitarfan/static/images/loading-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/loading-1.gif -------------------------------------------------------------------------------- /guitarfan/static/images/loading-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/loading-2.gif -------------------------------------------------------------------------------- /guitarfan/static/FineUploader/edit.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/FineUploader/edit.gif -------------------------------------------------------------------------------- /guitarfan/static/images/coming-soon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/coming-soon.png -------------------------------------------------------------------------------- /guitarfan/static/select2/img/select2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/select2/img/select2.png -------------------------------------------------------------------------------- /guitarfan/static/FineUploader/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/FineUploader/loading.gif -------------------------------------------------------------------------------- /guitarfan/static/select2/img/select2x2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/select2/img/select2x2.png -------------------------------------------------------------------------------- /guitarfan/static/FineUploader/processing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/FineUploader/processing.gif -------------------------------------------------------------------------------- /guitarfan/static/fancybox/fancybox_sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/fancybox/fancybox_sprite.png -------------------------------------------------------------------------------- /guitarfan/static/dataTables/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/dataTables/images/favicon.ico -------------------------------------------------------------------------------- /guitarfan/static/dataTables/images/sort_asc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/dataTables/images/sort_asc.png -------------------------------------------------------------------------------- /guitarfan/static/fancybox/fancybox_loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/fancybox/fancybox_loading.gif -------------------------------------------------------------------------------- /guitarfan/static/fancybox/fancybox_overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/fancybox/fancybox_overlay.png -------------------------------------------------------------------------------- /tabcrawler/scrapy.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | default = tabcrawler.settings 3 | 4 | [deploy] 5 | #url = http://localhost:6800/ 6 | project = tabcrawler 7 | -------------------------------------------------------------------------------- /guitarfan/static/FontAwesome/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/FontAwesome/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /guitarfan/static/dataTables/images/sort_both.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/dataTables/images/sort_both.png -------------------------------------------------------------------------------- /guitarfan/static/dataTables/images/sort_desc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/dataTables/images/sort_desc.png -------------------------------------------------------------------------------- /guitarfan/static/fancybox/fancybox_loading@2x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/fancybox/fancybox_loading@2x.gif -------------------------------------------------------------------------------- /guitarfan/static/fancybox/fancybox_sprite@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/fancybox/fancybox_sprite@2x.png -------------------------------------------------------------------------------- /guitarfan/static/select2/img/select2-spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/select2/img/select2-spinner.gif -------------------------------------------------------------------------------- /guitarfan/templates/site/videos.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /guitarfan/static/bootflat/img/check_flat/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/bootflat/img/check_flat/default.png -------------------------------------------------------------------------------- /guitarfan/static/dataTables/images/Sorting icons.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/dataTables/images/Sorting icons.psd -------------------------------------------------------------------------------- /guitarfan/static/dataTables/images/back_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/dataTables/images/back_disabled.png -------------------------------------------------------------------------------- /guitarfan/static/dataTables/images/back_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/dataTables/images/back_enabled.png -------------------------------------------------------------------------------- /guitarfan/static/bootstrap/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/bootstrap/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /guitarfan/static/dataTables/images/forward_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/dataTables/images/forward_disabled.png -------------------------------------------------------------------------------- /guitarfan/static/dataTables/images/forward_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/dataTables/images/forward_enabled.png -------------------------------------------------------------------------------- /guitarfan/static/fancybox/helpers/fancybox_buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/fancybox/helpers/fancybox_buttons.png -------------------------------------------------------------------------------- /guitarfan/static/dataTables/images/back_enabled_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/dataTables/images/back_enabled_hover.png -------------------------------------------------------------------------------- /guitarfan/static/dataTables/images/sort_asc_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/dataTables/images/sort_asc_disabled.png -------------------------------------------------------------------------------- /guitarfan/static/dataTables/images/sort_desc_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/dataTables/images/sort_desc_disabled.png -------------------------------------------------------------------------------- /guitarfan/extensions/flasksqlalchemy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from flask.ext.sqlalchemy import SQLAlchemy 5 | 6 | db = SQLAlchemy() 7 | -------------------------------------------------------------------------------- /guitarfan/static/FontAwesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/FontAwesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /guitarfan/static/FontAwesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/FontAwesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /guitarfan/static/FontAwesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/FontAwesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /guitarfan/static/dataTables/images/forward_enabled_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/dataTables/images/forward_enabled_hover.png -------------------------------------------------------------------------------- /guitarfan/static/images/Responsive-showcase-presentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/images/Responsive-showcase-presentation.png -------------------------------------------------------------------------------- /guitarfan/static/bootstrap/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/bootstrap/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | DateTime 2 | Flask 3 | Flask-Auth 4 | Flask-Cache 5 | Flask-Login 6 | Flask-Mail 7 | Flask-SQLAlchemy 8 | Flask-Testing 9 | Flask-Uploads 10 | Flask-WTF 11 | -------------------------------------------------------------------------------- /guitarfan/static/bootstrap3/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/bootstrap3/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /guitarfan/static/bootstrap3/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/bootstrap3/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /guitarfan/static/bootstrap3/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowrain/GuitarFan/HEAD/guitarfan/static/bootstrap3/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /guitarfan/static/FineUploader/iframe.xss.response-3.7.1.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var match = /(\{.+\}).+/.exec(document.body.innerHTML); 3 | if (match) { 4 | parent.postMessage(match[1], '*'); 5 | } 6 | }()); 7 | -------------------------------------------------------------------------------- /guitarfan/scrapy/items.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from scrapy.item import Item, Field 5 | 6 | 7 | class Artist(Item): 8 | name = Field() 9 | 10 | 11 | class Tab(Item): 12 | name = Field() -------------------------------------------------------------------------------- /guitarfan/scrapy/spiders/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Target Sites 5 | # http://pu.guitarsz.cn/listSinger.php 6 | # http://www.byguitar.com/tab/singers 7 | # http://pu.jitapusoso.com/singers/[X].htm -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | env 2 | init_db.py 3 | *.log 4 | *.pyc 5 | *.key 6 | 7 | .idea 8 | */.idea 9 | .DS_Store 10 | 11 | *.db 12 | supervisord.conf 13 | 14 | /guitarfan/static/artists 15 | /guitarfan/static/tabs 16 | 17 | /tabcrawler/tabs 18 | /tabcrawler/json -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from guitarfan import create_app 5 | 6 | app = create_app('settings') 7 | 8 | if __name__ == '__main__': 9 | app.run(host=app.config['HOST'], port=int(app.config['PORT']), debug=app.config['DEBUG']) -------------------------------------------------------------------------------- /guitarfan/models/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from administrator import Administrator 5 | from artist import Artist 6 | from tab import Tab 7 | from tabfile import TabFile 8 | from tag import Tag 9 | from enums import * 10 | -------------------------------------------------------------------------------- /guitarfan/scrapy/settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | SPIDER_MODULES = ['guitarfan.spiders'] 5 | NEWSPIDER_MODULE = 'guitarfan.spiders' 6 | DEFAULT_ITEM_CLASS = 'guitarfan.items.Artist' 7 | 8 | # ITEM_PIPELINES = ['guitarfan.pipelines.FilterWordsPipeline'] 9 | -------------------------------------------------------------------------------- /guitarfan/static/FontAwesome/less/core.less: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix} { 5 | display: inline-block; 6 | font-family: FontAwesome; 7 | font-style: normal; 8 | font-weight: normal; 9 | line-height: 1; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | } 13 | -------------------------------------------------------------------------------- /guitarfan/static/FontAwesome/scss/_core.scss: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix} { 5 | display: inline-block; 6 | font-family: FontAwesome; 7 | font-style: normal; 8 | font-weight: normal; 9 | line-height: 1; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | } 13 | -------------------------------------------------------------------------------- /tabcrawler/tabcrawler/items.py: -------------------------------------------------------------------------------- 1 | from scrapy.item import Item, Field 2 | 3 | 4 | class LetterArtistItem(Item): 5 | letter = Field() 6 | artists = Field() 7 | images = Field() 8 | image_urls = Field() 9 | 10 | 11 | class TabItem(Item): 12 | artist = Field() 13 | title = Field() 14 | format = Field() 15 | images = Field() 16 | image_urls = Field() -------------------------------------------------------------------------------- /guitarfan/extensions/flaskcache.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from flask import request 5 | from flask.ext.cache import Cache 6 | 7 | cache = Cache(config={'CACHE_TYPE': 'simple'}) 8 | 9 | def make_cache_key(*args, **kwargs): 10 | path = request.path 11 | args = str(hash(frozenset(request.args.items()))) 12 | return (path + args).encode('utf-8') -------------------------------------------------------------------------------- /guitarfan/extensions/flaskprincipal.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | from functools import partial 6 | from flask.ext.principal import Need, Permission 7 | 8 | 9 | UserAccessNeed = partial(Need, 'functions') 10 | 11 | 12 | class UserAccessPermission(Permission): 13 | def __init__(self, name): 14 | need = UserAccessNeed(name) 15 | super(UserAccessPermission, self).__init__(need) -------------------------------------------------------------------------------- /guitarfan/controlers/error.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from flask import render_template, Blueprint 5 | 6 | 7 | bp_error = Blueprint('bp_error', __name__, template_folder="../templates/error") 8 | 9 | @bp_error.app_errorhandler(404) 10 | def page_not_found(error): 11 | return render_template('404.html') 12 | 13 | 14 | @bp_error.app_errorhandler(500) 15 | def server_error(error): 16 | return render_template('500.html') -------------------------------------------------------------------------------- /guitarfan/static/FontAwesome/scss/font-awesome.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.1.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables"; 7 | @import "mixins"; 8 | @import "path"; 9 | @import "core"; 10 | @import "larger"; 11 | @import "fixed-width"; 12 | @import "list"; 13 | @import "bordered-pulled"; 14 | @import "spinning"; 15 | @import "rotated-flipped"; 16 | @import "stacked"; 17 | @import "icons"; 18 | -------------------------------------------------------------------------------- /guitarfan/extensions/flasklogin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from flask.ext.login import LoginManager 5 | 6 | from guitarfan.models.administrator import Administrator 7 | 8 | 9 | login_manager = LoginManager() 10 | login_manager.login_view = 'bp_admin_administrator.login' 11 | 12 | @login_manager.user_loader 13 | def load_user(id): 14 | try: 15 | administrator = Administrator.query.get(id) 16 | except Exception, e: 17 | administrator = None 18 | return administrator 19 | -------------------------------------------------------------------------------- /guitarfan/templates/site/coming.html: -------------------------------------------------------------------------------- 1 | {% extends "site/base.html" %} 2 | {% block navbar_search %}{% endblock %} 3 | {% block body %} 4 |
5 |
6 |
7 |
8 | 9 |

10 |
11 |
12 |
13 |
14 | {% endblock %} -------------------------------------------------------------------------------- /tabcrawler/tabcrawler/settings.py: -------------------------------------------------------------------------------- 1 | BOT_NAME = 'tabcrawler' 2 | 3 | SPIDER_MODULES = ['tabcrawler.spiders'] 4 | NEWSPIDER_MODULE = 'tabcrawler.spiders' 5 | DOWNLOAD_DELAY = 3 6 | ITEM_PIPELINES = ['scrapy.contrib.pipeline.images.ImagesPipeline'] 7 | IMAGES_STORE = '/Users/jinzemin/Desktop/GuitarFan/tabcrawler/tabs' 8 | 9 | # ITEM_PIPELINES = [ 10 | # 'tabcrawler.pipelines.ArtistPipeline', 11 | # ] 12 | 13 | # Crawl responsibly by identifying yourself (and your website) on the user-agent 14 | #USER_AGENT = 'tabcrawler (+http://www.yourdomain.com)' 15 | -------------------------------------------------------------------------------- /guitarfan/controlers/site/videos.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import math 5 | 6 | from flask import render_template, request, redirect, url_for, flash, Blueprint, jsonify, current_app 7 | from sqlalchemy import func, or_ 8 | 9 | from guitarfan.models import * 10 | from guitarfan.extensions.flasksqlalchemy import db 11 | 12 | bp_site_videos = Blueprint('bp_site_videos', __name__, template_folder="../../templates/site") 13 | 14 | 15 | @bp_site_videos.route('/videos') 16 | def videos(): 17 | return render_template('coming.html') -------------------------------------------------------------------------------- /guitarfan/controlers/site/courses.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import math 5 | 6 | from flask import render_template, request, redirect, url_for, flash, Blueprint, jsonify, current_app 7 | from sqlalchemy import func, or_ 8 | 9 | from guitarfan.models import * 10 | from guitarfan.extensions.flasksqlalchemy import db 11 | 12 | bp_site_courses = Blueprint('bp_site_courses', __name__, template_folder="../../templates/site") 13 | 14 | 15 | @bp_site_courses.route('/courses') 16 | def courses(): 17 | return render_template('coming.html') -------------------------------------------------------------------------------- /guitarfan/static/FontAwesome/less/font-awesome.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.1.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables.less"; 7 | @import "mixins.less"; 8 | @import "path.less"; 9 | @import "core.less"; 10 | @import "larger.less"; 11 | @import "fixed-width.less"; 12 | @import "list.less"; 13 | @import "bordered-pulled.less"; 14 | @import "spinning.less"; 15 | @import "rotated-flipped.less"; 16 | @import "stacked.less"; 17 | @import "icons.less"; 18 | -------------------------------------------------------------------------------- /guitarfan/templates/error/404.html: -------------------------------------------------------------------------------- 1 | {% extends "site/base.html" %} 2 | {% block navbar_search %}{% endblock %} 3 | {% block body %} 4 |
5 |
6 |
7 |
8 |

呃,你访问的页面或曲谱不存在 (⊙ˍ⊙),请继续搜索或 返回首页

9 | 10 |
11 |
12 |
13 |
14 | {% endblock %} -------------------------------------------------------------------------------- /guitarfan/controlers/admin/forms/tag.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from flask.ext.wtf import Form 5 | from wtforms.fields import TextField, HiddenField, SubmitField 6 | from wtforms.widgets import HiddenInput 7 | from wtforms.validators import Required 8 | 9 | from guitarfan.utilities import validator 10 | from guitarfan.models import * 11 | 12 | 13 | class TagFrom(Form): 14 | id = HiddenField(widget=HiddenInput()) 15 | name = TextField(u'Tag Name', validators=[Required(message=u'Tag name is required'), 16 | validator.Unique(Tag, Tag.name, message=u'The current tag is already in use')]) 17 | submit = SubmitField(u'Submit', id='submit') 18 | -------------------------------------------------------------------------------- /guitarfan/controlers/site/tabview.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import math 5 | 6 | from flask import render_template, request, redirect, url_for, flash, Blueprint, jsonify, current_app 7 | from sqlalchemy import func, or_ 8 | 9 | from guitarfan.models import * 10 | from guitarfan.extensions.flasksqlalchemy import db 11 | 12 | 13 | bp_site_tabview = Blueprint('bp_site_tabview', __name__, template_folder="../../templates/site") 14 | 15 | @bp_site_tabview.route('/tabview/') 16 | def tab_view(tab_id): 17 | tab = Tab.query.get(tab_id) 18 | db.engine.connect().execute("update tab set hits=hits+1 where id='%s'" % tab_id) 19 | return render_template('tabview.html', tab=tab) -------------------------------------------------------------------------------- /guitarfan/models/tag.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from sqlalchemy import func 5 | 6 | import time 7 | from guitarfan.extensions.flasksqlalchemy import db 8 | 9 | 10 | class Tag(db.Model): 11 | '''finger-style, classic songs, etude, new hot songs''' 12 | __tablename__ = 'tag' 13 | 14 | id = db.Column(db.String(50), primary_key=True, unique=True) 15 | name = db.Column(db.String, nullable=False) 16 | update_time = db.Column(db.String(20)) 17 | 18 | def __init__(self, id, name): 19 | self.id = id 20 | self.name = name 21 | self.update_time = time.strftime('%Y-%m-%d %H:%M:%S') 22 | 23 | def __repr__(self): 24 | return '' % self.name -------------------------------------------------------------------------------- /guitarfan/static/FontAwesome/less/path.less: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: ~"url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}')"; 7 | src: ~"url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype')", 8 | ~"url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff')", 9 | ~"url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype')", 10 | ~"url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg')"; 11 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 12 | font-weight: normal; 13 | font-style: normal; 14 | } 15 | -------------------------------------------------------------------------------- /guitarfan/static/FontAwesome/scss/_path.scss: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}'); 7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'), 8 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'), 9 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'), 10 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg'); 11 | //src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 12 | font-weight: normal; 13 | font-style: normal; 14 | } 15 | -------------------------------------------------------------------------------- /guitarfan/static/FontAwesome/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | .fa-icon-rotate(@degrees, @rotation) { 5 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation); 6 | -webkit-transform: rotate(@degrees); 7 | -moz-transform: rotate(@degrees); 8 | -ms-transform: rotate(@degrees); 9 | -o-transform: rotate(@degrees); 10 | transform: rotate(@degrees); 11 | } 12 | 13 | .fa-icon-flip(@horiz, @vert, @rotation) { 14 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation, mirror=1); 15 | -webkit-transform: scale(@horiz, @vert); 16 | -moz-transform: scale(@horiz, @vert); 17 | -ms-transform: scale(@horiz, @vert); 18 | -o-transform: scale(@horiz, @vert); 19 | transform: scale(@horiz, @vert); 20 | } 21 | -------------------------------------------------------------------------------- /guitarfan/static/FontAwesome/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | @mixin fa-icon-rotate($degrees, $rotation) { 5 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); 6 | -webkit-transform: rotate($degrees); 7 | -moz-transform: rotate($degrees); 8 | -ms-transform: rotate($degrees); 9 | -o-transform: rotate($degrees); 10 | transform: rotate($degrees); 11 | } 12 | 13 | @mixin fa-icon-flip($horiz, $vert, $rotation) { 14 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); 15 | -webkit-transform: scale($horiz, $vert); 16 | -moz-transform: scale($horiz, $vert); 17 | -ms-transform: scale($horiz, $vert); 18 | -o-transform: scale($horiz, $vert); 19 | transform: scale($horiz, $vert); 20 | } 21 | -------------------------------------------------------------------------------- /guitarfan/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from flask import Flask 5 | 6 | def create_app(config): 7 | # init Flask application object 8 | app = Flask(__name__) 9 | app.config.from_object(config) 10 | 11 | # init flask-login 12 | from guitarfan.extensions.flasklogin import login_manager 13 | login_manager.init_app(app) 14 | 15 | # init flask-sqlalchemy 16 | from guitarfan.extensions.flasksqlalchemy import db 17 | db.app = app # if without it, db query operation will throw exception in Form class 18 | db.init_app(app) 19 | 20 | # init flask-cache 21 | from guitarfan.extensions.flaskcache import cache 22 | cache.init_app(app) 23 | 24 | # register all blueprints 25 | import controlers 26 | controlers.Register_Blueprints(app) 27 | 28 | return app 29 | -------------------------------------------------------------------------------- /guitarfan/templates/error/500.html: -------------------------------------------------------------------------------- 1 | {% extends "site/base.html" %} 2 | {% block navbar_search %}{% endblock %} 3 | {% block body %} 4 |
5 |
6 |
7 |
8 | 9 |
10 |
11 |



12 |

啊啊啊究竟发生了什么好痛苦>_<,麻烦联系一下魂淡站长让他救救我吧~~

13 | 返回首页     14 | 联系站长 15 |
16 |
17 |
18 |
19 | {% endblock %} -------------------------------------------------------------------------------- /guitarfan/static/google-code-prettify/prettify.css: -------------------------------------------------------------------------------- 1 | .com { color: #93a1a1; } 2 | .lit { color: #195f91; } 3 | .pun, .opn, .clo { color: #93a1a1; } 4 | .fun { color: #dc322f; } 5 | .str, .atv { color: #D14; } 6 | .kwd, .prettyprint .tag { color: #1e347b; } 7 | .typ, .atn, .dec, .var { color: teal; } 8 | .pln { color: #48484c; } 9 | 10 | .prettyprint { 11 | padding: 8px; 12 | background-color: #f7f7f9; 13 | border: 1px solid #e1e1e8; 14 | } 15 | .prettyprint.linenums { 16 | -webkit-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0; 17 | -moz-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0; 18 | box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0; 19 | } 20 | 21 | /* Specify class=linenums on a pre to get line numbering */ 22 | ol.linenums { 23 | margin: 0 0 0 33px; /* IE indents via margin-left */ 24 | } 25 | ol.linenums li { 26 | padding-left: 12px; 27 | color: #bebec5; 28 | line-height: 20px; 29 | text-shadow: 0 1px 0 #fff; 30 | } -------------------------------------------------------------------------------- /guitarfan/static/fancybox/helpers/jquery.fancybox-thumbs.css: -------------------------------------------------------------------------------- 1 | #fancybox-thumbs { 2 | position: fixed; 3 | left: 0; 4 | width: 100%; 5 | overflow: hidden; 6 | z-index: 8050; 7 | } 8 | 9 | #fancybox-thumbs.bottom { 10 | bottom: 2px; 11 | } 12 | 13 | #fancybox-thumbs.top { 14 | top: 2px; 15 | } 16 | 17 | #fancybox-thumbs ul { 18 | position: relative; 19 | list-style: none; 20 | margin: 0; 21 | padding: 0; 22 | } 23 | 24 | #fancybox-thumbs ul li { 25 | float: left; 26 | padding: 1px; 27 | opacity: 0.5; 28 | } 29 | 30 | #fancybox-thumbs ul li.active { 31 | opacity: 0.75; 32 | padding: 0; 33 | border: 1px solid #fff; 34 | } 35 | 36 | #fancybox-thumbs ul li:hover { 37 | opacity: 1; 38 | } 39 | 40 | #fancybox-thumbs ul li a { 41 | display: block; 42 | position: relative; 43 | overflow: hidden; 44 | border: 1px solid #222; 45 | background: #111; 46 | outline: none; 47 | } 48 | 49 | #fancybox-thumbs ul li img { 50 | display: block; 51 | position: relative; 52 | border: 0; 53 | padding: 0; 54 | max-width: none; 55 | } -------------------------------------------------------------------------------- /guitarfan/controlers/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from admin.administrator import bp_admin_administrator 5 | from admin.artist import bp_admin_artist 6 | from admin.tag import bp_admin_tag 7 | from admin.tab import bp_admin_tab 8 | from admin.tabfile import bp_admin_tabfile 9 | from admin.data import bp_admin_data 10 | from site.index import bp_site_index 11 | from site.tabs import bp_site_tabs 12 | from site.tabview import bp_site_tabview 13 | from site.videos import bp_site_videos 14 | from site.courses import bp_site_courses 15 | from error import bp_error 16 | 17 | def Register_Blueprints(app): 18 | app.register_blueprint(bp_admin_administrator) 19 | app.register_blueprint(bp_admin_artist) 20 | app.register_blueprint(bp_admin_tag) 21 | app.register_blueprint(bp_admin_tab) 22 | app.register_blueprint(bp_admin_tabfile) 23 | app.register_blueprint(bp_admin_data) 24 | app.register_blueprint(bp_site_index) 25 | app.register_blueprint(bp_site_tabs) 26 | app.register_blueprint(bp_site_tabview) 27 | app.register_blueprint(bp_site_videos) 28 | app.register_blueprint(bp_site_courses) 29 | app.register_blueprint(bp_error) -------------------------------------------------------------------------------- /guitarfan/utilities/oshelper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | from flask import flash, current_app 6 | from settings import APP_PATH 7 | 8 | def get_abspath(path): 9 | path = path.lstrip('/') 10 | return os.path.join(APP_PATH, path) 11 | 12 | 13 | def get_tabfile_upload_abspath(): 14 | return get_abspath(current_app.config['TAB_FILE_FOLDER']) 15 | 16 | 17 | def get_artistphoto_upload_abspath(): 18 | return get_abspath(current_app.config['ARTIST_PHOTO_FOLDER']) 19 | 20 | 21 | def check_dir(path): 22 | path = path.strip() 23 | path = path.rstrip("\\") 24 | if not os.path.exists(path): 25 | os.makedirs(path) 26 | 27 | def upload_file(file, upload_path, new_filename): 28 | try: 29 | check_dir(upload_path) 30 | file_abspath = os.path.join(upload_path, new_filename) 31 | 32 | file.save(file_abspath) 33 | return file_abspath 34 | except Exception as e: 35 | flash(u'Upload file failed' + e.message + u'. Please try again!', 'warning') 36 | return '' 37 | 38 | 39 | def get_extension(fileName): 40 | filename, extension = os.path.splitext(fileName) 41 | return extension.lower() -------------------------------------------------------------------------------- /guitarfan/templates/site/popup_base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {% block body %}{% endblock %} 10 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (c) 2013 lowrain(jinzm1982@gmail.com). 5 | # 6 | # Created at 2013/07/18. 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in 16 | # all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | 27 | VERSION = '0.8' 28 | -------------------------------------------------------------------------------- /guitarfan/templates/admin/dashboard/base.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block active_tabs %}{% endblock %} 4 | {% block active_dashboard %}active{% endblock %} 5 | 6 | {% block sidebar %} 7 |
8 | 23 |
24 | {% endblock %} -------------------------------------------------------------------------------- /guitarfan/models/administrator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from werkzeug.security import generate_password_hash, check_password_hash 5 | 6 | from guitarfan.extensions.flasksqlalchemy import db 7 | 8 | 9 | class Administrator(db.Model): 10 | __tablename__ = 'administrator' 11 | 12 | id = db.Column(db.String(50), primary_key=True, unique=True) 13 | name = db.Column(db.String(50), nullable=False) 14 | email = db.Column(db.String, nullable=False) 15 | password = db.Column(db.String, nullable=False) 16 | status = db.Column(db.Integer, nullable=False, default=1) 17 | 18 | def __init__(self, id, name, email, password, status): 19 | self.id = id 20 | self.name = name 21 | self.email = email 22 | self.password = password 23 | self.status = status 24 | 25 | def check_password(self, password): 26 | return check_password_hash(self.password, password) 27 | 28 | def update_password(self, new_password): 29 | self.password = generate_password_hash(new_password, salt_length=8) 30 | 31 | def is_authenticated(self): 32 | return True 33 | 34 | def is_active(self): 35 | return self.status 36 | 37 | def is_anonymous(self): 38 | return False 39 | 40 | def get_id(self): 41 | return self.id 42 | 43 | def __repr__(self): 44 | return '' % self.name -------------------------------------------------------------------------------- /guitarfan/templates/admin/tabs/base.html: -------------------------------------------------------------------------------- 1 | {%- extends "admin/base.html" -%} 2 | 3 | {%- block active_tabs %}active{% endblock -%} 4 | 5 | {%- block sidebar -%} 6 |
7 | 27 |
28 | {%- endblock -%} -------------------------------------------------------------------------------- /guitarfan/static/jqcloud/jqcloud.css: -------------------------------------------------------------------------------- 1 | /* fonts */ 2 | 3 | div.jqcloud { 4 | font-family: "Helvetica", "Arial", sans-serif; 5 | font-size: 10px; 6 | line-height: normal; 7 | } 8 | 9 | div.jqcloud a { 10 | font-size: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | div.jqcloud span.w10 { font-size: 550%; } 15 | div.jqcloud span.w9 { font-size: 500%; } 16 | div.jqcloud span.w8 { font-size: 450%; } 17 | div.jqcloud span.w7 { font-size: 400%; } 18 | div.jqcloud span.w6 { font-size: 350%; } 19 | div.jqcloud span.w5 { font-size: 300%; } 20 | div.jqcloud span.w4 { font-size: 250%; } 21 | div.jqcloud span.w3 { font-size: 200%; } 22 | div.jqcloud span.w2 { font-size: 150%; } 23 | div.jqcloud span.w1 { font-size: 100%; } 24 | 25 | /* colors */ 26 | 27 | div.jqcloud { color: #09f; } 28 | div.jqcloud a { color: inherit; } 29 | div.jqcloud a:hover { color: #0df; } 30 | div.jqcloud a:hover { color: #0cf; } 31 | div.jqcloud span.w10 { color: #0cf; } 32 | div.jqcloud span.w9 { color: #0cf; } 33 | div.jqcloud span.w8 { color: #0cf; } 34 | div.jqcloud span.w7 { color: #39d; } 35 | div.jqcloud span.w6 { color: #90c5f0; } 36 | div.jqcloud span.w5 { color: #90a0dd; } 37 | div.jqcloud span.w4 { color: #90c5f0; } 38 | div.jqcloud span.w3 { color: #a0ddff; } 39 | div.jqcloud span.w2 { color: #99ccee; } 40 | div.jqcloud span.w1 { color: #aab5f0; } 41 | 42 | /* layout */ 43 | 44 | div.jqcloud { 45 | overflow: hidden; 46 | position: relative; 47 | } 48 | 49 | div.jqcloud span { padding: 0; } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Guitar123 version 0.8.5 2 | === 3 | 4 | Guitar tabs web app for guitar fans, powered by Python/Flask 5 | 6 | # Features 7 | * Guitar tabs search and browse 8 | * Easy to use for end user and they enjoy it when play with it 9 | * Adapt to common browers(ie, chrome, safari, firefox) and devices(desktop, mobile) 10 | 11 | # Technology 12 | * Language: Python 2.7.5 13 | * Framework: Flask + Blueprint + Flask-Login + Flask-WTF 14 | * Architecture: MVC + RESTful 15 | * UI: Responsive by Bootstrap2/3 + bootflat + FontAwesome + jQuery + jQuery.dataTables + FineUploader + select2 + fancyBox + animate 16 | * Data: SQLAlchemy + SQLite (update to mysql in future) 17 | * IDE & Tools: PyCharm, Fireworks, Photoshop, Terminal, Git, SQLiteManager 18 | 19 | # Modules 20 | * Index page: search box and featured tabs 21 | * Tabs List page: list all tabs by artists, styles, tags and search keyword 22 | * Videos: recommend great guitar videos (not implemented yet) 23 | * Courses: share useful guitar courses materials (not implemented yet) 24 | * Tools -- tuning, chord search, site/app recommended... (not implemented yet) 25 | * Backend Admin: data management, data crawling, application status monitor, logs view, database backup and so on... 26 | 27 | # Site Url 28 | http://www.guitar123.net 29 | 30 | # Contact 31 | * Email: jinzm1982@gmail.com 32 | * QQ: 86626118 33 | * Skype: jinzemin 34 | * LinkedIn: http://cn.linkedin.com/in/lowrain 35 | * Sina Weibo: http://weibo.com/jinzemin 36 | * Blog: www.lowrain.com 37 | -------------------------------------------------------------------------------- /guitarfan/controlers/admin/forms/tab.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from flask.ext.wtf import Form 5 | from wtforms.fields import TextField, HiddenField, SubmitField, SelectField 6 | from wtforms.widgets import HiddenInput 7 | from wtforms.ext.sqlalchemy.fields import QuerySelectMultipleField 8 | from wtforms.validators import Required, URL, Optional 9 | 10 | from guitarfan.models import * 11 | 12 | 13 | class HackQuerySelectMultipleField(QuerySelectMultipleField): 14 | def iter_choices(self): 15 | for pk, obj in self._get_object_list(): 16 | yield (pk, self.get_label(obj), obj.id in [o.id for o in self.data]) 17 | 18 | 19 | class TabFrom(Form): 20 | id = HiddenField(widget=HiddenInput()) 21 | tab_title = TextField(u'Title', validators=[Required(message=u'Title is required')]) 22 | artist = TextField(u'Artist', validators=[Required(message=u'Artist is required')]) 23 | format = SelectField(u'Format', choices=TabFormat.get_described_items(), default=1, coerce=int) 24 | difficulty = SelectField(u'Difficulty Degree', choices=DifficultyDegree.get_described_items(), default=1, coerce=int) 25 | style = SelectField(u'Music Style', choices=MusicStyle.get_described_items(), default=1, coerce=int) 26 | tags = HackQuerySelectMultipleField(u'Tags', query_factory=Tag.query.all, get_label='name') 27 | audio_url = TextField(u'Audio Url', validators=[Optional(), URL(message=u'Url is not correct')]) 28 | submit = SubmitField(u'Submit', id='submit') 29 | -------------------------------------------------------------------------------- /guitarfan/controlers/admin/forms/artist.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import string 5 | 6 | from wtforms.fields import TextField, HiddenField, SubmitField, SelectField, FileField 7 | from wtforms.widgets import HiddenInput 8 | from wtforms.validators import Required 9 | from flask.ext.wtf import Form 10 | 11 | from guitarfan.utilities import validator 12 | from guitarfan.models.artist import Artist 13 | from guitarfan.models.enums import * 14 | 15 | 16 | def get_letter_choices(): 17 | letters = string.uppercase[:26] 18 | letterChoices = [] 19 | for l in letters: 20 | letterChoices.append((l, l)) 21 | letterChoices.append(('0-9', '0-9')) 22 | letterChoices.append(('Other', 'Other')) 23 | return letterChoices 24 | 25 | 26 | class ArtistFrom(Form): 27 | id = HiddenField(widget=HiddenInput()) 28 | name = TextField(u'Name', description=u'Unrepeatable.', 29 | validators=[Required(message=u'Name is required'), validator.Unique(Artist, Artist.name, message=u'The current name is already in use')]) 30 | letter = SelectField(u'Letter', choices=get_letter_choices()) 31 | photo = FileField(u'Photo', description=u'Upload image file, 120x120.', 32 | validators=[validator.AllowedPhotoFile(Artist, Artist.photo, message=u'Upload photo file is not available format')]) 33 | region = SelectField(u'Region', choices=ArtistRegion.get_described_items(), default=1, coerce=int) 34 | category = SelectField(u'Category', choices=ArtistCategory.get_described_items(), default=1, coerce=int) 35 | submit = SubmitField(u'Submit', id='submit') 36 | -------------------------------------------------------------------------------- /guitarfan/templates/admin/macro.html: -------------------------------------------------------------------------------- 1 | {% macro feedback_message() -%} 2 | {% with flash_messages = get_flashed_messages(with_categories=true) %} 3 | {% if flash_messages %} 4 | {% for category, message in flash_messages %} 5 |
6 | × 7 | {% for msg in message.split(';') %} 8 | {{ msg }}
9 | {% endfor %} 10 |
11 | {% endfor %} 12 | {% endif %} 13 | {% endwith %} 14 | {%- endmacro %} 15 | 16 | {% macro get_adverse_desc(status) -%} 17 | {% if status == 0 %} 18 | Enable 19 | {% elif status == 1 %} 20 | Disable 21 | {% endif %} 22 | {%- endmacro %} 23 | 24 | {% macro get_status(status) -%} 25 | {% if status == False %} 26 | 27 | {% elif status == True %} 28 | 29 | {% endif %} 30 | {%- endmacro %} 31 | 32 | {% macro create_wtf_field(field) -%} 33 |
34 | 35 |
36 | {{ field(**kwargs) }} 37 | {%- if field.description -%} 38 | {{ field.description|safe }} 39 | {%- endif %} 40 |
41 |
42 | {%- endmacro %} -------------------------------------------------------------------------------- /guitarfan/templates/site/tabview.html: -------------------------------------------------------------------------------- 1 | {% if 'popup' in request.args %} 2 | {% extends "site/popup_base.html" %} 3 | {% else %} 4 | {% extends "site/base.html" %} 5 | {% block title %} - {{ tab.artist.name }}:{{ tab.title }}{% endblock %} 6 | {% block active_navbar_tabview %}active{% endblock %} 7 | {% block landing %} 8 |
9 | {% endblock %} 10 | {% endif %} 11 | {% block body %} 12 |
13 |
14 |
15 |
16 | {# {%- if 'popup' in request.args -%}#} 17 | {# #} 24 | {# {%- else -%}#} 25 | {# #} 26 | {# {%- endif -%}#} 27 | {%- for tabfile in tab.tabfiles -%} 28 | 29 | {% endfor %} 30 |
31 |
32 |
33 |
34 | 35 | {% endblock %} -------------------------------------------------------------------------------- /guitarfan/controlers/site/index.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from flask import render_template, request, redirect, url_for, flash, Blueprint, jsonify, current_app 5 | from sqlalchemy import func 6 | 7 | from guitarfan.models import * 8 | from guitarfan.extensions.flasksqlalchemy import db 9 | from guitarfan.extensions.flaskcache import cache 10 | 11 | 12 | bp_site_index = Blueprint('bp_site_index', __name__, template_folder="../../templates/site") 13 | 14 | 15 | @bp_site_index.route('/') 16 | @bp_site_index.route('/index') 17 | 18 | def index(): 19 | hot_tabs = Tab.query.order_by(Tab.hits.desc()).limit(12) 20 | new_tabs = Tab.query.order_by(Tab.update_time.desc()).limit(12) 21 | return render_template('index.html', hot_tabs=hot_tabs, new_tabs=new_tabs) 22 | 23 | 24 | @bp_site_index.route('/tagcloud.json') 25 | @cache.cached(3600, key_prefix='tag_cloud_json') 26 | def tag_cloud_json(): 27 | tags = [] 28 | for tag_id, tag_name, tab_count in db.session.query(Tag.id, Tag.name, func.count(Tab.id)).join(Tab, Tag.tabs).group_by(Tag.id): 29 | tags.append({'tagId': tag_id, 'tagName': tag_name, 'count': tab_count}) 30 | return jsonify(tags=tags) 31 | 32 | 33 | @bp_site_index.route('/stylecloud.json') 34 | @cache.cached(3600, key_prefix='style_cloud_json') 35 | def style_cloud_json(): 36 | styles = [] 37 | for style_id, tab_count in db.session.query(Tab.style_id, func.count(Tab.id)).group_by(Tab.style_id): 38 | styles.append({'styleId': style_id, 'styleName': MusicStyle.get_item_text(style_id), 'count': tab_count}) 39 | return jsonify(styles=styles) 40 | 41 | @bp_site_index.route('/robots.txt') 42 | def robots_txt(): 43 | return """ 44 | 45 | 46 |
User-agent: *
47 | Crawl-delay: 10
48 | 
49 | Disallow: /admin
50 | 
51 | 52 | """ -------------------------------------------------------------------------------- /guitarfan/models/tabfile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import json 4 | from flask import current_app 5 | 6 | from time import strftime 7 | from guitarfan.extensions.flasksqlalchemy import db 8 | from guitarfan.utilities.oshelper import * 9 | 10 | 11 | # def dump_datetime(value): 12 | # """Deserialize datetime object into string form for JSON processing.""" 13 | # if value is None: 14 | # return None 15 | # return [value.strftime("%Y-%m-%d"), value.strftime("%H:%M:%S")] 16 | 17 | 18 | 19 | class TabFile(db.Model): 20 | __tablename__ = 'tabfile' 21 | 22 | id = db.Column(db.String(50), primary_key=True, unique=True) 23 | filename = db.Column(db.String(200), nullable=False) 24 | update_time = db.Column(db.String(20), nullable=False) 25 | tab_id = db.Column(db.String(50), db.ForeignKey('tab.id', ondelete='CASCADE')) 26 | 27 | def __init__(self, id, tab_id, filename): 28 | self.id = id 29 | self.tab_id = tab_id 30 | self.filename = filename 31 | self.update_time = strftime('%Y-%m-%d %H:%M:%S') 32 | 33 | def __repr__(self): 34 | return '' % (self.id, self.file_basename) 35 | 36 | @property 37 | def file_relpath(self): 38 | return os.path.join(current_app.config['TAB_FILE_FOLDER'], self.tab_id, self.filename) 39 | 40 | @property 41 | def file_abspath(self): 42 | return os.path.join(get_tabfile_upload_abspath(), self.tab_id, self.filename) 43 | 44 | @property 45 | def file_basename(self): 46 | return self.filename.split('/')[-1] 47 | 48 | @property 49 | def serialize(self): 50 | """Return object data in easily serializeable format""" 51 | return {'id': self.id, 52 | 'tab_id': self.tab_id, 53 | 'update_time': self.update_time, 54 | 'file_basename': self.file_basename, 55 | 'file_relpath': self.file_relpath} 56 | -------------------------------------------------------------------------------- /guitarfan/templates/admin/login.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/base.html" %} 2 | 3 | {% block style %} 4 | 36 | {% endblock %} 37 | 38 | {% block body %} 39 |
40 | 49 |
50 | 51 | 52 | 57 | {% endblock %} 58 | -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (c) 2013 lowrain(jinzm1982@gmail.com). 5 | # 6 | # Created at 2013/07/18. 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in 16 | # all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | import os 27 | # Path Config 28 | APP_PATH = os.path.dirname(os.path.abspath(__file__)) + '/guitarfan' 29 | 30 | # Database Config 31 | SQLALCHEMY_DATABASE_URI = 'sqlite:///' + APP_PATH + '/data/sqlite.db' 32 | 33 | # Site Config 34 | PORT = 8888 35 | HOST = '127.0.0.1' 36 | SESSION_PROTECTION = 'strong' 37 | SECRET_KEY = 'b\n\x90\\\x13\x044Q\x9a>\x99v\x08\x8ez[\x11 \x82\x83' 38 | DEBUG = True 39 | APP_NAME = 'Guitar123' 40 | SITE_URL = 'http://www.guitar123.net' 41 | HOST_ADDR = '158.199.192.140' 42 | 43 | # Pagination 44 | TABS_PER_PAGE = 15 45 | 46 | # Uplods Config 47 | ARTIST_PHOTO_FOLDER = '/static/artists' 48 | TAB_FILE_FOLDER = '/static/tabs' 49 | ARTIST_PHOTO_ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif']) 50 | TAB_FILE_ALLOWED_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif'] 51 | FILE_UPLOAD_MAX_MEMORY_SIZE = '10485760' 52 | 53 | # CSRF Protection 54 | WTF_CSRF_ENABLED = True -------------------------------------------------------------------------------- /guitarfan/utilities/validator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from flask import current_app 5 | from wtforms.validators import ValidationError 6 | 7 | def catch_errors(errors): 8 | messages = '' 9 | if errors: 10 | for (field, errors) in errors.items()[::-1]: 11 | for error in errors: 12 | messages = '%s;%s' % (messages, error) 13 | 14 | return messages[1:] if messages else None 15 | 16 | 17 | class Unique(object): 18 | """ validator that checks field uniqueness """ 19 | def __init__(self, model, field, message=None): 20 | self.model = model 21 | self.field = field 22 | self.message = message if message else u'The current element value is already in use' 23 | 24 | def __call__(self, form, field): 25 | check = self.model.query.filter(self.field == field.data).first() 26 | id = form.id.data if 'id' in form else None 27 | if check and (id is None or id != check.id): 28 | raise ValidationError(self.message) 29 | 30 | 31 | class UnChange(object): 32 | """ validator that checks field unchange """ 33 | def __init__(self, model, field, message=None): 34 | self.model = model 35 | self.field = field 36 | self.message = message if message else u'The current element can not be modified' 37 | 38 | def __call__(self, form, field): 39 | check = self.model.query.filter_by(id=form.id.data).first() 40 | if check is not None and field.data != getattr(check, self.field): 41 | raise ValidationError(self.message) 42 | 43 | 44 | class AllowedPhotoFile(object): 45 | """ validator that checks upload file """ 46 | def __init__(self, model, field, message=None): 47 | self.model = model 48 | self.field = field 49 | self.message = message if message else u'Upload photo file is not available format' 50 | 51 | def __call__(self, form, field): 52 | if field.data: 53 | allowed_extenstions = current_app.config['ARTIST_PHOTO_ALLOWED_EXTENSIONS'] 54 | filename = field.data.filename 55 | if not ('.' in filename and filename.rsplit('.', 1)[1] in allowed_extenstions): 56 | raise ValidationError(self.message) -------------------------------------------------------------------------------- /guitarfan/static/bootflat/js/html5shiv.js: -------------------------------------------------------------------------------- 1 | /* 2 | HTML5 Shiv v3.6.2pre | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | (function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag(); 5 | a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/\w+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x"; 6 | c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode|| 7 | "undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",version:"3.6.2pre",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);if(g)return a.createDocumentFragment(); 8 | for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d a, 42 | .tabs-below .nav-tabs-square li > a, 43 | .tabs-right .nav-tabs-square li > a, 44 | .tabs-left .nav-tabs-square li > a, 45 | .nav-tabs-square .dropdown-menu, 46 | .tabs-below .nav-tabs-square .dropdown-menu, 47 | .nav-pills-square li a, 48 | .nav-pills-square .dropdown-menu, 49 | .navbar-square, 50 | .navbar-square .dropdown-menu, 51 | .pagination-square li:first-child a, 52 | .pagination-square li:first-child span, 53 | .pagination-square li:last-child a, 54 | .pagination-square li:last-child span, 55 | .pager-square li a:hover, 56 | .pager-square li a:focus, 57 | .panel-group-square .panel, 58 | .panel-group-square .panel-heading, 59 | .panel-group-square .panel-body, 60 | /*------------------------------------*\ 61 | $extend-square 62 | \*------------------------------------*/ 63 | .breadcrumb-arrow-square li:first-child a { 64 | -webkit-border-radius: 0; 65 | -moz-border-radius: 0; 66 | border-radius: 0; 67 | } 68 | 69 | 70 | -------------------------------------------------------------------------------- /guitarfan/utilities/pinyin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import unicode_literals 4 | 5 | import os.path 6 | 7 | 8 | class Pinyin(object): 9 | """translate chinese hanzi to pinyin by python, inspired by flyerhzm’s 10 | `chinese\_pinyin`_ gem 11 | 12 | usage 13 | ----- 14 | :: 15 | In [1]: from xpinyin import Pinyin 16 | In [2]: p = Pinyin() 17 | In [3]: p.get_pinyin(u"上海") 18 | Out[3]: 'shang-hai' 19 | In [4]: p.get_initials(u"上") 20 | Out[4]: 'S' 21 | 请输入utf8编码汉字 22 | .. _chinese\_pinyin: https://github.com/flyerhzm/chinese_pinyin 23 | """ 24 | 25 | data_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 26 | 'Mandarin.dat') 27 | 28 | def __init__(self, data_path=data_path): 29 | self.dict = {} 30 | for line in open(data_path): 31 | k, v = line.split('\t') 32 | self.dict[k] = v 33 | 34 | def get_pinyin(self, chars=u'你好', splitter=u''): 35 | result = [] 36 | flag = 1 37 | for char in chars: 38 | key = "%X" % ord(char) 39 | try: 40 | result.append( 41 | self.dict[key].split(" ")[0].strip()[:-1].lower()) 42 | flag = 1 43 | except KeyError: 44 | if flag: 45 | result.append(char) 46 | else: 47 | result[-1] += char 48 | flag = 0 49 | 50 | return splitter.join(result) 51 | 52 | def get_initial(self, char=u'你'): 53 | try: 54 | return self.dict["%X" % ord(char)].split(" ")[0][0] 55 | except KeyError: 56 | return char 57 | 58 | def get_initials(self, chars=u'你好', splitter=u''): 59 | result = [] 60 | flag = 1 61 | for char in chars: 62 | try: 63 | result.append(self.dict["%X" % ord(char)].split(" ")[0][0]) 64 | flag = 1 65 | except KeyError: 66 | if flag: 67 | result.append(char) 68 | else: 69 | result[-1] += char 70 | 71 | return splitter.join(result) 72 | 73 | 74 | 75 | 76 | if __name__ == "__main__": 77 | xpinyin = Pinyin() 78 | string = "时间都去哪了.png" 79 | print "in: %s" % string 80 | print "out: %s" % xpinyin.get_pinyin(string, '-') -------------------------------------------------------------------------------- /guitarfan/models/enums.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import operator 5 | 6 | 7 | # domain enum classes 8 | class EnumBase(object): 9 | _descriptions = {} 10 | 11 | @classmethod 12 | def get_items(cls): 13 | # change all enum items to sorted list 14 | # [(1,'aa'), (2,'bb'), (3,'cc')] 15 | items_dict = dict((v, k) for k, v in cls.__dict__.items() if not k.startswith('_') and not k.startswith('get_')) 16 | sorted_list = sorted(items_dict.iteritems(), key=operator.itemgetter(0)) 17 | return sorted_list 18 | 19 | @classmethod 20 | def get_described_items(cls): 21 | # get described enum items list which is for displaying to end user 22 | # [(1,u'描述1'), (2,u'描述2'), (3,u'描述3')] 23 | items = cls.get_items() 24 | new_items = [] 25 | for item in items: 26 | new_items.append((item[0], cls._descriptions[item[0]])) 27 | return new_items 28 | 29 | @classmethod 30 | # get the corresponding text for given enum item 31 | # band ==> u‘乐队' 32 | def get_item_text(cls, item_value): 33 | return cls._descriptions[item_value] 34 | 35 | 36 | class ArtistCategory(EnumBase): 37 | male = 1 38 | female = 2 39 | group = 3 40 | band = 4 41 | other = 100 42 | 43 | _descriptions = {male: u'男艺人', female: u'女艺人', group: u'组合', band: u'乐队', other: u'其他'} 44 | 45 | 46 | class ArtistRegion(EnumBase): 47 | hl = 1 48 | ht = 2 49 | jk = 3 50 | ue = 4 51 | other = 100 52 | 53 | _descriptions = {hl: u'内地', ht: u'港台', jk: u'日韩', ue: u'欧美', other: u'其他'} 54 | 55 | 56 | class MusicStyle(EnumBase): 57 | pop = 1 58 | rock = 2 59 | fork = 3 60 | rnb = 4 61 | blues = 5 62 | classic = 6 63 | country = 7 64 | jazz = 8 65 | child = 9 66 | national = 10 67 | other = 100 68 | 69 | _descriptions = {pop: u'流行', rock: u'摇滚', fork: u'民谣', rnb: u'R&B', blues: u'蓝调', classic: u'古典', country:u'乡村', 70 | jazz: u'爵士', child: u'儿歌', national: u'民族', other: u'其他'} 71 | 72 | 73 | class DifficultyDegree(EnumBase): 74 | beginner = 1 75 | intermediate = 2 76 | advanced = 3 77 | 78 | _descriptions = {beginner: u'入门', intermediate: u'中级', advanced: u'高级'} 79 | 80 | 81 | class TabFormat(EnumBase): 82 | img = 1 83 | txt = 2 84 | gtp = 3 85 | 86 | _descriptions = {img: u'图片', txt: u'文本', gtp: u'GTP'} -------------------------------------------------------------------------------- /guitarfan/controlers/admin/tag.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from uuid import uuid4 5 | 6 | from flask import render_template, request, redirect, url_for, flash, Blueprint 7 | from flask.ext.login import login_required 8 | 9 | from guitarfan.extensions.flasksqlalchemy import db 10 | from guitarfan.models import * 11 | from forms.tag import * 12 | 13 | 14 | bp_admin_tag = Blueprint('bp_admin_tag', __name__, template_folder="../../templates/admin/tabs") 15 | 16 | 17 | @bp_admin_tag.route('/admin/tags') 18 | @login_required 19 | def list(): 20 | tags = Tag.query.all() 21 | return render_template('tag_management.html', action='list', tags=tags) 22 | 23 | 24 | @bp_admin_tag.route('/admin/tags/add', methods=['GET', 'POST']) 25 | @login_required 26 | def add(): 27 | form = TagFrom() 28 | if request.method == 'GET': 29 | return render_template('tag_management.html', action='add', form=form) 30 | elif request.method == 'POST': 31 | if form.validate_on_submit(): 32 | tag = Tag(str(uuid4()), form.name.data) 33 | db.session.add(tag) 34 | db.session.commit() 35 | flash(u'Add new tag success', 'success') 36 | return redirect(url_for('bp_admin_tag.list')) 37 | else: 38 | flash(validator.catch_errors(form.errors), 'error') 39 | return render_template('tag_management.html', action='add', form=form) 40 | 41 | 42 | @bp_admin_tag.route('/admin/tags/', methods=['GET', 'POST']) 43 | @login_required 44 | def edit(id): 45 | tag = Tag.query.get(id) 46 | form = TagFrom(id=tag.id, name=tag.name) 47 | if request.method == 'GET': 48 | return render_template('tag_management.html', action='edit', form=form) 49 | elif request.method == 'POST': 50 | if form.validate_on_submit(): 51 | tag.name = form.name.data 52 | db.session.commit() 53 | flash(u'Update tag success', 'success') 54 | return redirect(url_for('bp_admin_tag.edit', id=id)) 55 | else: 56 | flash(validator.catch_errors(form.errors), 'error') 57 | return render_template('tab_management.html', action='edit', form=form) 58 | 59 | 60 | @bp_admin_tag.route('/admin/tabgs', methods=['DELETE']) 61 | @login_required 62 | def delete(): 63 | tag_id = request.values['id'] 64 | tag = Tag.query.filter_by(id=tag_id).first() 65 | db.session.delete(tag) 66 | db.session.commit() 67 | return 'success' -------------------------------------------------------------------------------- /guitarfan/models/artist.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import time 5 | import os 6 | from flask import url_for, current_app 7 | from guitarfan.extensions.flasksqlalchemy import db 8 | import guitarfan.utilities.oshelper as oshelper 9 | from enums import * 10 | 11 | class Artist(db.Model): 12 | __tablename__ = 'artist' 13 | 14 | id = db.Column(db.String(50), primary_key=True, unique=True) 15 | name = db.Column(db.String(50), nullable=False) 16 | letter = db.Column(db.String(5), nullable=False, index=True) 17 | photo = db.Column(db.String) 18 | region_id = db.Column(db.Integer, nullable=False, default=1) 19 | category_id = db.Column(db.Integer, nullable=False, default=1) 20 | update_time = db.Column(db.String(20), nullable=False, index=True) 21 | tabs = db.relationship('Tab', backref='artist', cascade='all,delete-orphan', lazy='dynamic') 22 | 23 | def __init__(self, id, name, letter, photo, region_id, category_id): 24 | self.id = id 25 | self.name = name 26 | self.letter = letter 27 | self.photo = photo 28 | self.category_id = category_id 29 | self.region_id = region_id 30 | self.update_time = time.strftime('%Y-%m-%d %H:%M:%S') 31 | 32 | def __repr__(self): 33 | return '' % self.name 34 | 35 | @property 36 | def region_text(self): 37 | return ArtistRegion.get_item_text(self.region_id) 38 | 39 | @property 40 | def category_text(self): 41 | return ArtistCategory.get_item_text(self.category_id) 42 | 43 | @property 44 | def photo_relative_path(self): 45 | nophoto_path = url_for('static', filename='images/nophoto.png') 46 | 47 | if self.photo == '': 48 | return nophoto_path 49 | 50 | photo_path = os.path.join(current_app.config['ARTIST_PHOTO_FOLDER'], self.photo) 51 | if os.path.isfile(oshelper.get_abspath(photo_path)): 52 | return photo_path 53 | else: 54 | return nophoto_path 55 | 56 | @property 57 | def serialize(self): 58 | """Return object data in easily serializeable format""" 59 | return {'id': self.id, 60 | 'name': self.name, 61 | 'letter': self.letter, 62 | 'category_id': self.category_id, 63 | 'category_text': self.category_text, 64 | 'region_id': self.region_id, 65 | 'region_text': self.region_text, 66 | 'photo_relative_path': self.photo_relative_path, 67 | 'update_time': self.update_time} 68 | -------------------------------------------------------------------------------- /guitarfan/controlers/admin/tabfile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | from uuid import uuid4 6 | 7 | from flask import render_template, request, Blueprint, jsonify 8 | from flask.ext.login import login_required 9 | 10 | from guitarfan.models import * 11 | from guitarfan.extensions.flasksqlalchemy import db 12 | from forms.tab import * 13 | from guitarfan.utilities.oshelper import * 14 | from guitarfan.utilities.qqFileUploader import qqFileUploader 15 | 16 | 17 | bp_admin_tabfile = Blueprint('bp_admin_tabfile', __name__, template_folder="../../templates/admin/tabs") 18 | 19 | 20 | @bp_admin_tabfile.route('/admin/tabfiles/', methods=['GET', 'PUT']) 21 | @login_required 22 | def edit(tab_id): 23 | tab = Tab.query.get(tab_id) 24 | if request.method == 'GET': 25 | if 'show_wizard' in request.args: 26 | return render_template('tabfile_edit.html', tab=tab, show_wizard=request.args['show_wizard']) 27 | else: 28 | return render_template('tabfile_edit.html', tab=tab) 29 | elif request.method == 'PUT': 30 | tabfile = TabFile(str(uuid4()), tab_id, request.form['filename']) 31 | db.session.add(tabfile) 32 | db.session.commit() 33 | tabfiles = TabFile.query.filter_by(tab_id=tab_id).all() 34 | return jsonify(tabfiles=[tabfile.serialize for tabfile in tabfiles]) 35 | 36 | 37 | @bp_admin_tabfile.route('/admin/tabfiles', methods=['DELETE']) 38 | @login_required 39 | def delete(): 40 | tabfile = TabFile.query.get(request.values['id']) 41 | db.session.delete(tabfile) 42 | db.session.commit() 43 | try: 44 | if os.path.isfile(tabfile.file_abspath): 45 | os.remove(tabfile.file_abspath) 46 | except Exception as e: 47 | return '%s: %s' % ('error:', e.message) 48 | return 'success' 49 | 50 | 51 | # TODO move this method to API controller when implementing API 52 | @bp_admin_tabfile.route('/admin/tabfiles.json') 53 | @login_required 54 | def list_json(): 55 | if 'tab_id' in request.args: 56 | tabfiles = TabFile.query.filter_by(tab_id=request.args['tab_id']).order_by(TabFile.filename.asc()) 57 | return jsonify(tabfiles=[tabfile.serialize for tabfile in tabfiles]) 58 | else: 59 | return jsonify(tabfiles=[]) 60 | 61 | 62 | @bp_admin_tabfile.route('/admin/tabfiles/upload/', methods=['POST']) 63 | @login_required 64 | def upload(tab_id): 65 | if request.method == 'POST': 66 | uploader = qqFileUploader(request, os.path.join(get_tabfile_upload_abspath(), str(tab_id)), 67 | current_app.config['TAB_FILE_ALLOWED_EXTENSIONS']) 68 | return uploader.handleUpload() -------------------------------------------------------------------------------- /guitarfan/static/js/jquery.highlight.js: -------------------------------------------------------------------------------- 1 | jQuery.extend({ 2 | highlight: function (node, re, nodeName, className) { 3 | if (node.nodeType === 3) { 4 | var match = node.data.match(re); 5 | if (match) { 6 | var highlight = document.createElement(nodeName || 'span'); 7 | highlight.className = className || 'highlight'; 8 | var wordNode = node.splitText(match.index); 9 | wordNode.splitText(match[0].length); 10 | var wordClone = wordNode.cloneNode(true); 11 | highlight.appendChild(wordClone); 12 | wordNode.parentNode.replaceChild(highlight, wordNode); 13 | return 1; //skip added node in parent 14 | } 15 | } else if ((node.nodeType === 1 && node.childNodes) && // only element nodes that have children 16 | !/(script|style)/i.test(node.tagName) && // ignore script and style nodes 17 | !(node.tagName === nodeName.toUpperCase() && node.className === className)) { // skip if already highlighted 18 | for (var i = 0; i < node.childNodes.length; i++) { 19 | i += jQuery.highlight(node.childNodes[i], re, nodeName, className); 20 | } 21 | } 22 | return 0; 23 | } 24 | }); 25 | 26 | jQuery.fn.unhighlight = function (options) { 27 | var settings = { className: 'highlight', element: 'span' }; 28 | jQuery.extend(settings, options); 29 | 30 | return this.find(settings.element + "." + settings.className).each(function () { 31 | var parent = this.parentNode; 32 | parent.replaceChild(this.firstChild, this); 33 | parent.normalize(); 34 | }).end(); 35 | }; 36 | 37 | jQuery.fn.highlight = function (words, options) { 38 | var settings = { className: 'highlight', element: 'span', caseSensitive: false, wordsOnly: false }; 39 | jQuery.extend(settings, options); 40 | 41 | if (words.constructor === String) { 42 | words = [words]; 43 | } 44 | words = jQuery.grep(words, function(word, i){ 45 | return word != ''; 46 | }); 47 | words = jQuery.map(words, function(word, i) { 48 | return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); 49 | }); 50 | if (words.length == 0) { return this; }; 51 | 52 | var flag = settings.caseSensitive ? "" : "i"; 53 | var pattern = "(" + words.join("|") + ")"; 54 | if (settings.wordsOnly) { 55 | pattern = "\\b" + pattern + "\\b"; 56 | } 57 | var re = new RegExp(pattern, flag); 58 | 59 | return this.each(function () { 60 | jQuery.highlight(this, re, settings.element, settings.className); 61 | }); 62 | }; 63 | 64 | -------------------------------------------------------------------------------- /changetabfilename.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | from uuid import uuid4 6 | from settings import APP_PATH 7 | from guitarfan.models.artist import Artist 8 | from guitarfan.models.tab import Tab 9 | from guitarfan.models.tabfile import TabFile 10 | from guitarfan.utilities.pinyin import Pinyin 11 | from guitarfan.extensions.flasksqlalchemy import db 12 | from guitarfan.utilities.oshelper import * 13 | 14 | def update_tabfile_filename(): 15 | pinyin = Pinyin() 16 | # tabfiles = TabFile.query.all() 17 | # for tabfile in tabfiles: 18 | # old_file_name = os.path.basename(tabfile.filename) 19 | # new_file_name = pinyin.get_initials(old_file_name) 20 | # 21 | # # update files name 22 | # new_file_path = os.path.join(get_tabfile_upload_abspath(), tabfile.tab_id, new_file_name) 23 | # fileexists = os.path.exists(tabfile.file_abspath) 24 | # isfile = os.path.isfile(tabfile.file_abspath) 25 | # #os.rename(tabfile.file_abspath, new_file_path) 26 | # 27 | # # update tabfile table 28 | # #tabfile.filename = tabfile.id + '/' + new_file_name 29 | # 30 | # #db.session.commit() 31 | 32 | tabs = Tab.query.all() 33 | for tab in tabs: 34 | tab_title_pinyin = pinyin.get_initials(tab.title).lower() 35 | tabfile_dir = os.path.join(get_tabfile_upload_abspath(), tab.id) 36 | 37 | if not os.path.exists(tabfile_dir): 38 | continue 39 | 40 | tabfiles = os.listdir(tabfile_dir) 41 | if len(tabfiles) == 1: 42 | old_fullpath = os.path.join(tabfile_dir, tabfiles[0]) 43 | new_fullpath = os.path.join(tabfile_dir, tab_title_pinyin + get_extension(tabfiles[0])) 44 | os.rename(old_fullpath, new_fullpath) 45 | elif len(tabfiles) > 1: 46 | i = 0 47 | for filename in tabfiles: 48 | i += 1 49 | old_fullpath = os.path.join(tabfile_dir, filename) 50 | new_fullpath = os.path.join(tabfile_dir, tab_title_pinyin + str(i) + get_extension(filename)) 51 | os.rename(old_fullpath, new_fullpath) 52 | return 53 | 54 | def update_tabfile_table(): 55 | tabs = Tab.query.all() 56 | for tab in tabs: 57 | tabfile_dir = os.path.join(get_tabfile_upload_abspath(), tab.id) 58 | 59 | if not os.path.exists(tabfile_dir): 60 | continue 61 | 62 | tabfiles = os.listdir(tabfile_dir) 63 | 64 | if len(tabfiles) >= 1: 65 | for filename in tabfiles: 66 | tabfile = TabFile(str(uuid4()), tab.id, filename) 67 | db.session.add(tabfile) 68 | db.session.commit() 69 | return -------------------------------------------------------------------------------- /guitarfan/static/js/jquery.tagcloud.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jquery.tagcloud.js 3 | * A Simple Tag Cloud Plugin for JQuery 4 | * 5 | * https://github.com/addywaddy/jquery.tagcloud.js 6 | * created by Adam Groves 7 | */ 8 | (function($) { 9 | 10 | /*global jQuery*/ 11 | "use strict"; 12 | 13 | var compareWeights = function(a, b) 14 | { 15 | return a - b; 16 | }; 17 | 18 | // Converts hex to an RGB array 19 | var toRGB = function(code) { 20 | if (code.length === 4) { 21 | code = code.replace(/(\w)(\w)(\w)/gi, "\$1\$1\$2\$2\$3\$3"); 22 | } 23 | var hex = /(\w{2})(\w{2})(\w{2})/.exec(code); 24 | return [parseInt(hex[1], 16), parseInt(hex[2], 16), parseInt(hex[3], 16)]; 25 | }; 26 | 27 | // Converts an RGB array to hex 28 | var toHex = function(ary) { 29 | return "#" + jQuery.map(ary, function(i) { 30 | var hex = i.toString(16); 31 | hex = (hex.length === 1) ? "0" + hex : hex; 32 | return hex; 33 | }).join(""); 34 | }; 35 | 36 | var colorIncrement = function(color, range) { 37 | return jQuery.map(toRGB(color.end), function(n, i) { 38 | return (n - toRGB(color.start)[i])/range; 39 | }); 40 | }; 41 | 42 | var tagColor = function(color, increment, weighting) { 43 | var rgb = jQuery.map(toRGB(color.start), function(n, i) { 44 | var ref = Math.round(n + (increment[i] * weighting)); 45 | if (ref > 255) { 46 | ref = 255; 47 | } else { 48 | if (ref < 0) { 49 | ref = 0; 50 | } 51 | } 52 | return ref; 53 | }); 54 | return toHex(rgb); 55 | }; 56 | 57 | $.fn.tagcloud = function(options) { 58 | 59 | var opts = $.extend({}, $.fn.tagcloud.defaults, options); 60 | var tagWeights = this.map(function(){ 61 | return $(this).attr("rel"); 62 | }); 63 | tagWeights = jQuery.makeArray(tagWeights).sort(compareWeights); 64 | var lowest = tagWeights[0]; 65 | var highest = tagWeights.pop(); 66 | var range = highest - lowest; 67 | if(range === 0) {range = 1;} 68 | // Sizes 69 | var fontIncr, colorIncr; 70 | if (opts.size) { 71 | fontIncr = (opts.size.end - opts.size.start)/range; 72 | } 73 | // Colors 74 | if (opts.color) { 75 | colorIncr = colorIncrement (opts.color, range); 76 | } 77 | return this.each(function() { 78 | var weighting = $(this).attr("rel") - lowest; 79 | if (opts.size) { 80 | $(this).css({"font-size": opts.size.start + (weighting * fontIncr) + opts.size.unit}); 81 | } 82 | if (opts.color) { 83 | $(this).css({"color": tagColor(opts.color, colorIncr, weighting)}); 84 | } 85 | }); 86 | }; 87 | 88 | $.fn.tagcloud.defaults = { 89 | size: {start: 14, end: 18, unit: "pt"} 90 | }; 91 | 92 | })(jQuery); -------------------------------------------------------------------------------- /guitarfan/static/jqcloud/jqcloud-1.0.4.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQCloud Plugin for jQuery 3 | * 4 | * Version 1.0.4 5 | * 6 | * Copyright 2011, Luca Ongaro 7 | * Licensed under the MIT license. 8 | * 9 | * Date: 2013-05-09 18:54:22 +0200 10 | */ 11 | (function(e){"use strict";e.fn.jQCloud=function(t,n){var r=this,i=r.attr("id")||Math.floor(Math.random()*1e6).toString(36),s={width:r.width(),height:r.height(),center:{x:(n&&n.width?n.width:r.width())/2,y:(n&&n.height?n.height:r.height())/2},delayedMode:t.length>50,shape:!1,encodeURI:!0,removeOverflowing:!0};n=e.extend(s,n||{}),r.addClass("jqcloud").width(n.width).height(n.height),r.css("position")==="static"&&r.css("position","relative");var o=function(){var s=function(e,t){var n=function(e,t){return Math.abs(2*e.offsetLeft+e.offsetWidth-2*t.offsetLeft-t.offsetWidth)t.weight?-1:0});var u=n.shape==="rectangular"?18:2,a=[],f=n.width/n.height,l=function(o,l){var c=i+"_word_"+o,h="#"+c,p=6.28*Math.random(),d=0,v=0,m=0,g=5,y="",b="",w;l.html=e.extend(l.html,{id:c}),l.html&&l.html["class"]&&(y=l.html["class"],delete l.html["class"]),t[0].weight>t[t.length-1].weight&&(g=Math.round((l.weight-t[t.length-1].weight)/(t[0].weight-t[t.length-1].weight)*9)+1),w=e("").attr(l.html).addClass("w"+g+" "+y),l.link?(typeof l.link=="string"&&(l.link={href:l.link}),n.encodeURI&&(l.link=e.extend(l.link,{href:encodeURI(l.link.href).replace(/'/g,"%27")})),b=e("").attr(l.link).text(l.text)):b=l.text,w.append(b);if(!!l.handlers)for(var E in l.handlers)l.handlers.hasOwnProperty(E)&&typeof l.handlers[E]=="function"&&e(w).bind(E,l.handlers[E]);r.append(w);var S=w.width(),x=w.height(),T=n.center.x-S/2,N=n.center.y-x/2,C=w[0].style;C.position="absolute",C.left=T+"px",C.top=N+"px";while(s(w[0],a)){if(n.shape==="rectangular"){v++,v*u>(1+Math.floor(m/2))*u*(m%4%2===0?1:f)&&(v=0,m++);switch(m%4){case 1:T+=u*f+Math.random()*2;break;case 2:N-=u+Math.random()*2;break;case 3:T-=u*f+Math.random()*2;break;case 0:N+=u+Math.random()*2}}else d+=u,p+=(o%2===0?1:-1)*u,T=n.center.x-S/2+d*Math.cos(p)*f,N=n.center.y+d*Math.sin(p)-x/2;C.left=T+"px",C.top=N+"px"}if(n.removeOverflowing&&(T<0||N<0||T+S>n.width||N+x>n.height)){w.remove();return}a.push(w[0]),e.isFunction(l.afterWordRender)&&l.afterWordRender.call(w)},c=function(i){i=i||0;if(!r.is(":visible")){setTimeout(function(){c(i)},10);return}i' % self.title 46 | 47 | @property 48 | def difficulty_text(self): 49 | return DifficultyDegree.get_item_text(self.difficulty_id) 50 | 51 | @property 52 | def style_text(self): 53 | return MusicStyle.get_item_text(self.style_id) 54 | 55 | @property 56 | def format_text(self): 57 | return TabFormat.get_item_text(self.format_id) 58 | 59 | def set_tags(self, value): 60 | if self.tags: 61 | for tag in self.tags: 62 | self.tags.remove(tag) 63 | if value: 64 | for tag in value: 65 | self.append_tag(tag) 66 | 67 | def append_tag(self, tag): 68 | if tag and isinstance(tag, Tag): 69 | # reload tag by id to void error that 70 | renew_tag = Tag.query.get(tag.id) 71 | if tag: 72 | self.tags.append(renew_tag) -------------------------------------------------------------------------------- /guitarfan/controlers/admin/forms/administrator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from flask.ext.wtf import Form 5 | from wtforms.fields import TextField, PasswordField, SubmitField, BooleanField 6 | from wtforms.validators import Required, Regexp, Email, Optional, EqualTo 7 | 8 | from guitarfan.utilities import validator 9 | from guitarfan.models.administrator import Administrator 10 | 11 | 12 | class AdministratorLoginForm(Form): 13 | name = TextField(u'User name or email', validators=[Required(message=u'User name or email is required')]) 14 | password = PasswordField(u'Password', validators=[Required(message=u'Password is required')]) 15 | submit = SubmitField(u'Login', id='submit') 16 | 17 | 18 | class AddAdministratorForm(Form): 19 | name = TextField(u'Name', description=u'Unrepeatable.', 20 | validators=[Required(message=u'Name is required'), 21 | Regexp(u'^[a-zA-Z0-9\_\-\.\ ]{1,20}$', message=u'Incorrect name format'), 22 | validator.Unique(Administrator, Administrator.name, message=u'The current name is already in use')]) 23 | email = TextField(u'Email', description=u'Unrepeatable.', 24 | validators=[Required(message=u'Email is required'), 25 | Email(message=u'Incorrect email format'), 26 | validator.Unique(Administrator, Administrator.email, message=u'The current email is already in use')]) 27 | # group = QuerySelectField(u'Group', description=u'', 28 | # query_factory=Group.query.all, get_label='desc', 29 | # validators=[Required(message=u'Group is required')]) 30 | password = PasswordField(u'Password', description=u'At least eight characters', 31 | validators=[Required(message=u'Password is required'), 32 | Regexp(u'^(.{8,20})$', message=u'Password are at least eight chars')]) 33 | confirm_password = PasswordField(u'Confirm Password', description=u'Re-enter the password', 34 | validators=[EqualTo('password', message=u'Passwords must be the same')]) 35 | submit = SubmitField(u'Submit', id='submit') 36 | 37 | 38 | class EditAdministratorForm(Form): 39 | new_password = PasswordField(u'New Password', description=u'At least eight characters, if not change please leave it empty', 40 | validators=[Optional(), Regexp(u'^(.{8,20})$', message=u'Password are at least eight chars')]) 41 | confirm_password = PasswordField(u'Confirm Password', description=u'Re-enter the password', 42 | validators=[EqualTo('new_password', message=u'Passwords must be the same')]) 43 | status = BooleanField(u'Status', description=u'Enable') 44 | submit = SubmitField(u'Submit', id='submit') -------------------------------------------------------------------------------- /guitarfan/static/select2/css/select2-bootstrap.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Select2 Bootstrap CSS 1.0 3 | * Compatible with select2 3.3.2 and bootstrap 2.3.1 4 | * MIT License 5 | */ 6 | .select2-container { 7 | vertical-align: middle; 8 | } 9 | .select2-container.input-mini { 10 | width: 60px; 11 | } 12 | .select2-container.input-small { 13 | width: 90px; 14 | } 15 | .select2-container.input-medium { 16 | width: 150px; 17 | } 18 | .select2-container.input-large { 19 | width: 210px; 20 | } 21 | .select2-container.input-xlarge { 22 | width: 270px; 23 | } 24 | .select2-container.input-xxlarge { 25 | width: 530px; 26 | } 27 | .select2-container.input-default { 28 | width: 220px; 29 | } 30 | .select2-container[class*="span"] { 31 | float: none; 32 | margin-left: 0; 33 | } 34 | 35 | .select2-container .select2-choice, 36 | .select2-container-multi .select2-choices { 37 | height: 28px; 38 | line-height: 29px; 39 | border: 1px solid #cccccc; 40 | -webkit-border-radius: 4px; 41 | -moz-border-radius: 4px; 42 | border-radius: 4px; 43 | background: none; 44 | background-color: white; 45 | filter: none; 46 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 47 | -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 48 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 49 | } 50 | 51 | .select2-container .select2-choice div, 52 | .select2-container.select2-container-disabled .select2-choice div { 53 | border-left: none; 54 | background: none; 55 | filter: none; 56 | } 57 | 58 | .control-group.error [class^="select2-choice"] { 59 | border-color: #b94a48; 60 | } 61 | 62 | .select2-container-multi .select2-choices .select2-search-field { 63 | height: 28px; 64 | line-height: 27px; 65 | } 66 | 67 | .select2-container-active .select2-choice, 68 | .select2-container-multi.select2-container-active .select2-choices { 69 | border-color: rgba(82, 168, 236, 0.8); 70 | outline: none; 71 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); 72 | -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); 73 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); 74 | } 75 | 76 | [class^="input-"] .select2-container { 77 | font-size: 14px; 78 | } 79 | 80 | .input-prepend [class^="select2-choice"] { 81 | border-top-left-radius: 0; 82 | border-bottom-left-radius: 0; 83 | } 84 | 85 | .input-append [class^="select2-choice"] { 86 | border-top-right-radius: 0; 87 | border-bottom-right-radius: 0; 88 | } 89 | 90 | .select2-dropdown-open [class^="select2-choice"] { 91 | border-bottom-left-radius: 0; 92 | border-bottom-right-radius: 0; 93 | } 94 | 95 | .select2-dropdown-open.select2-drop-above [class^="select2-choice"] { 96 | border-top-left-radius: 0; 97 | border-top-right-radius: 0; 98 | } 99 | 100 | [class^="input-"] .select2-offscreen { 101 | position: absolute; 102 | } 103 | 104 | /** 105 | * This stops the quick flash when a native selectbox is shown and 106 | * then replaced by a select2 input when javascript kicks in. This can be 107 | * removed if javascript is not present 108 | */ 109 | select.select2 { 110 | height: 28px; 111 | visibility: hidden; 112 | } 113 | -------------------------------------------------------------------------------- /guitarfan/static/browser/detection.css: -------------------------------------------------------------------------------- 1 | #browser-detection { 2 | background: #FFFFE5; 3 | color: #333333; 4 | position: fixed; 5 | _position: absolute; 6 | padding: 10px 15px; 7 | font-size: 13px; 8 | font-family: "Trebuchet MS", "Segoe UI", Arial, Tahoma, sans-serif; 9 | border-radius: 5px; 10 | border: 1px solid #D6D6C1; 11 | -moz-border-radius: 5px; 12 | width: 700px; 13 | z-index:1001; 14 | } 15 | #browser-detection P { 16 | margin: 0; 17 | padding: 0; 18 | background: transparent; 19 | line-height: 135%; 20 | width: auto; 21 | float: none; 22 | border: none; 23 | text-align: left; 24 | } 25 | #browser-detection P.bd-title { 26 | padding-top: 0px; 27 | font-size: 25px; 28 | line-height: 100%; 29 | } 30 | #browser-detection P.bd-notice { 31 | padding-bottom: 5px; 32 | padding-top: 5px; 33 | } 34 | #browser-detection SPAN.bd-highlight { color: #B50E0E; } 35 | #browser-detection A#browser-detection-close { 36 | width: 15px; 37 | height: 15px; 38 | outline: none; 39 | position: absolute; 40 | right: 10px; 41 | top: 10px; 42 | text-indent: -500em; 43 | line-height: 100%; 44 | background: url(close.gif) no-repeat center center; 45 | } 46 | #browser-detection A#browser-detection-close:HOVER { background-color: #F5F5DC; } 47 | #browser-detection UL.bd-browsers-list, #browser-detection UL.bd-browsers-list LI, 48 | #browser-detection UL.bd-skip-buttons, #browser-detection UL.bd-skip-buttons LI { 49 | padding: 0; 50 | margin: 0; 51 | float: left; 52 | list-style: none; 53 | } 54 | #browser-detection UL.bd-browsers-list { 55 | clear: both; 56 | margin-top: 3px; 57 | padding: 7px 0; 58 | border-top: 1px solid #F5F5DC; 59 | border-bottom: 1px solid #F5F5DC; 60 | width: 100%; 61 | } 62 | #browser-detection UL.bd-browsers-list LI { text-align: left; } 63 | #browser-detection UL.bd-browsers-list LI A { 64 | width: 60px; 65 | height: 55px; 66 | display: block; 67 | color: #666666; 68 | padding: 10px 10px 0 65px; 69 | text-decoration: none; 70 | } 71 | #browser-detection UL.bd-browsers-list LI A:HOVER { text-decoration: underline; } 72 | #browser-detection UL.bd-browsers-list LI.firefox A { background: url(firefox.gif) no-repeat left top; } 73 | #browser-detection UL.bd-browsers-list LI.chrome A { background: url(chrome.gif) no-repeat left top; } 74 | #browser-detection UL.bd-browsers-list LI.safari A { background: url(safari.gif) no-repeat left top; } 75 | #browser-detection UL.bd-browsers-list LI.opera A { background: url(opera.gif) no-repeat left top; } 76 | #browser-detection UL.bd-browsers-list LI.msie A { background: url(msie.gif) no-repeat left top; } 77 | #browser-detection UL.bd-skip-buttons { margin-top: 10px; } 78 | #browser-detection UL.bd-skip-buttons LI { 79 | display: inline; 80 | margin-right: 10px; 81 | } 82 | #browser-detection UL.bd-skip-buttons LI BUTTON { font-size: 13px; } 83 | #browser-detection DIV.bd-poweredby { 84 | font-size: 9px; 85 | position: absolute; 86 | bottom: 10px; 87 | right: 10px; 88 | font-style: italic; 89 | } 90 | #browser-detection DIV.bd-poweredby, #browser-detection DIV.bd-poweredby A { color: #AAAAAA; } 91 | #browser-detection DIV.bd-poweredby A { text-decoration: underline; } 92 | #browser-detection DIV.bd-poweredby A:HOVER { text-decoration: none; } 93 | #black_overlay { 94 | position: absolute; 95 | top: 0%; 96 | left: 0%; 97 | width: 100%; 98 | height: 100%; 99 | background-color: black; 100 | z-index:1000; 101 | -moz-opacity: 0.8; 102 | opacity:.80; 103 | filter: alpha(opacity=80); 104 | } -------------------------------------------------------------------------------- /guitarfan/static/fancybox/helpers/jquery.fancybox-buttons.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Buttons helper for fancyBox 3 | * version: 1.0.5 (Mon, 15 Oct 2012) 4 | * @requires fancyBox v2.0 or later 5 | * 6 | * Usage: 7 | * $(".fancybox").fancybox({ 8 | * helpers : { 9 | * buttons: { 10 | * position : 'top' 11 | * } 12 | * } 13 | * }); 14 | * 15 | */ 16 | (function ($) { 17 | //Shortcut for fancyBox object 18 | var F = $.fancybox; 19 | 20 | //Add helper object 21 | F.helpers.buttons = { 22 | defaults : { 23 | skipSingle : false, // disables if gallery contains single image 24 | position : 'top', // 'top' or 'bottom' 25 | tpl : '
' 26 | }, 27 | 28 | list : null, 29 | buttons: null, 30 | 31 | beforeLoad: function (opts, obj) { 32 | //Remove self if gallery do not have at least two items 33 | 34 | if (opts.skipSingle && obj.group.length < 2) { 35 | obj.helpers.buttons = false; 36 | obj.closeBtn = true; 37 | 38 | return; 39 | } 40 | 41 | //Increase top margin to give space for buttons 42 | obj.margin[ opts.position === 'bottom' ? 2 : 0 ] += 30; 43 | }, 44 | 45 | onPlayStart: function () { 46 | if (this.buttons) { 47 | this.buttons.play.attr('title', 'Pause slideshow').addClass('btnPlayOn'); 48 | } 49 | }, 50 | 51 | onPlayEnd: function () { 52 | if (this.buttons) { 53 | this.buttons.play.attr('title', 'Start slideshow').removeClass('btnPlayOn'); 54 | } 55 | }, 56 | 57 | afterShow: function (opts, obj) { 58 | var buttons = this.buttons; 59 | 60 | if (!buttons) { 61 | this.list = $(opts.tpl).addClass(opts.position).appendTo('body'); 62 | 63 | buttons = { 64 | prev : this.list.find('.btnPrev').click( F.prev ), 65 | next : this.list.find('.btnNext').click( F.next ), 66 | play : this.list.find('.btnPlay').click( F.play ), 67 | toggle : this.list.find('.btnToggle').click( F.toggle ), 68 | close : this.list.find('.btnClose').click( F.close ) 69 | } 70 | } 71 | 72 | //Prev 73 | if (obj.index > 0 || obj.loop) { 74 | buttons.prev.removeClass('btnDisabled'); 75 | } else { 76 | buttons.prev.addClass('btnDisabled'); 77 | } 78 | 79 | //Next / Play 80 | if (obj.loop || obj.index < obj.group.length - 1) { 81 | buttons.next.removeClass('btnDisabled'); 82 | buttons.play.removeClass('btnDisabled'); 83 | 84 | } else { 85 | buttons.next.addClass('btnDisabled'); 86 | buttons.play.addClass('btnDisabled'); 87 | } 88 | 89 | this.buttons = buttons; 90 | 91 | this.onUpdate(opts, obj); 92 | }, 93 | 94 | onUpdate: function (opts, obj) { 95 | var toggle; 96 | 97 | if (!this.buttons) { 98 | return; 99 | } 100 | 101 | toggle = this.buttons.toggle.removeClass('btnDisabled btnToggleOn'); 102 | 103 | //Size toggle button 104 | if (obj.canShrink) { 105 | toggle.addClass('btnToggleOn'); 106 | 107 | } else if (!obj.canExpand) { 108 | toggle.addClass('btnDisabled'); 109 | } 110 | }, 111 | 112 | beforeClose: function () { 113 | if (this.list) { 114 | this.list.remove(); 115 | } 116 | 117 | this.list = null; 118 | this.buttons = null; 119 | } 120 | }; 121 | 122 | }(jQuery)); 123 | -------------------------------------------------------------------------------- /guitarfan/static/FineUploader/fineuploader-3.7.1.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Fine Uploader 3 | * 4 | * Copyright 2013, Widen Enterprises, Inc. info@fineuploader.com 5 | * 6 | * Version: 3.7.1 7 | * 8 | * Homepage: http://fineuploader.com 9 | * 10 | * Repository: git://github.com/Widen/fine-uploader.git 11 | * 12 | * Licensed under GNU GPL v3, see LICENSE 13 | */ 14 | 15 | 16 | /*! fineuploader 2013-07-24 */ 17 | 18 | .qq-uploader{position:relative;width:100%}.qq-upload-button{display:block;width:105px;padding:7px 0;text-align:center;background:#800;border-bottom:1px solid #DDD;color:#FFF}.qq-upload-button-hover{background:#C00}.qq-upload-button-focus{outline:1px dotted #000}.qq-upload-drop-area,.qq-upload-extra-drop-area{position:absolute;top:0;left:0;width:100%;height:100%;min-height:30px;z-index:2;background:#FF9797;text-align:center}.qq-upload-drop-area span{display:block;position:absolute;top:50%;width:100%;margin-top:-8px;font-size:16px}.qq-upload-extra-drop-area{position:relative;margin-top:50px;font-size:16px;padding-top:30px;height:20px;min-height:40px}.qq-upload-drop-area-active{background:#FF7171}.qq-upload-list{margin:0;padding:0;list-style:none}.qq-upload-list li{margin:0;padding:9px;line-height:15px;font-size:16px;background-color:#FFF0BD}.qq-upload-file,.qq-upload-spinner,.qq-upload-size,.qq-upload-cancel,.qq-upload-retry,.qq-upload-failed-text,.qq-upload-finished,.qq-upload-delete{margin-right:12px}.qq-upload-file{}.qq-upload-spinner{display:inline-block;background:url(loading.gif);width:15px;height:15px;vertical-align:text-bottom}.qq-drop-processing{display:none}.qq-drop-processing-spinner{display:inline-block;background:url(processing.gif);width:24px;height:24px;vertical-align:text-bottom}.qq-upload-finished{display:none;width:15px;height:15px;vertical-align:text-bottom}.qq-upload-retry,.qq-upload-delete{display:none;color:#000}.qq-upload-cancel,.qq-upload-delete{color:#000}.qq-upload-retryable .qq-upload-retry{display:inline}.qq-upload-size,.qq-upload-cancel,.qq-upload-retry,.qq-upload-delete{font-size:12px;font-weight:400}.qq-upload-failed-text{display:none;font-style:italic;font-weight:700}.qq-upload-failed-icon{display:none;width:15px;height:15px;vertical-align:text-bottom}.qq-upload-fail .qq-upload-failed-text{display:inline}.qq-upload-retrying .qq-upload-failed-text{display:inline;color:#D60000}.qq-upload-list li.qq-upload-success{background-color:#5DA30C;color:#FFF}.qq-upload-list li.qq-upload-fail{background-color:#D60000;color:#FFF}.qq-progress-bar{background:-moz-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,rgba(30,87,153,1)),color-stop(50%,rgba(41,137,216,1)),color-stop(51%,rgba(32,124,202,1)),color-stop(100%,rgba(125,185,232,1)));background:-webkit-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-o-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-ms-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:linear-gradient(to bottom,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);width:0;height:15px;border-radius:6px;margin-bottom:3px;display:none}INPUT.qq-edit-filename{position:absolute;opacity:0;filter:alpha(opacity=0);z-index:-1;-ms-filter:"alpha(Opacity=0)"}.qq-upload-file.qq-editable{cursor:pointer}.qq-edit-filename-icon.qq-editable{display:inline-block;cursor:pointer}INPUT.qq-edit-filename.qq-editing{position:static;margin-top:-5px;margin-right:10px;margin-bottom:-5px;opacity:1;filter:alpha(opacity=100);-ms-filter:"alpha(Opacity=100)"}.qq-edit-filename-icon{display:none;background:url(edit.gif);width:15px;height:15px;vertical-align:text-bottom;margin-right:5px}INPUT.qq-edit-filename.qq-editing~.qq-upload-cancel{display:none} 19 | /*! 2013-07-24 */ 20 | -------------------------------------------------------------------------------- /guitarfan/static/bootflat/js/respond.min.js: -------------------------------------------------------------------------------- 1 | /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */ 2 | /*! NOTE: If you're already including a window.matchMedia polyfill via Modernizr or otherwise, you don't need this part */ 3 | window.matchMedia=window.matchMedia||function(a){"use strict";var c,d=a.documentElement,e=d.firstElementChild||d.firstChild,f=a.createElement("body"),g=a.createElement("div");return g.id="mq-test-1",g.style.cssText="position:absolute;top:-100em",f.style.background="none",f.appendChild(g),function(a){return g.innerHTML='­',d.insertBefore(f,e),c=42===g.offsetWidth,d.removeChild(f),{matches:c,media:a}}}(document); 4 | 5 | /*! Respond.js v1.1.0: min/max-width media query polyfill. (c) Scott Jehl. MIT/GPLv2 Lic. j.mp/respondjs */ 6 | (function(a){"use strict";function x(){u(!0)}var b={};if(a.respond=b,b.update=function(){},b.mediaQueriesSupported=a.matchMedia&&a.matchMedia("only all").matches,!b.mediaQueriesSupported){var q,r,t,c=a.document,d=c.documentElement,e=[],f=[],g=[],h={},i=30,j=c.getElementsByTagName("head")[0]||d,k=c.getElementsByTagName("base")[0],l=j.getElementsByTagName("link"),m=[],n=function(){for(var b=0;l.length>b;b++){var c=l[b],d=c.href,e=c.media,f=c.rel&&"stylesheet"===c.rel.toLowerCase();d&&f&&!h[d]&&(c.styleSheet&&c.styleSheet.rawCssText?(p(c.styleSheet.rawCssText,d,e),h[d]=!0):(!/^([a-zA-Z:]*\/\/)/.test(d)&&!k||d.replace(RegExp.$1,"").split("/")[0]===a.location.host)&&m.push({href:d,media:e}))}o()},o=function(){if(m.length){var b=m.shift();v(b.href,function(c){p(c,b.href,b.media),h[b.href]=!0,a.setTimeout(function(){o()},0)})}},p=function(a,b,c){var d=a.match(/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi),g=d&&d.length||0;b=b.substring(0,b.lastIndexOf("/"));var h=function(a){return a.replace(/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,"$1"+b+"$2$3")},i=!g&&c;b.length&&(b+="/"),i&&(g=1);for(var j=0;g>j;j++){var k,l,m,n;i?(k=c,f.push(h(a))):(k=d[j].match(/@media *([^\{]+)\{([\S\s]+?)$/)&&RegExp.$1,f.push(RegExp.$2&&h(RegExp.$2))),m=k.split(","),n=m.length;for(var o=0;n>o;o++)l=m[o],e.push({media:l.split("(")[0].match(/(only\s+)?([a-zA-Z]+)\s?/)&&RegExp.$2||"all",rules:f.length-1,hasquery:l.indexOf("(")>-1,minw:l.match(/\(\s*min\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:l.match(/\(\s*max\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},s=function(){var a,b=c.createElement("div"),e=c.body,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",e||(e=f=c.createElement("body"),e.style.background="none"),e.appendChild(b),d.insertBefore(e,d.firstChild),a=b.offsetWidth,f?d.removeChild(e):e.removeChild(b),a=t=parseFloat(a)},u=function(b){var h="clientWidth",k=d[h],m="CSS1Compat"===c.compatMode&&k||c.body[h]||k,n={},o=l[l.length-1],p=(new Date).getTime();if(b&&q&&i>p-q)return a.clearTimeout(r),r=a.setTimeout(u,i),void 0;q=p;for(var v in e)if(e.hasOwnProperty(v)){var w=e[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?t||s():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?t||s():1)),w.hasquery&&(z&&A||!(z||m>=x)||!(A||y>=m))||(n[w.media]||(n[w.media]=[]),n[w.media].push(f[w.rules]))}for(var C in g)g.hasOwnProperty(C)&&g[C]&&g[C].parentNode===j&&j.removeChild(g[C]);for(var D in n)if(n.hasOwnProperty(D)){var E=c.createElement("style"),F=n[D].join("\n");E.type="text/css",E.media=D,j.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(c.createTextNode(F)),g.push(E)}},v=function(a,b){var c=w();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))},w=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}();n(),b.update=n,a.addEventListener?a.addEventListener("resize",x,!1):a.attachEvent&&a.attachEvent("onresize",x)}})(this); 7 | -------------------------------------------------------------------------------- /tabcrawler/tabcrawler/spiders/sosospider.py: -------------------------------------------------------------------------------- 1 | from urlparse import urljoin 2 | from urllib2 import * 3 | import json 4 | 5 | from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor 6 | from scrapy.contrib.spiders import CrawlSpider, Rule 7 | from scrapy.selector import HtmlXPathSelector 8 | from scrapy.utils.response import get_base_url 9 | from scrapy.http import Request 10 | 11 | from tabcrawler.items import * 12 | 13 | 14 | class SoSoSpider(CrawlSpider): 15 | name = 'soso' 16 | start_urls = ['http://pu.jitapusoso.com'] 17 | allowed_domains = ['jitapusoso.com'] 18 | rules = ( 19 | Rule(SgmlLinkExtractor(allow='singers/[a]\.htm', tags='a'), follow=True), 20 | # Rule(SgmlLinkExtractor(allow='search/4_.+\.htm', tags='a'), callback='parse_tab_item'), 21 | Rule(SgmlLinkExtractor(allow='search/4_734b4c466f773d3d\.htm', tags='a'), callback='parse_tab_item'), 22 | ) 23 | 24 | def parse_artist_item(self, response): 25 | # from scrapy.shell import inspect_response 26 | # inspect_response(response) 27 | 28 | # artist_links = hxs.select('//p')[1].select('a') 29 | # 30 | # for index, link in enumerate(artist_links): 31 | # print link.select('text()').extract() 32 | 33 | hxs = HtmlXPathSelector(response) 34 | 35 | item = LetterArtistItem() 36 | # html sour like [a] 37 | item['letter'] = hxs.select('//span[@id="tetete"]/text()').extract()[0].rstrip(']') 38 | item['artists'] = [] 39 | 40 | # select all artist links inside second

of the page 41 | artist_links = hxs.select('//p[2]').select('a') 42 | for index, link in enumerate(artist_links): 43 | item['artists'].append(link.select('text()').extract()[0]) 44 | yield item 45 | 46 | 47 | def parse_tab_item(self, response): 48 | # from scrapy.shell import inspect_response 49 | # inspect_response(response) 50 | 51 | hxs = HtmlXPathSelector(response) 52 | 53 | artist = hxs.select('//p[1]/strong/span/text()').extract()[0] 54 | 55 | if not filter_artist(artist): 56 | return 57 | 58 | title_links = hxs.select("//p[2]/a[contains(@href, '.htm')]") 59 | for index, link in enumerate(title_links): 60 | tab_type = link.select('following-sibling::text()').extract()[0].strip() 61 | if tab_type == 'img': 62 | item = TabItem() 63 | item['artist'] = artist 64 | item['title'] = link.select('text()').extract()[0].strip() 65 | item['format'] = tab_type 66 | tab_url = urljoin(get_base_url(response), link.select('@href').extract()[0]) 67 | request = Request(tab_url, callback=self.parse_imgs) 68 | request.meta['item'] = item 69 | yield request 70 | # yield item 71 | 72 | 73 | def parse_imgs(self, response): 74 | # from scrapy.shell import inspect_response 75 | # inspect_response(response) 76 | 77 | hxs = HtmlXPathSelector(response) 78 | item = response.meta['item'] 79 | 80 | item['image_urls'] = [] 81 | imgs = hxs.select("//img[contains(@src, '../allpu/')]") 82 | for img in imgs: 83 | img_url = 'http://pu.jitapusoso.com/%s' % img.select('@src').extract()[0][3:] 84 | item['image_urls'].append(img_url) 85 | 86 | return item 87 | 88 | 89 | def filter_artist(artist_name): 90 | with open('json/soso_artists.json') as json_data: 91 | letter_artist_list = json.load(json_data) 92 | 93 | for letter_dict in letter_artist_list: 94 | if artist_name in letter_dict['artists']: 95 | return True 96 | 97 | return False 98 | 99 | 100 | 101 | # 102 | # import urllib2 103 | # import sys 104 | # 105 | # req = urllib2.Request('xxxx.html') 106 | # res = urllib2.urlopen(req) 107 | # html = res.read() 108 | # res.close() 109 | # 110 | # type = sys.getfilesystemencoding() 111 | # html = html.decode('GB2312').encode(type) 112 | # print html 113 | # 114 | # 115 | 116 | 117 | -------------------------------------------------------------------------------- /guitarfan/static/fancybox/helpers/jquery.fancybox-thumbs.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Thumbnail helper for fancyBox 3 | * version: 1.0.7 (Mon, 01 Oct 2012) 4 | * @requires fancyBox v2.0 or later 5 | * 6 | * Usage: 7 | * $(".fancybox").fancybox({ 8 | * helpers : { 9 | * thumbs: { 10 | * width : 50, 11 | * height : 50 12 | * } 13 | * } 14 | * }); 15 | * 16 | */ 17 | (function ($) { 18 | //Shortcut for fancyBox object 19 | var F = $.fancybox; 20 | 21 | //Add helper object 22 | F.helpers.thumbs = { 23 | defaults : { 24 | width : 50, // thumbnail width 25 | height : 50, // thumbnail height 26 | position : 'bottom', // 'top' or 'bottom' 27 | source : function ( item ) { // function to obtain the URL of the thumbnail image 28 | var href; 29 | 30 | if (item.element) { 31 | href = $(item.element).find('img').attr('src'); 32 | } 33 | 34 | if (!href && item.type === 'image' && item.href) { 35 | href = item.href; 36 | } 37 | 38 | return href; 39 | } 40 | }, 41 | 42 | wrap : null, 43 | list : null, 44 | width : 0, 45 | 46 | init: function (opts, obj) { 47 | var that = this, 48 | list, 49 | thumbWidth = opts.width, 50 | thumbHeight = opts.height, 51 | thumbSource = opts.source; 52 | 53 | //Build list structure 54 | list = ''; 55 | 56 | for (var n = 0; n < obj.group.length; n++) { 57 | list += '

  • '; 58 | } 59 | 60 | this.wrap = $('
    ').addClass(opts.position).appendTo('body'); 61 | this.list = $('
      ' + list + '
    ').appendTo(this.wrap); 62 | 63 | //Load each thumbnail 64 | $.each(obj.group, function (i) { 65 | var href = thumbSource( obj.group[ i ] ); 66 | 67 | if (!href) { 68 | return; 69 | } 70 | 71 | $("").load(function () { 72 | var width = this.width, 73 | height = this.height, 74 | widthRatio, heightRatio, parent; 75 | 76 | if (!that.list || !width || !height) { 77 | return; 78 | } 79 | 80 | //Calculate thumbnail width/height and center it 81 | widthRatio = width / thumbWidth; 82 | heightRatio = height / thumbHeight; 83 | 84 | parent = that.list.children().eq(i).find('a'); 85 | 86 | if (widthRatio >= 1 && heightRatio >= 1) { 87 | if (widthRatio > heightRatio) { 88 | width = Math.floor(width / heightRatio); 89 | height = thumbHeight; 90 | 91 | } else { 92 | width = thumbWidth; 93 | height = Math.floor(height / widthRatio); 94 | } 95 | } 96 | 97 | $(this).css({ 98 | width : width, 99 | height : height, 100 | top : Math.floor(thumbHeight / 2 - height / 2), 101 | left : Math.floor(thumbWidth / 2 - width / 2) 102 | }); 103 | 104 | parent.width(thumbWidth).height(thumbHeight); 105 | 106 | $(this).hide().appendTo(parent).fadeIn(300); 107 | 108 | }).attr('src', href); 109 | }); 110 | 111 | //Set initial width 112 | this.width = this.list.children().eq(0).outerWidth(true); 113 | 114 | this.list.width(this.width * (obj.group.length + 1)).css('left', Math.floor($(window).width() * 0.5 - (obj.index * this.width + this.width * 0.5))); 115 | }, 116 | 117 | beforeLoad: function (opts, obj) { 118 | //Remove self if gallery do not have at least two items 119 | if (obj.group.length < 2) { 120 | obj.helpers.thumbs = false; 121 | 122 | return; 123 | } 124 | 125 | //Increase bottom margin to give space for thumbs 126 | obj.margin[ opts.position === 'top' ? 0 : 2 ] += ((opts.height) + 15); 127 | }, 128 | 129 | afterShow: function (opts, obj) { 130 | //Check if exists and create or update list 131 | if (this.list) { 132 | this.onUpdate(opts, obj); 133 | 134 | } else { 135 | this.init(opts, obj); 136 | } 137 | 138 | //Set active element 139 | this.list.children().removeClass('active').eq(obj.index).addClass('active'); 140 | }, 141 | 142 | //Center list 143 | onUpdate: function (opts, obj) { 144 | if (this.list) { 145 | this.list.stop(true).animate({ 146 | 'left': Math.floor($(window).width() * 0.5 - (obj.index * this.width + this.width * 0.5)) 147 | }, 150); 148 | } 149 | }, 150 | 151 | beforeClose: function () { 152 | if (this.wrap) { 153 | this.wrap.remove(); 154 | } 155 | 156 | this.wrap = null; 157 | this.list = null; 158 | this.width = 0; 159 | } 160 | } 161 | 162 | }(jQuery)); -------------------------------------------------------------------------------- /guitarfan/controlers/admin/data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | import os 6 | import sys 7 | import shutil 8 | from uuid import uuid4 9 | 10 | from flask import render_template, request, current_app, redirect, url_for, flash, Blueprint, jsonify 11 | from flask.ext.login import login_required 12 | from sqlalchemy import exists 13 | 14 | from guitarfan.extensions.flasksqlalchemy import db 15 | from guitarfan.utilities import oshelper 16 | from guitarfan.utilities import validator 17 | from guitarfan.models import * 18 | from forms.tab import * 19 | 20 | bp_admin_data = Blueprint('bp_admin_data', __name__, template_folder="../../templates/admin/tabs") 21 | 22 | 23 | @bp_admin_data.route('/admin/data/import', methods=['GET', 'POST']) 24 | @login_required 25 | def data_import(): 26 | if request.method == 'GET': 27 | return render_template('data_import.html') 28 | elif request.method == 'POST': 29 | root_path = request.form['path'] if 'path' in request.form else '' 30 | result_info = { 31 | 'artists': 0, 32 | 'tabs': 0, 33 | 'errors': [] 34 | } 35 | 36 | # check path 37 | if root_path == '' or not os.path.isdir(root_path): 38 | return jsonify(result='failed', msg='invalid path') 39 | 40 | # valid folder names are '0-9', 'other', 'a', 'b', 'c'...'z' 41 | valid_letter = map(chr, range(97, 123)) + ['0-9', 'other'] 42 | 43 | 44 | ### Traverse first level folders - letters 45 | for letter_dir_name in os.listdir(root_path): 46 | letter_dir_path = os.path.join(root_path, letter_dir_name) 47 | if not os.path.isdir(letter_dir_path) or not letter_dir_name.lower() in valid_letter: 48 | continue 49 | 50 | 51 | ### Traverse second level folders - artists 52 | for artist_dir_name in os.listdir(letter_dir_path): 53 | artist_dir_path = os.path.join(letter_dir_path, artist_dir_name) 54 | if not os.path.isdir(artist_dir_path): 55 | continue 56 | 57 | # create artist if not exist or just fetch it 58 | artist = Artist.query.filter_by(name=artist_dir_name).first() 59 | if artist is None: 60 | artist = Artist(str(uuid4()), artist_dir_name, letter_dir_name, '', 1, 1) 61 | db.session.add(artist) 62 | result_info['artists'] += 1 63 | 64 | 65 | ## Traverse third level folders - tabs 66 | for tab_dir_name in os.listdir(artist_dir_path): 67 | tab_dir_path = os.path.join(artist_dir_path, tab_dir_name) 68 | if not os.path.isdir(tab_dir_path): 69 | continue 70 | 71 | # import tab if not exists 72 | if not db.session.query(exists().where(Tab.title == tab_dir_name and Tab.artist_id == artist.id)).scalar(): 73 | tab = Tab(str(uuid4()), tab_dir_name, 1, artist.id, 1, 1, '', None) 74 | db.session.add(tab) 75 | result_info['tabs'] += 1 76 | 77 | 78 | ### Traverse imgs files under tab folder 79 | for file_name in os.listdir(tab_dir_path): 80 | file_path = os.path.join(tab_dir_path, file_name) 81 | if not os.path.isfile(file_path): 82 | continue 83 | 84 | if not oshelper.get_extension(file_name) in current_app.config['TAB_FILE_ALLOWED_EXTENSIONS']: 85 | continue 86 | 87 | try: 88 | dest_path = os.path.join(oshelper.get_tabfile_upload_abspath(), tab.id) 89 | if not os.path.isdir(dest_path): 90 | os.mkdir(dest_path) 91 | shutil.copy(file_path, dest_path) 92 | 93 | tabfile = TabFile(str(uuid4()), tab.id, os.path.join(tab.id, file_name)) 94 | db.session.add(tabfile) 95 | 96 | except: 97 | e = sys.exc_info()[0] 98 | result_info['errors'].append({ 99 | 'artist': artist_dir_name, 100 | 'tab': tab_dir_name, 101 | 'file': file_name, 102 | 'error': e 103 | }) 104 | continue 105 | 106 | db.session.commit() 107 | 108 | return jsonify(result='success', msg=result_info) -------------------------------------------------------------------------------- /guitarfan/templates/admin/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Guitar123 {% block title %}{% endblock %} 6 | 7 | 8 | 9 | 10 | 11 | {# #} 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {% block style -%}{%- endblock %} 21 | 22 | 23 | 24 | {%- import "admin/macro.html" as macro -%} 25 | 26 | {%- block body %} 27 | 54 | 55 |
    56 |
    57 | {% block sidebar -%}{%- endblock %} 58 | {% block main -%}{%- endblock %} 59 |
    60 |
    61 |
    62 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | {% block script -%}{%- endblock %} 80 | 81 | {%- endblock %} 82 | 83 | -------------------------------------------------------------------------------- /guitarfan/templates/admin/tabs/data_import.html: -------------------------------------------------------------------------------- 1 | {% extends "tabs/base.html" %} 2 | 3 | {% block active_import_tag %}active{% endblock %} 4 | 5 | {% block main %} 6 |
    7 | 11 | 12 | {{ macro.feedback_message() }} 13 | 14 |
    15 | File Structure Sample: 16 |

    17 | 18 | |PathToDataSouceFolder
    19 | |----A
    20 | |    |----ArtistA
    21 | |    |----ArtistB
    22 | |    |    |----TabA
    23 | |    |    |----TabB
    24 | |    |    |    |----File1
    25 | |    |    |    |----File2
    26 | |----B
    27 | |----C
    28 | . . . . . .
    29 |
    30 |

    31 | 32 | 33 | 38 |
    39 | 43 |
    44 | {% endblock %} 45 | 46 | {% block script %} 47 | 109 | {% endblock %} -------------------------------------------------------------------------------- /guitarfan/static/dataTables/css/jquery.dataTables.css: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Table 4 | */ 5 | table.dataTable { 6 | margin: 0 auto; 7 | clear: both; 8 | width: 100%; 9 | } 10 | 11 | table.dataTable thead th { 12 | padding: 3px 18px 3px 10px; 13 | border-bottom: 1px solid black; 14 | font-weight: bold; 15 | cursor: pointer; 16 | *cursor: hand; 17 | } 18 | 19 | table.dataTable tfoot th { 20 | padding: 3px 18px 3px 10px; 21 | border-top: 1px solid black; 22 | font-weight: bold; 23 | } 24 | 25 | table.dataTable td { 26 | padding: 3px 10px; 27 | } 28 | 29 | table.dataTable td.center, 30 | table.dataTable td.dataTables_empty { 31 | text-align: center; 32 | } 33 | 34 | table.dataTable tr.odd { background-color: #E2E4FF; } 35 | table.dataTable tr.even { background-color: white; } 36 | 37 | table.dataTable tr.odd td.sorting_1 { background-color: #D3D6FF; } 38 | table.dataTable tr.odd td.sorting_2 { background-color: #DADCFF; } 39 | table.dataTable tr.odd td.sorting_3 { background-color: #E0E2FF; } 40 | table.dataTable tr.even td.sorting_1 { background-color: #EAEBFF; } 41 | table.dataTable tr.even td.sorting_2 { background-color: #F2F3FF; } 42 | table.dataTable tr.even td.sorting_3 { background-color: #F9F9FF; } 43 | 44 | 45 | /* 46 | * Table wrapper 47 | */ 48 | .dataTables_wrapper { 49 | position: relative; 50 | clear: both; 51 | *zoom: 1; 52 | } 53 | 54 | 55 | /* 56 | * Page length menu 57 | */ 58 | .dataTables_length { 59 | float: left; 60 | } 61 | 62 | 63 | /* 64 | * Filter 65 | */ 66 | .dataTables_filter { 67 | float: right; 68 | text-align: right; 69 | } 70 | 71 | 72 | /* 73 | * Table information 74 | */ 75 | .dataTables_info { 76 | clear: both; 77 | float: left; 78 | } 79 | 80 | 81 | /* 82 | * Pagination 83 | */ 84 | .dataTables_paginate { 85 | float: right; 86 | text-align: right; 87 | } 88 | 89 | /* Two button pagination - previous / next */ 90 | .paginate_disabled_previous, 91 | .paginate_enabled_previous, 92 | .paginate_disabled_next, 93 | .paginate_enabled_next { 94 | height: 19px; 95 | float: left; 96 | cursor: pointer; 97 | *cursor: hand; 98 | color: #111 !important; 99 | } 100 | .paginate_disabled_previous:hover, 101 | .paginate_enabled_previous:hover, 102 | .paginate_disabled_next:hover, 103 | .paginate_enabled_next:hover { 104 | text-decoration: none !important; 105 | } 106 | .paginate_disabled_previous:active, 107 | .paginate_enabled_previous:active, 108 | .paginate_disabled_next:active, 109 | .paginate_enabled_next:active { 110 | outline: none; 111 | } 112 | 113 | .paginate_disabled_previous, 114 | .paginate_disabled_next { 115 | color: #666 !important; 116 | } 117 | .paginate_disabled_previous, 118 | .paginate_enabled_previous { 119 | padding-left: 23px; 120 | } 121 | .paginate_disabled_next, 122 | .paginate_enabled_next { 123 | padding-right: 23px; 124 | margin-left: 10px; 125 | } 126 | 127 | .paginate_enabled_previous { background: url('../images/back_enabled.png') no-repeat top left; } 128 | .paginate_enabled_previous:hover { background: url('../images/back_enabled_hover.png') no-repeat top left; } 129 | .paginate_disabled_previous { background: url('../images/back_disabled.png') no-repeat top left; } 130 | 131 | .paginate_enabled_next { background: url('../images/forward_enabled.png') no-repeat top right; } 132 | .paginate_enabled_next:hover { background: url('../images/forward_enabled_hover.png') no-repeat top right; } 133 | .paginate_disabled_next { background: url('../images/forward_disabled.png') no-repeat top right; } 134 | 135 | /* Full number pagination */ 136 | .paging_full_numbers { 137 | height: 22px; 138 | line-height: 22px; 139 | } 140 | .paging_full_numbers a:active { 141 | outline: none 142 | } 143 | .paging_full_numbers a:hover { 144 | text-decoration: none; 145 | } 146 | 147 | .paging_full_numbers a.paginate_button, 148 | .paging_full_numbers a.paginate_active { 149 | border: 1px solid #aaa; 150 | -webkit-border-radius: 5px; 151 | -moz-border-radius: 5px; 152 | border-radius: 5px; 153 | padding: 2px 5px; 154 | margin: 0 3px; 155 | cursor: pointer; 156 | *cursor: hand; 157 | color: #333 !important; 158 | } 159 | 160 | .paging_full_numbers a.paginate_button { 161 | background-color: #ddd; 162 | } 163 | 164 | .paging_full_numbers a.paginate_button:hover { 165 | background-color: #ccc; 166 | text-decoration: none !important; 167 | } 168 | 169 | .paging_full_numbers a.paginate_active { 170 | background-color: #99B3FF; 171 | } 172 | 173 | 174 | /* 175 | * Processing indicator 176 | */ 177 | .dataTables_processing { 178 | position: absolute; 179 | top: 50%; 180 | left: 50%; 181 | width: 250px; 182 | height: 30px; 183 | margin-left: -125px; 184 | margin-top: -15px; 185 | padding: 14px 0 2px 0; 186 | border: 1px solid #ddd; 187 | text-align: center; 188 | color: #999; 189 | font-size: 14px; 190 | background-color: white; 191 | } 192 | 193 | 194 | /* 195 | * Sorting 196 | */ 197 | .sorting { background: url('../images/sort_both.png') no-repeat center right; } 198 | .sorting_asc { background: url('../images/sort_asc.png') no-repeat center right; } 199 | .sorting_desc { background: url('../images/sort_desc.png') no-repeat center right; } 200 | 201 | .sorting_asc_disabled { background: url('../images/sort_asc_disabled.png') no-repeat center right; } 202 | .sorting_desc_disabled { background: url('../images/sort_desc_disabled.png') no-repeat center right; } 203 | 204 | table.dataTable thead th:active, 205 | table.dataTable thead td:active { 206 | outline: none; 207 | } 208 | 209 | 210 | /* 211 | * Scrolling 212 | */ 213 | .dataTables_scroll { 214 | clear: both; 215 | } 216 | 217 | .dataTables_scrollBody { 218 | *margin-top: -1px; 219 | -webkit-overflow-scrolling: touch; 220 | } 221 | 222 | -------------------------------------------------------------------------------- /guitarfan/controlers/admin/administrator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from uuid import uuid4 5 | 6 | from flask import render_template, request, redirect, url_for, flash, Blueprint 7 | from flask.ext.login import login_user, logout_user, login_required, current_user 8 | from sqlalchemy import or_ 9 | 10 | from guitarfan.models.administrator import Administrator 11 | from guitarfan.extensions.flasksqlalchemy import db 12 | from forms.administrator import * 13 | 14 | bp_admin_administrator = Blueprint('bp_admin_administrator', __name__, template_folder="../../templates/admin") 15 | 16 | #@bp_admin_administrator.route('/admin') 17 | #@login_required 18 | #def index(): 19 | # return render_template('admin/index.html') 20 | 21 | 22 | @bp_admin_administrator.route('/admin/login', methods=['GET', 'POST']) 23 | def login(): 24 | form = AdministratorLoginForm() 25 | 26 | if request.method == 'GET': 27 | return render_template('login.html', form=form) 28 | elif request.method == 'POST': 29 | if form.validate_on_submit(): 30 | administrator = Administrator.query.filter(or_(Administrator.email == form.name.data, Administrator.name == form.name.data)).first() 31 | passed = True 32 | if administrator is None: 33 | flash(u'Account does not exist', 'error') 34 | passed = False 35 | elif not administrator.is_active(): 36 | flash(u'Account has been disabled', 'error') 37 | passed = False 38 | elif administrator.check_password(form.password.data): 39 | flash(u'Welcome ' + administrator.name + ', login successful', 'success') 40 | passed = True 41 | else: 42 | flash(u'Password is not correct', 'error') 43 | passed = False 44 | 45 | if passed: 46 | login_user(administrator) 47 | if 'next' in request.values: 48 | return redirect(request.values['next']) 49 | else: 50 | return redirect(url_for('bp_admin_administrator.list')) 51 | else: 52 | 53 | return render_template('login.html', form=form) 54 | else: 55 | flash(validator.catch_errors(form.errors), 'error') 56 | return render_template('login.html', form=form) 57 | 58 | 59 | @bp_admin_administrator.route('/admin/logout', methods=['GET']) 60 | def logout(): 61 | logout_user() 62 | return redirect(url_for('bp_admin_administrator.login')) 63 | 64 | 65 | @bp_admin_administrator.route('/admin/administrators') 66 | @login_required 67 | def list(): 68 | administrators = Administrator.query.all() 69 | return render_template('dashboard/administrator_management.html', action='list', administrators=administrators) 70 | 71 | 72 | @bp_admin_administrator.route('/admin/administrators/add', methods=['GET', 'POST']) 73 | @login_required 74 | def add(): 75 | form = AddAdministratorForm() 76 | 77 | if request.method == 'GET': 78 | return render_template('dashboard/administrator_management.html', action='add', form=form) 79 | elif request.method == 'POST': 80 | if form.validate_on_submit(): 81 | administrator = Administrator(str(uuid4()), form.name.data, form.email.data, form.password.data, 1) 82 | db.session.add(administrator) 83 | db.session.commit() 84 | flash(u'Add new administrator successfully', 'success') 85 | return redirect(url_for('bp_admin_administrator.list')) 86 | else: 87 | flash(validator.catch_errors(form.errors), 'error') 88 | return render_template('dashboard/administrator_management.html', action='add', form=form) 89 | 90 | 91 | @bp_admin_administrator.route('/admin/administrators/', methods=['GET', 'POST']) 92 | @login_required 93 | def edit(id): 94 | administrator = Administrator.query.get(id) 95 | form = EditAdministratorForm(status=administrator.status) 96 | if request.method == 'GET': 97 | return render_template('dashboard/administrator_management.html', action='edit', form=form, administrator=administrator) 98 | elif request.method == 'POST': 99 | if form.validate_on_submit(): 100 | if 8 <= len(form.new_password.data) <= 20: 101 | administrator.update_password(form.new_password.data) 102 | if current_user.name != administrator.name: 103 | administrator.status = form.status.data 104 | db.session.commit() 105 | 106 | flash(u'Update administrator successfully', 'success') 107 | return redirect(url_for('bp_admin_administrator.list')) 108 | else: 109 | flash(validator.catch_errors(form.errors), 'error') 110 | return render_template('dashboard/administrator_management.html', action='edit', form=form, administrator=administrator) 111 | 112 | 113 | @bp_admin_administrator.route('/admin/administrators', methods=['DELETE']) 114 | @login_required 115 | def delete(): 116 | administrator = Administrator.query.get(request.values['id']) 117 | db.session.delete(administrator) 118 | db.session.commit() 119 | return 'success' 120 | 121 | 122 | @bp_admin_administrator.route('/admin/administrators//status/') 123 | @login_required 124 | def update_status(id, status): 125 | administrator = Administrator.query.get(id) 126 | administrator.status = status 127 | db.session.commit() 128 | 129 | flash('Change administrator status successfully', 'success') 130 | return redirect(url_for('bp_admin_administrator.list')) 131 | -------------------------------------------------------------------------------- /guitarfan/templates/site/index.html: -------------------------------------------------------------------------------- 1 | {% extends "site/base.html" %} 2 | 3 | {% block title %} - 首页{% endblock %} 4 | {% block active_navbar_home %}active{% endblock %} 5 | {% block navbar_search %}{% endblock %} 6 | {% block body %} 7 | 8 |
    9 |
    10 |
    11 |
    12 |
    13 |
    14 |
    15 | 16 |

    热门曲谱 · · · · · ·

    17 |
    18 | 32 | 33 |
    34 |
    35 |
    36 |
    37 | 38 |

    最新加入 · · · · · ·

    39 |
    40 | 52 | 53 |
    54 |
    55 |
    56 |
    57 |
    58 |
    59 |

    音乐风格

    60 |
    61 |
    62 |
    63 |

    曲谱标签

    64 |
    65 |
    66 |
    67 |
    68 |
    69 |
    70 | {#
    #} 71 | {#
    #} 72 | {#
    #} 73 | {#
    #} 74 | {#
    #} 75 | {#
    #} 76 | {# #} 77 | {#
    #} 78 | {#
    #} 79 | {# #} 80 | {#
    #} 81 | {#
    #} 82 | {# #} 83 | {#
    #} 84 | {#
    #} 85 | {# #} 86 | {#
    #} 87 | {#
    #} 88 | {#
    #} 89 | {#
    #} 90 | {#
    #} 91 | {#
    #} 92 | {% endblock %} -------------------------------------------------------------------------------- /guitarfan/templates/admin/tabs/tag_management.html: -------------------------------------------------------------------------------- 1 | {% extends "tabs/base.html" %} 2 | 3 | {% block active_tabs_tag %}active{% endblock %} 4 | 5 | {% block main %} 6 |
    7 | {% if action == 'list' %} 8 | 12 | 13 | {{ macro.feedback_message() }} 14 | 15 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {% for tag in tags %} 34 | 35 | 36 | 37 | 38 | 49 | 50 | {% endfor %} 51 | 52 |
    tag nametabsupdate time
    {{ tag.name }}{{ tag.tabs|length }}{{ tag.update_time }} 39 | 48 |
    53 | {% elif action == 'add' %} 54 | 59 | 60 | {{ macro.feedback_message() }} 61 | 62 |
    63 |
    64 | {{ form.hidden_tag() }} 65 |
    66 | {{ macro.create_wtf_field(form.name, class="span8") }} 67 |
    68 | 69 |
    70 | {{ form.submit(class="btn btn-danger", value="Submit") }} 71 |
    72 |
    73 |
    74 | {% elif action == 'edit' %} 75 | 80 | 81 | {{ macro.feedback_message() }} 82 | 83 |
    84 |
    85 | {{ form.hidden_tag() }} 86 |
    87 | {{ macro.create_wtf_field(form.name, class="span8") }} 88 |
    89 | 90 |
    91 | {{ form.submit(class="btn btn-danger", value="Submit") }} 92 |
    93 |
    94 |
    95 | {% endif %} 96 |
    97 | {% endblock %} 98 | 99 | {% block script %} 100 | 131 | {% endblock %} -------------------------------------------------------------------------------- /guitarfan/static/dataTables/css/jquery.dataTables_themeroller.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* 4 | * Table 5 | */ 6 | table.dataTable { 7 | margin: 0 auto; 8 | clear: both; 9 | width: 100%; 10 | border-collapse: collapse; 11 | } 12 | 13 | table.dataTable thead th { 14 | padding: 3px 0px 3px 10px; 15 | cursor: pointer; 16 | *cursor: hand; 17 | } 18 | 19 | table.dataTable tfoot th { 20 | padding: 3px 10px; 21 | } 22 | 23 | table.dataTable td { 24 | padding: 3px 10px; 25 | } 26 | 27 | table.dataTable td.center, 28 | table.dataTable td.dataTables_empty { 29 | text-align: center; 30 | } 31 | 32 | table.dataTable tr.odd { background-color: #E2E4FF; } 33 | table.dataTable tr.even { background-color: white; } 34 | 35 | table.dataTable tr.odd td.sorting_1 { background-color: #D3D6FF; } 36 | table.dataTable tr.odd td.sorting_2 { background-color: #DADCFF; } 37 | table.dataTable tr.odd td.sorting_3 { background-color: #E0E2FF; } 38 | table.dataTable tr.even td.sorting_1 { background-color: #EAEBFF; } 39 | table.dataTable tr.even td.sorting_2 { background-color: #F2F3FF; } 40 | table.dataTable tr.even td.sorting_3 { background-color: #F9F9FF; } 41 | 42 | 43 | /* 44 | * Table wrapper 45 | */ 46 | .dataTables_wrapper { 47 | position: relative; 48 | clear: both; 49 | *zoom: 1; 50 | } 51 | .dataTables_wrapper .ui-widget-header { 52 | font-weight: normal; 53 | } 54 | .dataTables_wrapper .ui-toolbar { 55 | padding: 5px; 56 | } 57 | 58 | 59 | /* 60 | * Page length menu 61 | */ 62 | .dataTables_length { 63 | float: left; 64 | } 65 | 66 | 67 | /* 68 | * Filter 69 | */ 70 | .dataTables_filter { 71 | float: right; 72 | text-align: right; 73 | } 74 | 75 | 76 | /* 77 | * Table information 78 | */ 79 | .dataTables_info { 80 | padding-top: 3px; 81 | clear: both; 82 | float: left; 83 | } 84 | 85 | 86 | /* 87 | * Pagination 88 | */ 89 | .dataTables_paginate { 90 | float: right; 91 | text-align: right; 92 | } 93 | 94 | .dataTables_paginate .ui-button { 95 | margin-right: -0.1em !important; 96 | } 97 | 98 | .paging_two_button .ui-button { 99 | float: left; 100 | cursor: pointer; 101 | * cursor: hand; 102 | } 103 | 104 | .paging_full_numbers .ui-button { 105 | padding: 2px 6px; 106 | margin: 0; 107 | cursor: pointer; 108 | * cursor: hand; 109 | color: #333 !important; 110 | } 111 | 112 | /* Two button pagination - previous / next */ 113 | .paginate_disabled_previous, 114 | .paginate_enabled_previous, 115 | .paginate_disabled_next, 116 | .paginate_enabled_next { 117 | height: 19px; 118 | float: left; 119 | cursor: pointer; 120 | *cursor: hand; 121 | color: #111 !important; 122 | } 123 | .paginate_disabled_previous:hover, 124 | .paginate_enabled_previous:hover, 125 | .paginate_disabled_next:hover, 126 | .paginate_enabled_next:hover { 127 | text-decoration: none !important; 128 | } 129 | .paginate_disabled_previous:active, 130 | .paginate_enabled_previous:active, 131 | .paginate_disabled_next:active, 132 | .paginate_enabled_next:active { 133 | outline: none; 134 | } 135 | 136 | .paginate_disabled_previous, 137 | .paginate_disabled_next { 138 | color: #666 !important; 139 | } 140 | .paginate_disabled_previous, 141 | .paginate_enabled_previous { 142 | padding-left: 23px; 143 | } 144 | .paginate_disabled_next, 145 | .paginate_enabled_next { 146 | padding-right: 23px; 147 | margin-left: 10px; 148 | } 149 | 150 | .paginate_enabled_previous { background: url('../images/back_enabled.png') no-repeat top left; } 151 | .paginate_enabled_previous:hover { background: url('../images/back_enabled_hover.png') no-repeat top left; } 152 | .paginate_disabled_previous { background: url('../images/back_disabled.png') no-repeat top left; } 153 | 154 | .paginate_enabled_next { background: url('../images/forward_enabled.png') no-repeat top right; } 155 | .paginate_enabled_next:hover { background: url('../images/forward_enabled_hover.png') no-repeat top right; } 156 | .paginate_disabled_next { background: url('../images/forward_disabled.png') no-repeat top right; } 157 | 158 | /* Full number pagination */ 159 | .paging_full_numbers a:active { 160 | outline: none 161 | } 162 | .paging_full_numbers a:hover { 163 | text-decoration: none; 164 | } 165 | 166 | .paging_full_numbers a.paginate_button, 167 | .paging_full_numbers a.paginate_active { 168 | border: 1px solid #aaa; 169 | -webkit-border-radius: 5px; 170 | -moz-border-radius: 5px; 171 | border-radius: 5px; 172 | padding: 2px 5px; 173 | margin: 0 3px; 174 | cursor: pointer; 175 | *cursor: hand; 176 | color: #333 !important; 177 | } 178 | 179 | .paging_full_numbers a.paginate_button { 180 | background-color: #ddd; 181 | } 182 | 183 | .paging_full_numbers a.paginate_button:hover { 184 | background-color: #ccc; 185 | text-decoration: none !important; 186 | } 187 | 188 | .paging_full_numbers a.paginate_active { 189 | background-color: #99B3FF; 190 | } 191 | 192 | 193 | /* 194 | * Processing indicator 195 | */ 196 | .dataTables_processing { 197 | position: absolute; 198 | top: 50%; 199 | left: 50%; 200 | width: 250px; 201 | height: 30px; 202 | margin-left: -125px; 203 | margin-top: -15px; 204 | padding: 14px 0 2px 0; 205 | border: 1px solid #ddd; 206 | text-align: center; 207 | color: #999; 208 | font-size: 14px; 209 | background-color: white; 210 | } 211 | 212 | 213 | /* 214 | * Sorting 215 | */ 216 | table.dataTable thead th div.DataTables_sort_wrapper { 217 | position: relative; 218 | padding-right: 20px; 219 | } 220 | 221 | table.dataTable thead th div.DataTables_sort_wrapper span { 222 | position: absolute; 223 | top: 50%; 224 | margin-top: -8px; 225 | right: 0; 226 | } 227 | 228 | table.dataTable th:active { 229 | outline: none; 230 | } 231 | 232 | 233 | /* 234 | * Scrolling 235 | */ 236 | .dataTables_scroll { 237 | clear: both; 238 | } 239 | 240 | .dataTables_scrollBody { 241 | *margin-top: -1px; 242 | -webkit-overflow-scrolling: touch; 243 | } 244 | 245 | -------------------------------------------------------------------------------- /guitarfan/static/FineUploader/fineuploader-3.7.1.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Fine Uploader 3 | * 4 | * Copyright 2013, Widen Enterprises, Inc. info@fineuploader.com 5 | * 6 | * Version: 3.7.1 7 | * 8 | * Homepage: http://fineuploader.com 9 | * 10 | * Repository: git://github.com/Widen/fine-uploader.git 11 | * 12 | * Licensed under GNU GPL v3, see LICENSE 13 | */ 14 | 15 | 16 | .qq-uploader { 17 | position: relative; 18 | width: 100%; 19 | } 20 | .qq-upload-button { 21 | display: block; 22 | width: 105px; 23 | padding: 7px 0; 24 | text-align: center; 25 | background: #880000; 26 | border-bottom: 1px solid #DDD; 27 | color: #FFF; 28 | } 29 | .qq-upload-button-hover { 30 | background: #CC0000; 31 | } 32 | .qq-upload-button-focus { 33 | outline: 1px dotted #000000; 34 | } 35 | .qq-upload-drop-area, .qq-upload-extra-drop-area { 36 | position: absolute; 37 | top: 0; 38 | left: 0; 39 | width: 100%; 40 | height: 100%; 41 | min-height: 30px; 42 | z-index: 2; 43 | background: #FF9797; 44 | text-align: center; 45 | } 46 | .qq-upload-drop-area span { 47 | display: block; 48 | position: absolute; 49 | top: 50%; 50 | width: 100%; 51 | margin-top: -8px; 52 | font-size: 16px; 53 | } 54 | .qq-upload-extra-drop-area { 55 | position: relative; 56 | margin-top: 50px; 57 | font-size: 16px; 58 | padding-top: 30px; 59 | height: 20px; 60 | min-height: 40px; 61 | } 62 | .qq-upload-drop-area-active { 63 | background: #FF7171; 64 | } 65 | .qq-upload-list { 66 | margin: 0; 67 | padding: 0; 68 | list-style: none; 69 | } 70 | .qq-upload-list li { 71 | margin: 0; 72 | padding: 9px; 73 | line-height: 15px; 74 | font-size: 16px; 75 | background-color: #FFF0BD; 76 | } 77 | .qq-upload-file, .qq-upload-spinner, .qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-failed-text, .qq-upload-finished, .qq-upload-delete { 78 | margin-right: 12px; 79 | } 80 | .qq-upload-file { 81 | } 82 | .qq-upload-spinner { 83 | display: inline-block; 84 | background: url("loading.gif"); 85 | width: 15px; 86 | height: 15px; 87 | vertical-align: text-bottom; 88 | } 89 | .qq-drop-processing { 90 | display: none; 91 | } 92 | .qq-drop-processing-spinner { 93 | display: inline-block; 94 | background: url("processing.gif"); 95 | width: 24px; 96 | height: 24px; 97 | vertical-align: text-bottom; 98 | } 99 | .qq-upload-finished { 100 | display:none; 101 | width:15px; 102 | height:15px; 103 | vertical-align:text-bottom; 104 | } 105 | .qq-upload-retry, .qq-upload-delete { 106 | display: none; 107 | color: #000000; 108 | } 109 | .qq-upload-cancel, .qq-upload-delete { 110 | color: #000000; 111 | } 112 | .qq-upload-retryable .qq-upload-retry { 113 | display: inline; 114 | } 115 | .qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-delete { 116 | font-size: 12px; 117 | font-weight: normal; 118 | } 119 | .qq-upload-failed-text { 120 | display: none; 121 | font-style: italic; 122 | font-weight: bold; 123 | } 124 | .qq-upload-failed-icon { 125 | display:none; 126 | width:15px; 127 | height:15px; 128 | vertical-align:text-bottom; 129 | } 130 | .qq-upload-fail .qq-upload-failed-text { 131 | display: inline; 132 | } 133 | .qq-upload-retrying .qq-upload-failed-text { 134 | display: inline; 135 | color: #D60000; 136 | } 137 | .qq-upload-list li.qq-upload-success { 138 | background-color: #5DA30C; 139 | color: #FFFFFF; 140 | } 141 | .qq-upload-list li.qq-upload-fail { 142 | background-color: #D60000; 143 | color: #FFFFFF; 144 | } 145 | .qq-progress-bar { 146 | background: -moz-linear-gradient(top, rgba(30,87,153,1) 0%, rgba(41,137,216,1) 50%, rgba(32,124,202,1) 51%, rgba(125,185,232,1) 100%); /* FF3.6+ */ 147 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(30,87,153,1)), color-stop(50%,rgba(41,137,216,1)), color-stop(51%,rgba(32,124,202,1)), color-stop(100%,rgba(125,185,232,1))); /* Chrome,Safari4+ */ 148 | background: -webkit-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Chrome10+,Safari5.1+ */ 149 | background: -o-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Opera 11.10+ */ 150 | background: -ms-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* IE10+ */ 151 | background: linear-gradient(to bottom, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* W3C */ 152 | width: 0%; 153 | height: 15px; 154 | border-radius: 6px; 155 | margin-bottom: 3px; 156 | display: none; 157 | } 158 | 159 | INPUT.qq-edit-filename { 160 | position: absolute; 161 | opacity: 0; 162 | filter: alpha(opacity=0); 163 | z-index: -1; 164 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; 165 | } 166 | 167 | .qq-upload-file.qq-editable { 168 | cursor: pointer; 169 | } 170 | 171 | .qq-edit-filename-icon.qq-editable { 172 | display: inline-block; 173 | cursor: pointer; 174 | } 175 | 176 | INPUT.qq-edit-filename.qq-editing { 177 | position: static; 178 | margin-top: -5px; 179 | margin-right: 10px; 180 | margin-bottom: -5px; 181 | 182 | opacity: 1; 183 | filter: alpha(opacity=100); 184 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; 185 | } 186 | 187 | .qq-edit-filename-icon { 188 | display: none; 189 | background: url("edit.gif"); 190 | width: 15px; 191 | height: 15px; 192 | vertical-align: text-bottom; 193 | margin-right: 5px; 194 | } 195 | 196 | INPUT.qq-edit-filename.qq-editing ~ .qq-upload-cancel { 197 | display: none; 198 | } 199 | 200 | /*! 2013-07-24 */ 201 | -------------------------------------------------------------------------------- /guitarfan/static/js/admin.js: -------------------------------------------------------------------------------- 1 | ;(function($){ 2 | $(function() { 3 | var action_bar = $('.form-actions'); 4 | if(action_bar.length){ 5 | var height=action_bar[0].offsetTop + action_bar.outerHeight(); 6 | var onchange = function(){ 7 | var s=(document.body.scrollTop||document.documentElement.scrollTop) + window.innerHeight; 8 | if(s'+ 45 | ''+ 46 | ''+ 47 | '' 48 | ); 49 | var els = $('a', nPaging); 50 | $(els[0]).bind( 'click.DT', { action: "previous" }, fnClickHandler ); 51 | $(els[1]).bind( 'click.DT', { action: "next" }, fnClickHandler ); 52 | }, 53 | 54 | "fnUpdate": function ( oSettings, fnDraw ) { 55 | var iListLength = 5; 56 | var oPaging = oSettings.oInstance.fnPagingInfo(); 57 | var an = oSettings.aanFeatures.p; 58 | var i, j, sClass, iStart, iEnd, iHalf=Math.floor(iListLength/2); 59 | 60 | if ( oPaging.iTotalPages < iListLength) { 61 | iStart = 1; 62 | iEnd = oPaging.iTotalPages; 63 | } 64 | else if ( oPaging.iPage <= iHalf ) { 65 | iStart = 1; 66 | iEnd = iListLength; 67 | } else if ( oPaging.iPage >= (oPaging.iTotalPages-iHalf) ) { 68 | iStart = oPaging.iTotalPages - iListLength + 1; 69 | iEnd = oPaging.iTotalPages; 70 | } else { 71 | iStart = oPaging.iPage - iHalf + 1; 72 | iEnd = iStart + iListLength - 1; 73 | } 74 | 75 | for ( i=0, iLen=an.length ; i'+j+'') 83 | .insertBefore( $('li:last', an[i])[0] ) 84 | .bind('click', function (e) { 85 | e.preventDefault(); 86 | oSettings._iDisplayStart = (parseInt($('a', this).text(),10)-1) * oPaging.iLength; 87 | fnDraw( oSettings ); 88 | } ); 89 | } 90 | 91 | // Add / remove disabled classes from the static elements 92 | if ( oPaging.iPage === 0 ) { 93 | $('li:first', an[i]).addClass('disabled'); 94 | } else { 95 | $('li:first', an[i]).removeClass('disabled'); 96 | } 97 | 98 | if ( oPaging.iPage === oPaging.iTotalPages-1 || oPaging.iTotalPages === 0 ) { 99 | $('li:last', an[i]).addClass('disabled'); 100 | } else { 101 | $('li:last', an[i]).removeClass('disabled'); 102 | } 103 | } 104 | } 105 | } 106 | } ); 107 | 108 | 109 | /* fancyBox: Fade content when changing gallery items */ 110 | (function ($, F) { 111 | F.transitions.resizeIn = function() { 112 | var previous = F.previous, 113 | current = F.current, 114 | startPos = previous.wrap.stop(true).position(), 115 | endPos = $.extend({opacity : 1}, current.pos); 116 | 117 | startPos.width = previous.wrap.width(); 118 | startPos.height = previous.wrap.height(); 119 | 120 | previous.wrap.stop(true).trigger('onReset').remove(); 121 | 122 | delete endPos.position; 123 | 124 | current.inner.hide(); 125 | 126 | current.wrap.css(startPos).animate(endPos, { 127 | duration : current.nextSpeed, 128 | easing : current.nextEasing, 129 | step : F.transitions.step, 130 | complete : function() { 131 | F._afterZoomIn(); 132 | 133 | current.inner.fadeIn("fast"); 134 | } 135 | }); 136 | }; 137 | 138 | }(jQuery, jQuery.fancybox)); 139 | 140 | 141 | /* add format method to String's prototype */ 142 | if (!String.prototype.format) { 143 | String.prototype.format = function() { 144 | var args = arguments; 145 | return this.replace(/{(\d+)}/g, function(match, number) { 146 | return typeof args[number] != 'undefined' ? args[number] : match; 147 | }); 148 | }; 149 | }; -------------------------------------------------------------------------------- /guitarfan/templates/admin/tabs/artist_management.html: -------------------------------------------------------------------------------- 1 | {% extends "tabs/base.html" %} 2 | 3 | {% block active_tabs_artist %}active{% endblock %} 4 | 5 | {% block main %} 6 |
    7 | {% if action == 'list' %} 8 | 12 | 13 | {{ macro.feedback_message() }} 14 | 15 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
    artistregioncategorytabsphotoupdate time
    Loading...
    42 | {% elif action == 'add' %} 43 | 48 | 49 | {{ macro.feedback_message() }} 50 | 51 |
    52 |
    53 | {{ form.hidden_tag() }} 54 |
    55 | {{ macro.create_wtf_field(form.name) }} 56 | {{ macro.create_wtf_field(form.letter, class="select2 span2") }} 57 | {{ macro.create_wtf_field(form.region, class="select2 span2") }} 58 | {{ macro.create_wtf_field(form.category, class="select2 span2") }} 59 | {{ macro.create_wtf_field(form.photo) }} 60 |
    61 | 62 |
    63 | {{ form.submit(class="btn btn-danger") }} 64 |
    65 |
    66 |
    67 | {% elif action == 'edit' %} 68 | 73 | 74 | {{ macro.feedback_message() }} 75 | 76 |
    77 |
    78 | {{ form.hidden_tag() }} 79 |
    80 | {{ macro.create_wtf_field(form.name) }} 81 | {{ macro.create_wtf_field(form.letter, class="select2 span2") }} 82 | {{ macro.create_wtf_field(form.region, class="select2 span2") }} 83 | {{ macro.create_wtf_field(form.category, class="select2 span2") }} 84 | {{ macro.create_wtf_field(form.photo) }} 85 |
    86 | 87 |
    88 | 89 |
    90 |
    91 |
    92 | 93 |
    94 | {{ form.submit(class="btn btn-danger") }} 95 |
    96 |
    97 |
    98 | {% endif %} 99 |
    100 | {% endblock %} 101 | 102 | {% block script %} 103 | 140 | {% endblock %} -------------------------------------------------------------------------------- /guitarfan/static/fancybox/helpers/jquery.fancybox-media.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Media helper for fancyBox 3 | * version: 1.0.6 (Fri, 14 Jun 2013) 4 | * @requires fancyBox v2.0 or later 5 | * 6 | * Usage: 7 | * $(".fancybox").fancybox({ 8 | * helpers : { 9 | * media: true 10 | * } 11 | * }); 12 | * 13 | * Set custom URL parameters: 14 | * $(".fancybox").fancybox({ 15 | * helpers : { 16 | * media: { 17 | * youtube : { 18 | * params : { 19 | * autoplay : 0 20 | * } 21 | * } 22 | * } 23 | * } 24 | * }); 25 | * 26 | * Or: 27 | * $(".fancybox").fancybox({, 28 | * helpers : { 29 | * media: true 30 | * }, 31 | * youtube : { 32 | * autoplay: 0 33 | * } 34 | * }); 35 | * 36 | * Supports: 37 | * 38 | * Youtube 39 | * http://www.youtube.com/watch?v=opj24KnzrWo 40 | * http://www.youtube.com/embed/opj24KnzrWo 41 | * http://youtu.be/opj24KnzrWo 42 | * http://www.youtube-nocookie.com/embed/opj24KnzrWo 43 | * Vimeo 44 | * http://vimeo.com/40648169 45 | * http://vimeo.com/channels/staffpicks/38843628 46 | * http://vimeo.com/groups/surrealism/videos/36516384 47 | * http://player.vimeo.com/video/45074303 48 | * Metacafe 49 | * http://www.metacafe.com/watch/7635964/dr_seuss_the_lorax_movie_trailer/ 50 | * http://www.metacafe.com/watch/7635964/ 51 | * Dailymotion 52 | * http://www.dailymotion.com/video/xoytqh_dr-seuss-the-lorax-premiere_people 53 | * Twitvid 54 | * http://twitvid.com/QY7MD 55 | * Twitpic 56 | * http://twitpic.com/7p93st 57 | * Instagram 58 | * http://instagr.am/p/IejkuUGxQn/ 59 | * http://instagram.com/p/IejkuUGxQn/ 60 | * Google maps 61 | * http://maps.google.com/maps?q=Eiffel+Tower,+Avenue+Gustave+Eiffel,+Paris,+France&t=h&z=17 62 | * http://maps.google.com/?ll=48.857995,2.294297&spn=0.007666,0.021136&t=m&z=16 63 | * http://maps.google.com/?ll=48.859463,2.292626&spn=0.000965,0.002642&t=m&z=19&layer=c&cbll=48.859524,2.292532&panoid=YJ0lq28OOy3VT2IqIuVY0g&cbp=12,151.58,,0,-15.56 64 | */ 65 | (function ($) { 66 | "use strict"; 67 | 68 | //Shortcut for fancyBox object 69 | var F = $.fancybox, 70 | format = function( url, rez, params ) { 71 | params = params || ''; 72 | 73 | if ( $.type( params ) === "object" ) { 74 | params = $.param(params, true); 75 | } 76 | 77 | $.each(rez, function(key, value) { 78 | url = url.replace( '$' + key, value || '' ); 79 | }); 80 | 81 | if (params.length) { 82 | url += ( url.indexOf('?') > 0 ? '&' : '?' ) + params; 83 | } 84 | 85 | return url; 86 | }; 87 | 88 | //Add helper object 89 | F.helpers.media = { 90 | defaults : { 91 | youtube : { 92 | matcher : /(youtube\.com|youtu\.be|youtube-nocookie\.com)\/(watch\?v=|v\/|u\/|embed\/?)?(videoseries\?list=(.*)|[\w-]{11}|\?listType=(.*)&list=(.*)).*/i, 93 | params : { 94 | autoplay : 1, 95 | autohide : 1, 96 | fs : 1, 97 | rel : 0, 98 | hd : 1, 99 | wmode : 'opaque', 100 | enablejsapi : 1 101 | }, 102 | type : 'iframe', 103 | url : '//www.youtube.com/embed/$3' 104 | }, 105 | vimeo : { 106 | matcher : /(?:vimeo(?:pro)?.com)\/(?:[^\d]+)?(\d+)(?:.*)/, 107 | params : { 108 | autoplay : 1, 109 | hd : 1, 110 | show_title : 1, 111 | show_byline : 1, 112 | show_portrait : 0, 113 | fullscreen : 1 114 | }, 115 | type : 'iframe', 116 | url : '//player.vimeo.com/video/$1' 117 | }, 118 | metacafe : { 119 | matcher : /metacafe.com\/(?:watch|fplayer)\/([\w\-]{1,10})/, 120 | params : { 121 | autoPlay : 'yes' 122 | }, 123 | type : 'swf', 124 | url : function( rez, params, obj ) { 125 | obj.swf.flashVars = 'playerVars=' + $.param( params, true ); 126 | 127 | return '//www.metacafe.com/fplayer/' + rez[1] + '/.swf'; 128 | } 129 | }, 130 | dailymotion : { 131 | matcher : /dailymotion.com\/video\/(.*)\/?(.*)/, 132 | params : { 133 | additionalInfos : 0, 134 | autoStart : 1 135 | }, 136 | type : 'swf', 137 | url : '//www.dailymotion.com/swf/video/$1' 138 | }, 139 | twitvid : { 140 | matcher : /twitvid\.com\/([a-zA-Z0-9_\-\?\=]+)/i, 141 | params : { 142 | autoplay : 0 143 | }, 144 | type : 'iframe', 145 | url : '//www.twitvid.com/embed.php?guid=$1' 146 | }, 147 | twitpic : { 148 | matcher : /twitpic\.com\/(?!(?:place|photos|events)\/)([a-zA-Z0-9\?\=\-]+)/i, 149 | type : 'image', 150 | url : '//twitpic.com/show/full/$1/' 151 | }, 152 | instagram : { 153 | matcher : /(instagr\.am|instagram\.com)\/p\/([a-zA-Z0-9_\-]+)\/?/i, 154 | type : 'image', 155 | url : '//$1/p/$2/media/?size=l' 156 | }, 157 | google_maps : { 158 | matcher : /maps\.google\.([a-z]{2,3}(\.[a-z]{2})?)\/(\?ll=|maps\?)(.*)/i, 159 | type : 'iframe', 160 | url : function( rez ) { 161 | return '//maps.google.' + rez[1] + '/' + rez[3] + '' + rez[4] + '&output=' + (rez[4].indexOf('layer=c') > 0 ? 'svembed' : 'embed'); 162 | } 163 | } 164 | }, 165 | 166 | beforeLoad : function(opts, obj) { 167 | var url = obj.href || '', 168 | type = false, 169 | what, 170 | item, 171 | rez, 172 | params; 173 | 174 | for (what in opts) { 175 | if (opts.hasOwnProperty(what)) { 176 | item = opts[ what ]; 177 | rez = url.match( item.matcher ); 178 | 179 | if (rez) { 180 | type = item.type; 181 | params = $.extend(true, {}, item.params, obj[ what ] || ($.isPlainObject(opts[ what ]) ? opts[ what ].params : null)); 182 | 183 | url = $.type( item.url ) === "function" ? item.url.call( this, rez, params, obj ) : format( item.url, rez, params ); 184 | 185 | break; 186 | } 187 | } 188 | } 189 | 190 | if (type) { 191 | obj.href = url; 192 | obj.type = type; 193 | 194 | obj.autoHeight = false; 195 | } 196 | } 197 | }; 198 | 199 | }(jQuery)); -------------------------------------------------------------------------------- /guitarfan/static/fancybox/jquery.fancybox.css: -------------------------------------------------------------------------------- 1 | /*! fancyBox v2.1.5 fancyapps.com | fancyapps.com/fancybox/#license */ 2 | .fancybox-wrap, 3 | .fancybox-skin, 4 | .fancybox-outer, 5 | .fancybox-inner, 6 | .fancybox-image, 7 | .fancybox-wrap iframe, 8 | .fancybox-wrap object, 9 | .fancybox-nav, 10 | .fancybox-nav span, 11 | .fancybox-tmp 12 | { 13 | padding: 0; 14 | margin: 0; 15 | border: 0; 16 | outline: none; 17 | vertical-align: top; 18 | } 19 | 20 | .fancybox-wrap { 21 | position: absolute; 22 | top: 0; 23 | left: 0; 24 | z-index: 8020; 25 | } 26 | 27 | .fancybox-skin { 28 | position: relative; 29 | background: #f9f9f9; 30 | color: #444; 31 | text-shadow: none; 32 | -webkit-border-radius: 4px; 33 | -moz-border-radius: 4px; 34 | border-radius: 4px; 35 | } 36 | 37 | .fancybox-opened { 38 | z-index: 8030; 39 | } 40 | 41 | .fancybox-opened .fancybox-skin { 42 | -webkit-box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5); 43 | -moz-box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5); 44 | box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5); 45 | } 46 | 47 | .fancybox-outer, .fancybox-inner { 48 | position: relative; 49 | } 50 | 51 | .fancybox-inner { 52 | overflow: hidden; 53 | } 54 | 55 | .fancybox-type-iframe .fancybox-inner { 56 | -webkit-overflow-scrolling: touch; 57 | } 58 | 59 | .fancybox-error { 60 | color: #444; 61 | font: 14px/20px "Helvetica Neue",Helvetica,Arial,sans-serif; 62 | margin: 0; 63 | padding: 15px; 64 | white-space: nowrap; 65 | } 66 | 67 | .fancybox-image, .fancybox-iframe { 68 | display: block; 69 | width: 100%; 70 | height: 100%; 71 | } 72 | 73 | .fancybox-image { 74 | max-width: 100%; 75 | max-height: 100%; 76 | } 77 | 78 | #fancybox-loading, .fancybox-close, .fancybox-prev span, .fancybox-next span { 79 | background-image: url('fancybox_sprite.png'); 80 | } 81 | 82 | #fancybox-loading { 83 | position: fixed; 84 | top: 50%; 85 | left: 50%; 86 | margin-top: -22px; 87 | margin-left: -22px; 88 | background-position: 0 -108px; 89 | opacity: 0.8; 90 | cursor: pointer; 91 | z-index: 8060; 92 | } 93 | 94 | #fancybox-loading div { 95 | width: 44px; 96 | height: 44px; 97 | background: url('fancybox_loading.gif') center center no-repeat; 98 | } 99 | 100 | .fancybox-close { 101 | position: absolute; 102 | top: -18px; 103 | right: -18px; 104 | width: 36px; 105 | height: 36px; 106 | cursor: pointer; 107 | z-index: 8040; 108 | } 109 | 110 | .fancybox-nav { 111 | position: absolute; 112 | top: 0; 113 | width: 40%; 114 | height: 100%; 115 | cursor: pointer; 116 | text-decoration: none; 117 | background: transparent url('blank.gif'); /* helps IE */ 118 | -webkit-tap-highlight-color: rgba(0,0,0,0); 119 | z-index: 8040; 120 | } 121 | 122 | .fancybox-prev { 123 | left: 0; 124 | } 125 | 126 | .fancybox-next { 127 | right: 0; 128 | } 129 | 130 | .fancybox-nav span { 131 | position: absolute; 132 | top: 50%; 133 | width: 36px; 134 | height: 34px; 135 | margin-top: -18px; 136 | cursor: pointer; 137 | z-index: 8040; 138 | visibility: hidden; 139 | } 140 | 141 | .fancybox-prev span { 142 | left: 10px; 143 | background-position: 0 -36px; 144 | } 145 | 146 | .fancybox-next span { 147 | right: 10px; 148 | background-position: 0 -72px; 149 | } 150 | 151 | .fancybox-nav:hover span { 152 | visibility: visible; 153 | } 154 | 155 | .fancybox-tmp { 156 | position: absolute; 157 | top: -99999px; 158 | left: -99999px; 159 | visibility: hidden; 160 | max-width: 99999px; 161 | max-height: 99999px; 162 | overflow: visible !important; 163 | } 164 | 165 | /* Overlay helper */ 166 | 167 | .fancybox-lock { 168 | overflow: hidden !important; 169 | width: auto; 170 | } 171 | 172 | .fancybox-lock body { 173 | overflow: hidden !important; 174 | } 175 | 176 | .fancybox-lock-test { 177 | overflow-y: hidden !important; 178 | } 179 | 180 | .fancybox-overlay { 181 | position: absolute; 182 | top: 0; 183 | left: 0; 184 | overflow: hidden; 185 | display: none; 186 | z-index: 8010; 187 | background: url('fancybox_overlay.png'); 188 | } 189 | 190 | .fancybox-overlay-fixed { 191 | position: fixed; 192 | bottom: 0; 193 | right: 0; 194 | } 195 | 196 | .fancybox-lock .fancybox-overlay { 197 | overflow: auto; 198 | overflow-y: scroll; 199 | } 200 | 201 | /* Title helper */ 202 | 203 | .fancybox-title { 204 | visibility: hidden; 205 | font: normal 13px/20px "Helvetica Neue",Helvetica,Arial,sans-serif; 206 | position: relative; 207 | text-shadow: none; 208 | z-index: 8050; 209 | } 210 | 211 | .fancybox-opened .fancybox-title { 212 | visibility: visible; 213 | } 214 | 215 | .fancybox-title-float-wrap { 216 | position: absolute; 217 | bottom: 0; 218 | right: 50%; 219 | margin-bottom: -35px; 220 | z-index: 8050; 221 | text-align: center; 222 | } 223 | 224 | .fancybox-title-float-wrap .child { 225 | display: inline-block; 226 | margin-right: -100%; 227 | padding: 2px 20px; 228 | background: transparent; /* Fallback for web browsers that doesn't support RGBa */ 229 | background: rgba(0, 0, 0, 0.8); 230 | -webkit-border-radius: 15px; 231 | -moz-border-radius: 15px; 232 | border-radius: 15px; 233 | text-shadow: 0 1px 2px #222; 234 | color: #FFF; 235 | font-weight: bold; 236 | line-height: 24px; 237 | white-space: nowrap; 238 | } 239 | 240 | .fancybox-title-outside-wrap { 241 | position: relative; 242 | margin-top: 10px; 243 | color: #fff; 244 | } 245 | 246 | .fancybox-title-inside-wrap { 247 | padding-top: 10px; 248 | } 249 | 250 | .fancybox-title-over-wrap { 251 | position: absolute; 252 | bottom: 0; 253 | left: 0; 254 | color: #fff; 255 | padding: 10px; 256 | background: #000; 257 | background: rgba(0, 0, 0, .8); 258 | } 259 | 260 | /*Retina graphics!*/ 261 | @media only screen and (-webkit-min-device-pixel-ratio: 1.5), 262 | only screen and (min--moz-device-pixel-ratio: 1.5), 263 | only screen and (min-device-pixel-ratio: 1.5){ 264 | 265 | #fancybox-loading, .fancybox-close, .fancybox-prev span, .fancybox-next span { 266 | background-image: url('fancybox_sprite@2x.png'); 267 | background-size: 44px 152px; /*The size of the normal image, half the size of the hi-res image*/ 268 | } 269 | 270 | #fancybox-loading div { 271 | background-image: url('fancybox_loading@2x.gif'); 272 | background-size: 24px 24px; /*The size of the normal image, half the size of the hi-res image*/ 273 | } 274 | } -------------------------------------------------------------------------------- /guitarfan/utilities/qqFileUploader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import json 6 | import glob 7 | import hashlib 8 | import uuid 9 | from io import FileIO, BufferedWriter 10 | 11 | from flask import flash 12 | 13 | from oshelper import * 14 | 15 | class qqFileUploader(object): 16 | 17 | BUFFER_SIZE = 10485760 # 10MB 18 | 19 | def __init__(self, request, uploadDirectory=None, allowedExtensions=None, sizeLimit=None): 20 | self.allowedExtensions = allowedExtensions or [] 21 | self.sizeLimit = sizeLimit or current_app.config['FILE_UPLOAD_MAX_MEMORY_SIZE'] 22 | self.inputName = 'qqfile' 23 | self.chunksFolder = os.path.join(current_app.config['APP_PATH'], "chunks/") 24 | self.request = request 25 | self.uploadDirectory = uploadDirectory if uploadDirectory else os.path.join(current_app.config['APP_PATH'], "/static/upload/") 26 | self.uploadName = '' 27 | 28 | def getName(self): 29 | if self.request.values.get('qqfilename', None): 30 | return self.request.values.get('qqfilename') 31 | else: 32 | return self.request.files[self.inputName].filename 33 | 34 | # def isRaw(self): 35 | # return False if self.request.files else True 36 | 37 | def getUploadName(self): 38 | return self.uploadName 39 | 40 | 41 | def handleUpload(self, name=None): 42 | check_dir(self.uploadDirectory) 43 | 44 | if not os.access(self.uploadDirectory, os.W_OK): 45 | return json.dumps({"error": "Server error. Uploads directory isn't writable or executable."}) 46 | 47 | if self.request.content_type == '': 48 | return json.dumps({"error": "No files were uploaded."}) 49 | 50 | if not 'multipart/form-data' in self.request.content_type: 51 | return json.dumps({"error": "Server error. Not a multipart request. Please set forceMultipart to default value (true)."}) 52 | 53 | # if not self.isRaw(): 54 | uFile = self.request.files[self.inputName] 55 | uSize = int(self.request.content_length) 56 | # else: 57 | # uFile = self.request[self.inputName] 58 | # uSize = int(self.request.content_length) 59 | 60 | # uuid_f = hashlib.md5(str(uuid.uuid4())).hexdigest() 61 | 62 | if name is None: 63 | name = self.getName() 64 | 65 | # name = "%s_%s" % (uuid_f, name) 66 | 67 | if uSize == 0: 68 | return json.dumps({"error": "File is empty."}) 69 | 70 | if uSize > self.sizeLimit: 71 | return json.dumps({"error": "File is too large."}) 72 | 73 | if not (self._getExtensionFromFileName(name) in self.allowedExtensions and ".*" not in self.allowedExtensions): 74 | return json.dumps({"error": "File has an invalid extension, it should be one of %s." % ",".join(self.allowedExtensions)}) 75 | 76 | # totalParts = int(self.request.values['qqtotalparts']) if 'qqtotalparts' in self.request.values else 1 77 | 78 | # if totalParts > 1: 79 | # chunksFolder = self.chunksFolder 80 | # partIndex = int(self.request.REQUEST['qqpartindex']) 81 | # 82 | # if not os.access(chunksFolder, os.W_OK): 83 | # return json.dumps({"error": "Server error. Chunks directory isn't writable or executable."}) 84 | # 85 | # targetFolder = os.path.join(chunksFolder, uuid_f) 86 | # 87 | # if not os.path.exists(targetFolder): 88 | # os.mkdir(targetFolder) 89 | # 90 | # target = os.path.join("%s/" % targetFolder, str(partIndex)) 91 | # 92 | # with open(target, "wb+") as destination: 93 | # for chunk in uFile.chunks(): 94 | # destination.write(chunk) 95 | # 96 | # if totalParts - 1 == partIndex: 97 | # target = os.path.join(self.uploadDirectory, name) 98 | # self.uploadName = os.path.basename(target) 99 | # 100 | # target = open(target, "ab") 101 | # 102 | # for i in range(totalParts): 103 | # chunk = open("%s/%s" % (targetFolder, i), "rb") 104 | # target.write(chunk.read()) 105 | # chunk.close() 106 | # 107 | # target.close() 108 | # return json.dumps({"success": True}) 109 | # 110 | # return json.dumps({"success": True}) 111 | # 112 | # else: 113 | target = os.path.join(self.uploadDirectory, name) 114 | 115 | if target: 116 | self.uploadName = os.path.basename(target) 117 | 118 | try: 119 | # if self.isRaw(): 120 | # chunk = self.request.read(self.BUFFER_SIZE) 121 | # with open(target, "wb+") as destination: 122 | # while len(chunk) > 0: 123 | # destination.write(chunk) 124 | # 125 | # if int(destination.tell()) > self.sizeLimit: 126 | # destination.close() 127 | # os.unlink(target) 128 | # raise 129 | # 130 | # chunk = self.request.read(self.BUFFER_SIZE) 131 | # else: 132 | # with open(target, "wb+") as destination: 133 | # for chunk in uFile.chunks(): 134 | # destination.write(chunk) 135 | uFile.save(target) 136 | 137 | return json.dumps({"success": True}) 138 | except: 139 | pass 140 | 141 | return json.dumps({"error": "Could not save uploaded file. The upload was cancelled, or server error encountered"}) 142 | 143 | 144 | 145 | def _getExtensionFromFileName(self, fileName): 146 | filename, extension = os.path.splitext(fileName) 147 | return extension.lower() 148 | 149 | 150 | @staticmethod 151 | def deleteFile(uuid_f): 152 | """ 153 | Please add security here..... 154 | """ 155 | fileToDelete = os.path.join(qqFileUploader.UPLOAD_DIRECTORY, "%s_*.*" % uuid_f.replace("?","")) 156 | 157 | try: 158 | os.unlink(glob.glob(fileToDelete)[0]) 159 | except Exception as e: 160 | raise e 161 | 162 | return True -------------------------------------------------------------------------------- /guitarfan/controlers/site/tabs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import math 5 | 6 | from flask import render_template, request, redirect, url_for, flash, Blueprint, jsonify, current_app 7 | from sqlalchemy import func, or_ 8 | 9 | from guitarfan.models import * 10 | from guitarfan.extensions.flasksqlalchemy import db 11 | from guitarfan.extensions.flaskcache import cache, make_cache_key 12 | 13 | 14 | bp_site_tabs = Blueprint('bp_site_tabs', __name__, template_folder="../../templates/site") 15 | 16 | 17 | @bp_site_tabs.route('/tabs') 18 | @bp_site_tabs.route('/tabs/') 19 | @cache.cached(3600, key_prefix=make_cache_key) 20 | def tabs(page = 1): 21 | if 'artist' in request.args: 22 | artist_id = request.args['artist'] 23 | artist = Artist.query.get(artist_id) 24 | tabs = Tab.query.filter(Tab.artist_id == artist_id).order_by('update_time desc').paginate(page, current_app.config['TABS_PER_PAGE'], True) 25 | return render_template('tabs.html', tabs=tabs, artist=artist, mode='artist') 26 | elif 'style' in request.args: 27 | style_id = int(request.args['style']) 28 | style = enums.MusicStyle.get_item_text(style_id) 29 | tabs = Tab.query.filter(Tab.style_id == style_id).order_by('update_time desc').paginate(page, current_app.config['TABS_PER_PAGE'], True) 30 | return render_template('tabs.html', tabs=tabs, style=style, mode='style') 31 | elif 'tag' in request.args: 32 | tag_id = request.args['tag'] 33 | tag = Tag.query.get(tag_id) 34 | tabs = Tab.query.join(Tab.tags).filter(Tag.id == tag_id).order_by('Tab.update_time desc').paginate(page, current_app.config['TABS_PER_PAGE'], True) 35 | return render_template('tabs.html', tabs=tabs, tag=tag, mode='tag') 36 | # TODO search mode 37 | elif 'search' in request.args: 38 | # TODO limit match entire english word not single letter 39 | search = request.args['search'] 40 | tabs = Tab.query.join(Artist).filter(or_(Tab.title.like('%' + search + '%'), Artist.name.like('%' + search + '%')))\ 41 | .order_by('Tab.update_time desc').paginate(page, current_app.config['TABS_PER_PAGE'], True) 42 | return render_template('tabs.html', tabs=tabs, mode='search') 43 | else: 44 | letters = map(chr, range(65, 91)) 45 | letters.append('0-9') 46 | letters.append('Other') 47 | regions = ArtistRegion.get_described_items() 48 | categories = ArtistCategory.get_described_items() 49 | 50 | order_by = 'update_time' 51 | if 'order' in request.args and request.args['order'] == 'hot': 52 | order_by = 'hits' 53 | 54 | tabs = Tab.query.order_by(order_by + ' desc').paginate(page, current_app.config['TABS_PER_PAGE'], True) 55 | return render_template('tabs.html', letters=letters, regions=regions, categories=categories, tabs=tabs, mode='list') 56 | 57 | 58 | @bp_site_tabs.route('/artists.json', methods=['POST']) 59 | def artists_json(): 60 | letter = request.form['queryFilter[artistLetter]'] 61 | category_id = int(request.form['queryFilter[artistCategoryId]']) 62 | region_id = int(request.form['queryFilter[artistRegionId]']) 63 | artists = [] 64 | for id, name, category in db.session.query(Artist.id, Artist.name, Artist.category_id) \ 65 | .filter(or_(Artist.letter == letter)) \ 66 | .filter(or_(category_id == 0, Artist.category_id == category_id)) \ 67 | .filter(or_(region_id == 0, Artist.region_id == region_id)) \ 68 | .order_by('category_id'): 69 | artists.append({'id': id, 'name': name, 'category': category}) 70 | return jsonify(artists=artists) 71 | 72 | 73 | @bp_site_tabs.route('/tabs.json', methods=['POST']) 74 | def tabs_json(): 75 | letter = request.form['queryFilter[artistLetter]'] 76 | category_id = int(request.form['queryFilter[artistCategoryId]']) 77 | region_id = int(request.form['queryFilter[artistRegionId]']) 78 | order_by = 'Tab.update_time' if request.form['queryFilter[orderBy]'] == 'time' else 'Tab.hits' 79 | artists = request.form['queryFilter[artistIds]'].split('|') if request.form['queryFilter[artistIds]'] != '' else [] 80 | style_id = int(request.form['queryFilter[styleId]']) 81 | tag_id = request.form['queryFilter[tagId]'] 82 | search = request.form['queryFilter[search]'] 83 | page_index = int(request.form['queryFilter[pageIndex]']) 84 | tabs = [] 85 | 86 | page_size = current_app.config['TABS_PER_PAGE'] 87 | 88 | count_query = db.session.query(func.count(Tab.id)).join(Artist) 89 | tab_query = db.session.query(Tab.id, Tab.title, Tab.style_id, Tab.difficulty_id, Tab.hits, Tab.artist_id, Artist.name).join(Artist) 90 | 91 | if letter != 'All': 92 | tab_query = tab_query.filter(Artist.letter == letter) 93 | count_query = count_query.filter(Artist.letter == letter) 94 | if category_id > 0: 95 | tab_query = tab_query.filter(Artist.category_id == category_id) 96 | count_query = count_query.filter(Artist.category_id == category_id) 97 | if region_id > 0: 98 | tab_query = tab_query.filter(Artist.region_id == region_id) 99 | count_query = count_query.filter(Artist.region_id == region_id) 100 | if len(artists) > 0: 101 | tab_query = tab_query.filter(Artist.id.in_(artists)) 102 | count_query = count_query.filter(Artist.id.in_(artists)) 103 | if style_id > 0: 104 | tab_query = tab_query.filter(Tab.style_id == style_id) 105 | count_query = count_query.filter(Tab.style_id == style_id) 106 | if tag_id != '': 107 | tab_query = tab_query.join(Tab.tags).filter(Tag.id == tag_id) 108 | count_query = count_query.join(Tab.tags).filter(Tag.id == tag_id) 109 | if search != '': 110 | tab_query = tab_query.filter(or_(Tab.title.like('%' + search + '%'), Artist.name.like('%' + search + '%'))) 111 | count_query = count_query.filter(or_(Tab.title.like('%' + search + '%'), Artist.name.like('%' + search + '%'))) 112 | 113 | page_count = math.ceil(float(count_query.scalar())/page_size) 114 | tab_query = tab_query.order_by(order_by + ' desc').limit(page_size).offset(page_size * (page_index - 1)) 115 | 116 | for id, title, style_id, difficalty_id, hits, artist_id, artist_name in tab_query: 117 | tabs.append({ 118 | 'id': id, 119 | 'title': title, 120 | 'style': MusicStyle.get_item_text(style_id), 121 | 'difficalty': DifficultyDegree.get_item_text(difficalty_id), 122 | 'hits': hits, 123 | 'artistId': artist_id, 124 | 'artistName': artist_name 125 | }) 126 | 127 | return jsonify(tabs=tabs, pageIndex=page_index, pageCount=page_count) 128 | --------------------------------------------------------------------------------