├── .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 | [![Deploy](https://www.herokucdn.com/deploy/button.png)](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 = {}); // 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 72 | 73 |
74 | 75 |
76 | 77 | {% block content %}{% endblock %} 78 | 79 |
80 | 81 |
82 | 83 | {% verbatim %} 84 | 99 | {% endverbatim %} 100 | 101 | 102 | -------------------------------------------------------------------------------- /templates/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load tags %} 3 | {% load humanize %} 4 | 5 | {% block title %}Twitter Search Demo{% endblock %} 6 | 7 | {% block content %} 8 | 9 |
10 |
11 | 12 |
13 |
14 |   15 |
16 |
17 | 18 |
19 |
20 | 21 | 22 |
23 | 24 | 25 | 28 | 29 |
30 | 31 |
32 | 33 | 34 | 37 | 38 |
39 | 40 |
41 | 42 | 43 | 46 | 47 |
48 | 49 |

50 | add query 51 | | 52 | advanced options 53 |

54 |
55 |
56 | 57 |
58 |
59 | 60 |
61 |
62 | 63 |
64 |
65 | 66 |
67 | 68 | 69 |
70 |
71 |
72 | 73 |
74 | 75 | 76 |
77 |
78 | 79 |
80 | 81 |
82 |
83 | 86 |
87 |
88 |
89 |
90 | 93 |
94 |
95 |
96 |
97 | 101 |
102 |
103 |
104 | 105 |
106 |
107 |

For advanced query syntax, read the GNIP Search API Reference.

108 |
109 |
110 | 111 |
112 | 113 |
114 | 115 |
116 |
117 | 120 |
121 |
122 |
123 | 127 |
128 |
129 | 130 |
131 | 132 |
133 |
134 | 137 |
138 |
139 |
140 | 141 |
142 |
143 | 144 |
145 | 146 |
147 |
148 | 151 |
152 |
153 |
154 | 155 |
156 | 157 |
158 |
159 | 162 |
163 |
164 |
165 | 166 |
167 | 168 |
169 |
170 | 171 |
172 | 173 |
174 | 175 | 176 |
177 |
178 | 179 |
180 | 181 |

Activity Volume

182 | 183 |

Tweet count:

184 | 185 |
186 | 187 |
188 |
Please select a data source and click "Submit".
189 |
190 | 191 |
192 | 193 |
194 | 195 |
196 |
197 |
198 | 199 | 200 |
201 |
202 | 203 |

Activity

204 | 205 |

Last {{tweets|length}}

206 | 207 |
208 | 209 |
210 |
211 |
212 | 213 | 214 |
215 |
216 | 217 |

Term frequency

218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 |
TermMentions ()Mentions %Activities ()Activities %
229 | 230 |
231 |
232 | 233 |
234 | 235 |


236 | 237 | {% verbatim %} 238 | 247 | 248 | 253 | 254 | 264 | {% endverbatim %} 265 | 266 | {% endblock %} -------------------------------------------------------------------------------- /templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tweet Search 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 |
29 |
30 |
31 | 32 |
33 |
34 |

  Tweet Search

35 |
36 |
37 | Login via Twitter 38 |
39 |
40 | 41 |
42 |
43 |
44 | 45 | 46 | 47 |
48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /templates/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | -------------------------------------------------------------------------------- /terms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdevplatform/tweet-search/983de3165f6fc09bb1d8f348db1a1cb06b824a12/terms.png --------------------------------------------------------------------------------