├── .gitignore ├── .readthedocs.yaml ├── AUTHORS.txt ├── LICENSE.txt ├── README.md ├── docker ├── README.md ├── app-dev │ ├── Dockerfile │ ├── README.md │ ├── apache.conf │ ├── crons.conf │ ├── invoke.sh │ ├── local_settings.py │ ├── supervisord.conf │ └── wsgi.py ├── app-master │ ├── Dockerfile │ ├── README.md │ ├── apache.conf │ ├── crons.conf │ ├── invoke.sh │ ├── local_settings.py │ ├── supervisord.conf │ └── wsgi.py ├── app-prod │ ├── m5_002 │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── apache.conf │ │ ├── crons.conf │ │ ├── invoke.sh │ │ ├── local_settings.py │ │ ├── supervisord.conf │ │ ├── utils.py │ │ └── wsgi.py │ ├── m5_002_https │ │ ├── Dockerfile │ │ ├── README.md │ │ └── apache.conf │ ├── m5_003 │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── apache.conf │ │ ├── crons.conf │ │ ├── invoke.sh │ │ ├── local_settings.py │ │ ├── supervisord.conf │ │ └── wsgi.py │ ├── m5_003_https │ │ ├── Dockerfile │ │ ├── README.md │ │ └── apache.conf │ ├── m5_004 │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── apache.conf │ │ ├── crons.conf │ │ ├── invoke.sh │ │ ├── local_settings.py │ │ ├── supervisord.conf │ │ └── wsgi.py │ └── m5_004_https │ │ ├── Dockerfile │ │ ├── README.md │ │ └── apache.conf ├── db │ ├── Dockerfile │ ├── README.md │ └── initdb.sql ├── example.dev.docker-compose.yml ├── example.master.docker-compose.yml ├── example.prod-https.docker-compose.yml └── example.prod.docker-compose.yml ├── docs ├── Makefile ├── conf.py ├── dailyops.rst ├── data_dictionary.rst ├── faq.rst ├── index.rst ├── install.rst ├── intro.rst ├── m4_001_release_notes.rst ├── m4_002_release_notes.rst ├── m5_001_release_notes.rst ├── m5_002_release_notes.rst ├── m5_003_release_notes.rst ├── m5_004_release_notes.rst ├── mgmt_commands.rst ├── release_notes.rst ├── supervisor_and_streams.rst ├── troubleshooting.rst ├── twitter_filter_rule_sample.png └── use_cases.rst ├── requirements.txt └── sfm ├── manage.py ├── sfm ├── __init__.py ├── apache.conf ├── local_settings.py.template ├── settings.py ├── supervisor.d │ ├── __init__.py │ └── streamsample.conf.template ├── urls.py └── wsgi.py.template └── ui ├── __init__.py ├── admin.py ├── management ├── __init__.py └── commands │ ├── __init__.py │ ├── add_userlist.py │ ├── createconf.py │ ├── export.py │ ├── fetch_tweets_by_id.py │ ├── fetch_urls.py │ ├── filterstream.py │ ├── organizedata.py │ ├── populate_uids.py │ ├── streamsample.py │ ├── update_usernames.py │ └── user_timeline.py ├── migrations ├── 0001_initial.py ├── 0002_auto__add_status.py ├── 0003_auto__chg_field_status_avatar_url.py ├── 0004_auto.py ├── 0005_auto__add_trendweekly.py ├── 0006_auto__add_field_trendweekly_sequence_num.py ├── 0007_auto__add_trenddaily.py ├── 0008_auto__del_field_trendweekly_sequence_num__del_field_trenddaily_sequenc.py ├── 0009_auto__add_rule.py ├── 0010_auto__add_twitteruser__add_twitteruseritem.py ├── 0011_auto__add_unique_twitteruseritem_twitter_url.py ├── 0012_auto__del_status.py ├── 0013_auto__add_field_rule_user.py ├── 0014_auto__add_field_twitteruser_date_last_checked__del_field_twitteruserit.py ├── 0015_auto__add_dailytwitteruseritemcount.py ├── 0016_auto__add_field_twitteruser_is_active.py ├── 0017_auto__del_trendweekly__del_trenddaily.py ├── 0018_auto__add_twitteruserset__add_unique_twitteruserset_user_name.py ├── 0019_auto__add_field_twitteruser_former_names.py ├── 0020_auto__add_field_twitteruser_uid.py ├── 0021_auto__del_dailytwitteruseritemcount.py ├── 0022_auto__add_unique_twitteruser_uid.py ├── 0023_auto__add_twitterusertimelineerror__add_twitterusertimelinejob.py ├── 0024_rename_Rule_to_TwitterFilter.py ├── 0025_auto__add_twitteruseritemurl.py ├── 0026_auto__add_field_twitterfilter_uids.py └── __init__.py ├── models.py ├── static ├── admin │ ├── css │ │ ├── base.css │ │ ├── changelists.css │ │ ├── dashboard.css │ │ ├── forms.css │ │ ├── ie.css │ │ ├── login.css │ │ ├── rtl.css │ │ └── widgets.css │ ├── img │ │ ├── changelist-bg.gif │ │ ├── changelist-bg_rtl.gif │ │ ├── chooser-bg.gif │ │ ├── chooser_stacked-bg.gif │ │ ├── default-bg-reverse.gif │ │ ├── default-bg.gif │ │ ├── deleted-overlay.gif │ │ ├── gis │ │ │ ├── move_vertex_off.png │ │ │ └── move_vertex_on.png │ │ ├── icon-no.gif │ │ ├── icon-unknown.gif │ │ ├── icon-yes.gif │ │ ├── icon_addlink.gif │ │ ├── icon_alert.gif │ │ ├── icon_calendar.gif │ │ ├── icon_changelink.gif │ │ ├── icon_clock.gif │ │ ├── icon_deletelink.gif │ │ ├── icon_error.gif │ │ ├── icon_searchbox.png │ │ ├── icon_success.gif │ │ ├── inline-delete-8bit.png │ │ ├── inline-delete.png │ │ ├── inline-restore-8bit.png │ │ ├── inline-restore.png │ │ ├── inline-splitter-bg.gif │ │ ├── nav-bg-grabber.gif │ │ ├── nav-bg-reverse.gif │ │ ├── nav-bg-selected.gif │ │ ├── nav-bg.gif │ │ ├── selector-icons.gif │ │ ├── selector-search.gif │ │ ├── sorting-icons.gif │ │ ├── tool-left.gif │ │ ├── tool-left_over.gif │ │ ├── tool-right.gif │ │ ├── tool-right_over.gif │ │ ├── tooltag-add.gif │ │ ├── tooltag-add_over.gif │ │ ├── tooltag-arrowright.gif │ │ └── tooltag-arrowright_over.gif │ └── js │ │ ├── LICENSE-JQUERY.txt │ │ ├── SelectBox.js │ │ ├── SelectFilter2.js │ │ ├── actions.js │ │ ├── actions.min.js │ │ ├── admin │ │ ├── DateTimeShortcuts.js │ │ ├── RelatedObjectLookups.js │ │ └── ordering.js │ │ ├── calendar.js │ │ ├── collapse.js │ │ ├── collapse.min.js │ │ ├── compress.py │ │ ├── core.js │ │ ├── getElementsBySelector.js │ │ ├── inlines.js │ │ ├── inlines.min.js │ │ ├── jquery.init.js │ │ ├── jquery.js │ │ ├── jquery.min.js │ │ ├── prepopulate.js │ │ ├── prepopulate.min.js │ │ ├── timeparse.js │ │ └── urlify.js ├── css │ ├── libheader7_lite.css │ ├── libheader7_lite_bootswatch.css │ ├── libheader7_lite_ie.css │ └── sfm.css └── img │ ├── bg-local-nav-bottom.png │ ├── favicon.ico │ └── gwheaderlogo.png ├── templates ├── about.html ├── base.html ├── django_no_superuser.html ├── home.html ├── registration │ └── login.html ├── search.html ├── status.html ├── status_not_found.html ├── tweets.html ├── tweets_pagination.html ├── twitter_item_links.html ├── twitter_user.html ├── twitter_user_pagination.html ├── users_alpha.html └── users_alpha_pagination.html ├── templatetags ├── __init__.py ├── sfm_extras.py └── twitterize.py ├── tests.py ├── utils.py └── views.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.log 3 | ENV 4 | sfm/sfm/local_settings.py 5 | sfm/sfm/wsgi.py 6 | *.swp 7 | *.swo 8 | data 9 | *.rdb 10 | sfm/sfm/supervisor.d/*.conf 11 | _build 12 | .idea 13 | docker/dev.docker-compose.yml 14 | docker/master.docker-compose.yml 15 | docker/prod*.docker-compose.yml 16 | docker/docker-compose.yml 17 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the version of Python and other tools you might need 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.11" 13 | 14 | # Build documentation in the docs/ directory with Sphinx 15 | sphinx: 16 | configuration: docs/conf.py 17 | 18 | # We recommend specifying your dependencies to enable reproducible builds: 19 | # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 20 | # python: 21 | # install: 22 | # - requirements: docs/requirements.txt 23 | -------------------------------------------------------------------------------- /AUTHORS.txt: -------------------------------------------------------------------------------- 1 | Social Feed Manager (SFM) began in 2012 at GW Libraries in Washington, DC, USA. 2 | 3 | The programmer team (past and present) at GW Libraries includes: 4 | 5 | - Daniel Chudnov 6 | - Daniel Kerchner 7 | - Laura Wrubel 8 | - Christian Aldridge 9 | - Justin Littman 10 | - Rajat Vij 11 | - Ankushi Sharma 12 | 13 | The following people have contributed code to this repository and agreed 14 | to assign the copyright on their contributions to The George Washington 15 | University: 16 | 17 | - Brian Hoffman 18 | - Ed Summers 19 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2014 The George Washington University 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject 9 | to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 18 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 19 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | __THIS IS THE "OLD" SFM__, the original 2 | version of Social Feed Manager developed between 2012-2015. 3 | 4 | __IF YOU ARE LOOKING FOR THE "NEW" SFM__, which began development in 2015, head to __[go.gwu.edu/sfm](http://go.gwu.edu/sfm)__ for full information (and start at [https://github.com/gwu-libraries/sfm-ui](https://github.com/gwu-libraries/sfm-ui) if you want just the code) 5 | 6 | 7 | social feed manager 8 | =================== 9 | 10 | A django application for managing multiple feeds of social media data. 11 | 12 | Developed at the GWU Libraries in Washington, DC, USA. 13 | 14 | This project was made possible in part by the Institute of Museum and 15 | Library Services, Sparks Grant LG-46-13-0257 and the National 16 | Historical Publications and Records Commission (NHPRC), Innovation Grant 17 | NAR14-DI-50017-14. 18 | 19 | See also LICENSE.txt and AUTHORS.txt. 20 | 21 | Current status 22 | -------------- 23 | 24 | We're only addressing bugs in this version of SFM. However, we're actively 25 | developing the [new version](http://go.gwu.edu/sfm) of SFM with similar and extended functionality. 26 | 27 | Documentation 28 | ------------- 29 | 30 | A growing set of documentation is available under the docs/ directory, 31 | available also online: 32 | 33 | http://social-feed-manager.readthedocs.org/ 34 | 35 | We encourage you to read the Introduction before implementing this app. 36 | Maintaining growing feeds of social media data takes some planning 37 | and system adminstration experience. The Introduction should help you 38 | prepare for this. 39 | 40 | 41 | Development 42 | ----------- 43 | 44 | To work on sfm, clone/fork it in github. If you send pull requests from 45 | a fork, please make them as atomic as you can. 46 | 47 | We are actively working on this, making changes in response to university 48 | researcher requests. If the features it supports seem fickle and spotty 49 | it's because we're still just getting started and still figuring out 50 | what we need it to do. Please get in touch and tell us what you think. 51 | 52 | Installation and configuration 53 | ------------------------------ 54 | 55 | Installation and configuration is described [here](http://social-feed-manager.readthedocs.org/en/m5_003/install.html). 56 | Alternatively, instructions for installing and configuring with [Docker](http://www.docker.com/) are available [here](https://github.com/gwu-libraries/social-feed-manager/blob/master/docker/README.md). 57 | -------------------------------------------------------------------------------- /docker/app-dev/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:12.04 2 | MAINTAINER Justin Littman 3 | 4 | RUN apt-get update && apt-get install -y \ 5 | python-dev \ 6 | libxml2-dev \ 7 | libxslt1-dev \ 8 | libpq-dev \ 9 | supervisor \ 10 | git \ 11 | python-pip \ 12 | apache2 \ 13 | libapache2-mod-wsgi \ 14 | wget \ 15 | cron 16 | #Upgrade pip 17 | RUN pip install -U pip 18 | #This pre-fetches the most recent requirements.txt. 19 | ADD https://github.com/gwu-libraries/social-feed-manager/raw/master/requirements.txt /opt/sfm-setup/ 20 | RUN pip install -r /opt/sfm-setup/requirements.txt 21 | #This is used to automatically create the admin user. 22 | RUN pip install django-finalware==0.0.2 23 | #These will be copied over into the app by invoke.sh. 24 | ADD local_settings.py /tmp/ 25 | ADD wsgi.py /tmp/ 26 | #Enable sfm site 27 | ADD apache.conf /etc/apache2/sites-available/sfm 28 | RUN a2ensite sfm 29 | #Disable pre-existing default site 30 | RUN a2dissite 000-default 31 | #Configure supervisor 32 | ADD supervisord.conf /etc/supervisor/ 33 | RUN mkdir /var/log/sfm 34 | RUN mkdir /var/sfm && chmod ugo+w /var/sfm 35 | VOLUME /var/sfm 36 | RUN mkdir /var/supervisor.d && chown www-data:www-data /var/supervisor.d 37 | ADD invoke.sh /opt/ 38 | RUN chmod +x /opt/invoke.sh 39 | #Install appdeps to allow checking for application dependencies 40 | WORKDIR /opt/sfm-setup 41 | RUN wget -L --no-check-certificate https://github.com/gwu-libraries/appdeps/raw/master/appdeps.py 42 | #Cron 43 | ADD crons.conf /tmp/ 44 | RUN crontab /tmp/crons.conf 45 | CMD ["/opt/invoke.sh"] 46 | EXPOSE 80 47 | -------------------------------------------------------------------------------- /docker/app-dev/README.md: -------------------------------------------------------------------------------- 1 | SFM APP 2 | ------- 3 | 4 | An image for the [Social Feed Manager](https://github.com/gwu-libraries/social-feed-manager) application. 5 | 6 | For more information on using this image, see this [README](https://github.com/gwu-libraries/social-feed-manager/blob/master/docker/README.md). 7 | 8 | This image is for the old Social Feed Manager. For the new Social Feed Manager, see https://github.com/gwu-libraries/sfm-ui. -------------------------------------------------------------------------------- /docker/app-dev/apache.conf: -------------------------------------------------------------------------------- 1 | Alias /static/ /opt/social-feed-manager/sfm/ui/static/ 2 | 3 | Order deny,allow 4 | Allow from all 5 | 6 | 7 | # For WSGI daemon mode: 8 | # see http://code.google.com/p/modwsgi/wiki/QuickConfigurationGuide 9 | WSGIDaemonProcess sfm processes=2 threads=15 python-path=/opt/social-feed-manager/sfm 10 | WSGIProcessGroup sfm 11 | 12 | # For WSGI embedded mode: 13 | #WSGIPythonPath /PATH/TO/sfm 14 | # If using a virtualenv, uncomment and tweak next line (inc. python version): 15 | # WSGIPythonPath /PATH/TO/YOUR/VENV/lib/python/2.X/site-packages 16 | 17 | WSGIScriptAlias / /opt/social-feed-manager/sfm/sfm/wsgi.py 18 | 19 | 20 | 21 | Order deny,allow 22 | Allow from all 23 | 24 | 25 | -------------------------------------------------------------------------------- /docker/app-dev/crons.conf: -------------------------------------------------------------------------------- 1 | # SOCIAL FEED MANAGER TASKS 2 | # Fetch user timelines 3x daily 3 | # 5 */8 * * * cd /opt/social-feed-manager && /usr/bin/python /opt/social-feed-manager/sfm/manage.py user_timeline > /var/log/sfm/cron_user_timeline.log 2>&1 4 | # Organize stream files 4x daily 5 | # 35 */6 * * * cd /opt/social-feed-manager && /usr/bin/python /opt/social-feed-manager/sfm/manage.py organizedata > /var/log/sfm/cron_organizedata.log 2>&1 6 | # Update Twitter account names 1x weekly 7 | # 5 3 * * 0 cd /opt/social-feed-manager && /usr/bin/python /opt/social-feed-manager/sfm/manage.py update_usernames > /var/log/sfm/cron_update_usernames.log 2>&1 8 | # fetch_urls on tweets published between yesterday at midnight until today at midnight 9 | # 15 1 * * * cd /opt/social-feed-manager && /usr/bin/python /opt/social-feed-manager/sfm/manage.py fetch_urls --start-date=`date -d 'now - 1 day' +'\%Y-\%m-\%d'` --end-date=`date -d 'now' +'\%Y-\%m-\%d'` > /var/log/sfm/cron_fetch_urls.log 2>&1 10 | -------------------------------------------------------------------------------- /docker/app-dev/invoke.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Waiting for db" 3 | python /opt/sfm-setup/appdeps.py --wait-secs 30 --port-wait db:5432 --file /opt/social-feed-manager 4 | if [ "$?" = "1" ]; then 5 | echo "Problem with application dependencies." 6 | exit 1 7 | fi 8 | 9 | echo "Updating requirements" 10 | pip install -r /opt/social-feed-manager/requirements.txt 11 | 12 | echo "Writing local_settings" 13 | echo "env={}" > /opt/social-feed-manager/sfm/sfm/local_settings.py 14 | env | grep 'SFM_\|\DB_' | sed 's/\(.*\)=\(.*\)/env["\1"]="\2"/' >> /opt/social-feed-manager/sfm/sfm/local_settings.py 15 | cat /tmp/local_settings.py >> /opt/social-feed-manager/sfm/sfm/local_settings.py 16 | 17 | echo "Copying config" 18 | cp /tmp/wsgi.py /opt/social-feed-manager/sfm/sfm/ 19 | 20 | echo "Syncing db" 21 | /opt/social-feed-manager/sfm/manage.py syncdb --noinput 22 | 23 | echo "Migrating db" 24 | /opt/social-feed-manager/sfm/manage.py migrate --noinput 25 | 26 | echo "Starting supervisord" 27 | /etc/init.d/supervisor start 28 | 29 | echo "Starting cron" 30 | cron 31 | 32 | echo "Running server" 33 | #Not entirely sure why this is necessary, but it works. 34 | /etc/init.d/apache2 start 35 | #Make sure apache has started 36 | /etc/init.d/apache2 status 37 | while [ "$?" != "0" ]; do 38 | echo "Waiting for start" 39 | sleep 1 40 | /etc/init.d/apache2 status 41 | done 42 | echo "Stopping server" 43 | /etc/init.d/apache2 graceful-stop 44 | #Make sure apache has stopped 45 | /etc/init.d/apache2 status 46 | while [ "$?" = "0" ]; do 47 | echo "Waiting for stop" 48 | sleep 1 49 | /etc/init.d/apache2 status 50 | done 51 | echo "Starting server again" 52 | apachectl -DFOREGROUND 53 | -------------------------------------------------------------------------------- /docker/app-dev/local_settings.py: -------------------------------------------------------------------------------- 1 | DEBUG = env.get('SFM_DEBUG', 'True') == 'True' 2 | 3 | ADMINS = ( 4 | (env.get('SFM_ADMIN_NAME', 'sfmadmin'), env.get('SFM_ADMIN_EMAIL', 'nowhere@example.com')), 5 | ) 6 | 7 | MANAGERS = ADMINS 8 | 9 | # This value should be something like [sfm-test] (with a trailing space) 10 | EMAIL_SUBJECT_PREFIX = ' ' 11 | 12 | # Set SERVER_EMIL to root@myserver, e.g. 'root@gwsfm-test.wrlc.org' 13 | SERVER_EMAIL = '' 14 | 15 | DATABASES = { 16 | 'default': { 17 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 18 | 'NAME': 'sfm', 19 | 'USER': 'postgres', 20 | 'PASSWORD': env['DB_ENV_POSTGRES_PASSWORD'], 21 | 'HOST': 'db', 22 | 'PORT': '5432', 23 | } 24 | } 25 | 26 | ALLOWED_HOSTS = ['YOUR.PUBLIC.DOMAIN.NAME'] 27 | 28 | # See https://docs.djangoproject.com/en/1.4/topics/cache/ 29 | CACHES = { 30 | 'default': { 31 | 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 32 | 'LOCATION': 'your-unique-sfm-instance-name', 33 | } 34 | } 35 | 36 | TEMPLATE_DIRS = ( 37 | ) 38 | 39 | DATA_DIR = '/var/sfm' 40 | 41 | # If not specified, the supervisor configuration files 42 | # will use the owner of the sfm app process. The owner must 43 | # be able to write to /var/log/sfm 44 | #SUPERVISOR_PROCESS_USER = '' 45 | 46 | # Path to the supervisor Unix socket file (with no unix:// prefix) 47 | # e.g. '/var/run/supervisor.sock'. Note that this must match the Unix 48 | # socket file specified in /etc/supervisor/supervisord.conf 49 | SUPERVISOR_UNIX_SOCKET_FILE = '/var/run//supervisor.sock' 50 | 51 | TWITTER_DEFAULT_USERNAME = env['SFM_TWITTER_DEFAULT_USERNAME'] 52 | TWITTER_CONSUMER_KEY = env['SFM_TWITTER_CONSUMER_KEY'] 53 | TWITTER_CONSUMER_SECRET = env['SFM_TWITTER_CONSUMER_SECRET'] 54 | 55 | BRANDING = { 56 | # Required: 57 | 'institution': env.get('SFM_BRANDING_INSTITUTION', ''), 58 | 'URL': env.get('SFM_BRANDING_URL', ''), 59 | # Optional: 60 | # address may contain any number of elements 61 | 'address': [env.get('SFM_BRANDING_ADDRESS', '')], 62 | 'email': env.get('SFM_BRANDING_EMAIL', ''), 63 | # logofile should be placed in static/img 64 | # See https://github.com/gwu-libraries/social-feed-manager/issues/300 65 | 'logofile': '', 66 | } 67 | 68 | #Use django-finalware to create the superadmin account. 69 | #Redefining INSTALLED_APPS could be replaced with this hack: http://blog.christopherlawlor.com/2010/06/django-how-to-add-debugging-apps-to-your-local-settings/ 70 | INSTALLED_APPS = ( 71 | 'django.contrib.auth', 72 | 'django.contrib.contenttypes', 73 | 'django.contrib.sessions', 74 | 'django.contrib.sites', 75 | 'django.contrib.messages', 76 | 'django.contrib.staticfiles', 77 | 'django.contrib.admin', 78 | 'django.contrib.humanize', 79 | 'social_auth', 80 | 'south', 81 | 'ui', 82 | 'finalware' 83 | ) 84 | # This field is the superuser object ID. Pick something other than `1` for security reason. 85 | SITE_SUPERUSER_ID = '5' 86 | 87 | # This field is stored in `User.USERNAME_FIELD`. This is usually a `username` or an `email`. 88 | SITE_SUPERUSER_USERNAME = env.get('SFM_SITE_ADMIN_NAME', 'sfmadmin') 89 | 90 | # This field is stored in the `email` field, provided, that `User.USERNAME_FIELD` is not an `email`. 91 | # If `User.USERNAME_FIELD` is already an email address, set `SITE_SUPERUSER_EMAIL = SITE_SUPERUSER_USERNAME` 92 | SITE_SUPERUSER_EMAIL = env.get('SFM_SITE_ADMIN_EMAIL', 'nowhere@example.com') 93 | 94 | # A hashed version of `SITE_SUPERUSER_PASSWORD` will be store in superuser's `password` field. 95 | SITE_SUPERUSER_PASSWORD = env.get('SFM_SITE_ADMIN_PASSWORD', 'password') 96 | 97 | SUPERVISOR_ROOT = "/var/supervisor.d" 98 | -------------------------------------------------------------------------------- /docker/app-dev/supervisord.conf: -------------------------------------------------------------------------------- 1 | ; supervisor config file 2 | 3 | [unix_http_server] 4 | file=/var/run//supervisor.sock ; (the path to the socket file) 5 | chmod=0700 ; sockef file mode (default 0700) 6 | chown=www-data:www-data 7 | 8 | [supervisord] 9 | logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log) 10 | pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid) 11 | childlogdir=/var/log/supervisor ; ('AUTO' child log dir, default $TEMP) 12 | 13 | ; the below section must remain in the config file for RPC 14 | ; (supervisorctl/web interface) to work, additional interfaces may be 15 | ; added by defining them in separate rpcinterface: sections 16 | [rpcinterface:supervisor] 17 | supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface 18 | 19 | [supervisorctl] 20 | serverurl=unix:///var/run//supervisor.sock ; use a unix:// URL for a unix socket 21 | 22 | ; The [include] section can just contain the "files" setting. This 23 | ; setting can list multiple files (separated by whitespace or 24 | ; newlines). It can also contain wildcards. The filenames are 25 | ; interpreted as relative to this file. Included files *cannot* 26 | ; include files themselves. 27 | 28 | [include] 29 | files = /etc/supervisor/conf.d/*.conf /var/supervisor.d/*.conf 30 | -------------------------------------------------------------------------------- /docker/app-dev/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for sfm project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | # if using a virtualenv, uncomment and set the next three lines appropriately 17 | #import site 18 | #ENV = '/PATH/TO/YOUR/VIRTUALENV' 19 | #site.addsitedir(ENV + '/lib/python2.7/site-packages') 20 | 21 | import os 22 | 23 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sfm.settings") 24 | 25 | # This application object is used by any WSGI server configured to use this 26 | # file. This includes Django's development server, if the WSGI_APPLICATION 27 | # setting points here. 28 | from django.core.wsgi import get_wsgi_application 29 | application = get_wsgi_application() 30 | 31 | # Apply WSGI middleware here. 32 | # from helloworld.wsgi import HelloWorldApplication 33 | # application = HelloWorldApplication(application) 34 | -------------------------------------------------------------------------------- /docker/app-master/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:12.04 2 | MAINTAINER Justin Littman 3 | 4 | RUN apt-get update && apt-get install -y \ 5 | python-dev \ 6 | libxml2-dev \ 7 | libxslt1-dev \ 8 | libpq-dev \ 9 | supervisor \ 10 | python-pip \ 11 | apache2 \ 12 | libapache2-mod-wsgi \ 13 | wget \ 14 | zip \ 15 | cron 16 | #Upgrade pip 17 | RUN pip install -U pip 18 | WORKDIR /tmp 19 | RUN wget --no-check-certificate https://github.com/gwu-libraries/social-feed-manager/archive/master.zip 20 | RUN unzip master.zip 21 | RUN mv social-feed-manager-master /opt/social-feed-manager 22 | #This installs the requirements.txt. 23 | RUN pip install -r /opt/social-feed-manager/requirements.txt 24 | #This is used to automatically create the admin user. 25 | RUN pip install django-finalware==0.0.2 26 | ADD local_settings.py /tmp/ 27 | ADD wsgi.py /opt/social-feed-manager/sfm/sfm/ 28 | #Enable sfm site 29 | ADD apache.conf /etc/apache2/sites-available/sfm 30 | RUN a2ensite sfm 31 | #Disable pre-existing default site 32 | RUN a2dissite 000-default 33 | #Configure supervisor 34 | ADD supervisord.conf /etc/supervisor/ 35 | RUN mkdir /var/supervisor.d && chown www-data:www-data /var/supervisor.d 36 | RUN mkdir /var/log/sfm 37 | #Data volume 38 | RUN mkdir /var/sfm && chmod ugo+w /var/sfm 39 | VOLUME /var/sfm 40 | ADD invoke.sh /opt/ 41 | RUN chmod +x /opt/invoke.sh 42 | #Install appdeps to allow checking for application dependencies 43 | WORKDIR /opt/social-feed-manager 44 | RUN wget -L --no-check-certificate https://github.com/gwu-libraries/appdeps/raw/master/appdeps.py 45 | #Cron 46 | ADD crons.conf /tmp/ 47 | RUN crontab /tmp/crons.conf 48 | CMD ["/opt/invoke.sh"] 49 | EXPOSE 80 50 | -------------------------------------------------------------------------------- /docker/app-master/README.md: -------------------------------------------------------------------------------- 1 | SFM APP 2 | ------- 3 | 4 | An image for the [Social Feed Manager](https://github.com/gwu-libraries/social-feed-manager) application. 5 | 6 | For more information on using this image, see this [README](https://github.com/gwu-libraries/social-feed-manager/blob/master/docker/README.md). 7 | 8 | This image is for the old Social Feed Manager. For the new Social Feed Manager, see https://github.com/gwu-libraries/sfm-ui. -------------------------------------------------------------------------------- /docker/app-master/apache.conf: -------------------------------------------------------------------------------- 1 | Alias /static/ /opt/social-feed-manager/sfm/ui/static/ 2 | 3 | Order deny,allow 4 | Allow from all 5 | 6 | 7 | # For WSGI daemon mode: 8 | # see http://code.google.com/p/modwsgi/wiki/QuickConfigurationGuide 9 | WSGIDaemonProcess sfm processes=2 threads=15 python-path=/opt/social-feed-manager/sfm 10 | WSGIProcessGroup sfm 11 | 12 | # For WSGI embedded mode: 13 | #WSGIPythonPath /PATH/TO/sfm 14 | # If using a virtualenv, uncomment and tweak next line (inc. python version): 15 | # WSGIPythonPath /PATH/TO/YOUR/VENV/lib/python/2.X/site-packages 16 | 17 | WSGIScriptAlias / /opt/social-feed-manager/sfm/sfm/wsgi.py 18 | 19 | 20 | 21 | Order deny,allow 22 | Allow from all 23 | 24 | 25 | -------------------------------------------------------------------------------- /docker/app-master/crons.conf: -------------------------------------------------------------------------------- 1 | # SOCIAL FEED MANAGER TASKS 2 | # Fetch user timelines 3x daily 3 | 5 */8 * * * cd /opt/social-feed-manager && /usr/bin/python /opt/social-feed-manager/sfm/manage.py user_timeline > /var/log/sfm/cron_user_timeline.log 2>&1 4 | # Organize stream files 4x daily 5 | 35 */6 * * * cd /opt/social-feed-manager && /usr/bin/python /opt/social-feed-manager/sfm/manage.py organizedata > /var/log/sfm/cron_organizedata.log 2>&1 6 | # Update Twitter account names 1x weekly 7 | 5 3 * * 0 cd /opt/social-feed-manager && /usr/bin/python /opt/social-feed-manager/sfm/manage.py update_usernames > /var/log/sfm/cron_update_usernames.log 2>&1 8 | # fetch_urls on tweets published between yesterday at midnight until today at midnight 9 | 15 1 * * * cd /opt/social-feed-manager && /usr/bin/python /opt/social-feed-manager/sfm/manage.py fetch_urls --start-date=`date -d 'now - 1 day' +'\%Y-\%m-\%d'` --end-date=`date -d 'now' +'\%Y-\%m-\%d'` > /var/log/sfm/cron_fetch_urls.log 2>&1 10 | -------------------------------------------------------------------------------- /docker/app-master/invoke.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Waiting for db" 3 | python /opt/social-feed-manager/appdeps.py --wait-secs 30 --port-wait db:5432 4 | if [ "$?" = "1" ]; then 5 | echo "Problem with application dependencies." 6 | exit 1 7 | fi 8 | 9 | echo "Writing local_settings" 10 | echo "env={}" > /opt/social-feed-manager/sfm/sfm/local_settings.py 11 | env | grep 'SFM_\|\DB_' | sed 's/\(.*\)=\(.*\)/env["\1"]="\2"/' >> /opt/social-feed-manager/sfm/sfm/local_settings.py 12 | cat /tmp/local_settings.py >> /opt/social-feed-manager/sfm/sfm/local_settings.py 13 | 14 | echo "Syncing db" 15 | /opt/social-feed-manager/sfm/manage.py syncdb --noinput 16 | 17 | echo "Migrating db" 18 | /opt/social-feed-manager/sfm/manage.py migrate --noinput 19 | 20 | echo "Starting supervisord" 21 | /etc/init.d/supervisor start 22 | 23 | echo "Starting cron" 24 | cron 25 | 26 | echo "Running server" 27 | #Not entirely sure why this is necessary, but it works. 28 | /etc/init.d/apache2 start 29 | #Make sure apache has started 30 | /etc/init.d/apache2 status 31 | while [ "$?" != "0" ]; do 32 | echo "Waiting for start" 33 | sleep 1 34 | /etc/init.d/apache2 status 35 | done 36 | echo "Stopping server" 37 | /etc/init.d/apache2 graceful-stop 38 | #Make sure apache has stopped 39 | /etc/init.d/apache2 status 40 | while [ "$?" = "0" ]; do 41 | echo "Waiting for stop" 42 | sleep 1 43 | /etc/init.d/apache2 status 44 | done 45 | echo "Starting server again" 46 | apachectl -DFOREGROUND 47 | -------------------------------------------------------------------------------- /docker/app-master/local_settings.py: -------------------------------------------------------------------------------- 1 | DEBUG = env.get('SFM_DEBUG', 'True') == 'True' 2 | 3 | ADMINS = ( 4 | (env.get('SFM_ADMIN_NAME', 'sfmadmin'), env.get('SFM_ADMIN_EMAIL', 'nowhere@example.com')), 5 | ) 6 | 7 | MANAGERS = ADMINS 8 | 9 | # This value should be something like [sfm-test] (with a trailing space) 10 | EMAIL_SUBJECT_PREFIX = env.get('SFM_EMAIL_SUBJECT_PREFIX', '') + ' ' 11 | 12 | # Set SERVER_EMAIL to root@myserver, e.g. 'root@gwsfm-test.wrlc.org' 13 | SERVER_EMAIL = env.get('SFM_SERVER_EMAIL', '') 14 | 15 | DATABASES = { 16 | 'default': { 17 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 18 | 'NAME': 'sfm', 19 | 'USER': 'postgres', 20 | 'PASSWORD': env['DB_ENV_POSTGRES_PASSWORD'], 21 | 'HOST': 'db', 22 | 'PORT': '5432', 23 | } 24 | } 25 | 26 | # Optional. See https://docs.djangoproject.com/en/1.7/ref/settings/ 27 | # ALLOWED_HOSTS = ['YOUR.PUBLIC.DOMAIN.NAME'] 28 | 29 | # See https://docs.djangoproject.com/en/1.4/topics/cache/ 30 | CACHES = { 31 | 'default': { 32 | 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 33 | 'LOCATION': 'your-unique-sfm-instance-name', 34 | } 35 | } 36 | 37 | TEMPLATE_DIRS = ( 38 | ) 39 | 40 | DATA_DIR = '/var/sfm' 41 | 42 | # If not specified, the supervisor configuration files 43 | # will use the owner of the sfm app process. The owner must 44 | # be able to write to /var/log/sfm 45 | #SUPERVISOR_PROCESS_USER = '' 46 | 47 | # Path to the supervisor Unix socket file (with no unix:// prefix) 48 | # e.g. '/var/run/supervisor.sock'. Note that this must match the Unix 49 | # socket file specified in /etc/supervisor/supervisord.conf 50 | SUPERVISOR_UNIX_SOCKET_FILE = '/var/run//supervisor.sock' 51 | 52 | TWITTER_DEFAULT_USERNAME = env['SFM_TWITTER_DEFAULT_USERNAME'] 53 | TWITTER_CONSUMER_KEY = env['SFM_TWITTER_CONSUMER_KEY'] 54 | TWITTER_CONSUMER_SECRET = env['SFM_TWITTER_CONSUMER_SECRET'] 55 | 56 | BRANDING = { 57 | # Required: 58 | 'institution': env.get('SFM_BRANDING_INSTITUTION', ''), 59 | 'URL': env.get('SFM_BRANDING_URL', ''), 60 | # Optional: 61 | # address may contain any number of elements 62 | 'address': [env.get('SFM_BRANDING_ADDRESS', '')], 63 | 'email': env.get('SFM_BRANDING_EMAIL', ''), 64 | # logofile should be placed in static/img 65 | # See https://github.com/gwu-libraries/social-feed-manager/issues/300 66 | 'logofile': '', 67 | } 68 | 69 | # Use django-finalware to create the superadmin account. 70 | # Redefining INSTALLED_APPS could be replaced with this hack: 71 | # http://blog.christopherlawlor.com/2010/06/django-how-to-add-debugging-apps-to-your-local-settings/ 72 | INSTALLED_APPS = ( 73 | 'django.contrib.auth', 74 | 'django.contrib.contenttypes', 75 | 'django.contrib.sessions', 76 | 'django.contrib.sites', 77 | 'django.contrib.messages', 78 | 'django.contrib.staticfiles', 79 | 'django.contrib.admin', 80 | 'django.contrib.humanize', 81 | 'social_auth', 82 | 'south', 83 | 'ui', 84 | 'finalware' 85 | ) 86 | # This field is the superuser object ID. Pick something other than `1` for security reason. 87 | SITE_SUPERUSER_ID = '5' 88 | 89 | # This field is stored in `User.USERNAME_FIELD`. This is usually a `username` or an `email`. 90 | SITE_SUPERUSER_USERNAME = env.get('SFM_SITE_ADMIN_NAME', 'sfmadmin') 91 | 92 | # This field is stored in the `email` field, provided, that `User.USERNAME_FIELD` is not an `email`. 93 | # If `User.USERNAME_FIELD` is already an email address, set `SITE_SUPERUSER_EMAIL = SITE_SUPERUSER_USERNAME` 94 | SITE_SUPERUSER_EMAIL = env.get('SFM_SITE_ADMIN_EMAIL', 'nowhere@example.com') 95 | 96 | # A hashed version of `SITE_SUPERUSER_PASSWORD` will be store in superuser's `password` field. 97 | SITE_SUPERUSER_PASSWORD = env.get('SFM_SITE_ADMIN_PASSWORD', 'password') 98 | 99 | SUPERVISOR_ROOT = "/var/supervisor.d" 100 | -------------------------------------------------------------------------------- /docker/app-master/supervisord.conf: -------------------------------------------------------------------------------- 1 | ; supervisor config file 2 | 3 | [unix_http_server] 4 | file=/var/run//supervisor.sock ; (the path to the socket file) 5 | chmod=0700 ; sockef file mode (default 0700) 6 | chown=www-data:www-data 7 | 8 | [supervisord] 9 | logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log) 10 | pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid) 11 | childlogdir=/var/log/supervisor ; ('AUTO' child log dir, default $TEMP) 12 | 13 | ; the below section must remain in the config file for RPC 14 | ; (supervisorctl/web interface) to work, additional interfaces may be 15 | ; added by defining them in separate rpcinterface: sections 16 | [rpcinterface:supervisor] 17 | supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface 18 | 19 | [supervisorctl] 20 | serverurl=unix:///var/run//supervisor.sock ; use a unix:// URL for a unix socket 21 | 22 | ; The [include] section can just contain the "files" setting. This 23 | ; setting can list multiple files (separated by whitespace or 24 | ; newlines). It can also contain wildcards. The filenames are 25 | ; interpreted as relative to this file. Included files *cannot* 26 | ; include files themselves. 27 | 28 | [include] 29 | files = /etc/supervisor/conf.d/*.conf /var/supervisor.d/*.conf 30 | -------------------------------------------------------------------------------- /docker/app-master/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for sfm project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | # if using a virtualenv, uncomment and set the next three lines appropriately 17 | #import site 18 | #ENV = '/PATH/TO/YOUR/VIRTUALENV' 19 | #site.addsitedir(ENV + '/lib/python2.7/site-packages') 20 | 21 | import os 22 | 23 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sfm.settings") 24 | 25 | # This application object is used by any WSGI server configured to use this 26 | # file. This includes Django's development server, if the WSGI_APPLICATION 27 | # setting points here. 28 | from django.core.wsgi import get_wsgi_application 29 | application = get_wsgi_application() 30 | 31 | # Apply WSGI middleware here. 32 | # from helloworld.wsgi import HelloWorldApplication 33 | # application = HelloWorldApplication(application) 34 | -------------------------------------------------------------------------------- /docker/app-prod/m5_002/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:12.04 2 | MAINTAINER Justin Littman 3 | 4 | RUN apt-get update && apt-get install -y \ 5 | python-dev \ 6 | libxml2-dev \ 7 | libxslt1-dev \ 8 | libpq-dev \ 9 | supervisor \ 10 | python-pip \ 11 | apache2 \ 12 | libapache2-mod-wsgi \ 13 | wget \ 14 | zip \ 15 | cron 16 | #Upgrade pip 17 | RUN pip install -U pip 18 | WORKDIR /tmp 19 | RUN wget --no-check-certificate https://github.com/gwu-libraries/social-feed-manager/archive/m5_002.zip 20 | RUN unzip m5_002.zip 21 | RUN mv social-feed-manager-m5_002 /opt/social-feed-manager 22 | #Monkeypatch for hardcoding use of virtual env 23 | ADD utils.py /opt/social-feed-manager/sfm/ui/ 24 | RUN chmod ugo+r /opt/social-feed-manager/sfm/ui/utils.py 25 | #This installs the requirements.txt. 26 | RUN pip install -r /opt/social-feed-manager/requirements.txt 27 | #This pegs tweepy version, which isn't done in requirements.txt 28 | RUN pip install tweepy==2.3.0 29 | #xlwt is missing in m5_002 30 | RUN pip install xlwt 31 | #This is used to automatically create the admin user. 32 | RUN pip install django-finalware==0.0.2 33 | ADD local_settings.py /tmp/ 34 | ADD wsgi.py /opt/social-feed-manager/sfm/sfm/ 35 | #Enable sfm site 36 | ADD apache.conf /etc/apache2/sites-available/sfm 37 | RUN a2ensite sfm 38 | #Disable pre-existing default site 39 | RUN a2dissite 000-default 40 | #Configure supervisor 41 | ADD supervisord.conf /etc/supervisor/ 42 | #In m5_002, the supervisor.d path is not configurable 43 | RUN chown www-data:www-data /opt/social-feed-manager/sfm/sfm/supervisor.d 44 | RUN mkdir /var/log/sfm 45 | ADD invoke.sh /opt/ 46 | RUN chmod +x /opt/invoke.sh 47 | #Install appdeps to allow checking for application dependencies 48 | WORKDIR /opt/social-feed-manager 49 | RUN wget -L --no-check-certificate https://github.com/gwu-libraries/appdeps/raw/master/appdeps.py 50 | #Cron 51 | ADD crons.conf /tmp/ 52 | RUN crontab /tmp/crons.conf 53 | CMD ["/opt/invoke.sh"] 54 | EXPOSE 80 55 | -------------------------------------------------------------------------------- /docker/app-prod/m5_002/README.md: -------------------------------------------------------------------------------- 1 | SFM APP 2 | ------- 3 | 4 | An image for the [Social Feed Manager](https://github.com/gwu-libraries/social-feed-manager) application. 5 | 6 | For more information on using this image, see this [README](https://github.com/gwu-libraries/social-feed-manager/blob/master/docker/README.md). 7 | 8 | This image is for the old Social Feed Manager. For the new Social Feed Manager, see https://github.com/gwu-libraries/sfm-ui. -------------------------------------------------------------------------------- /docker/app-prod/m5_002/apache.conf: -------------------------------------------------------------------------------- 1 | Alias /static/ /opt/social-feed-manager/sfm/ui/static/ 2 | 3 | Order deny,allow 4 | Allow from all 5 | 6 | 7 | # For WSGI daemon mode: 8 | # see http://code.google.com/p/modwsgi/wiki/QuickConfigurationGuide 9 | WSGIDaemonProcess sfm processes=2 threads=15 python-path=/opt/social-feed-manager/sfm 10 | WSGIProcessGroup sfm 11 | 12 | # For WSGI embedded mode: 13 | #WSGIPythonPath /PATH/TO/sfm 14 | # If using a virtualenv, uncomment and tweak next line (inc. python version): 15 | # WSGIPythonPath /PATH/TO/YOUR/VENV/lib/python/2.X/site-packages 16 | 17 | WSGIScriptAlias / /opt/social-feed-manager/sfm/sfm/wsgi.py 18 | 19 | 20 | 21 | Order deny,allow 22 | Allow from all 23 | 24 | 25 | -------------------------------------------------------------------------------- /docker/app-prod/m5_002/crons.conf: -------------------------------------------------------------------------------- 1 | # SOCIAL FEED MANAGER TASKS 2 | # Fetch user timelines 3x daily 3 | 5 */8 * * * cd /opt/social-feed-manager && /usr/bin/python /opt/social-feed-manager/sfm/manage.py user_timeline > /var/log/sfm/cron_user_timeline.log 2>&1 4 | # Organize stream files 4x daily 5 | 35 */6 * * * cd /opt/social-feed-manager && /usr/bin/python /opt/social-feed-manager/sfm/manage.py organizedata > /var/log/sfm/cron_organizedata.log 2>&1 6 | # Update Twitter account names 1x weekly 7 | 5 3 * * 0 cd /opt/social-feed-manager && /usr/bin/python /opt/social-feed-manager/sfm/manage.py update_usernames > /var/log/sfm/cron_update_usernames.log 2>&1 8 | # fetch_urls on tweets published between yesterday at midnight until today at midnight 9 | 15 1 * * * cd /opt/social-feed-manager && /usr/bin/python /opt/social-feed-manager/sfm/manage.py fetch_urls --start-date=`date -d 'now - 1 day' +'\%Y-\%m-\%d'` --end-date=`date -d 'now' +'\%Y-\%m-\%d'` > /var/log/sfm/cron_fetch_urls.log 2>&1 10 | -------------------------------------------------------------------------------- /docker/app-prod/m5_002/invoke.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Waiting for db" 3 | python /opt/social-feed-manager/appdeps.py --wait-secs 30 --port-wait db:5432 4 | if [ "$?" = "1" ]; then 5 | echo "Problem with application dependencies." 6 | exit 1 7 | fi 8 | 9 | echo "Writing local_settings" 10 | echo "env={}" > /opt/social-feed-manager/sfm/sfm/local_settings.py 11 | env | grep 'SFM_\|\DB_' | sed 's/\(.*\)=\(.*\)/env["\1"]="\2"/' >> /opt/social-feed-manager/sfm/sfm/local_settings.py 12 | cat /tmp/local_settings.py >> /opt/social-feed-manager/sfm/sfm/local_settings.py 13 | 14 | echo "Syncing db" 15 | /opt/social-feed-manager/sfm/manage.py syncdb --noinput 16 | 17 | echo "Migrating db" 18 | /opt/social-feed-manager/sfm/manage.py migrate --noinput 19 | 20 | echo "Starting supervisord" 21 | /etc/init.d/supervisor start 22 | 23 | echo "Starting cron" 24 | cron 25 | 26 | echo "Running server" 27 | #Not entirely sure why this is necessary, but it works. 28 | /etc/init.d/apache2 start 29 | #Make sure apache has started 30 | /etc/init.d/apache2 status 31 | while [ "$?" != "0" ]; do 32 | echo "Waiting for start" 33 | sleep 1 34 | /etc/init.d/apache2 status 35 | done 36 | echo "Stopping server" 37 | /etc/init.d/apache2 graceful-stop 38 | #Make sure apache has stopped 39 | /etc/init.d/apache2 status 40 | while [ "$?" = "0" ]; do 41 | echo "Waiting for stop" 42 | sleep 1 43 | /etc/init.d/apache2 status 44 | done 45 | echo "Starting server again" 46 | apachectl -DFOREGROUND 47 | -------------------------------------------------------------------------------- /docker/app-prod/m5_002/local_settings.py: -------------------------------------------------------------------------------- 1 | DEBUG = env.get('SFM_DEBUG', 'True') == 'True' 2 | 3 | ADMINS = ( 4 | (env.get('SFM_ADMIN_NAME', 'sfmadmin'), env.get('SFM_ADMIN_EMAIL', 'nowhere@example.com')), 5 | ) 6 | 7 | MANAGERS = ADMINS 8 | 9 | # This value should be something like [sfm-test] (with a trailing space) 10 | EMAIL_SUBJECT_PREFIX = env.get('SFM_EMAIL_SUBJECT_PREFIX', '') + ' ' 11 | 12 | # Set SERVER_EMAIL to root@myserver, e.g. 'root@gwsfm-test.wrlc.org' 13 | SERVER_EMAIL = env.get('SFM_SERVER_EMAIL', '') 14 | 15 | DATABASES = { 16 | 'default': { 17 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 18 | 'NAME': 'sfm', 19 | 'USER': 'postgres', 20 | 'PASSWORD': env['DB_ENV_POSTGRES_PASSWORD'], 21 | 'HOST': 'db', 22 | 'PORT': '5432', 23 | } 24 | } 25 | 26 | # Optional. See https://docs.djangoproject.com/en/1.7/ref/settings/ 27 | # ALLOWED_HOSTS = ['YOUR.PUBLIC.DOMAIN.NAME'] 28 | 29 | # See https://docs.djangoproject.com/en/1.4/topics/cache/ 30 | CACHES = { 31 | 'default': { 32 | 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 33 | 'LOCATION': 'your-unique-sfm-instance-name', 34 | } 35 | } 36 | 37 | TEMPLATE_DIRS = ( 38 | ) 39 | 40 | DATA_DIR = '/var/sfm' 41 | 42 | # If not specified, the supervisor configuration files 43 | # will use the owner of the sfm app process. The owner must 44 | # be able to write to /var/log/sfm 45 | #SUPERVISOR_PROCESS_USER = '' 46 | 47 | # Path to the supervisor Unix socket file (with no unix:// prefix) 48 | # e.g. '/var/run/supervisor.sock'. Note that this must match the Unix 49 | # socket file specified in /etc/supervisor/supervisord.conf 50 | SUPERVISOR_UNIX_SOCKET_FILE = '/var/run//supervisor.sock' 51 | 52 | TWITTER_DEFAULT_USERNAME = env['SFM_TWITTER_DEFAULT_USERNAME'] 53 | TWITTER_CONSUMER_KEY = env['SFM_TWITTER_CONSUMER_KEY'] 54 | TWITTER_CONSUMER_SECRET = env['SFM_TWITTER_CONSUMER_SECRET'] 55 | 56 | BRANDING = { 57 | # Required: 58 | 'institution': env.get('SFM_BRANDING_INSTITUTION', ''), 59 | 'URL': env.get('SFM_BRANDING_URL', ''), 60 | # Optional: 61 | # address may contain any number of elements 62 | 'address': [env.get('SFM_BRANDING_ADDRESS', '')], 63 | 'email': env.get('SFM_BRANDING_EMAIL', ''), 64 | # logofile should be placed in static/img 65 | # See https://github.com/gwu-libraries/social-feed-manager/issues/300 66 | 'logofile': '', 67 | } 68 | 69 | # Use django-finalware to create the superadmin account. 70 | # Redefining INSTALLED_APPS could be replaced with this hack: 71 | # http://blog.christopherlawlor.com/2010/06/django-how-to-add-debugging-apps-to-your-local-settings/ 72 | INSTALLED_APPS = ( 73 | 'django.contrib.auth', 74 | 'django.contrib.contenttypes', 75 | 'django.contrib.sessions', 76 | 'django.contrib.sites', 77 | 'django.contrib.messages', 78 | 'django.contrib.staticfiles', 79 | 'django.contrib.admin', 80 | 'django.contrib.humanize', 81 | 'social_auth', 82 | 'south', 83 | 'ui', 84 | 'finalware' 85 | ) 86 | # This field is the superuser object ID. Pick something other than `1` for security reason. 87 | SITE_SUPERUSER_ID = '5' 88 | 89 | # This field is stored in `User.USERNAME_FIELD`. This is usually a `username` or an `email`. 90 | SITE_SUPERUSER_USERNAME = env.get('SFM_SITE_ADMIN_NAME', 'sfmadmin') 91 | 92 | # This field is stored in the `email` field, provided, that `User.USERNAME_FIELD` is not an `email`. 93 | # If `User.USERNAME_FIELD` is already an email address, set `SITE_SUPERUSER_EMAIL = SITE_SUPERUSER_USERNAME` 94 | SITE_SUPERUSER_EMAIL = env.get('SFM_SITE_ADMIN_EMAIL', 'nowhere@example.com') 95 | 96 | # A hashed version of `SITE_SUPERUSER_PASSWORD` will be store in superuser's `password` field. 97 | SITE_SUPERUSER_PASSWORD = env.get('SFM_SITE_ADMIN_PASSWORD', 'password') 98 | 99 | #This isn't configurable in m5_002 100 | #SUPERVISOR_ROOT = "/var/supervisor.d" 101 | -------------------------------------------------------------------------------- /docker/app-prod/m5_002/supervisord.conf: -------------------------------------------------------------------------------- 1 | ; supervisor config file 2 | 3 | [unix_http_server] 4 | file=/var/run//supervisor.sock ; (the path to the socket file) 5 | chmod=0700 ; sockef file mode (default 0700) 6 | chown=www-data:www-data 7 | 8 | [supervisord] 9 | logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log) 10 | pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid) 11 | childlogdir=/var/log/supervisor ; ('AUTO' child log dir, default $TEMP) 12 | 13 | ; the below section must remain in the config file for RPC 14 | ; (supervisorctl/web interface) to work, additional interfaces may be 15 | ; added by defining them in separate rpcinterface: sections 16 | [rpcinterface:supervisor] 17 | supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface 18 | 19 | [supervisorctl] 20 | serverurl=unix:///var/run//supervisor.sock ; use a unix:// URL for a unix socket 21 | 22 | ; The [include] section can just contain the "files" setting. This 23 | ; setting can list multiple files (separated by whitespace or 24 | ; newlines). It can also contain wildcards. The filenames are 25 | ; interpreted as relative to this file. Included files *cannot* 26 | ; include files themselves. 27 | 28 | [include] 29 | ; This is hardcoded in m5_002 30 | ; files = /etc/supervisor/conf.d/*.conf /var/supervisor.d/*.conf 31 | files = /etc/supervisor/conf.d/*.conf /opt/social-feed-manager/sfm/sfm/supervisor.d/*.conf 32 | -------------------------------------------------------------------------------- /docker/app-prod/m5_002/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for sfm project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | # if using a virtualenv, uncomment and set the next three lines appropriately 17 | #import site 18 | #ENV = '/PATH/TO/YOUR/VIRTUALENV' 19 | #site.addsitedir(ENV + '/lib/python2.7/site-packages') 20 | 21 | import os 22 | 23 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sfm.settings") 24 | 25 | # This application object is used by any WSGI server configured to use this 26 | # file. This includes Django's development server, if the WSGI_APPLICATION 27 | # setting points here. 28 | from django.core.wsgi import get_wsgi_application 29 | application = get_wsgi_application() 30 | 31 | # Apply WSGI middleware here. 32 | # from helloworld.wsgi import HelloWorldApplication 33 | # application = HelloWorldApplication(application) 34 | -------------------------------------------------------------------------------- /docker/app-prod/m5_002_https/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gwul/sfm_app:m5_002 2 | MAINTAINER Justin Littman 3 | 4 | #Enable SSL 5 | RUN a2enmod ssl 6 | RUN a2enmod rewrite 7 | ADD apache.conf /etc/apache2/sites-available/sfm 8 | EXPOSE 80 443 9 | -------------------------------------------------------------------------------- /docker/app-prod/m5_002_https/README.md: -------------------------------------------------------------------------------- 1 | SFM APP 2 | ------- 3 | 4 | An image for the [Social Feed Manager](https://github.com/gwu-libraries/social-feed-manager) application. 5 | 6 | For more information on using this image, see this [README](https://github.com/gwu-libraries/social-feed-manager/blob/master/docker/README.md). 7 | 8 | This image is for the old Social Feed Manager. For the new Social Feed Manager, see https://github.com/gwu-libraries/sfm-ui. -------------------------------------------------------------------------------- /docker/app-prod/m5_002_https/apache.conf: -------------------------------------------------------------------------------- 1 | 2 | RewriteEngine On 3 | # This will enable the Rewrite capabilities 4 | 5 | RewriteCond %{HTTPS} !=on 6 | # This checks to make sure the connection is not already HTTPS 7 | 8 | RewriteRule ^/?(.*) https://%{SERVER_NAME}/$1 [R,L] 9 | 10 | 11 | 12 | 13 | 14 | Alias /static/ /opt/social-feed-manager/sfm/ui/static/ 15 | 16 | Order deny,allow 17 | Allow from all 18 | 19 | 20 | # For WSGI daemon mode: 21 | # see http://code.google.com/p/modwsgi/wiki/QuickConfigurationGuide 22 | WSGIDaemonProcess sfm processes=2 threads=15 python-path=/opt/social-feed-manager/sfm 23 | WSGIProcessGroup sfm 24 | 25 | WSGIScriptAlias / /opt/social-feed-manager/sfm/sfm/wsgi.py 26 | 27 | 28 | 29 | Order deny,allow 30 | Allow from all 31 | 32 | 33 | 34 | SSLEngine on 35 | SSLCertificateFile /etc/ssl/certs/sfm.crt 36 | SSLCertificateKeyFile /etc/ssl/private/sfm.key 37 | 38 | 39 | -------------------------------------------------------------------------------- /docker/app-prod/m5_003/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:12.04 2 | MAINTAINER Justin Littman 3 | 4 | RUN apt-get update && apt-get install -y \ 5 | python-dev \ 6 | libxml2-dev \ 7 | libxslt1-dev \ 8 | libpq-dev \ 9 | supervisor \ 10 | python-pip \ 11 | apache2 \ 12 | libapache2-mod-wsgi \ 13 | wget \ 14 | zip \ 15 | cron 16 | #Upgrade pip 17 | RUN pip install -U pip 18 | WORKDIR /tmp 19 | RUN wget --no-check-certificate https://github.com/gwu-libraries/social-feed-manager/archive/m5_003.zip 20 | RUN unzip m5_003.zip 21 | RUN mv social-feed-manager-m5_003 /opt/social-feed-manager 22 | #This installs the requirements.txt. 23 | RUN pip install -r /opt/social-feed-manager/requirements.txt 24 | #This is used to automatically create the admin user. 25 | RUN pip install django-finalware==0.0.2 26 | ADD local_settings.py /tmp/ 27 | ADD wsgi.py /opt/social-feed-manager/sfm/sfm/ 28 | #Enable sfm site 29 | ADD apache.conf /etc/apache2/sites-available/sfm 30 | RUN a2ensite sfm 31 | #Disable pre-existing default site 32 | RUN a2dissite 000-default 33 | #Configure supervisor 34 | ADD supervisord.conf /etc/supervisor/ 35 | RUN mkdir /var/log/sfm 36 | ADD invoke.sh /opt/ 37 | RUN chmod +x /opt/invoke.sh 38 | #Install appdeps to allow checking for application dependencies 39 | WORKDIR /opt/social-feed-manager 40 | RUN wget -L --no-check-certificate https://github.com/gwu-libraries/appdeps/raw/master/appdeps.py 41 | #Cron 42 | ADD crons.conf /tmp/ 43 | RUN crontab /tmp/crons.conf 44 | CMD ["/opt/invoke.sh"] 45 | EXPOSE 80 46 | -------------------------------------------------------------------------------- /docker/app-prod/m5_003/README.md: -------------------------------------------------------------------------------- 1 | SFM APP 2 | ------- 3 | 4 | An image for the [Social Feed Manager](https://github.com/gwu-libraries/social-feed-manager) application. 5 | 6 | For more information on using this image, see this [README](https://github.com/gwu-libraries/social-feed-manager/blob/master/docker/README.md). 7 | 8 | This image is for the old Social Feed Manager. For the new Social Feed Manager, see https://github.com/gwu-libraries/sfm-ui. -------------------------------------------------------------------------------- /docker/app-prod/m5_003/apache.conf: -------------------------------------------------------------------------------- 1 | Alias /static/ /opt/social-feed-manager/sfm/ui/static/ 2 | 3 | Order deny,allow 4 | Allow from all 5 | 6 | 7 | # For WSGI daemon mode: 8 | # see http://code.google.com/p/modwsgi/wiki/QuickConfigurationGuide 9 | WSGIDaemonProcess sfm processes=2 threads=15 python-path=/opt/social-feed-manager/sfm 10 | WSGIProcessGroup sfm 11 | 12 | # For WSGI embedded mode: 13 | #WSGIPythonPath /PATH/TO/sfm 14 | # If using a virtualenv, uncomment and tweak next line (inc. python version): 15 | # WSGIPythonPath /PATH/TO/YOUR/VENV/lib/python/2.X/site-packages 16 | 17 | WSGIScriptAlias / /opt/social-feed-manager/sfm/sfm/wsgi.py 18 | 19 | 20 | 21 | Order deny,allow 22 | Allow from all 23 | 24 | 25 | -------------------------------------------------------------------------------- /docker/app-prod/m5_003/crons.conf: -------------------------------------------------------------------------------- 1 | # SOCIAL FEED MANAGER TASKS 2 | # Fetch user timelines 3x daily 3 | 5 */8 * * * cd /opt/social-feed-manager && /usr/bin/python /opt/social-feed-manager/sfm/manage.py user_timeline > /var/log/sfm/cron_user_timeline.log 2>&1 4 | # Organize stream files 4x daily 5 | 35 */6 * * * cd /opt/social-feed-manager && /usr/bin/python /opt/social-feed-manager/sfm/manage.py organizedata > /var/log/sfm/cron_organizedata.log 2>&1 6 | # Update Twitter account names 1x weekly 7 | 5 3 * * 0 cd /opt/social-feed-manager && /usr/bin/python /opt/social-feed-manager/sfm/manage.py update_usernames > /var/log/sfm/cron_update_usernames.log 2>&1 8 | # fetch_urls on tweets published between yesterday at midnight until today at midnight 9 | 15 1 * * * cd /opt/social-feed-manager && /usr/bin/python /opt/social-feed-manager/sfm/manage.py fetch_urls --start-date=`date -d 'now - 1 day' +'\%Y-\%m-\%d'` --end-date=`date -d 'now' +'\%Y-\%m-\%d'` > /var/log/sfm/cron_fetch_urls.log 2>&1 10 | -------------------------------------------------------------------------------- /docker/app-prod/m5_003/invoke.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Waiting for db" 3 | python /opt/social-feed-manager/appdeps.py --wait-secs 30 --port-wait db:5432 4 | if [ "$?" = "1" ]; then 5 | echo "Problem with application dependencies." 6 | exit 1 7 | fi 8 | 9 | echo "Writing local_settings" 10 | echo "env={}" > /opt/social-feed-manager/sfm/sfm/local_settings.py 11 | env | grep 'SFM_\|\DB_' | sed 's/\(.*\)=\(.*\)/env["\1"]="\2"/' >> /opt/social-feed-manager/sfm/sfm/local_settings.py 12 | cat /tmp/local_settings.py >> /opt/social-feed-manager/sfm/sfm/local_settings.py 13 | 14 | echo "Syncing db" 15 | /opt/social-feed-manager/sfm/manage.py syncdb --noinput 16 | 17 | echo "Migrating db" 18 | /opt/social-feed-manager/sfm/manage.py migrate --noinput 19 | 20 | echo "Starting supervisord" 21 | /etc/init.d/supervisor start 22 | 23 | echo "Starting cron" 24 | cron 25 | 26 | echo "Running server" 27 | #Not entirely sure why this is necessary, but it works. 28 | /etc/init.d/apache2 start 29 | #Make sure apache has started 30 | /etc/init.d/apache2 status 31 | while [ "$?" != "0" ]; do 32 | echo "Waiting for start" 33 | sleep 1 34 | /etc/init.d/apache2 status 35 | done 36 | echo "Stopping server" 37 | /etc/init.d/apache2 graceful-stop 38 | #Make sure apache has stopped 39 | /etc/init.d/apache2 status 40 | while [ "$?" = "0" ]; do 41 | echo "Waiting for stop" 42 | sleep 1 43 | /etc/init.d/apache2 status 44 | done 45 | echo "Starting server again" 46 | apachectl -DFOREGROUND 47 | -------------------------------------------------------------------------------- /docker/app-prod/m5_003/local_settings.py: -------------------------------------------------------------------------------- 1 | DEBUG = env.get('SFM_DEBUG', 'True') == 'True' 2 | 3 | ADMINS = ( 4 | (env.get('SFM_ADMIN_NAME', 'sfmadmin'), env.get('SFM_ADMIN_EMAIL', 'nowhere@example.com')), 5 | ) 6 | 7 | MANAGERS = ADMINS 8 | 9 | # This value should be something like [sfm-test] (with a trailing space) 10 | EMAIL_SUBJECT_PREFIX = env.get('SFM_EMAIL_SUBJECT_PREFIX', '') + ' ' 11 | 12 | # Set SERVER_EMAIL to root@myserver, e.g. 'root@gwsfm-test.wrlc.org' 13 | SERVER_EMAIL = env.get('SFM_SERVER_EMAIL', '') 14 | 15 | DATABASES = { 16 | 'default': { 17 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 18 | 'NAME': 'sfm', 19 | 'USER': 'postgres', 20 | 'PASSWORD': env['DB_ENV_POSTGRES_PASSWORD'], 21 | 'HOST': 'db', 22 | 'PORT': '5432', 23 | } 24 | } 25 | 26 | # Optional. See https://docs.djangoproject.com/en/1.7/ref/settings/ 27 | # ALLOWED_HOSTS = ['YOUR.PUBLIC.DOMAIN.NAME'] 28 | 29 | # See https://docs.djangoproject.com/en/1.4/topics/cache/ 30 | CACHES = { 31 | 'default': { 32 | 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 33 | 'LOCATION': 'your-unique-sfm-instance-name', 34 | } 35 | } 36 | 37 | TEMPLATE_DIRS = ( 38 | ) 39 | 40 | DATA_DIR = '/var/sfm' 41 | 42 | # If not specified, the supervisor configuration files 43 | # will use the owner of the sfm app process. The owner must 44 | # be able to write to /var/log/sfm 45 | #SUPERVISOR_PROCESS_USER = '' 46 | 47 | # Path to the supervisor Unix socket file (with no unix:// prefix) 48 | # e.g. '/var/run/supervisor.sock'. Note that this must match the Unix 49 | # socket file specified in /etc/supervisor/supervisord.conf 50 | SUPERVISOR_UNIX_SOCKET_FILE = '/var/run//supervisor.sock' 51 | 52 | TWITTER_DEFAULT_USERNAME = env['SFM_TWITTER_DEFAULT_USERNAME'] 53 | TWITTER_CONSUMER_KEY = env['SFM_TWITTER_CONSUMER_KEY'] 54 | TWITTER_CONSUMER_SECRET = env['SFM_TWITTER_CONSUMER_SECRET'] 55 | 56 | BRANDING = { 57 | # Required: 58 | 'institution': env.get('SFM_BRANDING_INSTITUTION', ''), 59 | 'URL': env.get('SFM_BRANDING_URL', ''), 60 | # Optional: 61 | # address may contain any number of elements 62 | 'address': [env.get('SFM_BRANDING_ADDRESS', '')], 63 | 'email': env.get('SFM_BRANDING_EMAIL', ''), 64 | # logofile should be placed in static/img 65 | # See https://github.com/gwu-libraries/social-feed-manager/issues/300 66 | 'logofile': '', 67 | } 68 | 69 | # Use django-finalware to create the superadmin account. 70 | # Redefining INSTALLED_APPS could be replaced with this hack: 71 | # http://blog.christopherlawlor.com/2010/06/django-how-to-add-debugging-apps-to-your-local-settings/ 72 | INSTALLED_APPS = ( 73 | 'django.contrib.auth', 74 | 'django.contrib.contenttypes', 75 | 'django.contrib.sessions', 76 | 'django.contrib.sites', 77 | 'django.contrib.messages', 78 | 'django.contrib.staticfiles', 79 | 'django.contrib.admin', 80 | 'django.contrib.humanize', 81 | 'social_auth', 82 | 'south', 83 | 'ui', 84 | 'finalware' 85 | ) 86 | # This field is the superuser object ID. Pick something other than `1` for security reason. 87 | SITE_SUPERUSER_ID = '5' 88 | 89 | # This field is stored in `User.USERNAME_FIELD`. This is usually a `username` or an `email`. 90 | SITE_SUPERUSER_USERNAME = env.get('SFM_SITE_ADMIN_NAME', 'sfmadmin') 91 | 92 | # This field is stored in the `email` field, provided, that `User.USERNAME_FIELD` is not an `email`. 93 | # If `User.USERNAME_FIELD` is already an email address, set `SITE_SUPERUSER_EMAIL = SITE_SUPERUSER_USERNAME` 94 | SITE_SUPERUSER_EMAIL = env.get('SFM_SITE_ADMIN_EMAIL', 'nowhere@example.com') 95 | 96 | # A hashed version of `SITE_SUPERUSER_PASSWORD` will be store in superuser's `password` field. 97 | SITE_SUPERUSER_PASSWORD = env.get('SFM_SITE_ADMIN_PASSWORD', 'password') 98 | 99 | SUPERVISOR_ROOT = "/var/supervisor.d" 100 | -------------------------------------------------------------------------------- /docker/app-prod/m5_003/supervisord.conf: -------------------------------------------------------------------------------- 1 | ; supervisor config file 2 | 3 | [unix_http_server] 4 | file=/var/run//supervisor.sock ; (the path to the socket file) 5 | chmod=0700 ; sockef file mode (default 0700) 6 | chown=www-data:www-data 7 | 8 | [supervisord] 9 | logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log) 10 | pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid) 11 | childlogdir=/var/log/supervisor ; ('AUTO' child log dir, default $TEMP) 12 | 13 | ; the below section must remain in the config file for RPC 14 | ; (supervisorctl/web interface) to work, additional interfaces may be 15 | ; added by defining them in separate rpcinterface: sections 16 | [rpcinterface:supervisor] 17 | supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface 18 | 19 | [supervisorctl] 20 | serverurl=unix:///var/run//supervisor.sock ; use a unix:// URL for a unix socket 21 | 22 | ; The [include] section can just contain the "files" setting. This 23 | ; setting can list multiple files (separated by whitespace or 24 | ; newlines). It can also contain wildcards. The filenames are 25 | ; interpreted as relative to this file. Included files *cannot* 26 | ; include files themselves. 27 | 28 | [include] 29 | files = /etc/supervisor/conf.d/*.conf /var/supervisor.d/*.conf 30 | -------------------------------------------------------------------------------- /docker/app-prod/m5_003/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for sfm project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | # if using a virtualenv, uncomment and set the next three lines appropriately 17 | #import site 18 | #ENV = '/PATH/TO/YOUR/VIRTUALENV' 19 | #site.addsitedir(ENV + '/lib/python2.7/site-packages') 20 | 21 | import os 22 | 23 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sfm.settings") 24 | 25 | # This application object is used by any WSGI server configured to use this 26 | # file. This includes Django's development server, if the WSGI_APPLICATION 27 | # setting points here. 28 | from django.core.wsgi import get_wsgi_application 29 | application = get_wsgi_application() 30 | 31 | # Apply WSGI middleware here. 32 | # from helloworld.wsgi import HelloWorldApplication 33 | # application = HelloWorldApplication(application) 34 | -------------------------------------------------------------------------------- /docker/app-prod/m5_003_https/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gwul/sfm_app:m5_003 2 | MAINTAINER Justin Littman 3 | 4 | #Enable SSL 5 | RUN a2enmod ssl 6 | RUN a2enmod rewrite 7 | ADD apache.conf /etc/apache2/sites-available/sfm 8 | EXPOSE 80 443 9 | -------------------------------------------------------------------------------- /docker/app-prod/m5_003_https/README.md: -------------------------------------------------------------------------------- 1 | SFM APP 2 | ------- 3 | 4 | An image for the [Social Feed Manager](https://github.com/gwu-libraries/social-feed-manager) application. 5 | 6 | For more information on using this image, see this [README](https://github.com/gwu-libraries/social-feed-manager/blob/master/docker/README.md). 7 | 8 | This image is for the old Social Feed Manager. For the new Social Feed Manager, see https://github.com/gwu-libraries/sfm-ui. -------------------------------------------------------------------------------- /docker/app-prod/m5_003_https/apache.conf: -------------------------------------------------------------------------------- 1 | 2 | RewriteEngine On 3 | # This will enable the Rewrite capabilities 4 | 5 | RewriteCond %{HTTPS} !=on 6 | # This checks to make sure the connection is not already HTTPS 7 | 8 | RewriteRule ^/?(.*) https://%{SERVER_NAME}/$1 [R,L] 9 | 10 | 11 | 12 | 13 | 14 | Alias /static/ /opt/social-feed-manager/sfm/ui/static/ 15 | 16 | Order deny,allow 17 | Allow from all 18 | 19 | 20 | # For WSGI daemon mode: 21 | # see http://code.google.com/p/modwsgi/wiki/QuickConfigurationGuide 22 | WSGIDaemonProcess sfm processes=2 threads=15 python-path=/opt/social-feed-manager/sfm 23 | WSGIProcessGroup sfm 24 | 25 | WSGIScriptAlias / /opt/social-feed-manager/sfm/sfm/wsgi.py 26 | 27 | 28 | 29 | Order deny,allow 30 | Allow from all 31 | 32 | 33 | 34 | SSLEngine on 35 | SSLCertificateFile /etc/ssl/certs/sfm.crt 36 | SSLCertificateKeyFile /etc/ssl/private/sfm.key 37 | 38 | 39 | -------------------------------------------------------------------------------- /docker/app-prod/m5_004/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:12.04 2 | MAINTAINER Justin Littman 3 | 4 | RUN apt-get update && apt-get install -y \ 5 | python-dev \ 6 | libxml2-dev \ 7 | libxslt1-dev \ 8 | libpq-dev \ 9 | supervisor \ 10 | python-pip \ 11 | apache2 \ 12 | libapache2-mod-wsgi \ 13 | wget \ 14 | zip \ 15 | cron 16 | #Upgrade pip 17 | RUN pip install -U pip 18 | WORKDIR /tmp 19 | RUN wget --no-check-certificate https://github.com/gwu-libraries/social-feed-manager/archive/m5_004.zip 20 | RUN unzip m5_004.zip 21 | RUN mv social-feed-manager-m5_004 /opt/social-feed-manager 22 | #This installs the requirements.txt. 23 | RUN pip install -r /opt/social-feed-manager/requirements.txt 24 | #This is used to automatically create the admin user. 25 | RUN pip install django-finalware==0.0.2 26 | ADD local_settings.py /tmp/ 27 | ADD wsgi.py /opt/social-feed-manager/sfm/sfm/ 28 | #Enable sfm site 29 | ADD apache.conf /etc/apache2/sites-available/sfm 30 | RUN a2ensite sfm 31 | #Disable pre-existing default site 32 | RUN a2dissite 000-default 33 | #Configure supervisor 34 | ADD supervisord.conf /etc/supervisor/ 35 | RUN mkdir /var/log/sfm 36 | ADD invoke.sh /opt/ 37 | RUN chmod +x /opt/invoke.sh 38 | #Install appdeps to allow checking for application dependencies 39 | WORKDIR /opt/social-feed-manager 40 | RUN wget -L --no-check-certificate https://github.com/gwu-libraries/appdeps/raw/master/appdeps.py 41 | #Cron 42 | ADD crons.conf /tmp/ 43 | RUN crontab /tmp/crons.conf 44 | CMD ["/opt/invoke.sh"] 45 | EXPOSE 80 46 | -------------------------------------------------------------------------------- /docker/app-prod/m5_004/README.md: -------------------------------------------------------------------------------- 1 | SFM APP 2 | ------- 3 | 4 | An image for the [Social Feed Manager](https://github.com/gwu-libraries/social-feed-manager) application. 5 | 6 | For more information on using this image, see this [README](https://github.com/gwu-libraries/social-feed-manager/blob/master/docker/README.md). 7 | 8 | This image is for the old Social Feed Manager. For the new Social Feed Manager, see https://github.com/gwu-libraries/sfm-ui. -------------------------------------------------------------------------------- /docker/app-prod/m5_004/apache.conf: -------------------------------------------------------------------------------- 1 | Alias /static/ /opt/social-feed-manager/sfm/ui/static/ 2 | 3 | Order deny,allow 4 | Allow from all 5 | 6 | 7 | # For WSGI daemon mode: 8 | # see http://code.google.com/p/modwsgi/wiki/QuickConfigurationGuide 9 | WSGIDaemonProcess sfm processes=2 threads=15 python-path=/opt/social-feed-manager/sfm 10 | WSGIProcessGroup sfm 11 | 12 | # For WSGI embedded mode: 13 | #WSGIPythonPath /PATH/TO/sfm 14 | # If using a virtualenv, uncomment and tweak next line (inc. python version): 15 | # WSGIPythonPath /PATH/TO/YOUR/VENV/lib/python/2.X/site-packages 16 | 17 | WSGIScriptAlias / /opt/social-feed-manager/sfm/sfm/wsgi.py 18 | 19 | 20 | 21 | Order deny,allow 22 | Allow from all 23 | 24 | 25 | -------------------------------------------------------------------------------- /docker/app-prod/m5_004/crons.conf: -------------------------------------------------------------------------------- 1 | # SOCIAL FEED MANAGER TASKS 2 | # Fetch user timelines 3x daily 3 | 5 */8 * * * cd /opt/social-feed-manager && /usr/bin/python /opt/social-feed-manager/sfm/manage.py user_timeline > /var/log/sfm/cron_user_timeline.log 2>&1 4 | # Organize stream files 4x daily 5 | 35 */6 * * * cd /opt/social-feed-manager && /usr/bin/python /opt/social-feed-manager/sfm/manage.py organizedata > /var/log/sfm/cron_organizedata.log 2>&1 6 | # Update Twitter account names 1x weekly 7 | 5 3 * * 0 cd /opt/social-feed-manager && /usr/bin/python /opt/social-feed-manager/sfm/manage.py update_usernames > /var/log/sfm/cron_update_usernames.log 2>&1 8 | # fetch_urls on tweets published between yesterday at midnight until today at midnight 9 | 15 1 * * * cd /opt/social-feed-manager && /usr/bin/python /opt/social-feed-manager/sfm/manage.py fetch_urls --start-date=`date -d 'now - 1 day' +'\%Y-\%m-\%d'` --end-date=`date -d 'now' +'\%Y-\%m-\%d'` > /var/log/sfm/cron_fetch_urls.log 2>&1 10 | -------------------------------------------------------------------------------- /docker/app-prod/m5_004/invoke.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Waiting for db" 3 | python /opt/social-feed-manager/appdeps.py --wait-secs 30 --port-wait db:5432 4 | if [ "$?" = "1" ]; then 5 | echo "Problem with application dependencies." 6 | exit 1 7 | fi 8 | 9 | echo "Writing local_settings" 10 | echo "env={}" > /opt/social-feed-manager/sfm/sfm/local_settings.py 11 | env | grep 'SFM_\|\DB_' | sed 's/\(.*\)=\(.*\)/env["\1"]="\2"/' >> /opt/social-feed-manager/sfm/sfm/local_settings.py 12 | cat /tmp/local_settings.py >> /opt/social-feed-manager/sfm/sfm/local_settings.py 13 | 14 | echo "Syncing db" 15 | /opt/social-feed-manager/sfm/manage.py syncdb --noinput 16 | 17 | echo "Migrating db" 18 | /opt/social-feed-manager/sfm/manage.py migrate --noinput 19 | 20 | echo "Starting supervisord" 21 | /etc/init.d/supervisor start 22 | 23 | echo "Starting cron" 24 | cron 25 | 26 | echo "Running server" 27 | #Not entirely sure why this is necessary, but it works. 28 | /etc/init.d/apache2 start 29 | #Make sure apache has started 30 | /etc/init.d/apache2 status 31 | while [ "$?" != "0" ]; do 32 | echo "Waiting for start" 33 | sleep 1 34 | /etc/init.d/apache2 status 35 | done 36 | echo "Stopping server" 37 | /etc/init.d/apache2 graceful-stop 38 | #Make sure apache has stopped 39 | /etc/init.d/apache2 status 40 | while [ "$?" = "0" ]; do 41 | echo "Waiting for stop" 42 | sleep 1 43 | /etc/init.d/apache2 status 44 | done 45 | echo "Starting server again" 46 | apachectl -DFOREGROUND 47 | -------------------------------------------------------------------------------- /docker/app-prod/m5_004/local_settings.py: -------------------------------------------------------------------------------- 1 | DEBUG = env.get('SFM_DEBUG', 'True') == 'True' 2 | 3 | ADMINS = ( 4 | (env.get('SFM_ADMIN_NAME', 'sfmadmin'), env.get('SFM_ADMIN_EMAIL', 'nowhere@example.com')), 5 | ) 6 | 7 | MANAGERS = ADMINS 8 | 9 | # This value should be something like [sfm-test] (with a trailing space) 10 | EMAIL_SUBJECT_PREFIX = env.get('SFM_EMAIL_SUBJECT_PREFIX', '') + ' ' 11 | 12 | # Set SERVER_EMAIL to root@myserver, e.g. 'root@gwsfm-test.wrlc.org' 13 | SERVER_EMAIL = env.get('SFM_SERVER_EMAIL', '') 14 | 15 | DATABASES = { 16 | 'default': { 17 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 18 | 'NAME': 'sfm', 19 | 'USER': 'postgres', 20 | 'PASSWORD': env['DB_ENV_POSTGRES_PASSWORD'], 21 | 'HOST': 'db', 22 | 'PORT': '5432', 23 | } 24 | } 25 | 26 | # Optional. See https://docs.djangoproject.com/en/1.7/ref/settings/ 27 | # ALLOWED_HOSTS = ['YOUR.PUBLIC.DOMAIN.NAME'] 28 | 29 | # See https://docs.djangoproject.com/en/1.4/topics/cache/ 30 | CACHES = { 31 | 'default': { 32 | 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 33 | 'LOCATION': 'your-unique-sfm-instance-name', 34 | } 35 | } 36 | 37 | TEMPLATE_DIRS = ( 38 | ) 39 | 40 | DATA_DIR = '/var/sfm' 41 | 42 | # If not specified, the supervisor configuration files 43 | # will use the owner of the sfm app process. The owner must 44 | # be able to write to /var/log/sfm 45 | #SUPERVISOR_PROCESS_USER = '' 46 | 47 | # Path to the supervisor Unix socket file (with no unix:// prefix) 48 | # e.g. '/var/run/supervisor.sock'. Note that this must match the Unix 49 | # socket file specified in /etc/supervisor/supervisord.conf 50 | SUPERVISOR_UNIX_SOCKET_FILE = '/var/run//supervisor.sock' 51 | 52 | TWITTER_DEFAULT_USERNAME = env['SFM_TWITTER_DEFAULT_USERNAME'] 53 | TWITTER_CONSUMER_KEY = env['SFM_TWITTER_CONSUMER_KEY'] 54 | TWITTER_CONSUMER_SECRET = env['SFM_TWITTER_CONSUMER_SECRET'] 55 | 56 | BRANDING = { 57 | # Required: 58 | 'institution': env.get('SFM_BRANDING_INSTITUTION', ''), 59 | 'URL': env.get('SFM_BRANDING_URL', ''), 60 | # Optional: 61 | # address may contain any number of elements 62 | 'address': [env.get('SFM_BRANDING_ADDRESS', '')], 63 | 'email': env.get('SFM_BRANDING_EMAIL', ''), 64 | # logofile should be placed in static/img 65 | # See https://github.com/gwu-libraries/social-feed-manager/issues/300 66 | 'logofile': '', 67 | } 68 | 69 | # Use django-finalware to create the superadmin account. 70 | # Redefining INSTALLED_APPS could be replaced with this hack: 71 | # http://blog.christopherlawlor.com/2010/06/django-how-to-add-debugging-apps-to-your-local-settings/ 72 | INSTALLED_APPS = ( 73 | 'django.contrib.auth', 74 | 'django.contrib.contenttypes', 75 | 'django.contrib.sessions', 76 | 'django.contrib.sites', 77 | 'django.contrib.messages', 78 | 'django.contrib.staticfiles', 79 | 'django.contrib.admin', 80 | 'django.contrib.humanize', 81 | 'social_auth', 82 | 'south', 83 | 'ui', 84 | 'finalware' 85 | ) 86 | # This field is the superuser object ID. Pick something other than `1` for security reason. 87 | SITE_SUPERUSER_ID = '5' 88 | 89 | # This field is stored in `User.USERNAME_FIELD`. This is usually a `username` or an `email`. 90 | SITE_SUPERUSER_USERNAME = env.get('SFM_SITE_ADMIN_NAME', 'sfmadmin') 91 | 92 | # This field is stored in the `email` field, provided, that `User.USERNAME_FIELD` is not an `email`. 93 | # If `User.USERNAME_FIELD` is already an email address, set `SITE_SUPERUSER_EMAIL = SITE_SUPERUSER_USERNAME` 94 | SITE_SUPERUSER_EMAIL = env.get('SFM_SITE_ADMIN_EMAIL', 'nowhere@example.com') 95 | 96 | # A hashed version of `SITE_SUPERUSER_PASSWORD` will be store in superuser's `password` field. 97 | SITE_SUPERUSER_PASSWORD = env.get('SFM_SITE_ADMIN_PASSWORD', 'password') 98 | 99 | SUPERVISOR_ROOT = "/var/supervisor.d" 100 | -------------------------------------------------------------------------------- /docker/app-prod/m5_004/supervisord.conf: -------------------------------------------------------------------------------- 1 | ; supervisor config file 2 | 3 | [unix_http_server] 4 | file=/var/run//supervisor.sock ; (the path to the socket file) 5 | chmod=0700 ; sockef file mode (default 0700) 6 | chown=www-data:www-data 7 | 8 | [supervisord] 9 | logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log) 10 | pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid) 11 | childlogdir=/var/log/supervisor ; ('AUTO' child log dir, default $TEMP) 12 | 13 | ; the below section must remain in the config file for RPC 14 | ; (supervisorctl/web interface) to work, additional interfaces may be 15 | ; added by defining them in separate rpcinterface: sections 16 | [rpcinterface:supervisor] 17 | supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface 18 | 19 | [supervisorctl] 20 | serverurl=unix:///var/run//supervisor.sock ; use a unix:// URL for a unix socket 21 | 22 | ; The [include] section can just contain the "files" setting. This 23 | ; setting can list multiple files (separated by whitespace or 24 | ; newlines). It can also contain wildcards. The filenames are 25 | ; interpreted as relative to this file. Included files *cannot* 26 | ; include files themselves. 27 | 28 | [include] 29 | files = /etc/supervisor/conf.d/*.conf /var/supervisor.d/*.conf 30 | -------------------------------------------------------------------------------- /docker/app-prod/m5_004/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for sfm project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | # if using a virtualenv, uncomment and set the next three lines appropriately 17 | #import site 18 | #ENV = '/PATH/TO/YOUR/VIRTUALENV' 19 | #site.addsitedir(ENV + '/lib/python2.7/site-packages') 20 | 21 | import os 22 | 23 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sfm.settings") 24 | 25 | # This application object is used by any WSGI server configured to use this 26 | # file. This includes Django's development server, if the WSGI_APPLICATION 27 | # setting points here. 28 | from django.core.wsgi import get_wsgi_application 29 | application = get_wsgi_application() 30 | 31 | # Apply WSGI middleware here. 32 | # from helloworld.wsgi import HelloWorldApplication 33 | # application = HelloWorldApplication(application) 34 | -------------------------------------------------------------------------------- /docker/app-prod/m5_004_https/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gwul/sfm_app:m5_004 2 | MAINTAINER Justin Littman 3 | 4 | #Enable SSL 5 | RUN a2enmod ssl 6 | RUN a2enmod rewrite 7 | ADD apache.conf /etc/apache2/sites-available/sfm 8 | EXPOSE 80 443 9 | -------------------------------------------------------------------------------- /docker/app-prod/m5_004_https/README.md: -------------------------------------------------------------------------------- 1 | SFM APP 2 | ------- 3 | 4 | An image for the [Social Feed Manager](https://github.com/gwu-libraries/social-feed-manager) application. 5 | 6 | For more information on using this image, see this [README](https://github.com/gwu-libraries/social-feed-manager/blob/master/docker/README.md). 7 | 8 | This image is for the old Social Feed Manager. For the new Social Feed Manager, see https://github.com/gwu-libraries/sfm-ui. -------------------------------------------------------------------------------- /docker/app-prod/m5_004_https/apache.conf: -------------------------------------------------------------------------------- 1 | 2 | RewriteEngine On 3 | # This will enable the Rewrite capabilities 4 | 5 | RewriteCond %{HTTPS} !=on 6 | # This checks to make sure the connection is not already HTTPS 7 | 8 | RewriteRule ^/?(.*) https://%{SERVER_NAME}/$1 [R,L] 9 | 10 | 11 | 12 | 13 | 14 | Alias /static/ /opt/social-feed-manager/sfm/ui/static/ 15 | 16 | Order deny,allow 17 | Allow from all 18 | 19 | 20 | # For WSGI daemon mode: 21 | # see http://code.google.com/p/modwsgi/wiki/QuickConfigurationGuide 22 | WSGIDaemonProcess sfm processes=2 threads=15 python-path=/opt/social-feed-manager/sfm 23 | WSGIProcessGroup sfm 24 | 25 | WSGIScriptAlias / /opt/social-feed-manager/sfm/sfm/wsgi.py 26 | 27 | 28 | 29 | Order deny,allow 30 | Allow from all 31 | 32 | 33 | 34 | SSLEngine on 35 | SSLCertificateFile /etc/ssl/certs/sfm.crt 36 | SSLCertificateKeyFile /etc/ssl/private/sfm.key 37 | 38 | 39 | -------------------------------------------------------------------------------- /docker/db/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres:9 2 | MAINTAINER Justin Littman 3 | 4 | ADD initdb.sql /docker-entrypoint-initdb.d/ 5 | -------------------------------------------------------------------------------- /docker/db/README.md: -------------------------------------------------------------------------------- 1 | SFM DB 2 | ------ 3 | 4 | An image for the [Social Feed Manager](https://github.com/gwu-libraries/social-feed-manager) database. 5 | 6 | For more information on using this image, see this [README](https://github.com/gwu-libraries/social-feed-manager/blob/master/docker/README.md). 7 | 8 | This image is for the old Social Feed Manager. For the new Social Feed Manager, see https://github.com/gwu-libraries/sfm-ui. -------------------------------------------------------------------------------- /docker/db/initdb.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE sfm; 2 | -------------------------------------------------------------------------------- /docker/example.dev.docker-compose.yml: -------------------------------------------------------------------------------- 1 | sfmdevdb: 2 | image: gwul/sfm_db 3 | environment: 4 | - POSTGRES_PASSWORD=gherD42#dl5 5 | sfmdevapp: 6 | image: gwul/sfm_app:dev 7 | ports: 8 | - "8000:80" 9 | links: 10 | - sfmdevdb:db 11 | volumes: 12 | - ~/social-feed-manager:/opt/social-feed-manager 13 | environment: 14 | - SFM_DEBUG=True 15 | - SFM_TWITTER_DEFAULT_USERNAME=jlittman_dev 16 | - SFM_TWITTER_CONSUMER_KEY=EHdoTe7ksBgflP6nUalEfhaeo 17 | - SFM_TWITTER_CONSUMER_SECRET=ZtUpemtBkf2Emaqiy52Ddihu9FPAiLebuMOmqN0jeQtXeAlen 18 | -------------------------------------------------------------------------------- /docker/example.master.docker-compose.yml: -------------------------------------------------------------------------------- 1 | sfmmasterdb: 2 | image: gwul/sfm_db 3 | environment: 4 | - POSTGRES_PASSWORD=gherD42#dl5 5 | sfmmasterapp: 6 | image: gwul/sfm_app:master 7 | ports: 8 | - "8000:80" 9 | links: 10 | - sfmmasterdb:db 11 | environment: 12 | - SFM_DEBUG=True 13 | - SFM_TWITTER_DEFAULT_USERNAME=jlittman_dev 14 | - SFM_TWITTER_CONSUMER_KEY=EHdoTff7ksBgflP5nUalEfhaeo 15 | - SFM_TWITTER_CONSUMER_SECRET=ZtUpmtkf2cEmaqiy52Ddihu9FPAiLebuMOmqN0jeQtXeAlen 16 | -------------------------------------------------------------------------------- /docker/example.prod-https.docker-compose.yml: -------------------------------------------------------------------------------- 1 | sfmdb: 2 | image: gwul/sfm_db 3 | environment: 4 | - POSTGRES_PASSWORD=gherD42#dl5 5 | restart: always 6 | sfmapp: 7 | image: gwul/sfm_app:latest_https 8 | ports: 9 | - "80:80" 10 | - "443:443" 11 | links: 12 | - sfmdb:db 13 | environment: 14 | - SFM_DEBUG=True 15 | - SFM_TWITTER_DEFAULT_USERNAME=jlittman_dev 16 | - SFM_TWITTER_CONSUMER_KEY=EHdoTe7ksgflP5nUalEfhaeo 17 | - SFM_TWITTER_CONSUMER_SECRET=ZtUpemBkf2cEmaqiy52Ddihu9FPAiLebuMOmqN0jeQtXeAlen 18 | volumes: 19 | - /sfm-data:/var/sfm 20 | - ~/social-feed-manager/docker/sfm.crt:/etc/ssl/certs/sfm.crt:ro 21 | - ~/social-feed-manager/docker/sfm.key:/etc/ssl/private/sfm.key:ro 22 | restart: always 23 | -------------------------------------------------------------------------------- /docker/example.prod.docker-compose.yml: -------------------------------------------------------------------------------- 1 | sfmdb: 2 | image: gwul/sfm_db 3 | environment: 4 | - POSTGRES_PASSWORD=gherD42#dl5 5 | restart: always 6 | sfmapp: 7 | image: gwul/sfm_app:latest 8 | ports: 9 | - "80:80" 10 | links: 11 | - sfmdb:db 12 | environment: 13 | - SFM_DEBUG=True 14 | - SFM_TWITTER_DEFAULT_USERNAME=jlittman_dev 15 | - SFM_TWITTER_CONSUMER_KEY=EHdoTe7ksgflP5nUalEfhaeo 16 | - SFM_TWITTER_CONSUMER_SECRET=ZtUpemBkf2cEmaqiy52Ddihu9FPAiLebuMOmqN0jeQtXeAlen 17 | volumes: 18 | - /sfm-data:/var/sfm 19 | restart: always 20 | -------------------------------------------------------------------------------- /docs/faq.rst: -------------------------------------------------------------------------------- 1 | .. Social Feed Manager FAQ file 2 | 3 | Frequently Asked Questions 4 | ========================== 5 | 6 | Does Social Feed Manager capture photos and other media embedded in tweets? 7 | --------------------------------------------------------------------------- 8 | 9 | As of version m5, no. But this is something we're looking forward 10 | to implementing in the near term. 11 | 12 | 13 | How far back in time does SFM go when collecting a TwitterUser's tweets? 14 | ------------------------------------------------------------------------ 15 | 16 | The Twitter API only provides up to the most recent 3200 tweets for an 17 | account. When a new TwitterUser is added in SFM, the user_timeline script 18 | will request as many tweets as the Twitter API can provide, i.e. up to 3200. 19 | 20 | 21 | Does Social Feed Manager capture the followers list? 22 | ---------------------------------------------------- 23 | 24 | No. SFM does capture the number of followers at the time the tweet was retrieved. However, the Twitter API does provide a way to retrieve an account's 25 | follower list. 26 | 27 | 28 | Do I have to set up supervisord in order to use filterstreams or streamsample? 29 | ------------------------------------------------------------------------------ 30 | 31 | No, filterstreams and streamsample can also be run manually using the 32 | *filterstream* and *streamsample* management commands described in the 33 | management commands page. 34 | 35 | 36 | The number of retweets in a TwitterItem is inconsistent with the number of retweets shown on the tweet in Twitter. Why? 37 | ------------------------------------------------------------------------------------------------------------------------- 38 | 39 | TwitterItems are created as they appear at the time they are captured 40 | from Twitter. However, tweets on Twitter can change afterwards; they 41 | can be further retweeted, they can be deleted, etc. In fact, an advantage 42 | of using SFM is that it takes a snapshot of tweets before they change or disappear! 43 | 44 | Currently there is no way to "update" a TwitterItem with any changes that 45 | may have occurred to the corresponding tweet. This is something we might 46 | consider if there is a use case for it. 47 | 48 | 49 | Does the SFM web interface provide a way to view the files generated by filterstream and streamsample? 50 | ------------------------------------------------------------------------------------------------------ 51 | Not yet. 52 | 53 | 54 | When I click on the link to view a raw tweet, it's difficult to read in my browser. 55 | ----------------------------------------------------------------------------------- 56 | There are a number of broswer plugins available (JSONovich, JSONView, and 57 | others) which improve the way that JSON is displayed. 58 | 59 | 60 | Can I set this up on a Mac? 61 | ------------------------------ 62 | 63 | We haven't been running this on a Mac, but a colleague we met at Code4Lib 2014 64 | has done it. Check out his blog post here: http://dicarve.blogspot.com/2014/04/an-relatively-easy-way-for-installing.html 65 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Social Feed Manager documentation master file, created by 2 | sphinx-quickstart on Mon Apr 28 14:17:36 2014. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Social Feed Manager! 7 | =============================== 8 | 9 | Social Feed Manager is a Django application developed by George Washington University Libraries to collect social media data from Twitter. It connects to Twitter's approved API to collect data in bulk and makes it possible for scholars, students, and librarians to identify, select, collect, and preserve Twitter data for research purposes. 10 | 11 | The application code is open source and `available on github`_. 12 | 13 | .. _available on github: http://github.com/gwu-libraries/social-feed-manager/ 14 | 15 | **Social Feed Manager was rewritten in 2016; this documentation is for the "old" SFM.** The new SFM documentation is located at `http://sfm.readthedocs.org/ `_ . The application code for the new SFM is available at the `sfm-ui repo on github`_ and there is a complete `project site `_ as well. 16 | 17 | .. _sfm-ui repo on github: https://github.com/gwu-libraries/sfm-ui 18 | .. _wiki: https://github.com/gwu-libraries/sfm-ui/wiki 19 | 20 | Contents: 21 | 22 | .. toctree:: 23 | :maxdepth: 2 24 | 25 | Introduction 26 | Installation and Configuration 27 | Daily operations 28 | Management commands 29 | Working with supervisor and streams 30 | Use Cases 31 | Data Dictionary 32 | Frequently Asked Questions 33 | Troubleshooting 34 | Release Notes 35 | -------------------------------------------------------------------------------- /docs/intro.rst: -------------------------------------------------------------------------------- 1 | .. Social Feed Manager Introduction file 2 | 3 | Introduction 4 | ============ 5 | 6 | Overview 7 | -------- 8 | Social Feed Manager is open source software for locally capturing public data from Twitter. 9 | It makes it possible for librarians, archivists, scholars, and students to: 10 | 11 | - identify, select, collect, and preserve "at risk" social media data 12 | - gather datasets of tweets in bulk for analysis in other software packages 13 | - fill gaps in special collections 14 | - archive the social media activity of their library or institution. 15 | 16 | The software connects to Twitter's approved public APIs to collect tweets by specific users, search current tweets by keyword, and filter by geolocation. We hope to add other social media platforms in the future. 17 | 18 | Features 19 | -------- 20 | - Collects tweets account by account 21 | - Queries streaming APIs by keyword, user, and geolocation 22 | - Captures Twitter's sample stream (currently ~0.5-1% of tweets) 23 | - Manages multiple streams reliably 24 | - Respectful of Twitter rate limits 25 | - Groups tweets into sets for easier management 26 | - CSV export, which can be uploaded into analysis software of the researcher's choice 27 | - Web-based interfaces for researchers / data users and administrators 28 | - Command-line updates for application administrator 29 | - Streaming data output to compressed rolling files in date/time hierarchy 30 | 31 | Current uses at George Washington University 32 | -------------------------------------------- 33 | - The University Archives is gathering tweets by university offices and student organizations, capturing an aspect of student life whose main online presence is on social media 34 | - Faculty in the School of Media and Public Affairs are studying how journalists, activist organizations, and members of Congress tweet 35 | - Students in digital journalism are learning how to analyze tweets to inform reporting 36 | - Computer science faculty are using tweet datasets to train machine learning algorithms 37 | 38 | 39 | Technical and staffing considerations 40 | ------------------------------------- 41 | Social Feed Manager is locally hosted and requires a system administrator to set up and manage the application in a Linux (Ubuntu 12.04) environment. Storage requirements vary depending on usage of the application: collecting data account-by-account requires less storage than connecting to the streaming APIs, which accumulates large files. 42 | 43 | Archivists, librarians and other service administrators, as determined by the library, use a web-based interface to add 44 | new Twitter users, specify keyword queries, and create sets of accounts. Researchers may directly download user timeline data 45 | from the web interface after signing into the site from an institutional IP address and using their own Twitter credentials. Currently, all captured user timeline data is available in the researcher interface and is not separated by researcher account. Accessing files generated from the streaming APIs requires mediation by a system administrator. 46 | 47 | Development and community 48 | ------------------------- 49 | Social Feed Manager was developed at The George Washington University 50 | Libraries in 2012 as a prototype application and is now being supported by 51 | multiple developers and an `IMLS Sparks! Innovation Grant 52 | ` [#f1]_. Several 53 | libraries and archives have installed it and are providing feedback to help 54 | prioritize development of new features. 55 | 56 | The software is available for use, study, copying, and modification under a 57 | free and open source software license (MIT license). We welcome others to 58 | become involved in the project and contribute to the code. 59 | 60 | Contact us 61 | ^^^^^^^^^^ 62 | 63 | - `sfm-dev Google Group `_ 64 | - Developers responsible for the app include Dan Chudnov (`@dchud `_), 65 | Dan Kerchner (`@dankerchner `_), 66 | Ankushi Sharma (`@ankushis `_), and 67 | Laura Wrubel (`@liblaura `_). 68 | 69 | Resources 70 | ^^^^^^^^^ 71 | `Social Feed Manager on Github 72 | `_ 73 | 74 | `Google Group (updates about new releases and discussion of features) 75 | `_ 76 | 77 | .. rubric:: Footnotes 78 | .. [#f1] Institute of Museum and Library Services Grant LG-46-13-0257 79 | -------------------------------------------------------------------------------- /docs/m4_001_release_notes.rst: -------------------------------------------------------------------------------- 1 | 2 | m4_001 release notes 3 | ==================== 4 | 5 | 6 | **m4_001** introduces collecting expanded urls in tweets, improves use 7 | of supervisord to manage multiple processes, and enhances organizedata 8 | to better structure data files. It also fixes bugs related to supervisord 9 | and cleans up twitteruser status and filterstream issues. 10 | 11 | If you are upgrading an existing SFM instance from a version prior to 12 | m4_001, to m4_001 or newer, and your instance contains active TwitterFilters, 13 | then you will need to run the :ref:`createconf` management command. 14 | 15 | **Enhancements** 16 | 17 | Social Feed Manager has streamsample and filterstream management 18 | commands which are used to fetch random or filtered twitter feeds. These 19 | management process are automated using supervisord. Supervisord manages 20 | the streamsample and filterstream processes, starting and stopping 21 | these processes when required. 22 | 23 | 24 | Supervisord control: 25 | 26 | - #135 - streamsample and filterstream are managed by supervisord, SFM no 27 | longer requires manual run of these commands, if supervisord is set up, 28 | everything is handled by supervisord. This is done using the post_save 29 | signal sent from the UI to initiate these processes. 30 | 31 | 32 | - #133 - Twitter API doesn't allow parallel streams like streamsample and 33 | filterstream to run concurrently with the same authorization 34 | credentials, so run a validation in the admin UI when adding the filters 35 | using twitterfilter, and validate that active streams do not conflict. 36 | 37 | - #170 - To simplify naming, renamed rules in admin UI to twitterfilter 38 | and throughout SFM. 39 | 40 | 41 | Twitter data organization: 42 | 43 | - #132 - Re-fit organizedata to use subdirs for different filters. 44 | 45 | 46 | - #119 - Added command and table to fetch and store expanded form of urls 47 | found in tweets. 48 | 49 | 50 | **Other issues and bugfixes** 51 | 52 | - #177 - Refactored signal call to createconf to be specific to the appropriate 53 | filter. 54 | 55 | - #150 - Better handling of deactivation of TwitterUser status for no-longer 56 | Twitter-valid accounts, validating and throwing errors if name is not unique. 57 | 58 | 59 | See the `complete list of changes for milestone m4_001 in github `_. 60 | 61 | .. _m4_001: https://github.com/gwu-libraries/social-feed-manager/issues?milestone=5&state=closed 62 | 63 | -------------------------------------------------------------------------------- /docs/m4_002_release_notes.rst: -------------------------------------------------------------------------------- 1 | 2 | m4_002 release notes 3 | ==================== 4 | 5 | **m4_002** improves process management under Supervisord. Previously 6 | it was necessary to start and stop SFM's supervisord-managed processes 7 | using the supervisorctl tool at the command line. 8 | 9 | With m4_002, SFM now automatically starts and stops twitterfilter 10 | processes when TwitterFilters are created, activated, deactivated, or 11 | deleted by an SFM admin user. 12 | 13 | A new management command, :ref:`fetch_tweets_by_id`, was also added. Given 14 | a list of tweet ids, the command fetches the associated tweets as JSON. 15 | 16 | **Significant issues and bugfixes** 17 | 18 | - #89 - Added management command to fetch tweets by a list of tweet ids. 19 | 20 | - #154 - Enabled supervisord to pick up new twitterfilter conf files and 21 | initiate processes correctly. 22 | 23 | See the `complete list of changes for milestone m4_002 in github `_. 24 | 25 | .. _m4_002: https://github.com/gwu-libraries/social-feed-manager/issues?milestone=7&page=1&state=closed 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/m5_001_release_notes.rst: -------------------------------------------------------------------------------- 1 | 2 | m5_001 release notes 3 | ==================== 4 | 5 | **m5_001** is release focused primarily on documentation. 6 | SFM now has substantial documentation on what it does, how it works, and how 7 | to use it. 8 | 9 | **Documentation** contains a list of docs explaining: 10 | 11 | * getting started 12 | * the installation and working of Social Feed Manager 13 | * its current use cases, where and how it's used now, and its scopes of 14 | enhancements 15 | * user lifecycle 16 | * features; how you can use them and automate them 17 | * FAQ and troubleshooting 18 | 19 | For more details visit the `Social Feed Manager docs`_. 20 | 21 | .. _Social Feed Manager docs: http://social-feed-manager.readthedocs.org/ 22 | 23 | **Non-Documentation Issues and Bugfixes** 24 | 25 | * #146 - Improved validation and error handling for TwitterUser.name 26 | 27 | 28 | See the `complete list of changes for milestone m5_001 in github `_. 29 | 30 | .. _m5_001: https://github.com/gwu-libraries/social-feed-manager/issues?milestone=6&page=1&state=closed 31 | -------------------------------------------------------------------------------- /docs/m5_002_release_notes.rst: -------------------------------------------------------------------------------- 1 | 2 | m5_002 release notes 3 | ==================== 4 | 5 | **m5_002** is release which provides: 6 | 7 | * a new management command, add_userlist, to add a list of users in bulk. 8 | This command can also add the new users to a TwitterUserSet 9 | * a new /status page available to admin users. This page displays the 10 | current status of all supervisord-managed SFM streams 11 | * a new --list option for the filterstream management command, to display 12 | the current status of all supervisord-managed SFM streams 13 | * improvements to the CSV export 14 | * links available in the UI header are now consistent across different pages 15 | * an Excel (.xls) download link available on user timeline pages 16 | * a new management command to export user timelines in .xls format 17 | * encoding improvements in the CSV export 18 | * improved instructions for supervisord setup and configuration 19 | * minor documentation fixes 20 | 21 | See the `complete list of changes for milestone m5_002 in github `_. 22 | 23 | .. _m5_002: https://github.com/gwu-libraries/social-feed-manager/issues?q=milestone%3Am5_002+is%3Aclosed 24 | -------------------------------------------------------------------------------- /docs/m5_003_release_notes.rst: -------------------------------------------------------------------------------- 1 | 2 | m5_003 release notes 3 | ==================== 4 | 5 | **m5_003** is release which provides: 6 | 7 | * Improvements to the extracts: 8 | 9 | * A header row with column titles has been added to the CSV version 10 | * A column has been added for ['coordinates'] when present in a tweet 11 | 12 | * Efficiency improvements 13 | 14 | * Improvements to fetch_urls, making use of django-queryset-iterator and newer features of the requests library (thanks `@cazzerson `_) 15 | * UID lookups by name are now done using a single bulk lookup API call 16 | * tweet rehydration (with the ``fetch_tweets_by_id`` management command) is now done using a bulk API call (thanks `@edsu `_) 17 | 18 | * Updated python dependencies, with tweepy version now pinned to 3.2.0 (prior to this, it was not pinned, and the app only worked with =< 2.3.0). Requests is now constrained to > 2.4.1 19 | * Simplified naming scheme for the timestamped filterstream and samplestream files; colons have been removed from the naming scheme. 20 | * Cleaner error messages in add_userlist. add_userlist now provides more readable and specific messages when an account name was found to be invalid or suspended. 21 | * Elimination of blank lines between tweets written to zip files by ``filterstream`` (thanks `@edsu `_). **NOTE: This may affect any scripts that read or process your filterstream output files.** 22 | * Better use of the Twitter streaming API through SFM TwitterFilters. Rewrote TwitterFilter parsing of Words, People, and Location fields. These parameters now leverage the full capability of Twitter's streaming API, as follows: 23 | 24 | * *Words* (passed to Twitter ``track`` parameter). Commas between terms function as logical ORs; spaces between terms function as logical ANDs. More information at `Twitter's documentation of the streaming request parameters `_. 25 | * *People* (passed to the Twitter ``follow`` parameter). This parameter is now a comma-separated list of valid Twitter usernames. Twitterfilters already present in SFM will be migrated as part of the database migration required for m5_003. Prior to m5_003, People values had been sent (incorrectly) as account names rather than uids; they are now sent as uids. Since the People parameter had been a space-separated list, People values are migrated to comma-separated lists. The migration script also looks up and stores the Twitter id for each account in the People list; accounts whose ids were not found (e.g. suspended accounts, accounts not found, etc.) are logged in the migration log file. When updating or saving a Twitterfilter in m5_003, SFM checks the validity of each of the values in the People list and will not allow saving to proceed if it contains an account name whose id cannot be found. 26 | * *Locations* (passed to the Twitter ``locations`` parameter). This parameter should be a comma-separated list of numeric values, which each value between -180 and 180. Each set of 4 values defines a geographic bounding box (long, lat, long, lat); a list of 8, 12, etc. values would define multiple bounding boxes. While this parameter has not changed, m5_003 provides improved validation. 27 | 28 | 29 | **NOTE: The requirements.txt file has changed, so upgrading to m5_003 requires updating python library dependencies within your virtualenv.** To update dependencies: 30 | :: 31 | % source ENV/bin/activate 32 | % pip install -r requirements.txt 33 | 34 | **NOTE: Upgrading to m5_003 requires running a database migration.** To run the migration: 35 | :: 36 | % ./manage.py migrate ui 37 | 38 | The migration number is 0026. If any active TwitterFilters contained People values for which Twitter uids were not available (for example, if the account was not found or was suspended), then a migration log file, ``0026_migration.log``, is generated, listing the account names for which uids were not available. 39 | 40 | 41 | See the `complete list of changes for milestone m5_003 in github `_. 42 | 43 | .. _m5_003: https://github.com/gwu-libraries/social-feed-manager/issues?q=is%3Aissue+is%3Aclosed+milestone%3Am5_003 44 | .. _twitter_track: https://dev.twitter.com/streaming/overview/request-parameters#track 45 | -------------------------------------------------------------------------------- /docs/m5_004_release_notes.rst: -------------------------------------------------------------------------------- 1 | 2 | m5_004 release notes 3 | ==================== 4 | 5 | **m5_004** is release which provides: 6 | 7 | * Docker support (see https://github.com/gwu-libraries/social-feed-manager/blob/m5_004/docker/README.md) 8 | * Extract files now in XLSX format (was XLS). The XLSX format removes the limitation 9 | of 65,536 rows. SFM m5_004 substituted the openpyxl library for xlwt. 10 | * SFM now uses tweepy v3.4.0. This seems to eliminate the problem observed in m5_003, 11 | which used tweepy 3.2.0, where filterstream jobs stopped writing and/or slowed down 12 | the server. 13 | * Several documentation improvements, mostly around installation and around supervisor/filterstreams setup. 14 | * UI cleanup: Improved consistency of date-time format rendering. 15 | * Enhanced validation logic around checking People values when adding or updating filterstreams: 16 | If one or more People account names wasn't found when checking against Twitter, the TwitterFilter 17 | does save, but presents a warning listing the invalid accounts. 18 | * Now allows deletion of filterstreams even if Supervisor isn't running. 19 | * Now allows creation/update of filterstreams even if Supervisor isn't running (however, these won't automatically reflect updates when Supervisor is restarted (See `#376 `_). 20 | * Updated Apache2 configuration file to note recommended changes for deployment on Ubuntu 14 / Apache2 v2.4+ 21 | * Enhanced fetch_urls to more gracefully handle (skip) truncated URLs that may appear in retweets with an ellipsis, and to pull URLs from 'media' if possible. 22 | * And several other minor enhancements. 23 | 24 | See the `complete list of changes for milestone m5_004 in github `_ as well as the `code changes from m5_003 to m5_004 `_. 25 | 26 | Upgrade Notes: 27 | 28 | * The requirements.txt file has changed, so Python library dependencies must 29 | be updated within your virtualenv. To update dependencies: 30 | 31 | :: 32 | 33 | % source ENV/bin/activate 34 | % pip install -r requirements.txt 35 | 36 | 37 | Contributors: 38 | 39 | * Dan Chudnov, Dan Kerchner, Laura Wrubel, Justin Littman, Ankushi Sharma, and Rajat Vij at The George Washington University Libraries 40 | * Ed Summers at Maryland Institute for the Humanities (MITH) 41 | * Martin Klein at UCLA Research Library 42 | 43 | 44 | .. _m5_004: https://github.com/gwu-libraries/social-feed-manager/issues?q=is%3Aissue+is%3Aclosed+milestone%3Am5_004 45 | .. _twitter_track: https://dev.twitter.com/streaming/overview/request-parameters#track 46 | .. _m5_003_to_m5_004_diff: https://github.com/gwu-libraries/social-feed-manager/compare/m5_003...m5_004 47 | -------------------------------------------------------------------------------- /docs/release_notes.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | Release Notes 4 | ============= 5 | Release notes for the official SFM releases. Each release note will tell you what’s new in each version, and will also describe any backwards-incompatible changes made in that version. 6 | 7 | For those upgrading to a new version of SFM, you will need to check all the backwards-incompatible changes and deprecated features for each ‘final’ release from the one after your current SFM version, up to and including the new version. 8 | 9 | Final Releases: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | m5_004 release notes 15 | m5_003 release notes 16 | m5_002 release notes 17 | m5_001 release notes 18 | m4_002 release notes 19 | m4_001 release notes 20 | 21 | -------------------------------------------------------------------------------- /docs/troubleshooting.rst: -------------------------------------------------------------------------------- 1 | .. Social Feed Manager Troubleshooting file 2 | 3 | Troubleshooting 4 | =============== 5 | 6 | TwitterUserItemUrls is empty. Why isn't SFM fetching URLS? 7 | ----------------------------------------------------------- 8 | 9 | Have you set up a cron job to run fetch_urls? 10 | 11 | 12 | I tried to add a filterstream using the user that I've configured as TWITTER_DEFAULT_USER, but SFM is telling me that Streamsample is also configured to authenticate as that user. But I'm not using Streamsample! 13 | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 14 | 15 | SFM makes the assumption that streamsample either is being used or may be 16 | used in the future, and streamsample authenticates with the Twitter API using 17 | TWITTER_DEFAULT_USER. Due to Twitter API's rate limiting, SFM prevents 18 | the possibility of having multiple streams (in this case, streamsample and a 19 | filterstream) simultaneously calling the Twitter streaming API with the same Twitter user name. 20 | -------------------------------------------------------------------------------- /docs/twitter_filter_rule_sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/docs/twitter_filter_rule_sample.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | django>1.6,<1.7 2 | django-social-auth 3 | lxml 4 | psycopg2 5 | pytz 6 | requests==2.5.3 7 | south 8 | tweepy==3.4.0 9 | supervisor 10 | django-queryset-iterator 11 | openpyxl 12 | -------------------------------------------------------------------------------- /sfm/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", "sfm.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /sfm/sfm/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/sfm/__init__.py -------------------------------------------------------------------------------- /sfm/sfm/apache.conf: -------------------------------------------------------------------------------- 1 | Alias /static/ /PATH/TO/sfm/ui/static/ 2 | 3 | # For Ubuntu 14 / Apache2 v2.4 or above, substitute 4 | # Require all granted 5 | # in place of the next two lines. The next two lines are used for 6 | # Ubuntu 12 / Apache2 v2.2 deployments. 7 | Order deny, allow 8 | Allow from all 9 | 10 | 11 | # For WSGI daemon mode: 12 | # see http://code.google.com/p/modwsgi/wiki/QuickConfigurationGuide 13 | WSGIDaemonProcess YOUR.HOSTNAME.HERE processes=2 threads=15 python-path=/PATH/TO/YOUR/VENV/lib/python2.7/site-packages:/PATH/TO/sfm 14 | WSGIProcessGroup YOUR.HOSTNAME.HERE 15 | 16 | # For WSGI embedded mode: 17 | #WSGIPythonPath /PATH/TO/sfm 18 | # If using a virtualenv, uncomment and tweak next line (inc. python version): 19 | # WSGIPythonPath /PATH/TO/YOUR/VENV/lib/python2.7/site-packages 20 | 21 | WSGIScriptAlias / /PATH/TO/sfm/sfm/wsgi.py 22 | 23 | 24 | 25 | # For Ubuntu 14 / Apache2 v2.4 or above, substitute 26 | # Require all granted 27 | # in place of the next two lines. The next two lines are used for 28 | # Ubuntu 12 / Apache2 v2.2 deployments. 29 | Order deny, allow 30 | Allow from all 31 | 32 | 33 | -------------------------------------------------------------------------------- /sfm/sfm/local_settings.py.template: -------------------------------------------------------------------------------- 1 | DEBUG = True 2 | 3 | ADMINS = ( 4 | ('Your Name', 'your.address@example.com'), 5 | ) 6 | 7 | MANAGERS = ADMINS 8 | 9 | # This value should be something like [sfm-test] (with a trailing space) 10 | EMAIL_SUBJECT_PREFIX = ' ' 11 | 12 | # Set SERVER_EMIL to root@myserver, e.g. 'root@gwsfm-test.wrlc.org' 13 | SERVER_EMAIL = '' 14 | 15 | DATABASES = { 16 | 'default': { 17 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 18 | 'NAME': 'sfm', 19 | 'USER': '', 20 | 'PASSWORD': '', 21 | 'HOST': '', 22 | 'PORT': '', 23 | } 24 | } 25 | 26 | ALLOWED_HOSTS = ['YOUR.PUBLIC.DOMAIN.NAME'] 27 | 28 | # See https://docs.djangoproject.com/en/1.4/topics/cache/ 29 | CACHES = { 30 | 'default': { 31 | 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 32 | 'LOCATION': 'your-unique-sfm-instance-name', 33 | } 34 | } 35 | 36 | TEMPLATE_DIRS = ( 37 | ) 38 | 39 | DATA_DIR = '/PATH/TO/YOUR/DATA/DIR' 40 | 41 | # If not specified, the supervisor configuration files 42 | # will use the owner of the sfm app process. The owner must 43 | # be able to write to /var/log/sfm 44 | #SUPERVISOR_PROCESS_USER = '' 45 | 46 | # Path to the supervisor Unix socket file (with no unix:// prefix) 47 | # e.g. '/var/run/supervisor.sock'. Note that this must match the Unix 48 | # socket file specified in /etc/supervisor/supervisord.conf 49 | SUPERVISOR_UNIX_SOCKET_FILE = '/var/run/supervisor.sock' 50 | 51 | TWITTER_DEFAULT_USERNAME = 'MY FANCY USERNAME' 52 | TWITTER_CONSUMER_KEY = 'ITS CONSUMER KEY' 53 | TWITTER_CONSUMER_SECRET = 'ITS CONSUMER SECRET' 54 | 55 | BRANDING = { 56 | # Required: 57 | 'institution': '', 58 | 'URL': '', 59 | # Optional: 60 | # address may contain any number of elements 61 | 'address': ['', '', ''], 62 | 'email': '', 63 | # logofile should be placed in static/img 64 | 'logofile': '', 65 | } 66 | -------------------------------------------------------------------------------- /sfm/sfm/supervisor.d/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/sfm/supervisor.d/__init__.py -------------------------------------------------------------------------------- /sfm/sfm/supervisor.d/streamsample.conf.template: -------------------------------------------------------------------------------- 1 | [program:streamsample] 2 | command=/ENV/bin/python /sfm/manage.py streamsample --save 3 | user= 4 | autostart=true 5 | autorestart=true 6 | stderr_logfile=/var/log/sfm/streamsample.err.log 7 | stdout_logfile=/var/log/sfm/streamsample.out.log 8 | -------------------------------------------------------------------------------- /sfm/sfm/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | from django.contrib import admin 3 | from django.views.generic import TemplateView 4 | 5 | admin.autodiscover() 6 | 7 | urlpatterns = patterns('', 8 | url(r'^admin/', include(admin.site.urls)), 9 | url(r'^login/$', 'django.contrib.auth.views.login', name='login'), 10 | ) 11 | 12 | urlpatterns += patterns('ui.views', 13 | url(r'^$', 'home', name='home'), 14 | url(r'^logout/$', 'logout', name='logout'), 15 | url(r'^about/$', TemplateView.as_view(template_name='about.html'), 16 | name='about'), 17 | url(r'^django_no_superuser/$', TemplateView.as_view(template_name= 18 | 'django_no_superuser.html'), name='django_no_superuser'), 19 | 20 | # twitter data patterns 21 | url(r'^search/$', 'search', name='search'), 22 | url(r'^tweets/$', 'tweets', name='tweets'), 23 | url(r'^users/alpha/$', 'users_alpha', name='users_alpha'), 24 | url(r'^twitter-user/(?P[a-zA-Z0-9_]+)/$', 'twitter_user', 25 | name='twitter_user'), 26 | url(r'^twitter-user/(?P[a-zA-Z0-9_]+).csv$', 'twitter_user_csv', 27 | name='twitter_user_csv'), 28 | url(r'^twitter-user/(?P[a-zA-Z0-9_]+).xls', 'twitter_user_xls', 29 | name='twitter_user_xls'), 30 | url(r'^twitter-item/(?P[0-9]+)/$', 'twitter_item', 31 | name='twitter_item'), 32 | url(r'^twitter-item/(?P[0-9]+)/links/$', 'twitter_item_links', 33 | name='twitter_item_links'), 34 | url(r'^status/$', 'status', name='status'), 35 | 36 | url(r'', include('social_auth.urls')), 37 | ) 38 | -------------------------------------------------------------------------------- /sfm/sfm/wsgi.py.template: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for sfm project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | # if using a virtualenv, uncomment and set the next three lines appropriately 17 | #import site 18 | #ENV = '/PATH/TO/YOUR/VIRTUALENV' 19 | #site.addsitedir(ENV + '/lib/python2.7/site-packages') 20 | 21 | import os 22 | 23 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sfm.settings") 24 | 25 | # This application object is used by any WSGI server configured to use this 26 | # file. This includes Django's development server, if the WSGI_APPLICATION 27 | # setting points here. 28 | from django.core.wsgi import get_wsgi_application 29 | application = get_wsgi_application() 30 | 31 | # Apply WSGI middleware here. 32 | # from helloworld.wsgi import HelloWorldApplication 33 | # application = HelloWorldApplication(application) 34 | -------------------------------------------------------------------------------- /sfm/ui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/__init__.py -------------------------------------------------------------------------------- /sfm/ui/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/management/__init__.py -------------------------------------------------------------------------------- /sfm/ui/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/management/commands/__init__.py -------------------------------------------------------------------------------- /sfm/ui/management/commands/createconf.py: -------------------------------------------------------------------------------- 1 | from optparse import make_option 2 | 3 | from django.core.management.base import BaseCommand 4 | 5 | from ui.models import TwitterFilter 6 | from ui.utils import create_conf_file 7 | 8 | 9 | class Command(BaseCommand): 10 | help = "create/update filterstream process config files for supervisord" 11 | option_list = BaseCommand.option_list + ( 12 | make_option('--twitterfilter', action='store', default=None, 13 | dest='twitterfilter', help='specify the filter rule id'), 14 | ) 15 | 16 | def handle(self, *args, **options): 17 | twitter_filters = TwitterFilter.objects.filter(is_active=True) 18 | if options.get('twitterfilter', None): 19 | twitter_filters = twitter_filters.filter( 20 | id=options.get('twitterfilter')) 21 | for tf in twitter_filters: 22 | create_conf_file(tf.id) 23 | -------------------------------------------------------------------------------- /sfm/ui/management/commands/fetch_tweets_by_id.py: -------------------------------------------------------------------------------- 1 | import json 2 | from optparse import make_option 3 | 4 | from django.conf import settings 5 | from django.core.management.base import BaseCommand 6 | 7 | import tweepy 8 | import sys 9 | from ui.models import authenticated_api 10 | 11 | 12 | class Command(BaseCommand): 13 | help = 'Fetch tweet data as JSON for a list of tweet ids' \ 14 | 15 | option_list = BaseCommand.option_list + ( 16 | make_option('--inputfile', action='store', 17 | default=None, help='Path of the input file containing \ 18 | a list of tweet ids, each on a separate line'), 19 | make_option('--outputfile', action='store', 20 | default=None, help='Path of the output file'), 21 | make_option('--limit', action='store', 22 | default=None, help='Limit on the number of tweets to fetch.'), 23 | 24 | ) 25 | 26 | def handle(self, *args, **options): 27 | if options['inputfile'] is None: 28 | print 'Please specify a valid input file using --inputfile' 29 | return 30 | 31 | infile = options['inputfile'] 32 | fin = open(infile, 'r+') 33 | logfile = infile + '.log' 34 | flog = open(logfile, 'w') 35 | if options['outputfile']: 36 | outfile = options['outputfile'] 37 | outstream = open(outfile, 'w') 38 | else: 39 | outstream = sys.stdout 40 | limit = int(options['limit']) if options['limit'] else None 41 | api = authenticated_api(username=settings.TWITTER_DEFAULT_USERNAME) 42 | assert api 43 | errors_occurred = False 44 | tweet_ids = [] 45 | for count, tweetidline in enumerate(fin): 46 | if limit and limit == count: 47 | print "Reached limit of %s tweets" % limit 48 | break 49 | tweet_ids.append(tweetidline[:-1]) 50 | if len(tweet_ids) == 100: 51 | errors_occurred = self.fetch(tweet_ids, api, outstream, flog) or errors_occurred 52 | tweet_ids = [] 53 | #Final fetch 54 | errors_occurred = self.fetch(tweet_ids, api, outstream, flog) or errors_occurred 55 | fin.close() 56 | flog.close() 57 | if options.get('outputfile', True): 58 | outstream.close() 59 | if errors_occurred: 60 | print 'Completed with errors. Please view the log file (%s) for details' % logfile 61 | 62 | def fetch(self, tweet_ids, api, outstream, flog): 63 | if tweet_ids: 64 | try: 65 | statuses = api.statuses_lookup(tweet_ids) 66 | for status in statuses: 67 | json_value = json.dumps(status) + '\n\n' 68 | outstream.write(json_value) 69 | except tweepy.error.TweepError as e: 70 | content = 'Error: %s for the tweetids: %s' \ 71 | % (e, tweet_ids) + '\n' 72 | flog.write(content) 73 | #Return true if errors occurred 74 | return True 75 | return False 76 | -------------------------------------------------------------------------------- /sfm/ui/management/commands/organizedata.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.core.management.base import BaseCommand 3 | 4 | import os 5 | import shutil 6 | import time 7 | 8 | 9 | class Command(BaseCommand): 10 | help = 'move data from the data_dir into date-structured dirs' 11 | 12 | def handle(self, *args, **options): 13 | # for every file in the data dir, eg: 14 | # DATA/PREFIX-2012-04-22T17-33-44Z.xml.gz 15 | # if the modification time is greater than 16 | # (2 * settings.SAVE_INTERVAL_SECONDS): 17 | # make sure there's a directory under DATA names PREFIX 18 | # make sure there's a DATA/PREFIX/2012/04/22 19 | # move it there, without changing its name 20 | data_files = os.listdir(settings.DATA_DIR) 21 | for fname in data_files: 22 | data_file = '%s/%s' % (settings.DATA_DIR, fname) 23 | stat = os.stat(data_file) 24 | threshhold_seconds = 2 * settings.SAVE_INTERVAL_SECONDS 25 | if time.time() - stat.st_mtime < threshhold_seconds: 26 | continue 27 | # pull out the prefix, year, month, day, and hour 28 | try: 29 | # patterns: 30 | # twitterfilter-2-2014-04-11T04-34-28Z.gz 31 | # sample-2014-04-11T04-34-28Z.gz 32 | prefixed_date, t, time_ext = fname.partition('T') 33 | # get process name by truncating last 11 chars: -YYYY-MM-DD 34 | processname = prefixed_date[:-11] 35 | # get date as the last 10 chars: YYYY-MM-DD 36 | the_date = prefixed_date[-10:] 37 | year, month, day = the_date.split('-') 38 | #This supports old-style filenames, which used : as separator 39 | #as well as new-style, which uses -. 40 | hour, minute, seconds_ext = time_ext.split(':' if ':' in time_ext else '-') 41 | except: 42 | # probably a prefix/directory 43 | continue 44 | subdir = '%s/%s/%s/%s/%s/%s' % (settings.DATA_DIR, processname, 45 | year, month, day, hour) 46 | try: 47 | os.stat(subdir) 48 | except: 49 | os.makedirs(subdir) 50 | try: 51 | shutil.copy2(data_file, subdir) 52 | os.remove(data_file) 53 | print 'moved %s to %s' % (data_file, subdir) 54 | except Exception, e: 55 | print 'unable to move %s' % data_file 56 | print e 57 | -------------------------------------------------------------------------------- /sfm/ui/management/commands/populate_uids.py: -------------------------------------------------------------------------------- 1 | from optparse import make_option 2 | 3 | from django.conf import settings 4 | from django.core.management.base import BaseCommand 5 | 6 | from ui.models import authenticated_api 7 | from ui.models import populate_uid 8 | from ui.models import TwitterUser 9 | 10 | 11 | class Command(BaseCommand): 12 | """ 13 | Cycle through all the TwitterUsers we're tracking. For each TwitterUser, 14 | if the uid==0 (default value, i.e. it hasn't been set yet), look the 15 | user up by name and populate the uid. 16 | """ 17 | 18 | help = 'Fetch uids for twitter users by name, where uids ' \ 19 | + 'are not populated. Intended for migrating old ' \ 20 | + 'databases prior to m2_001.' 21 | 22 | option_list = BaseCommand.option_list + ( 23 | make_option('--user', dest='user', 24 | default=None, help='Specific user to update'), 25 | ) 26 | 27 | def handle(self, *args, **options): 28 | api = authenticated_api(username=settings.TWITTER_DEFAULT_USERNAME) 29 | qs_tweeps = TwitterUser.objects.filter(is_active=True) 30 | # if a username has been specified, limit to only that user 31 | if options.get('user', None): 32 | qs_tweeps = qs_tweeps.filter(name=options.get('user')) 33 | for tweep in qs_tweeps: 34 | print 'user: %s' % tweep.name 35 | # check user status, update twitter user name if it has changed 36 | populate_uid(tweep.name, api) 37 | -------------------------------------------------------------------------------- /sfm/ui/management/commands/streamsample.py: -------------------------------------------------------------------------------- 1 | from optparse import make_option 2 | 3 | from django.conf import settings 4 | from django.core.management.base import BaseCommand 5 | from django.contrib.auth.models import User 6 | 7 | import tweepy 8 | from tweepy.streaming import StreamListener 9 | 10 | from ui.models import RotatingFile 11 | 12 | 13 | class StdOutListener(StreamListener): 14 | 15 | def on_data(self, data): 16 | print data 17 | return True 18 | 19 | def on_error(self, status): 20 | print status 21 | 22 | 23 | class Command(BaseCommand): 24 | help = 'Show or save the Twitter sample/spritzer feed' 25 | 26 | option_list = BaseCommand.option_list + ( 27 | make_option('--save', action='store_true', default=False, 28 | dest='save', help='save the data to disk'), 29 | make_option('--dir', action='store', type='string', 30 | default=settings.DATA_DIR, dest='dir', 31 | help='directory for storing the data (default=%s)' 32 | % settings.DATA_DIR), 33 | make_option('--interval', action='store', type='int', 34 | default=settings.SAVE_INTERVAL_SECONDS, dest='interval', 35 | help='how often to save data (default=%s)' 36 | % settings.SAVE_INTERVAL_SECONDS), 37 | ) 38 | 39 | def handle(self, *args, **options): 40 | user = User.objects.get(username=settings.TWITTER_DEFAULT_USERNAME) 41 | sa = user.social_auth.all()[0] 42 | auth = tweepy.OAuthHandler(settings.TWITTER_CONSUMER_KEY, 43 | settings.TWITTER_CONSUMER_SECRET) 44 | auth.set_access_token(sa.tokens['oauth_token'], 45 | sa.tokens['oauth_token_secret']) 46 | if options.get('save', True): 47 | listener = RotatingFile( 48 | filename_prefix='streamsample', 49 | save_interval_seconds=options['interval'], 50 | data_dir=options['dir']) 51 | stream = tweepy.Stream(auth, listener) 52 | stream.sample() 53 | else: 54 | listener = StdOutListener() 55 | stream = tweepy.Stream(auth, listener) 56 | StdOutListener(stream.sample()) 57 | -------------------------------------------------------------------------------- /sfm/ui/management/commands/update_usernames.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import json 3 | from optparse import make_option 4 | import time 5 | 6 | from django.conf import settings 7 | from django.core.management.base import BaseCommand 8 | from django.core.paginator import Paginator 9 | 10 | import tweepy 11 | 12 | from ui.models import authenticated_api 13 | from ui.models import TwitterUser 14 | from ui.utils import set_wait_time 15 | 16 | 17 | class Command(BaseCommand): 18 | help = 'update any screen names that have changed' 19 | """ 20 | Cycle through all the TwitterUsers we're tracking and update any 21 | screen names that have changed. 22 | 23 | see https://dev.twitter.com/docs/api/1.1/get/statuses/user_timeline 24 | for explanation of user_timeline call 25 | see https://dev.twitter.com/docs/working-with-timelines 26 | for explanation of max_id, since_id usage 27 | see also: 28 | https://dev.twitter.com/docs/error-codes-responses 29 | https://dev.twitter.com/docs/rate-limiting 30 | """ 31 | 32 | option_list = BaseCommand.option_list + ( 33 | make_option('--user', action='store', dest='user', 34 | default=None, help='Specific user to fetch'), 35 | ) 36 | 37 | def handle(self, *args, **options): 38 | api = authenticated_api(username=settings.TWITTER_DEFAULT_USERNAME) 39 | qs_tweeps = TwitterUser.objects.filter(is_active=True) 40 | if options.get('user', None): 41 | qs_tweeps = qs_tweeps.filter(name=options.get('user')) 42 | paginator = Paginator(qs_tweeps, 100) 43 | page_count = paginator.num_pages 44 | for page_counter in range(1, page_count + 1): 45 | print "Page %s of %s" % (page_counter, page_count) 46 | qs_page = paginator.page(page_counter) 47 | tweep_map = {} 48 | for tweep in qs_page: 49 | # check user status, update twitter user name if it has changed 50 | if tweep.uid == 0: 51 | print 'user: %s' % tweep.name 52 | print ' -- uid has not been set yet - skipping.' 53 | continue 54 | else: 55 | tweep_map[tweep.uid] = tweep 56 | if tweep_map: 57 | try: 58 | user_statuses = api.lookup_users(user_ids=tweep_map.keys()) 59 | for user_status in user_statuses: 60 | tweep = tweep_map[user_status['id']] 61 | print 'user: %s' % tweep.name 62 | if user_status['screen_name'] != tweep.name: 63 | print ' -- updating screen name to %s' % \ 64 | user_status['screen_name'] 65 | former_names = tweep.former_names 66 | if not tweep.former_names: 67 | former_names = '{}' 68 | oldnames = json.loads(former_names) 69 | oldnames[datetime.datetime.utcnow().strftime( 70 | '%Y-%m-%dT%H:%M:%SZ')] = tweep.name 71 | tweep.former_names = json.dumps(oldnames) 72 | tweep.name = user_status['screen_name'] 73 | #TODO: Is this save unnecessary, since it gets saved below? 74 | tweep.save() 75 | except tweepy.error.TweepError as e: 76 | print 'Error: %s' % e 77 | #go to the next tweep in the for loop 78 | continue 79 | finally: 80 | time.sleep(set_wait_time(api.last_response)) 81 | -------------------------------------------------------------------------------- /sfm/ui/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | class Migration(SchemaMigration): 8 | 9 | def forwards(self, orm): 10 | pass 11 | 12 | 13 | def backwards(self, orm): 14 | pass 15 | 16 | 17 | models = { 18 | 19 | } 20 | 21 | complete_apps = ['ui'] 22 | -------------------------------------------------------------------------------- /sfm/ui/migrations/0002_auto__add_status.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | class Migration(SchemaMigration): 8 | 9 | def forwards(self, orm): 10 | 11 | # Adding model 'Status' 12 | db.create_table('ui_status', ( 13 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('user_id', self.gf('django.db.models.fields.URLField')(max_length=200)), 15 | ('date_published', self.gf('django.db.models.fields.DateTimeField')()), 16 | ('avatar_url', self.gf('django.db.models.fields.URLField')(max_length=200)), 17 | ('status_id', self.gf('django.db.models.fields.URLField')(max_length=200)), 18 | ('summary', self.gf('django.db.models.fields.TextField')()), 19 | ('content', self.gf('django.db.models.fields.TextField')()), 20 | ('rule_tag', self.gf('django.db.models.fields.TextField')(db_index=True)), 21 | ('rule_match', self.gf('django.db.models.fields.TextField')(db_index=True)), 22 | )) 23 | db.send_create_signal('ui', ['Status']) 24 | 25 | 26 | def backwards(self, orm): 27 | 28 | # Deleting model 'Status' 29 | db.delete_table('ui_status') 30 | 31 | 32 | models = { 33 | 'ui.status': { 34 | 'Meta': {'object_name': 'Status'}, 35 | 'avatar_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}), 36 | 'content': ('django.db.models.fields.TextField', [], {}), 37 | 'date_published': ('django.db.models.fields.DateTimeField', [], {}), 38 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 39 | 'rule_match': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 40 | 'rule_tag': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 41 | 'status_id': ('django.db.models.fields.URLField', [], {'max_length': '200'}), 42 | 'summary': ('django.db.models.fields.TextField', [], {}), 43 | 'user_id': ('django.db.models.fields.URLField', [], {'max_length': '200'}) 44 | } 45 | } 46 | 47 | complete_apps = ['ui'] 48 | -------------------------------------------------------------------------------- /sfm/ui/migrations/0003_auto__chg_field_status_avatar_url.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | class Migration(SchemaMigration): 8 | 9 | def forwards(self, orm): 10 | 11 | # Changing field 'Status.avatar_url' 12 | db.alter_column('ui_status', 'avatar_url', self.gf('django.db.models.fields.TextField')()) 13 | 14 | 15 | def backwards(self, orm): 16 | 17 | # Changing field 'Status.avatar_url' 18 | db.alter_column('ui_status', 'avatar_url', self.gf('django.db.models.fields.URLField')(max_length=200)) 19 | 20 | 21 | models = { 22 | 'ui.status': { 23 | 'Meta': {'object_name': 'Status'}, 24 | 'avatar_url': ('django.db.models.fields.TextField', [], {}), 25 | 'content': ('django.db.models.fields.TextField', [], {}), 26 | 'date_published': ('django.db.models.fields.DateTimeField', [], {}), 27 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 28 | 'rule_match': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 29 | 'rule_tag': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 30 | 'status_id': ('django.db.models.fields.URLField', [], {'max_length': '200'}), 31 | 'summary': ('django.db.models.fields.TextField', [], {}), 32 | 'user_id': ('django.db.models.fields.URLField', [], {'max_length': '200'}) 33 | } 34 | } 35 | 36 | complete_apps = ['ui'] 37 | -------------------------------------------------------------------------------- /sfm/ui/migrations/0004_auto.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | class Migration(SchemaMigration): 8 | 9 | def forwards(self, orm): 10 | 11 | # Adding index on 'Status', fields ['date_published'] 12 | db.create_index('ui_status', ['date_published']) 13 | 14 | 15 | def backwards(self, orm): 16 | 17 | # Removing index on 'Status', fields ['date_published'] 18 | db.delete_index('ui_status', ['date_published']) 19 | 20 | 21 | models = { 22 | 'ui.status': { 23 | 'Meta': {'ordering': "['-date_published']", 'object_name': 'Status'}, 24 | 'avatar_url': ('django.db.models.fields.TextField', [], {}), 25 | 'content': ('django.db.models.fields.TextField', [], {}), 26 | 'date_published': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), 27 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 28 | 'rule_match': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 29 | 'rule_tag': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 30 | 'status_id': ('django.db.models.fields.URLField', [], {'max_length': '200'}), 31 | 'summary': ('django.db.models.fields.TextField', [], {}), 32 | 'user_id': ('django.db.models.fields.URLField', [], {'max_length': '200'}) 33 | } 34 | } 35 | 36 | complete_apps = ['ui'] 37 | -------------------------------------------------------------------------------- /sfm/ui/migrations/0005_auto__add_trendweekly.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | class Migration(SchemaMigration): 8 | 9 | def forwards(self, orm): 10 | 11 | # Adding model 'TrendWeekly' 12 | db.create_table('ui_trendweekly', ( 13 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('date', self.gf('django.db.models.fields.DateField')(db_index=True)), 15 | ('events', self.gf('django.db.models.fields.TextField')(blank=True)), 16 | ('name', self.gf('django.db.models.fields.TextField')(db_index=True)), 17 | ('promoted_content', self.gf('django.db.models.fields.TextField')(blank=True)), 18 | ('query', self.gf('django.db.models.fields.TextField')()), 19 | )) 20 | db.send_create_signal('ui', ['TrendWeekly']) 21 | 22 | 23 | def backwards(self, orm): 24 | 25 | # Deleting model 'TrendWeekly' 26 | db.delete_table('ui_trendweekly') 27 | 28 | 29 | models = { 30 | 'ui.status': { 31 | 'Meta': {'ordering': "['-date_published']", 'object_name': 'Status'}, 32 | 'avatar_url': ('django.db.models.fields.TextField', [], {}), 33 | 'content': ('django.db.models.fields.TextField', [], {}), 34 | 'date_published': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), 35 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 36 | 'rule_match': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 37 | 'rule_tag': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 38 | 'status_id': ('django.db.models.fields.URLField', [], {'max_length': '200'}), 39 | 'summary': ('django.db.models.fields.TextField', [], {}), 40 | 'user_id': ('django.db.models.fields.URLField', [], {'max_length': '200'}) 41 | }, 42 | 'ui.trendweekly': { 43 | 'Meta': {'object_name': 'TrendWeekly'}, 44 | 'date': ('django.db.models.fields.DateField', [], {'db_index': 'True'}), 45 | 'events': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 46 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 47 | 'name': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 48 | 'promoted_content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 49 | 'query': ('django.db.models.fields.TextField', [], {}) 50 | } 51 | } 52 | 53 | complete_apps = ['ui'] 54 | -------------------------------------------------------------------------------- /sfm/ui/migrations/0006_auto__add_field_trendweekly_sequence_num.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | class Migration(SchemaMigration): 8 | 9 | def forwards(self, orm): 10 | 11 | # Adding field 'TrendWeekly.sequence_num' 12 | db.add_column('ui_trendweekly', 'sequence_num', self.gf('django.db.models.fields.SmallIntegerField')(default=1), keep_default=False) 13 | 14 | 15 | def backwards(self, orm): 16 | 17 | # Deleting field 'TrendWeekly.sequence_num' 18 | db.delete_column('ui_trendweekly', 'sequence_num') 19 | 20 | 21 | models = { 22 | 'ui.status': { 23 | 'Meta': {'ordering': "['-date_published']", 'object_name': 'Status'}, 24 | 'avatar_url': ('django.db.models.fields.TextField', [], {}), 25 | 'content': ('django.db.models.fields.TextField', [], {}), 26 | 'date_published': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), 27 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 28 | 'rule_match': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 29 | 'rule_tag': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 30 | 'status_id': ('django.db.models.fields.URLField', [], {'max_length': '200'}), 31 | 'summary': ('django.db.models.fields.TextField', [], {}), 32 | 'user_id': ('django.db.models.fields.URLField', [], {'max_length': '200'}) 33 | }, 34 | 'ui.trendweekly': { 35 | 'Meta': {'ordering': "['-date', 'sequence_num']", 'object_name': 'TrendWeekly'}, 36 | 'date': ('django.db.models.fields.DateField', [], {'db_index': 'True'}), 37 | 'events': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 38 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 39 | 'name': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 40 | 'promoted_content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 41 | 'query': ('django.db.models.fields.TextField', [], {}), 42 | 'sequence_num': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}) 43 | } 44 | } 45 | 46 | complete_apps = ['ui'] 47 | -------------------------------------------------------------------------------- /sfm/ui/migrations/0007_auto__add_trenddaily.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | class Migration(SchemaMigration): 8 | 9 | def forwards(self, orm): 10 | 11 | # Adding model 'TrendDaily' 12 | db.create_table('ui_trenddaily', ( 13 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('date', self.gf('django.db.models.fields.DateTimeField')(db_index=True)), 15 | ('events', self.gf('django.db.models.fields.TextField')(blank=True)), 16 | ('name', self.gf('django.db.models.fields.TextField')(db_index=True)), 17 | ('promoted_content', self.gf('django.db.models.fields.TextField')(blank=True)), 18 | ('query', self.gf('django.db.models.fields.TextField')()), 19 | ('sequence_num', self.gf('django.db.models.fields.SmallIntegerField')(default=1)), 20 | )) 21 | db.send_create_signal('ui', ['TrendDaily']) 22 | 23 | 24 | def backwards(self, orm): 25 | 26 | # Deleting model 'TrendDaily' 27 | db.delete_table('ui_trenddaily') 28 | 29 | 30 | models = { 31 | 'ui.status': { 32 | 'Meta': {'ordering': "['-date_published']", 'object_name': 'Status'}, 33 | 'avatar_url': ('django.db.models.fields.TextField', [], {}), 34 | 'content': ('django.db.models.fields.TextField', [], {}), 35 | 'date_published': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), 36 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 37 | 'rule_match': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 38 | 'rule_tag': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 39 | 'status_id': ('django.db.models.fields.URLField', [], {'max_length': '200'}), 40 | 'summary': ('django.db.models.fields.TextField', [], {}), 41 | 'user_id': ('django.db.models.fields.URLField', [], {'max_length': '200'}) 42 | }, 43 | 'ui.trenddaily': { 44 | 'Meta': {'ordering': "['-date', 'sequence_num']", 'object_name': 'TrendDaily'}, 45 | 'date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), 46 | 'events': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 47 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 48 | 'name': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 49 | 'promoted_content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 50 | 'query': ('django.db.models.fields.TextField', [], {}), 51 | 'sequence_num': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}) 52 | }, 53 | 'ui.trendweekly': { 54 | 'Meta': {'ordering': "['-date', 'sequence_num']", 'object_name': 'TrendWeekly'}, 55 | 'date': ('django.db.models.fields.DateField', [], {'db_index': 'True'}), 56 | 'events': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 57 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 58 | 'name': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 59 | 'promoted_content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 60 | 'query': ('django.db.models.fields.TextField', [], {}), 61 | 'sequence_num': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}) 62 | } 63 | } 64 | 65 | complete_apps = ['ui'] 66 | -------------------------------------------------------------------------------- /sfm/ui/migrations/0008_auto__del_field_trendweekly_sequence_num__del_field_trenddaily_sequenc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Deleting field 'TrendWeekly.sequence_num' 12 | db.delete_column('ui_trendweekly', 'sequence_num') 13 | 14 | # Deleting field 'TrendDaily.sequence_num' 15 | db.delete_column('ui_trenddaily', 'sequence_num') 16 | 17 | def backwards(self, orm): 18 | # Adding field 'TrendWeekly.sequence_num' 19 | db.add_column('ui_trendweekly', 'sequence_num', 20 | self.gf('django.db.models.fields.SmallIntegerField')(default=1), 21 | keep_default=False) 22 | 23 | # Adding field 'TrendDaily.sequence_num' 24 | db.add_column('ui_trenddaily', 'sequence_num', 25 | self.gf('django.db.models.fields.SmallIntegerField')(default=1), 26 | keep_default=False) 27 | 28 | models = { 29 | 'ui.status': { 30 | 'Meta': {'ordering': "['-date_published']", 'object_name': 'Status'}, 31 | 'avatar_url': ('django.db.models.fields.TextField', [], {}), 32 | 'content': ('django.db.models.fields.TextField', [], {}), 33 | 'date_published': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), 34 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 35 | 'rule_match': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 36 | 'rule_tag': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 37 | 'status_id': ('django.db.models.fields.URLField', [], {'max_length': '200'}), 38 | 'summary': ('django.db.models.fields.TextField', [], {}), 39 | 'user_id': ('django.db.models.fields.URLField', [], {'max_length': '200'}) 40 | }, 41 | 'ui.trenddaily': { 42 | 'Meta': {'ordering': "['-date', 'name']", 'object_name': 'TrendDaily'}, 43 | 'date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), 44 | 'events': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 45 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 46 | 'name': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 47 | 'promoted_content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 48 | 'query': ('django.db.models.fields.TextField', [], {}) 49 | }, 50 | 'ui.trendweekly': { 51 | 'Meta': {'ordering': "['-date', 'name']", 'object_name': 'TrendWeekly'}, 52 | 'date': ('django.db.models.fields.DateField', [], {'db_index': 'True'}), 53 | 'events': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 54 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 55 | 'name': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 56 | 'promoted_content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 57 | 'query': ('django.db.models.fields.TextField', [], {}) 58 | } 59 | } 60 | 61 | complete_apps = ['ui'] -------------------------------------------------------------------------------- /sfm/ui/migrations/0009_auto__add_rule.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding model 'Rule' 12 | db.create_table('ui_rule', ( 13 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)), 15 | ('is_active', self.gf('django.db.models.fields.BooleanField')(default=False)), 16 | ('people', self.gf('django.db.models.fields.TextField')(blank=True)), 17 | ('words', self.gf('django.db.models.fields.TextField')(blank=True)), 18 | ('locations', self.gf('django.db.models.fields.TextField')(blank=True)), 19 | )) 20 | db.send_create_signal('ui', ['Rule']) 21 | 22 | def backwards(self, orm): 23 | # Deleting model 'Rule' 24 | db.delete_table('ui_rule') 25 | 26 | models = { 27 | 'ui.rule': { 28 | 'Meta': {'object_name': 'Rule'}, 29 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 30 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 31 | 'locations': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 32 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), 33 | 'people': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 34 | 'words': ('django.db.models.fields.TextField', [], {'blank': 'True'}) 35 | }, 36 | 'ui.status': { 37 | 'Meta': {'ordering': "['-date_published']", 'object_name': 'Status'}, 38 | 'avatar_url': ('django.db.models.fields.TextField', [], {}), 39 | 'content': ('django.db.models.fields.TextField', [], {}), 40 | 'date_published': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), 41 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 42 | 'rule_match': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 43 | 'rule_tag': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 44 | 'status_id': ('django.db.models.fields.URLField', [], {'max_length': '200'}), 45 | 'summary': ('django.db.models.fields.TextField', [], {}), 46 | 'user_id': ('django.db.models.fields.URLField', [], {'max_length': '200'}) 47 | }, 48 | 'ui.trenddaily': { 49 | 'Meta': {'ordering': "['-date', 'name']", 'object_name': 'TrendDaily'}, 50 | 'date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), 51 | 'events': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 52 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 53 | 'name': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 54 | 'promoted_content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 55 | 'query': ('django.db.models.fields.TextField', [], {}) 56 | }, 57 | 'ui.trendweekly': { 58 | 'Meta': {'ordering': "['-date', 'name']", 'object_name': 'TrendWeekly'}, 59 | 'date': ('django.db.models.fields.DateField', [], {'db_index': 'True'}), 60 | 'events': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 61 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 62 | 'name': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 63 | 'promoted_content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 64 | 'query': ('django.db.models.fields.TextField', [], {}) 65 | } 66 | } 67 | 68 | complete_apps = ['ui'] -------------------------------------------------------------------------------- /sfm/ui/migrations/0012_auto__del_status.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Deleting model 'Status' 12 | db.delete_table('ui_status') 13 | 14 | def backwards(self, orm): 15 | # Adding model 'Status' 16 | db.create_table('ui_status', ( 17 | ('content', self.gf('django.db.models.fields.TextField')()), 18 | ('date_published', self.gf('django.db.models.fields.DateTimeField')(db_index=True)), 19 | ('user_id', self.gf('django.db.models.fields.URLField')(max_length=200)), 20 | ('rule_match', self.gf('django.db.models.fields.TextField')(db_index=True)), 21 | ('avatar_url', self.gf('django.db.models.fields.TextField')()), 22 | ('rule_tag', self.gf('django.db.models.fields.TextField')(db_index=True)), 23 | ('status_id', self.gf('django.db.models.fields.URLField')(max_length=200)), 24 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 25 | ('summary', self.gf('django.db.models.fields.TextField')()), 26 | )) 27 | db.send_create_signal('ui', ['Status']) 28 | 29 | models = { 30 | 'ui.rule': { 31 | 'Meta': {'object_name': 'Rule'}, 32 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 33 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 34 | 'locations': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 35 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), 36 | 'people': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 37 | 'words': ('django.db.models.fields.TextField', [], {'blank': 'True'}) 38 | }, 39 | 'ui.trenddaily': { 40 | 'Meta': {'ordering': "['-date', 'name']", 'object_name': 'TrendDaily'}, 41 | 'date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), 42 | 'events': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 43 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 44 | 'name': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 45 | 'promoted_content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 46 | 'query': ('django.db.models.fields.TextField', [], {}) 47 | }, 48 | 'ui.trendweekly': { 49 | 'Meta': {'ordering': "['-date', 'name']", 'object_name': 'TrendWeekly'}, 50 | 'date': ('django.db.models.fields.DateField', [], {'db_index': 'True'}), 51 | 'events': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 52 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 53 | 'name': ('django.db.models.fields.TextField', [], {'db_index': 'True'}), 54 | 'promoted_content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 55 | 'query': ('django.db.models.fields.TextField', [], {}) 56 | }, 57 | 'ui.twitteruser': { 58 | 'Meta': {'object_name': 'TwitterUser'}, 59 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 60 | 'name': ('django.db.models.fields.TextField', [], {'db_index': 'True'}) 61 | }, 62 | 'ui.twitteruseritem': { 63 | 'Meta': {'object_name': 'TwitterUserItem'}, 64 | 'date_published': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), 65 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 66 | 'item_json': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), 67 | 'item_text': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), 68 | 'place': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), 69 | 'source': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), 70 | 'twitter_url': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '200'}), 71 | 'twitter_user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'to': "orm['ui.TwitterUser']"}) 72 | } 73 | } 74 | 75 | complete_apps = ['ui'] -------------------------------------------------------------------------------- /sfm/ui/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/migrations/__init__.py -------------------------------------------------------------------------------- /sfm/ui/static/admin/css/dashboard.css: -------------------------------------------------------------------------------- 1 | /* DASHBOARD */ 2 | 3 | .dashboard .module table th { 4 | width: 100%; 5 | } 6 | 7 | .dashboard .module table td { 8 | white-space: nowrap; 9 | } 10 | 11 | .dashboard .module table td a { 12 | display: block; 13 | padding-right: .6em; 14 | } 15 | 16 | /* RECENT ACTIONS MODULE */ 17 | 18 | .module ul.actionlist { 19 | margin-left: 0; 20 | } 21 | 22 | ul.actionlist li { 23 | list-style-type: none; 24 | } 25 | 26 | ul.actionlist li.changelink { 27 | overflow: hidden; 28 | text-overflow: ellipsis; 29 | -o-text-overflow: ellipsis; 30 | } -------------------------------------------------------------------------------- /sfm/ui/static/admin/css/ie.css: -------------------------------------------------------------------------------- 1 | /* IE 6 & 7 */ 2 | 3 | /* Proper fixed width for dashboard in IE6 */ 4 | 5 | .dashboard #content { 6 | *width: 768px; 7 | } 8 | 9 | .dashboard #content-main { 10 | *width: 535px; 11 | } 12 | 13 | /* IE 6 ONLY */ 14 | 15 | /* Keep header from flowing off the page */ 16 | 17 | #container { 18 | _position: static; 19 | } 20 | 21 | /* Put the right sidebars back on the page */ 22 | 23 | .colMS #content-related { 24 | _margin-right: 0; 25 | _margin-left: 10px; 26 | _position: static; 27 | } 28 | 29 | /* Put the left sidebars back on the page */ 30 | 31 | .colSM #content-related { 32 | _margin-right: 10px; 33 | _margin-left: -115px; 34 | _position: static; 35 | } 36 | 37 | .form-row { 38 | _height: 1%; 39 | } 40 | 41 | /* Fix right margin for changelist filters in IE6 */ 42 | 43 | #changelist-filter ul { 44 | _margin-right: -10px; 45 | } 46 | 47 | /* IE ignores min-height, but treats height as if it were min-height */ 48 | 49 | .change-list .filtered { 50 | _height: 400px; 51 | } 52 | 53 | /* IE doesn't know alpha transparency in PNGs */ 54 | 55 | .inline-deletelink { 56 | background: transparent url(../img/inline-delete-8bit.png) no-repeat; 57 | } 58 | 59 | /* IE7 doesn't support inline-block */ 60 | .change-list ul.toplinks li { 61 | zoom: 1; 62 | *display: inline; 63 | } -------------------------------------------------------------------------------- /sfm/ui/static/admin/css/login.css: -------------------------------------------------------------------------------- 1 | /* LOGIN FORM */ 2 | 3 | body.login { 4 | background: #eee; 5 | } 6 | 7 | .login #container { 8 | background: white; 9 | border: 1px solid #ccc; 10 | width: 28em; 11 | min-width: 300px; 12 | margin-left: auto; 13 | margin-right: auto; 14 | margin-top: 100px; 15 | } 16 | 17 | .login #content-main { 18 | width: 100%; 19 | } 20 | 21 | .login form { 22 | margin-top: 1em; 23 | } 24 | 25 | .login .form-row { 26 | padding: 4px 0; 27 | float: left; 28 | width: 100%; 29 | } 30 | 31 | .login .form-row label { 32 | float: left; 33 | width: 9em; 34 | padding-right: 0.5em; 35 | line-height: 2em; 36 | text-align: right; 37 | font-size: 1em; 38 | color: #333; 39 | } 40 | 41 | .login .form-row #id_username, .login .form-row #id_password { 42 | width: 14em; 43 | } 44 | 45 | .login span.help { 46 | font-size: 10px; 47 | display: block; 48 | } 49 | 50 | .login .submit-row { 51 | clear: both; 52 | padding: 1em 0 0 9.4em; 53 | } 54 | 55 | .login .password-reset-link { 56 | text-align: center; 57 | } 58 | -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/changelist-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/changelist-bg.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/changelist-bg_rtl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/changelist-bg_rtl.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/chooser-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/chooser-bg.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/chooser_stacked-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/chooser_stacked-bg.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/default-bg-reverse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/default-bg-reverse.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/default-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/default-bg.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/deleted-overlay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/deleted-overlay.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/gis/move_vertex_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/gis/move_vertex_off.png -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/gis/move_vertex_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/gis/move_vertex_on.png -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/icon-no.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/icon-no.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/icon-unknown.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/icon-unknown.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/icon-yes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/icon-yes.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/icon_addlink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/icon_addlink.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/icon_alert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/icon_alert.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/icon_calendar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/icon_calendar.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/icon_changelink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/icon_changelink.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/icon_clock.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/icon_clock.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/icon_deletelink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/icon_deletelink.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/icon_error.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/icon_error.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/icon_searchbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/icon_searchbox.png -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/icon_success.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/icon_success.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/inline-delete-8bit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/inline-delete-8bit.png -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/inline-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/inline-delete.png -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/inline-restore-8bit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/inline-restore-8bit.png -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/inline-restore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/inline-restore.png -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/inline-splitter-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/inline-splitter-bg.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/nav-bg-grabber.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/nav-bg-grabber.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/nav-bg-reverse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/nav-bg-reverse.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/nav-bg-selected.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/nav-bg-selected.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/nav-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/nav-bg.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/selector-icons.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/selector-icons.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/selector-search.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/selector-search.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/sorting-icons.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/sorting-icons.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/tool-left.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/tool-left.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/tool-left_over.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/tool-left_over.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/tool-right.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/tool-right.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/tool-right_over.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/tool-right_over.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/tooltag-add.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/tooltag-add.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/tooltag-add_over.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/tooltag-add_over.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/tooltag-arrowright.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/tooltag-arrowright.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/img/tooltag-arrowright_over.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/static/admin/img/tooltag-arrowright_over.gif -------------------------------------------------------------------------------- /sfm/ui/static/admin/js/LICENSE-JQUERY.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 John Resig, http://jquery.com/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /sfm/ui/static/admin/js/SelectBox.js: -------------------------------------------------------------------------------- 1 | var SelectBox = { 2 | cache: new Object(), 3 | init: function(id) { 4 | var box = document.getElementById(id); 5 | var node; 6 | SelectBox.cache[id] = new Array(); 7 | var cache = SelectBox.cache[id]; 8 | for (var i = 0; (node = box.options[i]); i++) { 9 | cache.push({value: node.value, text: node.text, displayed: 1}); 10 | } 11 | }, 12 | redisplay: function(id) { 13 | // Repopulate HTML select box from cache 14 | var box = document.getElementById(id); 15 | box.options.length = 0; // clear all options 16 | for (var i = 0, j = SelectBox.cache[id].length; i < j; i++) { 17 | var node = SelectBox.cache[id][i]; 18 | if (node.displayed) { 19 | box.options[box.options.length] = new Option(node.text, node.value, false, false); 20 | } 21 | } 22 | }, 23 | filter: function(id, text) { 24 | // Redisplay the HTML select box, displaying only the choices containing ALL 25 | // the words in text. (It's an AND search.) 26 | var tokens = text.toLowerCase().split(/\s+/); 27 | var node, token; 28 | for (var i = 0; (node = SelectBox.cache[id][i]); i++) { 29 | node.displayed = 1; 30 | for (var j = 0; (token = tokens[j]); j++) { 31 | if (node.text.toLowerCase().indexOf(token) == -1) { 32 | node.displayed = 0; 33 | } 34 | } 35 | } 36 | SelectBox.redisplay(id); 37 | }, 38 | delete_from_cache: function(id, value) { 39 | var node, delete_index = null; 40 | for (var i = 0; (node = SelectBox.cache[id][i]); i++) { 41 | if (node.value == value) { 42 | delete_index = i; 43 | break; 44 | } 45 | } 46 | var j = SelectBox.cache[id].length - 1; 47 | for (var i = delete_index; i < j; i++) { 48 | SelectBox.cache[id][i] = SelectBox.cache[id][i+1]; 49 | } 50 | SelectBox.cache[id].length--; 51 | }, 52 | add_to_cache: function(id, option) { 53 | SelectBox.cache[id].push({value: option.value, text: option.text, displayed: 1}); 54 | }, 55 | cache_contains: function(id, value) { 56 | // Check if an item is contained in the cache 57 | var node; 58 | for (var i = 0; (node = SelectBox.cache[id][i]); i++) { 59 | if (node.value == value) { 60 | return true; 61 | } 62 | } 63 | return false; 64 | }, 65 | move: function(from, to) { 66 | var from_box = document.getElementById(from); 67 | var to_box = document.getElementById(to); 68 | var option; 69 | for (var i = 0; (option = from_box.options[i]); i++) { 70 | if (option.selected && SelectBox.cache_contains(from, option.value)) { 71 | SelectBox.add_to_cache(to, {value: option.value, text: option.text, displayed: 1}); 72 | SelectBox.delete_from_cache(from, option.value); 73 | } 74 | } 75 | SelectBox.redisplay(from); 76 | SelectBox.redisplay(to); 77 | }, 78 | move_all: function(from, to) { 79 | var from_box = document.getElementById(from); 80 | var to_box = document.getElementById(to); 81 | var option; 82 | for (var i = 0; (option = from_box.options[i]); i++) { 83 | if (SelectBox.cache_contains(from, option.value)) { 84 | SelectBox.add_to_cache(to, {value: option.value, text: option.text, displayed: 1}); 85 | SelectBox.delete_from_cache(from, option.value); 86 | } 87 | } 88 | SelectBox.redisplay(from); 89 | SelectBox.redisplay(to); 90 | }, 91 | sort: function(id) { 92 | SelectBox.cache[id].sort( function(a, b) { 93 | a = a.text.toLowerCase(); 94 | b = b.text.toLowerCase(); 95 | try { 96 | if (a > b) return 1; 97 | if (a < b) return -1; 98 | } 99 | catch (e) { 100 | // silently fail on IE 'unknown' exception 101 | } 102 | return 0; 103 | } ); 104 | }, 105 | select_all: function(id) { 106 | var box = document.getElementById(id); 107 | for (var i = 0; i < box.options.length; i++) { 108 | box.options[i].selected = 'selected'; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /sfm/ui/static/admin/js/actions.min.js: -------------------------------------------------------------------------------- 1 | (function(a){a.fn.actions=function(g){var b=a.extend({},a.fn.actions.defaults,g),f=a(this),e=!1;checker=function(c){c?showQuestion():reset();a(f).attr("checked",c).parent().parent().toggleClass(b.selectedClass,c)};updateCounter=function(){var c=a(f).filter(":checked").length;a(b.counterContainer).html(interpolate(ngettext("%(sel)s of %(cnt)s selected","%(sel)s of %(cnt)s selected",c),{sel:c,cnt:_actions_icnt},!0));a(b.allToggle).attr("checked",function(){c==f.length?(value=!0,showQuestion()):(value= 2 | !1,clearAcross());return value})};showQuestion=function(){a(b.acrossClears).hide();a(b.acrossQuestions).show();a(b.allContainer).hide()};showClear=function(){a(b.acrossClears).show();a(b.acrossQuestions).hide();a(b.actionContainer).toggleClass(b.selectedClass);a(b.allContainer).show();a(b.counterContainer).hide()};reset=function(){a(b.acrossClears).hide();a(b.acrossQuestions).hide();a(b.allContainer).hide();a(b.counterContainer).show()};clearAcross=function(){reset();a(b.acrossInput).val(0);a(b.actionContainer).removeClass(b.selectedClass)}; 3 | a(b.counterContainer).show();a(this).filter(":checked").each(function(){a(this).parent().parent().toggleClass(b.selectedClass);updateCounter();1==a(b.acrossInput).val()&&showClear()});a(b.allToggle).show().click(function(){checker(a(this).attr("checked"));updateCounter()});a("div.actions span.question a").click(function(c){c.preventDefault();a(b.acrossInput).val(1);showClear()});a("div.actions span.clear a").click(function(c){c.preventDefault();a(b.allToggle).attr("checked",!1);clearAcross();checker(0); 4 | updateCounter()});lastChecked=null;a(f).click(function(c){if(!c)c=window.event;var d=c.target?c.target:c.srcElement;if(lastChecked&&a.data(lastChecked)!=a.data(d)&&!0==c.shiftKey){var e=!1;a(lastChecked).attr("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked);a(f).each(function(){if(a.data(this)==a.data(lastChecked)||a.data(this)==a.data(d))e=e?!1:!0;e&&a(this).attr("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked)})}a(d).parent().parent().toggleClass(b.selectedClass, 5 | d.checked);lastChecked=d;updateCounter()});a("form#changelist-form table#result_list tr").find("td:gt(0) :input").change(function(){e=!0});a('form#changelist-form button[name="index"]').click(function(){if(e)return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."))});a('form#changelist-form input[name="_save"]').click(function(){var b=!1;a("div.actions select option:selected").each(function(){a(this).val()&&(b=!0)}); 6 | if(b)return e?confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action.")):confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button."))})};a.fn.actions.defaults={actionContainer:"div.actions",counterContainer:"span.action-counter",allContainer:"div.actions span.all",acrossInput:"div.actions input.select-across", 7 | acrossQuestions:"div.actions span.question",acrossClears:"div.actions span.clear",allToggle:"#action-toggle",selectedClass:"selected"}})(django.jQuery); 8 | -------------------------------------------------------------------------------- /sfm/ui/static/admin/js/admin/RelatedObjectLookups.js: -------------------------------------------------------------------------------- 1 | // Handles related-objects functionality: lookup link for raw_id_fields 2 | // and Add Another links. 3 | 4 | function html_unescape(text) { 5 | // Unescape a string that was escaped using django.utils.html.escape. 6 | text = text.replace(/</g, '<'); 7 | text = text.replace(/>/g, '>'); 8 | text = text.replace(/"/g, '"'); 9 | text = text.replace(/'/g, "'"); 10 | text = text.replace(/&/g, '&'); 11 | return text; 12 | } 13 | 14 | // IE doesn't accept periods or dashes in the window name, but the element IDs 15 | // we use to generate popup window names may contain them, therefore we map them 16 | // to allowed characters in a reversible way so that we can locate the correct 17 | // element when the popup window is dismissed. 18 | function id_to_windowname(text) { 19 | text = text.replace(/\./g, '__dot__'); 20 | text = text.replace(/\-/g, '__dash__'); 21 | return text; 22 | } 23 | 24 | function windowname_to_id(text) { 25 | text = text.replace(/__dot__/g, '.'); 26 | text = text.replace(/__dash__/g, '-'); 27 | return text; 28 | } 29 | 30 | function showRelatedObjectLookupPopup(triggeringLink) { 31 | var name = triggeringLink.id.replace(/^lookup_/, ''); 32 | name = id_to_windowname(name); 33 | var href; 34 | if (triggeringLink.href.search(/\?/) >= 0) { 35 | href = triggeringLink.href + '&pop=1'; 36 | } else { 37 | href = triggeringLink.href + '?pop=1'; 38 | } 39 | var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); 40 | win.focus(); 41 | return false; 42 | } 43 | 44 | function dismissRelatedLookupPopup(win, chosenId) { 45 | var name = windowname_to_id(win.name); 46 | var elem = document.getElementById(name); 47 | if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) { 48 | elem.value += ',' + chosenId; 49 | } else { 50 | document.getElementById(name).value = chosenId; 51 | } 52 | win.close(); 53 | } 54 | 55 | function showAddAnotherPopup(triggeringLink) { 56 | var name = triggeringLink.id.replace(/^add_/, ''); 57 | name = id_to_windowname(name); 58 | href = triggeringLink.href 59 | if (href.indexOf('?') == -1) { 60 | href += '?_popup=1'; 61 | } else { 62 | href += '&_popup=1'; 63 | } 64 | var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); 65 | win.focus(); 66 | return false; 67 | } 68 | 69 | function dismissAddAnotherPopup(win, newId, newRepr) { 70 | // newId and newRepr are expected to have previously been escaped by 71 | // django.utils.html.escape. 72 | newId = html_unescape(newId); 73 | newRepr = html_unescape(newRepr); 74 | var name = windowname_to_id(win.name); 75 | var elem = document.getElementById(name); 76 | if (elem) { 77 | var elemName = elem.nodeName.toUpperCase(); 78 | if (elemName == 'SELECT') { 79 | var o = new Option(newRepr, newId); 80 | elem.options[elem.options.length] = o; 81 | o.selected = true; 82 | } else if (elemName == 'INPUT') { 83 | if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) { 84 | elem.value += ',' + newId; 85 | } else { 86 | elem.value = newId; 87 | } 88 | } 89 | } else { 90 | var toId = name + "_to"; 91 | elem = document.getElementById(toId); 92 | var o = new Option(newRepr, newId); 93 | SelectBox.add_to_cache(toId, o); 94 | SelectBox.redisplay(toId); 95 | } 96 | win.close(); 97 | } 98 | -------------------------------------------------------------------------------- /sfm/ui/static/admin/js/admin/ordering.js: -------------------------------------------------------------------------------- 1 | addEvent(window, 'load', reorder_init); 2 | 3 | var lis; 4 | var top = 0; 5 | var left = 0; 6 | var height = 30; 7 | 8 | function reorder_init() { 9 | lis = document.getElementsBySelector('ul#orderthese li'); 10 | var input = document.getElementsBySelector('input[name=order_]')[0]; 11 | setOrder(input.value.split(',')); 12 | input.disabled = true; 13 | draw(); 14 | // Now initialize the dragging behavior 15 | var limit = (lis.length - 1) * height; 16 | for (var i = 0; i < lis.length; i++) { 17 | var li = lis[i]; 18 | var img = document.getElementById('handle'+li.id); 19 | li.style.zIndex = 1; 20 | Drag.init(img, li, left + 10, left + 10, top + 10, top + 10 + limit); 21 | li.onDragStart = startDrag; 22 | li.onDragEnd = endDrag; 23 | img.style.cursor = 'move'; 24 | } 25 | } 26 | 27 | function submitOrderForm() { 28 | var inputOrder = document.getElementsBySelector('input[name=order_]')[0]; 29 | inputOrder.value = getOrder(); 30 | inputOrder.disabled=false; 31 | } 32 | 33 | function startDrag() { 34 | this.style.zIndex = '10'; 35 | this.className = 'dragging'; 36 | } 37 | 38 | function endDrag(x, y) { 39 | this.style.zIndex = '1'; 40 | this.className = ''; 41 | // Work out how far along it has been dropped, using x co-ordinate 42 | var oldIndex = this.index; 43 | var newIndex = Math.round((y - 10 - top) / height); 44 | // 'Snap' to the correct position 45 | this.style.top = (10 + top + newIndex * height) + 'px'; 46 | this.index = newIndex; 47 | moveItem(oldIndex, newIndex); 48 | } 49 | 50 | function moveItem(oldIndex, newIndex) { 51 | // Swaps two items, adjusts the index and left co-ord for all others 52 | if (oldIndex == newIndex) { 53 | return; // Nothing to swap; 54 | } 55 | var direction, lo, hi; 56 | if (newIndex > oldIndex) { 57 | lo = oldIndex; 58 | hi = newIndex; 59 | direction = -1; 60 | } else { 61 | direction = 1; 62 | hi = oldIndex; 63 | lo = newIndex; 64 | } 65 | var lis2 = new Array(); // We will build the new order in this array 66 | for (var i = 0; i < lis.length; i++) { 67 | if (i < lo || i > hi) { 68 | // Position of items not between the indexes is unaffected 69 | lis2[i] = lis[i]; 70 | continue; 71 | } else if (i == newIndex) { 72 | lis2[i] = lis[oldIndex]; 73 | continue; 74 | } else { 75 | // Item is between the two indexes - move it along 1 76 | lis2[i] = lis[i - direction]; 77 | } 78 | } 79 | // Re-index everything 80 | reIndex(lis2); 81 | lis = lis2; 82 | draw(); 83 | // document.getElementById('hiddenOrder').value = getOrder(); 84 | document.getElementsBySelector('input[name=order_]')[0].value = getOrder(); 85 | } 86 | 87 | function reIndex(lis) { 88 | for (var i = 0; i < lis.length; i++) { 89 | lis[i].index = i; 90 | } 91 | } 92 | 93 | function draw() { 94 | for (var i = 0; i < lis.length; i++) { 95 | var li = lis[i]; 96 | li.index = i; 97 | li.style.position = 'absolute'; 98 | li.style.left = (10 + left) + 'px'; 99 | li.style.top = (10 + top + (i * height)) + 'px'; 100 | } 101 | } 102 | 103 | function getOrder() { 104 | var order = new Array(lis.length); 105 | for (var i = 0; i < lis.length; i++) { 106 | order[i] = lis[i].id.substring(1, 100); 107 | } 108 | return order.join(','); 109 | } 110 | 111 | function setOrder(id_list) { 112 | /* Set the current order to match the lsit of IDs */ 113 | var temp_lis = new Array(); 114 | for (var i = 0; i < id_list.length; i++) { 115 | var id = 'p' + id_list[i]; 116 | temp_lis[temp_lis.length] = document.getElementById(id); 117 | } 118 | reIndex(temp_lis); 119 | lis = temp_lis; 120 | draw(); 121 | } 122 | 123 | function addEvent(elm, evType, fn, useCapture) 124 | // addEvent and removeEvent 125 | // cross-browser event handling for IE5+, NS6 and Mozilla 126 | // By Scott Andrew 127 | { 128 | if (elm.addEventListener){ 129 | elm.addEventListener(evType, fn, useCapture); 130 | return true; 131 | } else if (elm.attachEvent){ 132 | var r = elm.attachEvent("on"+evType, fn); 133 | return r; 134 | } else { 135 | elm['on'+evType] = fn; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /sfm/ui/static/admin/js/collapse.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | $(document).ready(function() { 3 | // Add anchor tag for Show/Hide link 4 | $("fieldset.collapse").each(function(i, elem) { 5 | // Don't hide if fields in this fieldset have errors 6 | if ($(elem).find("div.errors").length == 0) { 7 | $(elem).addClass("collapsed").find("h2").first().append(' (' + gettext("Show") + 9 | ')'); 10 | } 11 | }); 12 | // Add toggle to anchor tag 13 | $("fieldset.collapse a.collapse-toggle").toggle( 14 | function() { // Show 15 | $(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset", [$(this).attr("id")]); 16 | return false; 17 | }, 18 | function() { // Hide 19 | $(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset", [$(this).attr("id")]); 20 | return false; 21 | } 22 | ); 23 | }); 24 | })(django.jQuery); 25 | -------------------------------------------------------------------------------- /sfm/ui/static/admin/js/collapse.min.js: -------------------------------------------------------------------------------- 1 | (function(a){a(document).ready(function(){a("fieldset.collapse").each(function(c,b){0==a(b).find("div.errors").length&&a(b).addClass("collapsed").find("h2").first().append(' ('+gettext("Show")+")")});a("fieldset.collapse a.collapse-toggle").toggle(function(){a(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset",[a(this).attr("id")]);return!1},function(){a(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset", 2 | [a(this).attr("id")]);return!1})})})(django.jQuery); 3 | -------------------------------------------------------------------------------- /sfm/ui/static/admin/js/compress.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import optparse 4 | import subprocess 5 | import sys 6 | 7 | here = os.path.dirname(__file__) 8 | 9 | def main(): 10 | usage = "usage: %prog [file1..fileN]" 11 | description = """With no file paths given this script will automatically 12 | compress all jQuery-based files of the admin app. Requires the Google Closure 13 | Compiler library and Java version 6 or later.""" 14 | parser = optparse.OptionParser(usage, description=description) 15 | parser.add_option("-c", dest="compiler", default="~/bin/compiler.jar", 16 | help="path to Closure Compiler jar file") 17 | parser.add_option("-v", "--verbose", 18 | action="store_true", dest="verbose") 19 | parser.add_option("-q", "--quiet", 20 | action="store_false", dest="verbose") 21 | (options, args) = parser.parse_args() 22 | 23 | compiler = os.path.expanduser(options.compiler) 24 | if not os.path.exists(compiler): 25 | sys.exit("Google Closure compiler jar file %s not found. Please use the -c option to specify the path." % compiler) 26 | 27 | if not args: 28 | if options.verbose: 29 | sys.stdout.write("No filenames given; defaulting to admin scripts\n") 30 | args = [os.path.join(here, f) for f in [ 31 | "actions.js", "collapse.js", "inlines.js", "prepopulate.js"]] 32 | 33 | for arg in args: 34 | if not arg.endswith(".js"): 35 | arg = arg + ".js" 36 | to_compress = os.path.expanduser(arg) 37 | if os.path.exists(to_compress): 38 | to_compress_min = "%s.min.js" % "".join(arg.rsplit(".js")) 39 | cmd = "java -jar %s --js %s --js_output_file %s" % (compiler, to_compress, to_compress_min) 40 | if options.verbose: 41 | sys.stdout.write("Running: %s\n" % cmd) 42 | subprocess.call(cmd.split()) 43 | else: 44 | sys.stdout.write("File %s not found. Sure it exists?\n" % to_compress) 45 | 46 | if __name__ == '__main__': 47 | main() 48 | -------------------------------------------------------------------------------- /sfm/ui/static/admin/js/inlines.min.js: -------------------------------------------------------------------------------- 1 | (function(b){b.fn.formset=function(c){var a=b.extend({},b.fn.formset.defaults,c),j=function(a,e,d){var i=RegExp("("+e+"-(\\d+|__prefix__))"),e=e+"-"+d;b(a).attr("for")&&b(a).attr("for",b(a).attr("for").replace(i,e));if(a.id)a.id=a.id.replace(i,e);if(a.name)a.name=a.name.replace(i,e)},c=b("#id_"+a.prefix+"-TOTAL_FORMS").attr("autocomplete","off"),g=parseInt(c.val()),f=b("#id_"+a.prefix+"-MAX_NUM_FORMS").attr("autocomplete","off"),c=""==f.val()||0'+a.addText+""),h=b(this).parent().find("tr:last a")):(b(this).filter(":last").after('"),h=b(this).filter(":last").next().find("a"));h.click(function(){var c=b("#id_"+a.prefix+ 3 | "-TOTAL_FORMS"),e=b("#"+a.prefix+"-empty"),d=e.clone(!0);d.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+g);d.is("tr")?d.children(":last").append('"):d.is("ul")||d.is("ol")?d.append('
  • '+a.deleteText+"
  • "):d.children(":first").append(''+a.deleteText+ 4 | "");d.find("*").each(function(){j(this,a.prefix,c.val())});d.insertBefore(b(e));b(c).val(parseInt(c.val())+1);g+=1;""!=f.val()&&0>=f.val()-c.val()&&h.parent().hide();d.find("a."+a.deleteCssClass).click(function(){var c=b(this).parents("."+a.formCssClass);c.remove();g-=1;a.removed&&a.removed(c);c=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(c.length);(""==f.val()||0 0) { 25 | values.push($(field).val()); 26 | } 27 | }) 28 | field.val(URLify(values.join(' '), maxLength)); 29 | }; 30 | 31 | $(dependencies.join(',')).keyup(populate).change(populate).focus(populate); 32 | }); 33 | }; 34 | })(django.jQuery); 35 | -------------------------------------------------------------------------------- /sfm/ui/static/admin/js/prepopulate.min.js: -------------------------------------------------------------------------------- 1 | (function(a){a.fn.prepopulate=function(d,e){return this.each(function(){var b=a(this);b.data("_changed",!1);b.change(function(){b.data("_changed",!0)});var c=function(){if(!0!=b.data("_changed")){var c=[];a.each(d,function(b,d){0 6 |

    about sfm

    7 | 8 | 9 |

    10 | This is a simple app that lets us manage one or more feeds of data from 11 | social media sites like Twitter. Here you can see the content from 12 | those feeds and in some cases link back to the original items on their 13 | source sites. 14 |

    15 | 16 |

    17 | Data Dictionary for Twitter csv export 18 |

    19 | 20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /sfm/ui/templates/django_no_superuser.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load humanize %} 3 | 4 | {% block content %} 5 |

    Not Authorized

    6 |   7 |

    Sorry, you do not have the permissions to view this page.

    8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /sfm/ui/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | 5 | 6 | 9 | 10 |
    11 |
    12 | 13 | {% if user.is_authenticated %} 14 |

    15 | But you're already logged in, {{ user.username }}! 16 |

    17 | {% else %} 18 | 19 |

    20 | Log in with Twitter 21 |

    22 | 23 |

    24 | Note: your use of data from Twitter is subject to the 25 | terms of service you agreed to when you signed up for Twitter. 26 |

    27 | 28 |

    29 | Please do not republish data you receive here. 30 | We provide data to you for your personal and research use. 31 | Twitter's terms of service do not allow republishing of full 32 | datasets. 33 |

    34 | {% endif %} 35 |
    36 |
    37 | 38 | {% endblock %} 39 | -------------------------------------------------------------------------------- /sfm/ui/templates/search.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load humanize %} 3 | 4 | {% block content %} 5 |
    6 | 7 | {% if title %} 8 | 11 | {% endif %} 12 | 13 | {% if users %} 14 | 15 | 16 | 17 | 18 | 19 | 20 | {% for user in users %} 21 | 22 | 23 | 24 | 25 | {% endfor %} 26 | 27 |
    useritems
    {{ user.name }}{{ user.items.count }}
    28 | {% else %} 29 |

    30 | Sorry, I didn't find any users that match. 31 |

    32 | {% endif %} 33 | 34 |
    35 | {% endblock content %} 36 | -------------------------------------------------------------------------------- /sfm/ui/templates/status.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load humanize %} 3 | 4 | {% block javascript_extra %} 5 | 12 | 13 | 14 | {% endblock javascript_extra %} 15 | 16 | {% block content %} 17 |
    18 |
    19 |

    supervisord process status

    20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 30 | 31 | 32 | 33 | {% for i in list %} 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 55 | {% endif %} 56 | 57 | {% endfor %} 58 | 59 |
    processnamestart-datestart-timestatus 27 | stop-datestop-timedescription
    {{ i.name }} {{ i.start_date }} {{ i.start_time }} {{ i.status }} {{ i.stop_date }} {{ i.stop_time }} 42 | {% if i.description == '0' %} 43 | {% if i.days != '0' %} 44 | up for {{ i.days }} day{{ i.days|pluralize }} 45 | {{ i.hour}} hour{{i.hour|pluralize }} 46 | {{ i.min}} minute{{i.min|pluralize }} 47 | {{ i.sec}} second{{i.sec|pluralize }} 48 | {% else %} 49 | up for {{ i.hour}} hour{{i.hour|pluralize }} 50 | {{ i.min}} minute{{i.min|pluralize }} 51 | {{ i.sec}} second{{i.sec|pluralize }} 52 | {% endif %} 53 | {% else %} 54 | {{ i.description }}
    60 |
    61 |
    62 | 63 | {% endblock content %} 64 | -------------------------------------------------------------------------------- /sfm/ui/templates/status_not_found.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load humanize %} 3 |   4 | {% block title %}page not found{% endblock %} 5 |   6 | {% block content %} 7 |

    Supervsiord not configured

    8 |   9 |

    Sorry, Supervisord is not configured, or is not in a running state to report the details.

    10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /sfm/ui/templates/tweets.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load humanize %} 3 | {% load twitterize %} 4 | 5 | {% block javascript_extra %} 6 | 14 | {% endblock %} 15 | 16 | {% block content %} 17 | 18 | 21 | 22 |
    23 | 24 | {% include "tweets_pagination.html" %} 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | {% for item in tweets %} 39 | 40 | 46 | 47 | 48 | 57 | 61 | 62 | {% endfor %} 63 | 64 |
    date 29 |
    30 | followers/following
    userrt #textview
    41 | {{ item.date_published|date:"Y-m-d h:i:s a" }} 42 |
    43 | {{ item.tweet.user.followers_count|intcomma }} - 44 | {{ item.tweet.user.friends_count|intcomma }} 45 |
    {{ item.twitter_user.name }}{{ item.tweet.retweet_count }} 49 | {{ item.text|twitterize }} 50 | {% if item.links %} 51 | 55 | {% endif %} 56 | in twitter 58 |
    59 | raw cached 60 |
    65 | {% include "tweets_pagination.html" %} 66 |
    67 | 68 | 69 | {% endblock content %} 70 | -------------------------------------------------------------------------------- /sfm/ui/templates/tweets_pagination.html: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /sfm/ui/templates/twitter_item_links.html: -------------------------------------------------------------------------------- 1 | {% for stack in unshortened %} 2 | {% for link in stack %} 3 |
    4 | {{ link }} 10 | {% endfor %} 11 | {% endfor %} 12 | -------------------------------------------------------------------------------- /sfm/ui/templates/twitter_user.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load humanize %} 3 | {% load twitterize %} 4 | 5 | {% block stylesheet_extra %} 6 | 18 | {% endblock stylesheet_extra %} 19 | 20 | {% block javascript_extra %} 21 | 29 | 30 | 31 | 63 | {% endblock %} 64 | 65 | {% block content %} 66 | 67 |
    68 |

    {{ twitter_user.name }} - 69 | {{ twitter_user.items.count|intcomma }} item{{ twitter_user.items.count|pluralize }} 70 |

    71 |
    72 | {% if recent_tweet %} 73 | {% with recent_tweet.tweet as t %} 74 |

    75 | Twitter user {{ t.user.id|intcomma }} - created {{ t.user.created_at }}. 76 |
    77 | {{ t.user.statuses_count|intcomma }} tweets - 78 | {{ t.user.followers_count|intcomma }} followers - 79 | {{ t.user.friends_count|intcomma }} following - 80 | view in Twitter 81 |
    82 | export this data as 83 | csv 84 | or as Excel 85 |

    86 | {% endwith %} 87 | 88 | {% include "twitter_user_pagination.html" %} 89 | 90 | 91 | 92 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | {% for item in tweets %} 102 | 103 | 109 | 110 | 119 | 123 | 124 | {% endfor %} 125 | 126 |
    date 93 |
    94 | followers/following
    rt #textview
    104 | {{ item.date_published|date:"Y-m-d h:i:s"}} {{ item.date_published|date:"a" }} 105 |
    106 | {{ item.tweet.user.followers_count|intcomma }} - 107 | {{ item.tweet.user.friends_count|intcomma }} 108 |
    {{ item.tweet.retweet_count }} 111 | {{ item.text|twitterize }} 112 | {% if item.links %} 113 | 117 | {% endif %} 118 | in twitter 120 |
    121 | raw cached 122 |
    127 | {% include "twitter_user_pagination.html" %} 128 | {% endif %} 129 |
    130 | {% endblock content %} 131 | -------------------------------------------------------------------------------- /sfm/ui/templates/twitter_user_pagination.html: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /sfm/ui/templates/users_alpha.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load humanize %} 3 | 4 | {% block content %} 5 | 6 |
    7 | 10 | 11 | {% if users %} 12 | {% include "users_alpha_pagination.html" %} 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {% for user in users %} 23 | 24 | 25 | 26 | 27 | 28 | {% endfor %} 29 | 30 |
    useritemsinactive?
    {{ user.name }}{{ user.items.count|intcomma }}{% if not user.is_active %}inactive{% endif %}
    31 | {% include "users_alpha_pagination.html" %} 32 | {% endif %} 33 |
    34 | 35 | 36 | {% endblock content %} 37 | -------------------------------------------------------------------------------- /sfm/ui/templates/users_alpha_pagination.html: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /sfm/ui/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwu-libraries/social-feed-manager/ae71ea87ce93b1589025440fa887e3330019c986/sfm/ui/templatetags/__init__.py -------------------------------------------------------------------------------- /sfm/ui/templatetags/sfm_extras.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.conf import settings 3 | 4 | register = template.Library() 5 | 6 | 7 | @register.simple_tag 8 | def settings_value(name): 9 | return getattr(settings, name, '') 10 | 11 | 12 | @register.assignment_tag 13 | def assign_settings_value(name): 14 | return getattr(settings, name, '') 15 | -------------------------------------------------------------------------------- /sfm/ui/templatetags/twitterize.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from django import template 4 | from django.template.defaultfilters import stringfilter 5 | from django.utils.html import urlize 6 | from django.utils.safestring import mark_safe 7 | 8 | register = template.Library() 9 | 10 | 11 | @register.filter(name='twitterize') 12 | @stringfilter 13 | def twitterize(value, autoescape=None): 14 | # Link URLs 15 | value = urlize(value, nofollow=False, autoescape=autoescape) 16 | # Link twitter usernames prefixed with @ 17 | value = re.sub(r'(\s+|\A)@([a-zA-Z0-9\-_]*)\b', 18 | r'\1@\2', value) 19 | # Link hash tags 20 | value = re.sub(r'(\s+|\A)#([a-zA-Z0-9\-_]*)\b', 21 | r'\1#\2', 22 | value) 23 | return mark_safe(value) 24 | twitterize.is_safe = True 25 | twitterize.needs_autoescape = True 26 | -------------------------------------------------------------------------------- /sfm/ui/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates writing tests using the unittest module. These will pass 3 | when you run "manage.py test". 4 | 5 | Replace this with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | 11 | class SimpleTest(TestCase): 12 | def test_basic_addition(self): 13 | """ 14 | Tests that 1 + 1 always equals 2. 15 | """ 16 | self.assertEqual(1 + 1, 2) 17 | --------------------------------------------------------------------------------