├── .gitignore
├── LICENSE.txt
├── Procfile
├── README.md
├── advanced.png
├── app.json
├── app
├── __init__.py
├── settings.py
├── settings_my.py
├── urls.py
└── wsgi.py
├── fabfile.py
├── gnip_search
├── __init__.py
└── gnip_search_api.py
├── home
├── __init__.py
├── admin.py
├── chart.py
├── frequency.py
├── models.py
├── tests.py
├── timeframe.py
├── timeseries.py
├── tweets.py
├── utils.py
└── views.py
├── manage.py
├── requirements.txt
├── runtime.txt
├── screenshot.png
├── services
├── __init__.py
└── middleware.py
├── static
├── css
│ ├── bootstrap-datetimepicker.min.css
│ ├── bootstrap-theme.css
│ ├── bootstrap-theme.min.css
│ ├── bootstrap.min.css
│ ├── c3.css
│ ├── c3.min.css
│ ├── font-awesome.min.css
│ ├── fonts
│ │ ├── glyphicons-halflings-regular.eot
│ │ ├── glyphicons-halflings-regular.svg
│ │ ├── glyphicons-halflings-regular.ttf
│ │ └── glyphicons-halflings-regular.woff
│ └── site.css
├── fonts
│ ├── FontAwesome.otf
│ ├── fontawesome-webfont.eot
│ ├── fontawesome-webfont.svg
│ ├── fontawesome-webfont.ttf
│ ├── fontawesome-webfont.woff
│ ├── futura.ttf
│ ├── glyphicons-halflings-regular.eot
│ ├── glyphicons-halflings-regular.svg
│ ├── glyphicons-halflings-regular.ttf
│ └── glyphicons-halflings-regular.woff
├── images
│ ├── bird.ico
│ ├── bird.png
│ ├── spinner.gif
│ └── trans.png
├── js
│ ├── bootstrap-datetimepicker.min.js
│ ├── bootstrap.js
│ ├── bootstrap.min.js
│ ├── c3.js
│ ├── c3.min.js
│ ├── d3.js
│ ├── jquery-1.10.1.min.js
│ ├── jquery-1.10.1.min.map
│ ├── jquery-ui-1.10.4.custom.js
│ ├── jquery.cookie.js
│ ├── moment.min.js
│ ├── mustache.js
│ ├── pinterest_grid.js
│ └── script.js
├── less
│ ├── bordered-pulled.less
│ ├── core.less
│ ├── fixed-width.less
│ ├── font-awesome.less
│ ├── icons.less
│ ├── larger.less
│ ├── list.less
│ ├── mixins.less
│ ├── path.less
│ ├── rotated-flipped.less
│ ├── spinning.less
│ ├── stacked.less
│ └── variables.less
└── scss
│ ├── _bordered-pulled.scss
│ ├── _core.scss
│ ├── _fixed-width.scss
│ ├── _icons.scss
│ ├── _larger.scss
│ ├── _list.scss
│ ├── _mixins.scss
│ ├── _path.scss
│ ├── _rotated-flipped.scss
│ ├── _spinning.scss
│ ├── _stacked.scss
│ ├── _variables.scss
│ └── font-awesome.scss
├── tags
├── __init__.py
└── templatetags
│ ├── __init__.py
│ └── tags.py
├── templates
├── base.html
├── home.html
├── login.html
└── robots.txt
└── terms.png
/.gitignore:
--------------------------------------------------------------------------------
1 | */examples*/*
2 | */results*
3 | *.pyc
4 | .project
5 | .pydevproject
6 | app/settings_my.py
7 | fabfile.py
8 | db.sqlite3
9 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013-2014 Twitter, Inc
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: gunicorn app.wsgi --log-file -
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Tweet Search
2 | =================
3 |
4 | This sample uses GNIP full archive search to show the activity volume and latest tweets on any given topic. It also renders tweets using Twitter's widgets.js.
5 |
6 |
7 |
8 | As always, when developing on top of the Twitter platform, you must abide by the [Developer Agreement & Policy](https://dev.twitter.com/overview/terms/agreement-and-policy).
9 |
10 | [](https://heroku.com/deploy?template=https://github.com/twitterdev/tweet-search)
11 |
12 |
13 | Requirements
14 | ============
15 |
16 | To run this sample code, you can install the required libraries with:
17 |
18 | `pip install -r requirements.txt`
19 |
20 | Getting Started
21 | ============
22 |
23 | - Create a Twitter App (https://apps.twitter.com/). Also, ensure that the Callback URL is populated. This can point to http://localhost:9000 to start. If it is not included, you will get Client Authorization errors upon login.
24 |
25 | - Specify your API and GNIP credentials in app/settings_my.py under the following section:
26 |
27 | `GNIP_USERNAME = 'YOUR_GNIP_USERNAME'`
28 |
29 | `GNIP_PASSWORD = 'YOUR_GNIP_PASSWORD'`
30 |
31 | `GNIP_SEARCH_ENDPOINT = 'YOUR_GNIP_FULL_ARCHIVE_SEARCH_ENDPOINT'`
32 |
33 | - To initialize your database, run the from the `tweet-search` directory:
34 |
35 | `python manage.py syncdb --settings=app.settings_my`
36 |
37 | Then run:
38 |
39 | `python manage.py migrate --settings=app.settings_my`
40 |
41 | - To start the server, run the following from the `tweet-search` directory:
42 |
43 | `fab start`
44 |
45 | - Open a browser and go to http://localhost:9000
46 |
47 | Note that the GNIP_SEARCH_ENDPOINT is a URL to the full archive search URL, and is in the format `https://data-api.twitter.com/search/fullarchive/...`.
48 | If you want to use the 30-day search, open the `gnip_search_api.py` file, search for the term "30 DAY" and follow the instructions. (You also need to
49 | use the 30-day search URL, and not the full arhive search URL.)
50 |
51 | Invalidate Twitter tokens
52 | --------
53 |
54 | For security, this code sample has a batch process to clear out Twitter auth tokens for users that either:
55 |
56 | - Have a login of greater than 30 days ago, or
57 | - Have never logged in and joined greater than 30 days ago
58 |
59 | To run the process, simply execute:
60 |
61 | `fab invalidate'
62 |
63 | Deploying to Heroku
64 | ============
65 |
66 | Deploying to Heroku is even easier.
67 |
68 | - Create a Twitter App (https://apps.twitter.com/)
69 | - Click on the Heroku button below
70 | - When prompted during the Heroku install, specify your:
71 |
72 | - CONSUMER_KEY
73 | - CONSUMER_SECRET
74 | - ACCESS_TOKEN
75 | - ACCESS_TOKEN_SECRET
76 |
77 | - After deploying, in the Twitter App config, ensure the Callback URL is `http://your-app-name.herokuapp.com/complete/twitter`
78 |
79 | - To sync the database, use the Heroku CLI and run the following:
80 |
81 | `heroku run python manage.py migrate --app your-app-name`
82 |
83 | - Open a browser and go to the URL specified by your deploy (http://your-app-name.herokuapp.com)
84 |
85 | - To create an admin user, use the following Heroku CLI command:
86 |
87 | `heroku run python manage.py createsuperuser --username=USERNAME --email=EMAIL --app your-app-name`
88 |
89 | Then log in via the Admin console and update your initial Twitter login user accordingly.
90 |
91 | Invalidating Twitter tokens on Heroku
92 | --------
93 |
94 | To ensure the token invalidation script works properly on Heroku, run the following from your machine:
95 |
96 | `heroku run fab invalidate --app=MY_APP_NAME'
97 |
98 | If this runs properly, follow the below steps to run it as a scheduled job on Heroku:
99 |
100 | - Run `heroku addons:add scheduler:standard --app=MY_APP_NAME`
101 | - Log into heroku.com, open your app and go to "Resources"
102 | - Click on "Heroku Scheduler" and then "Add a New Job"
103 | - Type in `fab invalidate`
104 |
105 | Confirm successful execution by viewing the output in the Heroku app logs.
106 |
107 |
108 | Sample Queries
109 | ============
110 |
111 | Some sample queries to run:
112 |
113 | - Hashtag search (default AND): `#MLB #SFGiants`
114 | - Mention search, no retweets: `@TwitterDev -(is:retweet)`
115 | - Search with images/videos: `walking dead (has:media)`
116 |
117 | Advanced Options
118 | ============
119 |
120 | In the UI, there is a link to show advanced options. Specifically:
121 |
122 | - Start/end dates. GNIP search allows a variable timeframe to search. For optimal results, 30 days will return a response in a reasonable timeframe.
123 | - Has media. This appends `(has:media)` to your query
124 |
125 |
126 |
127 | Related Terms
128 | ============
129 |
130 | The GNIP search can also suggest additional related terms to add to your query. Click on the 'related terms'
131 | link and a drop-down will appear to suggest (and add) additional terms to your query:
132 |
133 |
134 |
--------------------------------------------------------------------------------
/advanced.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdevplatform/tweet-search/983de3165f6fc09bb1d8f348db1a1cb06b824a12/advanced.png
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name":"tweet-search",
3 | "description":"Sample code showing tweet activity volume using GNIP search API. Built with Django, Tweet embeds and C3.",
4 | "repository":"https://github.com/twitterdev/tweet-search",
5 | "addons": ["heroku-postgresql:hobby-dev"],
6 | "keywords":[
7 | "python",
8 | "django",
9 | "gnip",
10 | "twitter",
11 | "template",
12 | "skeleton"
13 | ],
14 | "env":{
15 | "DJANGO_SECRET_KEY":{
16 | "description":"A randomly generated secret to secure your Django installation",
17 | "generator":"secret"
18 | },
19 | "CONSUMER_KEY":{
20 | "description":"Twitter API consumer key (Visit http://apps.twitter.com)",
21 | "value":"",
22 | "required":true
23 | },
24 | "CONSUMER_SECRET":{
25 | "description":"Twitter API consumer secret",
26 | "value":"",
27 | "required":true
28 | },
29 | "ACCESS_TOKEN":{
30 | "description":"Twitter API access token",
31 | "value":"",
32 | "required":true
33 | },
34 | "ACCESS_TOKEN_SECRET":{
35 | "description":"Twitter API access token secret",
36 | "value":"",
37 | "required":true
38 | },
39 | "GNIP_USERNAME":{
40 | "description":"Gnip username",
41 | "value":"",
42 | "required":true
43 | },
44 | "GNIP_PASSWORD":{
45 | "description":"Gnip password",
46 | "value":"",
47 | "required":true
48 | },
49 | "GNIP_SEARCH_ENDPOINT":{
50 | "description":"Gnip search endpoint",
51 | "value":"",
52 | "required":true
53 | }
54 |
55 | },
56 | "scripts":{
57 | "postdeploy":"python manage.py makemigrations --noinput; python manage.py migrate --noinput; python manage.py collectstatic --noinput;"
58 | }
59 | }
--------------------------------------------------------------------------------
/app/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdevplatform/tweet-search/983de3165f6fc09bb1d8f348db1a1cb06b824a12/app/__init__.py
--------------------------------------------------------------------------------
/app/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for app project.
3 |
4 | For more information on this file, see
5 | https://docs.djangoproject.com/en/1.6/topics/settings/
6 |
7 | For the full list of settings and their values, see
8 | https://docs.djangoproject.com/en/1.6/ref/settings/
9 | """
10 |
11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
12 | import os
13 | from os import environ
14 |
15 | BASE_DIR = os.path.dirname(os.path.dirname(__file__))
16 |
17 | # SECURITY WARNING: don't run with debug turned on in production!
18 | DEBUG = False
19 | TEMPLATE_DEBUG = DEBUG
20 |
21 | # Quick-start development settings - unsuitable for production
22 | # See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/
23 |
24 | # SECURITY WARNING: keep the secret key used in production secret!
25 | SECRET_KEY = environ.get('DJANGO_SECRET_KEY')
26 |
27 | ALLOWED_HOSTS = [
28 | '.tweet-search2.herokuapp.com',
29 | '127.0.0.1',
30 | 'localhost',
31 | ]
32 |
33 | # Application definition
34 |
35 | INSTALLED_APPS = (
36 | 'django.contrib.admin',
37 | 'django.contrib.auth',
38 | 'django.contrib.contenttypes',
39 | 'django.contrib.sessions',
40 | 'django.contrib.messages',
41 | 'django.contrib.staticfiles',
42 | 'django.contrib.humanize',
43 | 'social.apps.django_app.default',
44 | 'tags',
45 | 'app',
46 | 'services',
47 | 'home',
48 | 'gnip_search'
49 | )
50 |
51 | MIDDLEWARE_CLASSES = (
52 | 'services.middleware.SSLMiddleware',
53 | 'django.middleware.security.SecurityMiddleware',
54 | 'csp.middleware.CSPMiddleware',
55 | 'django.contrib.sessions.middleware.SessionMiddleware',
56 | 'django.middleware.common.CommonMiddleware',
57 | 'django.middleware.csrf.CsrfViewMiddleware',
58 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
59 | 'django.contrib.messages.middleware.MessageMiddleware',
60 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
61 | )
62 |
63 | AUTHENTICATION_BACKENDS = (
64 | 'social.backends.twitter.TwitterOAuth',
65 | 'django.contrib.auth.backends.ModelBackend',
66 | )
67 |
68 | TEMPLATE_DIRS = (
69 | os.path.join(BASE_DIR, "templates"),
70 | )
71 |
72 | TEMPLATE_CONTEXT_PROCESSORS = (
73 | 'social.apps.django_app.context_processors.backends',
74 | 'social.apps.django_app.context_processors.login_redirect',
75 | 'django.core.context_processors.static',
76 | 'django.contrib.auth.context_processors.auth',
77 | )
78 |
79 | ROOT_URLCONF = 'app.urls'
80 |
81 | WSGI_APPLICATION = 'app.wsgi.application'
82 |
83 | # Database
84 | # https://docs.djangoproject.com/en/1.6/ref/settings/#databases
85 |
86 | # Uncomment for Heroku
87 | import dj_database_url
88 | DATABASES = {
89 | 'default': dj_database_url.config(default='sqlite://tweet-search.db')
90 | }
91 |
92 | # Uncomment for local database
93 | # DATABASES = {
94 | # 'default': {
95 | # 'ENGINE': 'django.db.backends.sqlite3',
96 | # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
97 | # }
98 | # }
99 |
100 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
101 | MEDIA_URL = '/media/'
102 |
103 | # Internationalization
104 | # https://docs.djangoproject.com/en/1.6/topics/i18n/
105 |
106 | LANGUAGE_CODE = 'en-us'
107 |
108 | TIME_ZONE = 'UTC'
109 |
110 | USE_I18N = True
111 |
112 | USE_L10N = True
113 |
114 | USE_TZ = True
115 |
116 |
117 | # Static files (CSS, JavaScript, Images)
118 | # https://docs.djangoproject.com/en/1.6/howto/static-files/
119 |
120 | STATIC_ROOT = 'staticfiles'
121 | STATIC_URL = '/static/'
122 |
123 | # Additional locations of static files
124 | STATICFILES_DIRS = (
125 | # Put strings here, like "/home/html/static" or "C:/www/django/static".
126 | # Always use forward slashes, even on Windows.
127 | # Don't forget to use absolute paths, not relative paths.
128 | os.path.join(BASE_DIR, "static"),
129 | )
130 |
131 | # security: https://docs.djangoproject.com/en/1.9/ref/middleware/#module-django.middleware.security
132 | SECURE_HSTS_SECONDS = 31536000
133 | SECURE_HSTS_INCLUDE_SUBDOMAINS = True
134 | SECURE_FRAME_DENY = True
135 | SECURE_CONTENT_TYPE_NOSNIFF = True
136 | SECURE_BROWSER_XSS_FILTER = True
137 | SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
138 |
139 | # security: https://django-csp.readthedocs.org/en/latest/configuration.html#policy-settings
140 | CSP_DEFAULT_SRC = ("'self'", 'http://*.twitter.com', 'https://*.twitter.com', 'https://*.twimg.com', 'https://*.vine.co',)
141 | CSP_IMG_SRC = CSP_DEFAULT_SRC + ('data:', 'https://www.google-analytics.com', )
142 | CSP_SCRIPT_SRC = CSP_DEFAULT_SRC + ('https://www.google-analytics.com', 'https://ajax.googleapis.com', 'https://maxcdn.bootstrapcdn.com', )
143 | CSP_STYLE_SRC = CSP_DEFAULT_SRC + ("'unsafe-inline'", 'https://fonts.googleapis.com', 'https://maxcdn.bootstrapcdn.com', )
144 | CSP_FONT_SRC = CSP_DEFAULT_SRC + ('https://fonts.gstatic.com', 'https://maxcdn.bootstrapcdn.com', )
145 | CSP_OBJECT_SRC = ("'none'", )
146 |
147 | SOCIAL_AUTH_LOGIN_URL = '/login'
148 | SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/home'
149 | SOCIAL_AUTH_LOGIN_ERROR_URL = '/login-error/'
150 |
151 | LOGIN_URL = '/login/twitter'
152 |
153 | # Get your Twitter key/secret from https://apps.twitter.com/
154 | SOCIAL_AUTH_TWITTER_KEY = environ.get('CONSUMER_KEY') # Twitter API Consumer Key
155 | SOCIAL_AUTH_TWITTER_SECRET = environ.get('CONSUMER_SECRET') # Twitter API Consumer Secret
156 |
157 | TWITTER_ACCESS_TOKEN = environ.get('ACCESS_TOKEN') # Twitter API Access Token
158 | TWITTER_ACCESS_TOKEN_SECRET = environ.get('ACCESS_TOKEN_SECRET') # Twitter API Access Secret
159 |
160 | GNIP_USERNAME = environ.get('GNIP_USERNAME') # Gnip username
161 | GNIP_PASSWORD = environ.get('GNIP_PASSWORD') # Gnip password
162 | GNIP_SEARCH_ENDPOINT = environ.get('GNIP_SEARCH_ENDPOINT') # Gnip search endpoint
163 |
164 |
165 |
--------------------------------------------------------------------------------
/app/settings_my.py:
--------------------------------------------------------------------------------
1 | try:
2 | from app.settings import *
3 | except ImportError, exp:
4 | pass
5 |
6 | DEBUG = True
7 |
8 | SOCIAL_AUTH_TWITTER_KEY = ''
9 | SOCIAL_AUTH_TWITTER_SECRET = ''
10 |
11 | TWITTER_ACCESS_TOKEN = ''
12 | TWITTER_ACCESS_TOKEN_SECRET = ''
13 |
14 | GNIP_USERNAME = ""
15 | GNIP_PASSWORD = ""
16 | GNIP_SEARCH_ENDPOINT = ""
17 |
18 | DATABASES = {
19 | 'default': {
20 | 'ENGINE': 'django.db.backends.sqlite3',
21 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import patterns, include, url
2 |
3 | from django.contrib import admin
4 | admin.autodiscover()
5 |
6 | from app import settings
7 | from home import views
8 |
9 | urlpatterns = patterns('',
10 | url(r'^$', views.login, name='login'),
11 | url(r'^home$', views.home, name='home'),
12 | url(r'^logout$', views.logout, name='logout'),
13 | url(r'^query/chart', views.query_chart, name='query_chart'),
14 | url(r'^query/tweets', views.query_tweets, name='query_tweets'),
15 | url(r'^query/frequency', views.query_frequency, name='query_frequency'),
16 |
17 | url('', include('social.apps.django_app.urls', namespace='social')),
18 | url(r'^admin/', admin.site.urls),
19 | )
20 |
21 | urlpatterns += patterns('',
22 | (r'^static/(.*)$', 'django.views.static.serve', {'document_root': settings.STATIC_ROOT}),
23 | )
--------------------------------------------------------------------------------
/app/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for app project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings")
12 |
13 | from django.core.wsgi import get_wsgi_application
14 | application = get_wsgi_application()
15 |
--------------------------------------------------------------------------------
/fabfile.py:
--------------------------------------------------------------------------------
1 | from __future__ import with_statement
2 |
3 | import os
4 | import django
5 |
6 | SETTINGS_FILE = "app.settings"
7 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", SETTINGS_FILE)
8 | django.setup()
9 |
10 | PORT = os.environ.get('PORT', 9000)
11 |
12 | from fabric.api import *
13 |
14 | # run server locally
15 | def start():
16 | local("python manage.py runserver 127.0.0.1:%s --traceback --settings=%s" % (PORT, SETTINGS_FILE))
17 |
18 | # process to invalidate Twitter auth tokens for dormant users
19 | def invalidate():
20 |
21 | OFFBOARD_DAYS = 31
22 |
23 | from django.utils import timezone
24 | from django.contrib.auth.models import User
25 | from django.db.models import Q
26 | from social.apps.django_app.default.models import UserSocialAuth
27 |
28 | invalidate_delta = timezone.now() - timezone.timedelta(OFFBOARD_DAYS)
29 | print "Running invalidate process with date: %s" % (invalidate_delta)
30 |
31 | # has auth credentials (non-null)
32 | criteria = Q(extra_data__isnull=False)
33 |
34 | # also is past timeframe
35 | criteria = criteria & (Q(user__last_login__lte=invalidate_delta) | (Q(user__last_login__isnull=True) & Q(user__date_joined__lte=invalidate_delta)))
36 |
37 | users = UserSocialAuth.objects.filter(criteria)
38 | for u in users:
39 |
40 | try:
41 | u.extra_data = None
42 | u.save()
43 | print "\tInvalidate user %s (last_login=%s, date_joined=%s)" % (u.user, u.user.last_login, u.user.date_joined)
44 | except UserSocialAuth.DoesNotExist, e:
45 | # no record exists; no need to clear anything
46 | pass
47 |
--------------------------------------------------------------------------------
/gnip_search/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdevplatform/tweet-search/983de3165f6fc09bb1d8f348db1a1cb06b824a12/gnip_search/__init__.py
--------------------------------------------------------------------------------
/gnip_search/gnip_search_api.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: UTF-8 -*-
3 | __author__="Scott Hendrickson, Josh Montague"
4 |
5 | import sys
6 | import requests
7 | import json
8 | import codecs
9 | import datetime
10 | import time
11 | import os
12 | import re
13 |
14 | try:
15 | from acscsv.twitter_acs import TwacsCSV
16 | except ImportError:
17 | # previous versions of gnacs used a different module name
18 | from acscsv.twacscsv import TwacsCSV
19 | from simple_n_grams.simple_n_grams import SimpleNGrams
20 |
21 | reload(sys)
22 | sys.stdout = codecs.getwriter('utf-8')(sys.stdout)
23 | sys.stdin = codecs.getreader('utf-8')(sys.stdin)
24 |
25 | # formatter of data from API
26 | TIME_FMT = "%Y%m%d%H%M"
27 | PAUSE = 3 # seconds between page requests
28 |
29 | #############################################
30 | # Some constants to configure column retrieval from TwacsCSV
31 | DATE_INDEX = 1
32 | TEXT_INDEX = 2
33 | LINKS_INDEX = 3
34 | USER_NAME_INDEX = 7
35 |
36 | class GnipSearchAPI(object):
37 |
38 | def __init__(self
39 | , user
40 | , password
41 | , stream_url
42 | , paged = False
43 | , output_file_path = None
44 | , token_list_size = 20
45 | ):
46 | #############################################
47 | # set up query paramters
48 | # default tokenizer and character limit
49 | self.token_list_size = int(token_list_size)
50 | self.output_file_path = output_file_path
51 | self.paged = paged
52 | self.user = user
53 | self.password = password
54 | self.stream_url = stream_url
55 | # get a parser for the twitter columns
56 | # TODO: use the updated retriveal methods in gnacs instead of this
57 | self.twitter_parser = TwacsCSV(",", None, False, True, False, True, False, False, False)
58 |
59 | def set_index(self, use_case, count_bucket):
60 | self.use_case = use_case
61 | space_tokenizer = False
62 | char_upper_cutoff = 20 # longer than for normal words because of user names
63 | if use_case.startswith("links"):
64 | char_upper_cutoff=100
65 | space_tokenizer = True
66 | # self.freq = SimpleNGrams(charUpperCutoff=char_upper_cutoff, space_tokenizer=space_tokenizer)
67 | self.freq = SimpleNGrams(char_upper_cutoff=char_upper_cutoff, tokenizer="space")
68 | if use_case.startswith("user"):
69 | self.index = USER_NAME_INDEX
70 | elif use_case.startswith("wordc"):
71 | self.index = TEXT_INDEX
72 | elif use_case.startswith("rate"):
73 | self.index = DATE_INDEX
74 | elif use_case.startswith("link"):
75 | self.index = LINKS_INDEX
76 | elif use_case.startswith("time"):
77 | if not self.stream_url.endswith("counts.json"):
78 | self.stream_url = self.stream_url[:-5] + "/counts.json"
79 | if count_bucket not in ['day', 'minute', 'hour']:
80 | print >> sys.stderr, "Error. Invalid count bucket: %s \n"%str(count_bucket)
81 | sys.exit()
82 |
83 | def set_dates(self, start, end):
84 | # re for the acceptable datetime formats
85 | timeRE = re.compile("([0-9]{4}).([0-9]{2}).([0-9]{2}).([0-9]{2}):([0-9]{2})")
86 | if start:
87 | dt = re.search(timeRE, start)
88 | if not dt:
89 | print >> sys.stderr, "Error. Invalid start-date format: %s \n"%str(start)
90 | sys.exit()
91 | else:
92 | f =''
93 | for i in range(re.compile(timeRE).groups):
94 | f += dt.group(i+1)
95 | self.fromDate = f
96 | if end:
97 | dt = re.search(timeRE, end)
98 | if not dt:
99 | print >> sys.stderr, "Error. Invalid end-date format: %s \n"%str(end)
100 | sys.exit()
101 | else:
102 | e =''
103 | for i in range(re.compile(timeRE).groups):
104 | e += dt.group(i+1)
105 | self.toDate = e
106 |
107 | def name_munger(self, f):
108 | """Creates a file name per input rule when reading multiple input rules."""
109 | f = re.sub(' +','_',f)
110 | f = f.replace(':','_')
111 | f = f.replace('"','_Q_')
112 | f = f.replace('(','_p_')
113 | f = f.replace(')','_p_')
114 | self.file_name_prefix = f[:42]
115 |
116 | def req(self):
117 | try:
118 | s = requests.Session()
119 | s.headers = {'Accept-encoding': 'gzip'}
120 | s.auth = (self.user, self.password)
121 | res = s.post(self.stream_url, data=json.dumps(self.rule_payload))
122 | except requests.exceptions.ConnectionError, e:
123 | print >> sys.stderr, "Error (%s). Exiting without results."%str(e)
124 | sys.exit()
125 | except requests.exceptions.HTTPError, e:
126 | print >> sys.stderr, "Error (%s). Exiting without results."%str(e)
127 | sys.exit()
128 | return res.text
129 |
130 | def parse_JSON(self):
131 | acs = []
132 | repeat = True
133 | page_count = 0
134 | while repeat:
135 | doc = self.req()
136 | try:
137 | tmp_response = json.loads(doc)
138 | if "results" in tmp_response:
139 | acs.extend(tmp_response["results"])
140 | if "error" in tmp_response:
141 | raise QueryError(tmp_response.get("error").get("message"), self.rule_payload, tmp_response)
142 | # print >> sys.stderr, "Error, invalid request"
143 | # print >> sys.stderr, "Query: %s"%self.rule_payload
144 | # print >> sys.stderr, "Response: %s"%doc
145 | except ValueError:
146 | raise QueryError("No GNIP response", None, None)
147 |
148 | # print >> sys.stderr, "Error, results not parsable"
149 | # print >> sys.stderr, doc
150 | # sys.exit()
151 |
152 | repeat = False
153 | if self.paged:
154 | if len(acs) > 0:
155 | if self.output_file_path is not None:
156 | file_name = self.output_file_path + "/{0}_{1}.json".format(
157 | str(datetime.datetime.utcnow().strftime(
158 | "%Y%m%d%H%M%S"))
159 | , str(self.file_name_prefix))
160 | with codecs.open(file_name, "wb","utf-8") as out:
161 | print >> sys.stderr, "(writing to file ...)"
162 | for item in tmp_response["results"]:
163 | out.write(json.dumps(item)+"\n")
164 | # else:
165 | # # if writing to file, don't keep track of all the data in memory
166 | # acs = []
167 | else:
168 | print >> sys.stderr, "no results returned for rule:{0}".format(str(self.rule_payload))
169 | if "next" in tmp_response:
170 | self.rule_payload["next"]=tmp_response["next"]
171 | repeat = True
172 | page_count += 1
173 | print >> sys.stderr, "Fetching page {}...".format(page_count)
174 | else:
175 | if "next" in self.rule_payload:
176 | del self.rule_payload["next"]
177 | repeat = False
178 | time.sleep(PAUSE)
179 | return acs
180 |
181 | def query_api(self
182 | , pt_filter
183 | , max_results = 100
184 | , use_case = "wordcount"
185 | , start = None
186 | , end = None
187 | , count_bucket = "day"
188 | , csv_flag = False
189 | , query = False):
190 | self.set_index(use_case, count_bucket)
191 | self.set_dates(start, end)
192 | self.name_munger(pt_filter)
193 | if self.paged:
194 | # avoid making many small requests
195 | max_results = 500
196 | self.rule_payload = { 'query': pt_filter }
197 |
198 | # 30 DAY: to use 30 day search, replace the above line with the below updated rule payload
199 | # self.rule_payload = {
200 | # 'query': pt_filter,
201 | # 'maxResults': int(max_results),
202 | # 'publisher': 'twitter'
203 | # }
204 |
205 | if start:
206 | self.rule_payload["fromDate"] = self.fromDate
207 | if end:
208 | self.rule_payload["toDate"] = self.toDate
209 | if use_case.startswith("time"):
210 | self.rule_payload["bucket"] = count_bucket
211 | if query:
212 | print >>sys.stderr, "API query:"
213 | print >>sys.stderr, self.rule_payload
214 | sys.exit()
215 |
216 | print self.rule_payload
217 |
218 | self.doc = []
219 | self.res_cnt = 0
220 | self.delta_t = 1 # keeps non-'rate' use-cases from crashing
221 | # default delta_t = 30d & search only goes back 30 days
222 | self.oldest_t = datetime.datetime.utcnow() - datetime.timedelta(days=30)
223 | self.newest_t = datetime.datetime.utcnow()
224 | for rec in self.parse_JSON():
225 | self.res_cnt += 1
226 | if use_case.startswith("rate"):
227 | t_str = self.twitter_parser.procRecordToList(rec)[self.index]
228 | t = datetime.datetime.strptime(t_str,"%Y-%m-%dT%H:%M:%S.000Z")
229 | if t < self.oldest_t:
230 | self.oldest_t = t
231 | if t > self.newest_t:
232 | self.newest_t = t
233 | self.delta_t = (self.newest_t - self.oldest_t).total_seconds()/60.
234 | elif use_case.startswith("links"):
235 | link_str = self.twitter_parser.procRecordToList(rec)[self.index]
236 | print "+"*20
237 | print link_str
238 | if link_str != "GNIPEMPTYFIELD" and link_str != "None":
239 | exec("link_list=%s"%link_str)
240 | for l in link_list:
241 | self.freq.add(l)
242 | else:
243 | self.freq.add("NoLinks")
244 | elif use_case.startswith("json"):
245 | self.doc.append(json.dumps(rec))
246 | elif use_case.startswith("tweets"):
247 | self.doc.append(rec)
248 | elif use_case.startswith("geo"):
249 | lat, lng = None, None
250 | if "geo" in rec:
251 | if "coordinates" in rec["geo"]:
252 | [lat,lng] = rec["geo"]["coordinates"]
253 | record = { "id": rec["id"].split(":")[2]
254 | , "postedTime": rec["postedTime"].strip(".000Z")
255 | , "latitude": lat
256 | , "longitude": lng }
257 | self.doc.append(record)
258 | #self.doc.append(json.dumps(record))
259 | elif use_case.startswith("time"):
260 | self.doc.append(rec)
261 | else:
262 | thing = self.twitter_parser.procRecordToList(rec)[self.index]
263 | self.freq.add(thing)
264 | return self.get_repr(pt_filter, csv_flag)
265 |
266 | def get_repr(self, pt_filter, csv_flag):
267 | WIDTH = 60
268 | res = [u"-"*WIDTH]
269 | if self.use_case.startswith("rate"):
270 | rate = float(self.res_cnt)/self.delta_t
271 | unit = "Tweets/Minute"
272 | if rate < 0.01:
273 | rate *= 60.
274 | unit = "Tweets/Hour"
275 | res.append(" PowerTrack Rule: \"%s\""%pt_filter)
276 | res.append("Oldest Tweet (UTC): %s"%str(self.oldest_t))
277 | res.append("Newest Tweet (UTC): %s"%str(self.newest_t))
278 | res.append(" Now (UTC): %s"%str(datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")))
279 | res.append(" %5d Tweets: %6.3f %s"%(self.res_cnt, rate, unit))
280 | res.append("-"*WIDTH)
281 | elif self.use_case.startswith("geo"):
282 | if csv_flag:
283 | res = []
284 | for x in self.doc:
285 | try:
286 | res.append("{},{},{},{}".format(x["id"], x["postedTime"], x["longitude"], x["latitude"]))
287 | except KeyError, e:
288 | print >> sys.stderr, str(e)
289 | else:
290 | res = [json.dumps(x) for x in self.doc]
291 | elif self.use_case.startswith("json"):
292 | res = self.doc
293 | elif self.use_case.startswith("tweets"):
294 | res = self.doc
295 | return res
296 | elif self.use_case.startswith("word") or self.use_case.startswith("user"):
297 | res.append("%22s -- %10s %8s (%d)"%( "terms", "mentions", "activities", self.res_cnt))
298 | res.append("-"*WIDTH)
299 | for x in self.freq.get_tokens(self.token_list_size):
300 | res.append("%22s -- %4d %5.2f%% %4d %5.2f%%"%(x[4], x[0], x[1]*100., x[2], x[3]*100.))
301 | res.append("-"*WIDTH)
302 | elif self.use_case.startswith("time"):
303 | if csv_flag:
304 | res = ["{:%Y-%m-%dT%H:%M:%S},{}".format(
305 | datetime.datetime.strptime(x["timePeriod"]
306 | , TIME_FMT)
307 | , x["count"])
308 | for x in self.doc]
309 | else:
310 | res = [json.dumps({"results": self.doc})]
311 | else:
312 | res[-1]+=u"-"*WIDTH
313 | res.append("%100s -- %10s %8s (%d)"%("links", "mentions", "activities", self.res_cnt))
314 | res.append("-"*2*WIDTH)
315 | for x in self.freq.get_tokens(self.token_list_size):
316 | res.append("%100s -- %4d %5.2f%% %4d %5.2f%%"%(x[4], x[0], x[1]*100., x[2], x[3]*100.))
317 | res.append("-"*WIDTH)
318 | return "\n".join(res)
319 |
320 | class QueryError(Exception):
321 |
322 | def __init__(self, message, payload, response):
323 | self.message = message
324 | self.payload = payload
325 | self.response = response
326 |
327 | def __str__(self):
328 | return repr("%s (%s, %s)" % (self.message, self.payload, self.response))
329 |
330 | if __name__ == "__main__":
331 | g = GnipSearchAPI("USER"
332 | , "PASSWORD"
333 | , "STREAM_URL",
334 | )
335 |
336 | term = "captain america"
337 |
338 | print g.query_api(term)
339 | print g.query_api(term, 50)
340 | print g.query_api(term, 10, "json")
341 | print g.query_api(term, 0, "timeline")
342 | print g.query_api(term, 10, "users")
343 | print g.query_api(term, 10, "rate")
344 | print g.query_api(term, 10, "links")
345 |
--------------------------------------------------------------------------------
/home/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdevplatform/tweet-search/983de3165f6fc09bb1d8f348db1a1cb06b824a12/home/__init__.py
--------------------------------------------------------------------------------
/home/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | from django.contrib.auth.admin import UserAdmin
4 | from django.contrib.auth.models import User
5 |
6 | # Register your models here.
7 |
8 | UserAdmin.list_display = ('username', 'first_name', 'last_name', 'date_joined', 'is_staff', 'is_superuser')
9 |
10 | admin.site.unregister(User)
11 | admin.site.register(User, UserAdmin)
12 |
13 |
14 |
--------------------------------------------------------------------------------
/home/chart.py:
--------------------------------------------------------------------------------
1 | from timeseries import Timeseries
2 | from django.conf import settings
3 |
4 | from home.utils import *
5 |
6 | class Chart:
7 | """
8 | Class for creating line graph chart
9 | """
10 | DATE_FORMAT = "%Y-%m-%d %H:%M"
11 |
12 | def __init__(self, queries, start, end, interval):
13 | self.queries = queries
14 | self.start = start
15 | self.end = end
16 | self.interval = interval
17 | self.total = 0
18 | self.query_count = 0
19 | self.x_axis = None
20 | self.columns = self.create()
21 |
22 | def create(self):
23 | """
24 | Returns data in format {"columns": } used in UI
25 | """
26 | # New gnip client with fresh endpoint
27 | g = get_gnip(True)
28 |
29 | columns = []
30 | for q in self.queries:
31 | timeline = g.query_api(pt_filter = str(q),
32 | max_results = 0,
33 | use_case = "timeline",
34 | start = self.start.strftime(self.DATE_FORMAT),
35 | end = self.end.strftime(self.DATE_FORMAT),
36 | count_bucket = self.interval,
37 | csv_flag = False)
38 |
39 | # Process timeseries on the GNIP Data
40 | time_series_data = Timeseries(q, timeline, columns, self.total, self.x_axis)
41 | column = time_series_data.columns
42 |
43 | if self.x_axis == None:
44 | self.x_axis = time_series_data.xAxis
45 | columns.insert(0, self.x_axis)
46 |
47 | columns.append(time_series_data.series)
48 | self.total = time_series_data.total
49 |
50 | return columns
51 |
--------------------------------------------------------------------------------
/home/frequency.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 |
3 | from home.utils import *
4 |
5 | class Frequency:
6 | """
7 | Class collection for Frequency
8 | """
9 | DATE_FORMAT = "%Y-%m-%d %H:%M"
10 |
11 | def __init__(self, query, sample, start, end):
12 | self.query = query
13 | self.sample = sample
14 | self.start = start
15 | self.end = end
16 | self.freq = self.get(self.get_data())
17 |
18 | def get_data(self):
19 | """
20 | Returns data for frequency in list view
21 | """
22 | # New gnip client with fresh endpoint (this one sets to counts.json)
23 | g = get_gnip(False)
24 |
25 | timeline = g.query_api(self.query, self.sample, use_case="wordcount", start=self.start.strftime(self.DATE_FORMAT), end=self.end.strftime(self.DATE_FORMAT), csv_flag=False)
26 |
27 | result = g.freq.get_tokens(20)
28 | return result
29 |
30 | def get(self, data):
31 | response_data = []
32 | for f in data:
33 | response_data.append(f)
34 | response_data = sorted(response_data, key=lambda f: -f[3])
35 | return response_data
36 |
--------------------------------------------------------------------------------
/home/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | # Create your models here.
4 |
--------------------------------------------------------------------------------
/home/tests.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import timeframe
3 | import timeseries
4 | import datetime
5 | import frequency
6 | #from django.test import TestCase
7 |
8 | # Create your tests here.
9 | class Tests(unittest.TestCase):
10 | """
11 | Class for tests
12 | """
13 | def setUp(self):
14 | pass
15 |
16 | def test_timeframe(self):
17 | empty_timeframe = timeframe.Timeframe("", "", "")
18 | self.assertEqual(type(empty_timeframe.start), datetime.datetime)
19 | self.assertEqual(type(empty_timeframe.end), datetime.datetime)
20 |
21 | def test_timeseries(self):
22 | ts = timeseries.Timeseries(query="one direction",
23 | timeline="""{"results": [{"count": 268477, "timePeriod": "201512220000"}, {"count": 316681, "timePeriod": "201512210000"}]}""",
24 | columns=[],
25 | total=0)
26 | self.assertEqual(type(ts.columns), list)
27 |
28 | def test_chart(self):
29 | # TODO: fix chart
30 | request_timeframe = timeframe.Timeframe(start = "", end = "", interval = "")
31 | queries = ["#TwitterDev"]
32 | self.assertEqual(type(queries), list)
33 |
34 | def test_frequency(self):
35 | # TODO: fix with GNIP
36 | request_timeframe = timeframe.Timeframe(start = "", end = "", interval = "")
37 | frequency = []
38 | self.assertEqual(type(frequency), list)
39 |
40 | if __name__ == '__main__':
41 | unittest.main()
42 |
--------------------------------------------------------------------------------
/home/timeframe.py:
--------------------------------------------------------------------------------
1 | import datetime
2 |
3 | class Timeframe:
4 | """
5 | The Timeframe class provides a container for timeframe elements required by the GNIP API
6 | """
7 | DEFAULT_TIMEFRAME = 90 # When not specified or needed to constrain, this # of days lookback
8 | TIMEDELTA_DEFAULT_TIMEFRAME = datetime.timedelta(days=DEFAULT_TIMEFRAME)
9 | TIMEDELTA_DEFAULT_30 = datetime.timedelta(days=30)
10 | DATE_FORMAT = "%Y-%m-%d %H:%M"
11 | DATE_FORMAT_JSON = "%Y-%m-%dT%H:%M:%S"
12 |
13 | def __init__(self, start, end, interval):
14 | self.end = self.get_end(end)
15 | self.start = self.get_start(start)
16 | self.interval = interval
17 | self.days = (self.end-self.start).days
18 |
19 | # if dates wrong, use default
20 | if self.start > self.end:
21 | self.start = self.end - self.TIMEDELTA_DEFAULT_TIMEFRAME
22 |
23 | def get_end(self, end):
24 | """
25 | Returns processed end date.
26 | """
27 | # ensure end always exists
28 | if not end:
29 | end = datetime.datetime.now() - datetime.timedelta(minutes=1)
30 | else:
31 | end = datetime.datetime.strptime(end, self.DATE_FORMAT)
32 | return end
33 |
34 | def get_start(self, start):
35 | """
36 | Returns processed start date.
37 | """
38 | # ensure start always exists
39 | if not start:
40 | start = self.end - self.TIMEDELTA_DEFAULT_TIMEFRAME
41 | else:
42 | start = datetime.datetime.strptime(start, self.DATE_FORMAT)
43 | return start
44 |
45 | if __name__ == "__main__":
46 | # Test that Timeframe supplies the output
47 | time_frame = Timeframe("", "", "")
48 | print time_frame.start
49 | print time_frame.end
50 |
--------------------------------------------------------------------------------
/home/timeseries.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | class Timeseries:
4 | """
5 | Class to produce timeseries data
6 | """
7 | def __init__(self, query, timeline, columns, total, xAxis=None):
8 | self.columns = columns
9 | self.query = query
10 | self.timeline = json.loads(timeline)
11 | self.query_total = 0
12 | self.series = self.create_series()
13 | self.columns.append(self.series)
14 | self.total = total + self.query_total
15 | self.xAxis = self.create_x_axis(xAxis)
16 |
17 | def create_series(self):
18 | """
19 | Returns a series based on `results` from timeline
20 | """
21 | series = []
22 | for timeline_object in self.timeline['results']:
23 | count = timeline_object["count"]
24 | series.insert(0, count)
25 | self.query_total = self.query_total + count
26 | label = self.query[0:30]
27 | if len(self.query) > 30:
28 | label = label + "..."
29 | label = label + " (" + str(self.query_total) + ")"
30 | series.insert(0, label)
31 | return series
32 |
33 | def create_x_axis(self, xAxis):
34 | """
35 | Returns xAxis for timeseries
36 | """
37 | if not xAxis:
38 | xAxis = []
39 | for t in self.timeline['results']:
40 | time_period = t["timePeriod"]
41 | day = str(time_period[0:4] + "-" +
42 | time_period[4:6] + "-" +
43 | time_period[6:8] + " " +
44 | time_period[8:10] + ":" +
45 | "00:00")
46 | xAxis.append(day)
47 | return xAxis
48 |
49 | if __name__ == "__main__":
50 | ts = Timeseries(query="gareth",
51 | timeline="""{"results": [{"count": 268477, "timePeriod": "201512220000"}, {"count": 316681, "timePeriod": "201512210000"}]}""",
52 | columns=[],
53 | total=0)
54 | print ts.columns
55 |
--------------------------------------------------------------------------------
/home/tweets.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import csv
3 | from django.conf import settings
4 |
5 | from home.utils import *
6 |
7 | class Tweets:
8 | DEFAULT_TIMEFRAME = 90
9 | DATE_FORMAT = "%Y-%m-%d %H:%M"
10 | TIMEDELTA_DEFAULT_TIMEFRAME = datetime.timedelta(days=DEFAULT_TIMEFRAME)
11 |
12 | def __init__(self, query, query_count, start, end, export=None):
13 | self.query = query
14 | self.query_count = query_count
15 | self.start = start
16 | self.end = end
17 | self.export = export
18 | self.data = self.get_data()
19 |
20 | def get_data(self):
21 |
22 | g = get_gnip(False)
23 |
24 | if (self.start < datetime.datetime.now() - self.TIMEDELTA_DEFAULT_TIMEFRAME) and (self.start + self.TIMEDELTA_DEFAULT_TIMEFRAME > self.end):
25 | end = self.start + self.TIMEDELTA_DEFAULT_TIMEFRAME
26 | query_nrt = self.query
27 |
28 | # scrub tweet display query for no retweets
29 | not_rt = "-(is:retweet)"
30 | if (not_rt not in query_nrt):
31 | query_nrt = query_nrt.replace("is:retweet", "")
32 | query_nrt = "%s %s" % (query_nrt, not_rt)
33 |
34 | print "%s (%s)" % (query_nrt, self.query_count)
35 |
36 | if self.query_count > 500:
37 | g.paged = True
38 |
39 | tweets = g.query_api(query_nrt, self.query_count, use_case="tweets", start=self.start.strftime(self.DATE_FORMAT), end=self.end.strftime(self.DATE_FORMAT))
40 |
41 | return tweets
42 |
--------------------------------------------------------------------------------
/home/utils.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | from django.http import HttpResponse
4 | from django.conf import settings
5 |
6 | from gnip_search.gnip_search_api import GnipSearchAPI
7 |
8 | def get_gnip(paged=False):
9 | """
10 | Returns Gnip Search API
11 | """
12 | g = GnipSearchAPI(settings.GNIP_USERNAME,
13 | settings.GNIP_PASSWORD,
14 | settings.GNIP_SEARCH_ENDPOINT,
15 | paged=paged)
16 | return g
17 |
18 | def handleQueryError(e):
19 |
20 | response_data = {}
21 | response_data['error'] = e.message
22 | response_data['response'] = e.response
23 | response_data['payload'] = e.payload
24 |
25 | print response_data
26 |
27 | return HttpResponse(json.dumps(response_data), status=400, content_type="application/json")
28 |
--------------------------------------------------------------------------------
/home/views.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import random
3 | import csv
4 | import json
5 |
6 | # TODO: Fix * imports
7 | from django.shortcuts import *
8 | from django.contrib.auth.decorators import login_required, user_passes_test
9 | from django.contrib.auth import logout as auth_logout
10 |
11 | from social.apps.django_app.default.models import UserSocialAuth
12 |
13 | from gnip_search.gnip_search_api import QueryError as GNIPQueryError
14 |
15 | from chart import Chart
16 | from timeframe import Timeframe
17 | from frequency import Frequency
18 | from tweets import Tweets
19 | from home.utils import *
20 |
21 | # import twitter
22 | KEYWORD_RELEVANCE_THRESHOLD = .1 # Only show related terms if > 10%
23 | TWEET_QUERY_COUNT = 10 # For real identification, > 100. Max of 500 via Search API.
24 | DEFAULT_TIMEFRAME = 1 # When not specified or needed to constrain, this # of days lookback
25 | TIMEDELTA_DEFAULT_TIMEFRAME = datetime.timedelta(days=DEFAULT_TIMEFRAME)
26 | TIMEDELTA_DEFAULT_30 = datetime.timedelta(days=30)
27 | DATE_FORMAT = "%Y-%m-%d %H:%M"
28 | DATE_FORMAT_JSON = "%Y-%m-%dT%H:%M:%S"
29 |
30 | def login(request):
31 | """
32 | Returns login page for given request
33 | """
34 | context = {"request": request}
35 | return render_to_response('login.html', context, context_instance=RequestContext(request))
36 |
37 | @login_required
38 | # @user_passes_test(lambda u: u.is_staff or u.is_superuser, login_url='/')
39 | def home(request):
40 | """
41 | Returns home page for given request
42 | """
43 | query = request.GET.get("query", "")
44 | context = {"request": request, "query0": query}
45 | tweets = []
46 | return render_to_response('home.html', context, context_instance=RequestContext(request))
47 |
48 | @login_required
49 | def query_chart(request):
50 | """
51 | Returns query chart for given request
52 | """
53 | # TODO: Move this to one line e.g. queries to query
54 | query = request.GET.get("query", None)
55 | queries = request.GET.getlist("queries[]")
56 | if query:
57 | queries = [query]
58 |
59 | request_timeframe = Timeframe(start = request.GET.get("start", None),
60 | end = request.GET.get("end", None),
61 | interval = request.GET.get("interval", "hour"))
62 |
63 | response_chart = None
64 | try:
65 | response_chart = Chart(queries = queries,
66 | start = request_timeframe.start,
67 | end = request_timeframe.end,
68 | interval = request_timeframe.interval)
69 | except GNIPQueryError as e:
70 | return handleQueryError(e)
71 |
72 | response_data = {}
73 | response_data['days'] = request_timeframe.days
74 | response_data['start'] = request_timeframe.start.strftime(DATE_FORMAT_JSON)
75 | response_data['end'] = request_timeframe.end.strftime(DATE_FORMAT_JSON)
76 | response_data['columns'] = response_chart.columns
77 | response_data['total'] = response_chart.total
78 |
79 | return HttpResponse(json.dumps(response_data), content_type="application/json")
80 |
81 | @login_required
82 | def query_frequency(request):
83 | query = request.GET.get("query", None)
84 | response_data = {}
85 | sample = 500
86 | if query is not None:
87 |
88 | # Get Timeframe e.g. process time from request
89 | request_timeframe = Timeframe(start = request.GET.get("start", None),
90 | end = request.GET.get("end", None),
91 | interval = request.GET.get("interval", "hour"))
92 |
93 | data = None
94 | try:
95 | # Query GNIP and get frequency
96 | data = Frequency(query = query,
97 | sample = sample,
98 | start = request_timeframe.start,
99 | end = request_timeframe.end)
100 | except GNIPQueryError as e:
101 | return handleQueryError(e)
102 |
103 | response_data["frequency"] = data.freq
104 | response_data["sample"] = sample
105 | return HttpResponse(json.dumps(response_data), content_type="application/json")
106 |
107 | @login_required
108 | def query_tweets(request):
109 | """
110 | Returns tweet query
111 | """
112 | request_timeframe = Timeframe(start = request.GET.get("start", None),
113 | end = request.GET.get("end", None),
114 | interval = request.GET.get("interval", "hour"))
115 |
116 | query_count = int(request.GET.get("embedCount", TWEET_QUERY_COUNT))
117 | export = request.GET.get("export", None)
118 | query = request.GET.get("query", "")
119 |
120 | try:
121 | tweets = Tweets(query=query, query_count=query_count, start=request_timeframe.start, end=request_timeframe.end, export=export)
122 | except GNIPQueryError as e:
123 | return handleQueryError(e)
124 |
125 | response_data = {}
126 | if export == "csv":
127 | response = HttpResponse(content_type='text/csv')
128 | response['Content-Disposition'] = 'attachment; filename="export.csv"'
129 | writer = csv.writer(response, delimiter=',', quotechar="'", quoting=csv.QUOTE_ALL)
130 | writer.writerow(['count','time','id','user_screen_name','user_id','status','retweet_count','favorite_count','is_retweet','in_reply_to_tweet_id','in_reply_to_screen_name'])
131 | count = 0;
132 | for t in tweets.get_data():
133 | count = count + 1
134 | body = t['body'].encode('ascii', 'replace')
135 | status_id = t['id']
136 | status_id = status_id[status_id.rfind(':')+1:]
137 | user_id = t['actor']['id']
138 | user_id = user_id[user_id.rfind(':')+1:]
139 | writer.writerow([count, t['postedTime'], status_id, t['actor']['preferredUsername'], user_id, body, t['retweetCount'], t['favoritesCount'], 'X', 'X', 'X'])
140 | return response
141 | else:
142 | response_data['tweets'] = tweets.get_data()
143 | return HttpResponse(json.dumps(response_data), content_type="application/json")
144 |
145 | def logout(request):
146 | """
147 | Returns a redirect response and logs out user
148 | """
149 | auth_logout(request)
150 | return HttpResponseRedirect('/')
151 |
152 |
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | if __name__ == "__main__":
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings")
7 |
8 | from django.core.management import execute_from_command_line
9 |
10 | execute_from_command_line(sys.argv)
11 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Django==1.9.1
2 | dj-database-url==0.3.0
3 | gunicorn==19.1.1
4 | psycopg2==2.5.1
5 | python-social-auth
6 | django-adminrestrict
7 | django-csp==2.0.3
8 | Fabric==1.7.0
9 | gnacs
10 | sngrams
11 | -e git://github.com/DrSkippy/Simple-n-grams.git@bbfd782614b39e2d0a1bc01fc6a75cc5df235e3e#egg=Simple-n-grams
12 |
--------------------------------------------------------------------------------
/runtime.txt:
--------------------------------------------------------------------------------
1 | python-2.7.4
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdevplatform/tweet-search/983de3165f6fc09bb1d8f348db1a1cb06b824a12/screenshot.png
--------------------------------------------------------------------------------
/services/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdevplatform/tweet-search/983de3165f6fc09bb1d8f348db1a1cb06b824a12/services/__init__.py
--------------------------------------------------------------------------------
/services/middleware.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.http import HttpResponseRedirect
3 |
4 | class SSLMiddleware(object):
5 |
6 | def process_request(self, request):
7 | if not any([settings.DEBUG, request.is_secure(), request.META.get("HTTP_X_FORWARDED_PROTO", "") == 'https']):
8 | url = request.build_absolute_uri(request.get_full_path())
9 | secure_url = url.replace("http://", "https://")
10 | return HttpResponseRedirect(secure_url)
--------------------------------------------------------------------------------
/static/css/bootstrap-datetimepicker.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Datetimepicker for Bootstrap v3
3 | * https://github.com/Eonasdan/bootstrap-datetimepicker/
4 | */.bootstrap-datetimepicker-widget{top:0;left:0;width:250px;padding:4px;margin-top:1px;z-index:99999 !important;border-radius:4px}.bootstrap-datetimepicker-widget.timepicker-sbs{width:600px}.bootstrap-datetimepicker-widget.bottom:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0,0,0,0.2);position:absolute;top:-7px;left:7px}.bootstrap-datetimepicker-widget.bottom:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;top:-6px;left:8px}.bootstrap-datetimepicker-widget.top:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-top:7px solid #ccc;border-top-color:rgba(0,0,0,0.2);position:absolute;bottom:-7px;left:6px}.bootstrap-datetimepicker-widget.top:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #fff;position:absolute;bottom:-6px;left:7px}.bootstrap-datetimepicker-widget .dow{width:14.2857%}.bootstrap-datetimepicker-widget.pull-right:before{left:auto;right:6px}.bootstrap-datetimepicker-widget.pull-right:after{left:auto;right:7px}.bootstrap-datetimepicker-widget>ul{list-style-type:none;margin:0}.bootstrap-datetimepicker-widget a[data-action]{padding:6px 0}.bootstrap-datetimepicker-widget .timepicker-hour,.bootstrap-datetimepicker-widget .timepicker-minute,.bootstrap-datetimepicker-widget .timepicker-second{width:54px;font-weight:bold;font-size:1.2em;margin:0}.bootstrap-datetimepicker-widget button[data-action]{padding:6px}.bootstrap-datetimepicker-widget table[data-hour-format="12"] .separator{width:4px;padding:0;margin:0}.bootstrap-datetimepicker-widget .datepicker>div{display:none}.bootstrap-datetimepicker-widget .picker-switch{text-align:center}.bootstrap-datetimepicker-widget table{width:100%;margin:0}.bootstrap-datetimepicker-widget td,.bootstrap-datetimepicker-widget th{text-align:center;border-radius:4px}.bootstrap-datetimepicker-widget td{height:54px;line-height:54px;width:54px}.bootstrap-datetimepicker-widget td.day{height:20px;line-height:20px;width:20px}.bootstrap-datetimepicker-widget td.day:hover,.bootstrap-datetimepicker-widget td.hour:hover,.bootstrap-datetimepicker-widget td.minute:hover,.bootstrap-datetimepicker-widget td.second:hover{background:#eee;cursor:pointer}.bootstrap-datetimepicker-widget td.old,.bootstrap-datetimepicker-widget td.new{color:#999}.bootstrap-datetimepicker-widget td.today{position:relative}.bootstrap-datetimepicker-widget td.today:before{content:'';display:inline-block;border-left:7px solid transparent;border-bottom:7px solid #428bca;border-top-color:rgba(0,0,0,0.2);position:absolute;bottom:4px;right:4px}.bootstrap-datetimepicker-widget td.active,.bootstrap-datetimepicker-widget td.active:hover{background-color:#428bca;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.bootstrap-datetimepicker-widget td.active.today:before{border-bottom-color:#fff}.bootstrap-datetimepicker-widget td.disabled,.bootstrap-datetimepicker-widget td.disabled:hover{background:none;color:#999;cursor:not-allowed}.bootstrap-datetimepicker-widget td span{display:block;width:54px;height:54px;line-height:54px;float:left;margin:2px 1.5px;cursor:pointer;border-radius:4px}.bootstrap-datetimepicker-widget td span:hover{background:#eee}.bootstrap-datetimepicker-widget td span.active{background-color:#428bca;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.bootstrap-datetimepicker-widget td span.old{color:#999}.bootstrap-datetimepicker-widget td span.disabled,.bootstrap-datetimepicker-widget td span.disabled:hover{background:none;color:#999;cursor:not-allowed}.bootstrap-datetimepicker-widget th{height:20px;line-height:20px;width:20px}.bootstrap-datetimepicker-widget th.switch{width:145px}.bootstrap-datetimepicker-widget th.next,.bootstrap-datetimepicker-widget th.prev{font-size:21px}.bootstrap-datetimepicker-widget th.disabled,.bootstrap-datetimepicker-widget th.disabled:hover{background:none;color:#999;cursor:not-allowed}.bootstrap-datetimepicker-widget thead tr:first-child th{cursor:pointer}.bootstrap-datetimepicker-widget thead tr:first-child th:hover{background:#eee}.input-group.date .input-group-addon span{display:block;cursor:pointer;width:16px;height:16px}.bootstrap-datetimepicker-widget.left-oriented:before{left:auto;right:6px}.bootstrap-datetimepicker-widget.left-oriented:after{left:auto;right:7px}.bootstrap-datetimepicker-widget ul.list-unstyled li div.timepicker div.timepicker-picker table.table-condensed tbody>tr>td{padding:0 !important}@media screen and (max-width:767px){.bootstrap-datetimepicker-widget.timepicker-sbs{width:283px}}
--------------------------------------------------------------------------------
/static/css/bootstrap-theme.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.0.3 (http://getbootstrap.com)
3 | * Copyright 2013 Twitter, Inc.
4 | * Licensed under http://www.apache.org/licenses/LICENSE-2.0
5 | */
6 |
7 | .btn-default,
8 | .btn-primary,
9 | .btn-success,
10 | .btn-info,
11 | .btn-warning,
12 | .btn-danger {
13 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
14 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
15 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
16 | }
17 |
18 | .btn-default:active,
19 | .btn-primary:active,
20 | .btn-success:active,
21 | .btn-info:active,
22 | .btn-warning:active,
23 | .btn-danger:active,
24 | .btn-default.active,
25 | .btn-primary.active,
26 | .btn-success.active,
27 | .btn-info.active,
28 | .btn-warning.active,
29 | .btn-danger.active {
30 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
31 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
32 | }
33 |
34 | .btn:active,
35 | .btn.active {
36 | background-image: none;
37 | }
38 |
39 | .btn-default {
40 | text-shadow: 0 1px 0 #fff;
41 | background-image: -webkit-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);
42 | background-image: linear-gradient(to bottom, #ffffff 0%, #e0e0e0 100%);
43 | background-repeat: repeat-x;
44 | border-color: #dbdbdb;
45 | border-color: #ccc;
46 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
47 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
48 | }
49 |
50 | .btn-default:hover,
51 | .btn-default:focus {
52 | background-color: #e0e0e0;
53 | background-position: 0 -15px;
54 | }
55 |
56 | .btn-default:active,
57 | .btn-default.active {
58 | background-color: #e0e0e0;
59 | border-color: #dbdbdb;
60 | }
61 |
62 | .btn-primary {
63 | background-image: -webkit-linear-gradient(top, #428bca 0%, #2d6ca2 100%);
64 | background-image: linear-gradient(to bottom, #428bca 0%, #2d6ca2 100%);
65 | background-repeat: repeat-x;
66 | border-color: #2b669a;
67 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);
68 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
69 | }
70 |
71 | .btn-primary:hover,
72 | .btn-primary:focus {
73 | background-color: #2d6ca2;
74 | background-position: 0 -15px;
75 | }
76 |
77 | .btn-primary:active,
78 | .btn-primary.active {
79 | background-color: #2d6ca2;
80 | border-color: #2b669a;
81 | }
82 |
83 | .btn-success {
84 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
85 | background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
86 | background-repeat: repeat-x;
87 | border-color: #3e8f3e;
88 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
89 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
90 | }
91 |
92 | .btn-success:hover,
93 | .btn-success:focus {
94 | background-color: #419641;
95 | background-position: 0 -15px;
96 | }
97 |
98 | .btn-success:active,
99 | .btn-success.active {
100 | background-color: #419641;
101 | border-color: #3e8f3e;
102 | }
103 |
104 | .btn-warning {
105 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
106 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
107 | background-repeat: repeat-x;
108 | border-color: #e38d13;
109 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
110 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
111 | }
112 |
113 | .btn-warning:hover,
114 | .btn-warning:focus {
115 | background-color: #eb9316;
116 | background-position: 0 -15px;
117 | }
118 |
119 | .btn-warning:active,
120 | .btn-warning.active {
121 | background-color: #eb9316;
122 | border-color: #e38d13;
123 | }
124 |
125 | .btn-danger {
126 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
127 | background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
128 | background-repeat: repeat-x;
129 | border-color: #b92c28;
130 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
131 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
132 | }
133 |
134 | .btn-danger:hover,
135 | .btn-danger:focus {
136 | background-color: #c12e2a;
137 | background-position: 0 -15px;
138 | }
139 |
140 | .btn-danger:active,
141 | .btn-danger.active {
142 | background-color: #c12e2a;
143 | border-color: #b92c28;
144 | }
145 |
146 | .btn-info {
147 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
148 | background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
149 | background-repeat: repeat-x;
150 | border-color: #28a4c9;
151 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
152 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
153 | }
154 |
155 | .btn-info:hover,
156 | .btn-info:focus {
157 | background-color: #2aabd2;
158 | background-position: 0 -15px;
159 | }
160 |
161 | .btn-info:active,
162 | .btn-info.active {
163 | background-color: #2aabd2;
164 | border-color: #28a4c9;
165 | }
166 |
167 | .thumbnail,
168 | .img-thumbnail {
169 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
170 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
171 | }
172 |
173 | .dropdown-menu > li > a:hover,
174 | .dropdown-menu > li > a:focus {
175 | background-color: #e8e8e8;
176 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
177 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
178 | background-repeat: repeat-x;
179 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
180 | }
181 |
182 | .dropdown-menu > .active > a,
183 | .dropdown-menu > .active > a:hover,
184 | .dropdown-menu > .active > a:focus {
185 | background-color: #357ebd;
186 | background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
187 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
188 | background-repeat: repeat-x;
189 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
190 | }
191 |
192 | .navbar-default {
193 | background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);
194 | background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%);
195 | background-repeat: repeat-x;
196 | border-radius: 4px;
197 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
198 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
199 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
200 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
201 | }
202 |
203 | .navbar-default .navbar-nav > .active > a {
204 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%);
205 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f3f3f3 100%);
206 | background-repeat: repeat-x;
207 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);
208 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);
209 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);
210 | }
211 |
212 | .navbar-brand,
213 | .navbar-nav > li > a {
214 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);
215 | }
216 |
217 | .navbar-inverse {
218 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222222 100%);
219 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%);
220 | background-repeat: repeat-x;
221 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
222 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
223 | }
224 |
225 | .navbar-inverse .navbar-nav > .active > a {
226 | background-image: -webkit-linear-gradient(top, #222222 0%, #282828 100%);
227 | background-image: linear-gradient(to bottom, #222222 0%, #282828 100%);
228 | background-repeat: repeat-x;
229 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);
230 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);
231 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);
232 | }
233 |
234 | .navbar-inverse .navbar-brand,
235 | .navbar-inverse .navbar-nav > li > a {
236 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
237 | }
238 |
239 | .navbar-static-top,
240 | .navbar-fixed-top,
241 | .navbar-fixed-bottom {
242 | border-radius: 0;
243 | }
244 |
245 | .alert {
246 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);
247 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
248 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
249 | }
250 |
251 | .alert-success {
252 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
253 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
254 | background-repeat: repeat-x;
255 | border-color: #b2dba1;
256 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
257 | }
258 |
259 | .alert-info {
260 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
261 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
262 | background-repeat: repeat-x;
263 | border-color: #9acfea;
264 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
265 | }
266 |
267 | .alert-warning {
268 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
269 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
270 | background-repeat: repeat-x;
271 | border-color: #f5e79e;
272 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
273 | }
274 |
275 | .alert-danger {
276 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
277 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
278 | background-repeat: repeat-x;
279 | border-color: #dca7a7;
280 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
281 | }
282 |
283 | .progress {
284 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
285 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
286 | background-repeat: repeat-x;
287 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
288 | }
289 |
290 | .progress-bar {
291 | background-image: -webkit-linear-gradient(top, #428bca 0%, #3071a9 100%);
292 | background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%);
293 | background-repeat: repeat-x;
294 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);
295 | }
296 |
297 | .progress-bar-success {
298 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
299 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
300 | background-repeat: repeat-x;
301 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
302 | }
303 |
304 | .progress-bar-info {
305 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
306 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
307 | background-repeat: repeat-x;
308 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
309 | }
310 |
311 | .progress-bar-warning {
312 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
313 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
314 | background-repeat: repeat-x;
315 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
316 | }
317 |
318 | .progress-bar-danger {
319 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
320 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
321 | background-repeat: repeat-x;
322 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
323 | }
324 |
325 | .list-group {
326 | border-radius: 4px;
327 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
328 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
329 | }
330 |
331 | .list-group-item.active,
332 | .list-group-item.active:hover,
333 | .list-group-item.active:focus {
334 | text-shadow: 0 -1px 0 #3071a9;
335 | background-image: -webkit-linear-gradient(top, #428bca 0%, #3278b3 100%);
336 | background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%);
337 | background-repeat: repeat-x;
338 | border-color: #3278b3;
339 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);
340 | }
341 |
342 | .panel {
343 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
344 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
345 | }
346 |
347 | .panel-default > .panel-heading {
348 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
349 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
350 | background-repeat: repeat-x;
351 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
352 | }
353 |
354 | .panel-primary > .panel-heading {
355 | background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
356 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
357 | background-repeat: repeat-x;
358 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
359 | }
360 |
361 | .panel-success > .panel-heading {
362 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
363 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
364 | background-repeat: repeat-x;
365 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
366 | }
367 |
368 | .panel-info > .panel-heading {
369 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
370 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
371 | background-repeat: repeat-x;
372 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
373 | }
374 |
375 | .panel-warning > .panel-heading {
376 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
377 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
378 | background-repeat: repeat-x;
379 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
380 | }
381 |
382 | .panel-danger > .panel-heading {
383 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
384 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
385 | background-repeat: repeat-x;
386 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
387 | }
388 |
389 | .well {
390 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
391 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
392 | background-repeat: repeat-x;
393 | border-color: #dcdcdc;
394 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
395 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
396 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
397 | }
--------------------------------------------------------------------------------
/static/css/bootstrap-theme.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.0.3 (http://getbootstrap.com)
3 | * Copyright 2013 Twitter, Inc.
4 | * Licensed under http://www.apache.org/licenses/LICENSE-2.0
5 | */
6 |
7 | .btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn:active,.btn.active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe0e0e0',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-primary{background-image:-webkit-linear-gradient(top,#428bca 0,#2d6ca2 100%);background-image:linear-gradient(to bottom,#428bca 0,#2d6ca2 100%);background-repeat:repeat-x;border-color:#2b669a;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff2d6ca2',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:focus{background-color:#2d6ca2;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#2d6ca2;border-color:#2b669a}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);background-repeat:repeat-x;border-color:#3e8f3e;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c',endColorstr='#ff419641',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);background-repeat:repeat-x;border-color:#e38d13;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e',endColorstr='#ffeb9316',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);background-repeat:repeat-x;border-color:#b92c28;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f',endColorstr='#ffc12e2a',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);background-repeat:repeat-x;border-color:#28a4c9;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2aabd2',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:focus{background-color:#2aabd2;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#2aabd2;border-color:#28a4c9}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#ffe8e8e8',GradientType=0)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-color:#357ebd;background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff357ebd',GradientType=0)}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);background-repeat:repeat-x;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff8f8f8',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075)}.navbar-default .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f3f3f3 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f3f3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb',endColorstr='#fff3f3f3',GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,0.075);box-shadow:inset 0 3px 9px rgba(0,0,0,0.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,0.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c',endColorstr='#ff222222',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#222 0,#282828 100%);background-image:linear-gradient(to bottom,#222 0,#282828 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222',endColorstr='#ff282828',GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,0.25);box-shadow:inset 0 3px 9px rgba(0,0,0,0.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);background-repeat:repeat-x;border-color:#b2dba1;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8',endColorstr='#ffc8e5bc',GradientType=0)}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);background-repeat:repeat-x;border-color:#9acfea;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7',endColorstr='#ffb9def0',GradientType=0)}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);background-repeat:repeat-x;border-color:#f5e79e;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3',endColorstr='#fff8efc0',GradientType=0)}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);background-repeat:repeat-x;border-color:#dca7a7;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede',endColorstr='#ffe7c3c3',GradientType=0)}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb',endColorstr='#fff5f5f5',GradientType=0)}.progress-bar{background-image:-webkit-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3071a9',GradientType=0)}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c',endColorstr='#ff449d44',GradientType=0)}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff31b0d5',GradientType=0)}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e',endColorstr='#ffec971f',GradientType=0)}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f',endColorstr='#ffc9302c',GradientType=0)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:linear-gradient(to bottom,#428bca 0,#3278b3 100%);background-repeat:repeat-x;border-color:#3278b3;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3278b3',GradientType=0)}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#ffe8e8e8',GradientType=0)}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff357ebd',GradientType=0)}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8',endColorstr='#ffd0e9c6',GradientType=0)}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7',endColorstr='#ffc4e3f3',GradientType=0)}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3',endColorstr='#fffaf2cc',GradientType=0)}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede',endColorstr='#ffebcccc',GradientType=0)}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);background-repeat:repeat-x;border-color:#dcdcdc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1)}
--------------------------------------------------------------------------------
/static/css/c3.css:
--------------------------------------------------------------------------------
1 | /*-- Chart --*/
2 | .c3 svg {
3 | font: 10px sans-serif; }
4 |
5 | .c3 path, .c3 line {
6 | fill: none;
7 | stroke: #000; }
8 |
9 | .c3 text {
10 | -webkit-user-select: none;
11 | -moz-user-select: none;
12 | user-select: none; }
13 |
14 | .c3-legend-item-tile, .c3-xgrid-focus, .c3-ygrid, .c3-event-rect, .c3-bars path {
15 | shape-rendering: crispEdges; }
16 |
17 | .c3-chart-arc path {
18 | stroke: #fff; }
19 |
20 | .c3-chart-arc text {
21 | fill: #fff;
22 | font-size: 13px; }
23 |
24 | /*-- Axis --*/
25 | /*-- Grid --*/
26 | .c3-grid line {
27 | stroke: #aaa; }
28 |
29 | .c3-grid text {
30 | fill: #aaa; }
31 |
32 | .c3-xgrid, .c3-ygrid {
33 | stroke-dasharray: 3 3; }
34 |
35 | /*-- Text on Chart --*/
36 | .c3-text.c3-empty {
37 | fill: #808080;
38 | font-size: 2em; }
39 |
40 | /*-- Line --*/
41 | .c3-line {
42 | stroke-width: 1px; }
43 |
44 | /*-- Point --*/
45 | .c3-circle._expanded_ {
46 | stroke-width: 1px;
47 | stroke: white; }
48 |
49 | .c3-selected-circle {
50 | fill: white;
51 | stroke-width: 2px; }
52 |
53 | /*-- Bar --*/
54 | .c3-bar {
55 | stroke-width: 0; }
56 |
57 | .c3-bar._expanded_ {
58 | fill-opacity: 0.75; }
59 |
60 | /*-- Focus --*/
61 | .c3-target.c3-focused {
62 | opacity: 1; }
63 |
64 | .c3-target.c3-focused path.c3-line, .c3-target.c3-focused path.c3-step {
65 | stroke-width: 2px; }
66 |
67 | .c3-target.c3-defocused {
68 | opacity: 0.3 !important; }
69 |
70 | /*-- Region --*/
71 | .c3-region {
72 | fill: steelblue;
73 | fill-opacity: 0.1; }
74 |
75 | /*-- Brush --*/
76 | .c3-brush .extent {
77 | fill-opacity: 0.1; }
78 |
79 | /*-- Select - Drag --*/
80 | /*-- Legend --*/
81 | .c3-legend-item {
82 | font-size: 12px; }
83 |
84 | .c3-legend-item-hidden {
85 | opacity: 0.15; }
86 |
87 | .c3-legend-background {
88 | opacity: 0.75;
89 | fill: white;
90 | stroke: lightgray;
91 | stroke-width: 1; }
92 |
93 | /*-- Tooltip --*/
94 | .c3-tooltip-container {
95 | z-index: 10; }
96 |
97 | .c3-tooltip {
98 | border-collapse: collapse;
99 | border-spacing: 0;
100 | background-color: #fff;
101 | empty-cells: show;
102 | -webkit-box-shadow: 7px 7px 12px -9px #777777;
103 | -moz-box-shadow: 7px 7px 12px -9px #777777;
104 | box-shadow: 7px 7px 12px -9px #777777;
105 | opacity: 0.9; }
106 |
107 | .c3-tooltip tr {
108 | border: 1px solid #CCC; }
109 |
110 | .c3-tooltip th {
111 | background-color: #aaa;
112 | font-size: 14px;
113 | padding: 2px 5px;
114 | text-align: left;
115 | color: #FFF; }
116 |
117 | .c3-tooltip td {
118 | font-size: 13px;
119 | padding: 3px 6px;
120 | background-color: #fff;
121 | border-left: 1px dotted #999; }
122 |
123 | .c3-tooltip td > span {
124 | display: inline-block;
125 | width: 10px;
126 | height: 10px;
127 | margin-right: 6px; }
128 |
129 | .c3-tooltip td.value {
130 | text-align: right; }
131 |
132 | /*-- Area --*/
133 | .c3-area {
134 | stroke-width: 0;
135 | opacity: 0.2; }
136 |
137 | /*-- Arc --*/
138 | .c3-chart-arcs-title {
139 | dominant-baseline: middle;
140 | font-size: 1.3em; }
141 |
142 | .c3-chart-arcs .c3-chart-arcs-background {
143 | fill: #e0e0e0;
144 | stroke: none; }
145 |
146 | .c3-chart-arcs .c3-chart-arcs-gauge-unit {
147 | fill: #000;
148 | font-size: 16px; }
149 |
150 | .c3-chart-arcs .c3-chart-arcs-gauge-max {
151 | fill: #777; }
152 |
153 | .c3-chart-arcs .c3-chart-arcs-gauge-min {
154 | fill: #777; }
155 |
156 | .c3-chart-arc .c3-gauge-value {
157 | fill: #000;
158 | /* font-size: 28px !important;*/ }
159 |
--------------------------------------------------------------------------------
/static/css/c3.min.css:
--------------------------------------------------------------------------------
1 | .c3 svg{font:10px sans-serif}.c3 line,.c3 path{fill:none;stroke:#000}.c3 text{-webkit-user-select:none;-moz-user-select:none;user-select:none}.c3-bars path,.c3-event-rect,.c3-legend-item-tile,.c3-xgrid-focus,.c3-ygrid{shape-rendering:crispEdges}.c3-chart-arc path{stroke:#fff}.c3-chart-arc text{fill:#fff;font-size:13px}.c3-grid line{stroke:#aaa}.c3-grid text{fill:#aaa}.c3-xgrid,.c3-ygrid{stroke-dasharray:3 3}.c3-text.c3-empty{fill:gray;font-size:2em}.c3-line{stroke-width:1px}.c3-circle._expanded_{stroke-width:1px;stroke:#fff}.c3-selected-circle{fill:#fff;stroke-width:2px}.c3-bar{stroke-width:0}.c3-bar._expanded_{fill-opacity:.75}.c3-target.c3-focused{opacity:1}.c3-target.c3-focused path.c3-line,.c3-target.c3-focused path.c3-step{stroke-width:2px}.c3-target.c3-defocused{opacity:.3!important}.c3-region{fill:#4682b4;fill-opacity:.1}.c3-brush .extent{fill-opacity:.1}.c3-legend-item{font-size:12px}.c3-legend-item-hidden{opacity:.15}.c3-legend-background{opacity:.75;fill:#fff;stroke:#d3d3d3;stroke-width:1}.c3-tooltip-container{z-index:10}.c3-tooltip{border-collapse:collapse;border-spacing:0;background-color:#fff;empty-cells:show;-webkit-box-shadow:7px 7px 12px -9px #777;-moz-box-shadow:7px 7px 12px -9px #777;box-shadow:7px 7px 12px -9px #777;opacity:.9}.c3-tooltip tr{border:1px solid #CCC}.c3-tooltip th{background-color:#aaa;font-size:14px;padding:2px 5px;text-align:left;color:#FFF}.c3-tooltip td{font-size:13px;padding:3px 6px;background-color:#fff;border-left:1px dotted #999}.c3-tooltip td>span{display:inline-block;width:10px;height:10px;margin-right:6px}.c3-tooltip td.value{text-align:right}.c3-area{stroke-width:0;opacity:.2}.c3-chart-arcs-title{dominant-baseline:middle;font-size:1.3em}.c3-chart-arcs .c3-chart-arcs-background{fill:#e0e0e0;stroke:none}.c3-chart-arcs .c3-chart-arcs-gauge-unit{fill:#000;font-size:16px}.c3-chart-arcs .c3-chart-arcs-gauge-max,.c3-chart-arcs .c3-chart-arcs-gauge-min{fill:#777}.c3-chart-arc .c3-gauge-value{fill:#000}
--------------------------------------------------------------------------------
/static/css/font-awesome.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome 4.0.3 by @davegandy - http://fontawesome.io - @fontawesome
3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.0.3');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.0.3') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff?v=4.0.3') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.0.3') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.0.3#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.3333333333333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.2857142857142858em;text-align:center}.fa-ul{padding-left:0;margin-left:2.142857142857143em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.142857142857143em;width:2.142857142857143em;top:.14285714285714285em;text-align:center}.fa-li.fa-lg{left:-1.8571428571428572em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg)}100%{-ms-transform:rotate(359deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0,mirror=1);-webkit-transform:scale(-1,1);-moz-transform:scale(-1,1);-ms-transform:scale(-1,1);-o-transform:scale(-1,1);transform:scale(-1,1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2,mirror=1);-webkit-transform:scale(1,-1);-moz-transform:scale(1,-1);-ms-transform:scale(1,-1);-o-transform:scale(1,-1);transform:scale(1,-1)}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-asc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-desc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-reply-all:before{content:"\f122"}.fa-mail-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}
--------------------------------------------------------------------------------
/static/css/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdevplatform/tweet-search/983de3165f6fc09bb1d8f348db1a1cb06b824a12/static/css/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/static/css/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdevplatform/tweet-search/983de3165f6fc09bb1d8f348db1a1cb06b824a12/static/css/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/static/css/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdevplatform/tweet-search/983de3165f6fc09bb1d8f348db1a1cb06b824a12/static/css/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/static/css/site.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding-top: 30px;
3 | }
4 |
5 | .navbar {
6 | background-color:rgba(64, 153, 255, 0.02)
7 | }
8 |
9 | div.container div.main {
10 | margin-top: 40px;
11 | }
12 |
13 | .starter-template {
14 | padding: 40px 15px;
15 | text-align: left;
16 | }
17 |
18 | .full-screen {
19 | width: 90%;
20 | height: 100%;
21 | margin-left: 5%;
22 | top: 0;
23 | left: 0;
24 | }
25 |
26 | .tweet {
27 | border: 1px solid #ccc;
28 | border-radius: 5px;
29 | box-shadow: 2px 2px 2px #999;
30 | }
31 |
32 | .top-buffer {
33 | margin-top:80px;
34 | }
35 |
36 | blockquote img {
37 | margin-right: 15px;
38 | }
39 |
40 | .white-panel {
41 | position: absolute;
42 | background: white;
43 | }
44 |
45 | .loading {
46 | width: 100%;
47 | text-align: center;
48 | margin: 12px 0px;
49 | }
50 |
51 | .loading img {
52 | width: 32px;
53 | }
54 |
55 | .query-add {
56 | margin-top: 12px;
57 | margin-bottom: 4px;
58 | }
--------------------------------------------------------------------------------
/static/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdevplatform/tweet-search/983de3165f6fc09bb1d8f348db1a1cb06b824a12/static/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/static/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdevplatform/tweet-search/983de3165f6fc09bb1d8f348db1a1cb06b824a12/static/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/static/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdevplatform/tweet-search/983de3165f6fc09bb1d8f348db1a1cb06b824a12/static/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/static/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdevplatform/tweet-search/983de3165f6fc09bb1d8f348db1a1cb06b824a12/static/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/static/fonts/futura.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdevplatform/tweet-search/983de3165f6fc09bb1d8f348db1a1cb06b824a12/static/fonts/futura.ttf
--------------------------------------------------------------------------------
/static/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdevplatform/tweet-search/983de3165f6fc09bb1d8f348db1a1cb06b824a12/static/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/static/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdevplatform/tweet-search/983de3165f6fc09bb1d8f348db1a1cb06b824a12/static/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/static/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdevplatform/tweet-search/983de3165f6fc09bb1d8f348db1a1cb06b824a12/static/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/static/images/bird.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdevplatform/tweet-search/983de3165f6fc09bb1d8f348db1a1cb06b824a12/static/images/bird.ico
--------------------------------------------------------------------------------
/static/images/bird.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdevplatform/tweet-search/983de3165f6fc09bb1d8f348db1a1cb06b824a12/static/images/bird.png
--------------------------------------------------------------------------------
/static/images/spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdevplatform/tweet-search/983de3165f6fc09bb1d8f348db1a1cb06b824a12/static/images/spinner.gif
--------------------------------------------------------------------------------
/static/images/trans.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdevplatform/tweet-search/983de3165f6fc09bb1d8f348db1a1cb06b824a12/static/images/trans.png
--------------------------------------------------------------------------------
/static/js/jquery.cookie.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * jQuery Cookie Plugin v1.4.1
3 | * https://github.com/carhartl/jquery-cookie
4 | *
5 | * Copyright 2013 Klaus Hartl
6 | * Released under the MIT license
7 | */
8 | (function (factory) {
9 | if (typeof define === 'function' && define.amd) {
10 | // AMD
11 | define(['jquery'], factory);
12 | } else if (typeof exports === 'object') {
13 | // CommonJS
14 | factory(require('jquery'));
15 | } else {
16 | // Browser globals
17 | factory(jQuery);
18 | }
19 | }(function ($) {
20 |
21 | var pluses = /\+/g;
22 |
23 | function encode(s) {
24 | return config.raw ? s : encodeURIComponent(s);
25 | }
26 |
27 | function decode(s) {
28 | return config.raw ? s : decodeURIComponent(s);
29 | }
30 |
31 | function stringifyCookieValue(value) {
32 | return encode(config.json ? JSON.stringify(value) : String(value));
33 | }
34 |
35 | function parseCookieValue(s) {
36 | if (s.indexOf('"') === 0) {
37 | // This is a quoted cookie as according to RFC2068, unescape...
38 | s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
39 | }
40 |
41 | try {
42 | // Replace server-side written pluses with spaces.
43 | // If we can't decode the cookie, ignore it, it's unusable.
44 | // If we can't parse the cookie, ignore it, it's unusable.
45 | s = decodeURIComponent(s.replace(pluses, ' '));
46 | return config.json ? JSON.parse(s) : s;
47 | } catch(e) {}
48 | }
49 |
50 | function read(s, converter) {
51 | var value = config.raw ? s : parseCookieValue(s);
52 | return $.isFunction(converter) ? converter(value) : value;
53 | }
54 |
55 | var config = $.cookie = function (key, value, options) {
56 |
57 | // Write
58 |
59 | if (value !== undefined && !$.isFunction(value)) {
60 | options = $.extend({}, config.defaults, options);
61 |
62 | if (typeof options.expires === 'number') {
63 | var days = options.expires, t = options.expires = new Date();
64 | t.setTime(+t + days * 864e+5);
65 | }
66 |
67 | return (document.cookie = [
68 | encode(key), '=', stringifyCookieValue(value),
69 | options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
70 | options.path ? '; path=' + options.path : '',
71 | options.domain ? '; domain=' + options.domain : '',
72 | options.secure ? '; secure' : ''
73 | ].join(''));
74 | }
75 |
76 | // Read
77 |
78 | var result = key ? undefined : {};
79 |
80 | // To prevent the for loop in the first place assign an empty array
81 | // in case there are no cookies at all. Also prevents odd result when
82 | // calling $.cookie().
83 | var cookies = document.cookie ? document.cookie.split('; ') : [];
84 |
85 | for (var i = 0, l = cookies.length; i < l; i++) {
86 | var parts = cookies[i].split('=');
87 | var name = decode(parts.shift());
88 | var cookie = parts.join('=');
89 |
90 | if (key && key === name) {
91 | // If second argument (value) is a function it's a converter...
92 | result = read(cookie, value);
93 | break;
94 | }
95 |
96 | // Prevent storing a cookie that we couldn't decode.
97 | if (!key && (cookie = read(cookie)) !== undefined) {
98 | result[name] = cookie;
99 | }
100 | }
101 |
102 | return result;
103 | };
104 |
105 | config.defaults = {};
106 |
107 | $.removeCookie = function (key, options) {
108 | if ($.cookie(key) === undefined) {
109 | return false;
110 | }
111 |
112 | // Must not alter options, thus extending a fresh object...
113 | $.cookie(key, '', $.extend({}, options, { expires: -1 }));
114 | return !$.cookie(key);
115 | };
116 |
117 | }));
118 |
--------------------------------------------------------------------------------
/static/js/mustache.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * mustache.js - Logic-less {{mustache}} templates with JavaScript
3 | * http://github.com/janl/mustache.js
4 | */
5 |
6 | /*global define: false*/
7 |
8 | (function (global, factory) {
9 | if (typeof exports === "object" && exports) {
10 | factory(exports); // CommonJS
11 | } else if (typeof define === "function" && define.amd) {
12 | define(['exports'], factory); // AMD
13 | } else {
14 | factory(global.Mustache = {}); //
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |