├── .gitignore ├── .gitmodules ├── LICENSE ├── MANIFEST.in ├── README.md ├── Vagrantfile ├── bin ├── compile-mo.sh ├── crontab │ ├── crontab.tpl │ └── gen-crons.py ├── jenkins.sh ├── update │ ├── commander_settings.py-dist │ └── deploy.py └── update_site.py ├── docs ├── Makefile ├── _static │ └── .gitkeep ├── _templates │ └── .gitkeep ├── build-github.zsh ├── conf.py └── index.rst ├── lib └── product_details_json │ ├── firefox_beta_builds.json │ ├── firefox_history_development_releases.json │ ├── firefox_history_major_releases.json │ ├── firefox_history_stability_releases.json │ ├── firefox_primary_builds.json │ ├── firefox_versions.json │ ├── languages.json │ ├── mobile_details.json │ ├── mobile_history_development_releases.json │ ├── mobile_history_major_releases.json │ ├── mobile_history_stability_releases.json │ ├── thunderbird_beta_builds.json │ ├── thunderbird_history_development_releases.json │ ├── thunderbird_history_major_releases.json │ ├── thunderbird_history_stability_releases.json │ ├── thunderbird_primary_builds.json │ └── thunderbird_versions.json ├── manage.py ├── media ├── admin │ ├── css │ │ ├── base.css │ │ ├── changelists.css │ │ ├── dashboard.css │ │ ├── forms.css │ │ ├── ie.css │ │ ├── login.css │ │ ├── rtl.css │ │ └── widgets.css │ ├── img │ │ ├── admin │ │ │ ├── arrow-down.gif │ │ │ ├── arrow-up.gif │ │ │ ├── changelist-bg.gif │ │ │ ├── changelist-bg_rtl.gif │ │ │ ├── chooser-bg.gif │ │ │ ├── chooser_stacked-bg.gif │ │ │ ├── default-bg-reverse.gif │ │ │ ├── default-bg.gif │ │ │ ├── deleted-overlay.gif │ │ │ ├── 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.gif │ │ │ ├── selector-add.gif │ │ │ ├── selector-addall.gif │ │ │ ├── selector-remove.gif │ │ │ ├── selector-removeall.gif │ │ │ ├── selector-search.gif │ │ │ ├── selector_stacked-add.gif │ │ │ ├── selector_stacked-remove.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 │ │ ├── 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 │ │ ├── dateparse.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 └── img │ ├── .gitignore │ └── missing.png ├── migrations ├── 01-noop.sql ├── __init__.py └── schematic_settings.py ├── puppet ├── files │ └── etc │ │ └── httpd │ │ └── conf.d │ │ └── playdoh.conf └── manifests │ ├── classes │ ├── apache.pp │ ├── custom.pp │ ├── init.pp │ ├── memcached.pp │ ├── mysql.pp │ ├── playdoh.pp │ └── python.pp │ └── vagrant.pp ├── requirements ├── compiled.txt ├── dev.txt └── prod.txt ├── setup.py ├── source ├── __init__.py ├── api │ ├── __init__.py │ ├── urls.py │ └── views.py ├── articles │ ├── __init__.py │ ├── admin.py │ ├── fixtures │ │ ├── initial_sections.json │ │ └── test_data.json │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── migrate_article_categories.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto__add_field_article_article_js_header__add_field_article_article_j.py │ │ └── __init__.py │ ├── models.py │ ├── search_indexes.py │ ├── static │ │ └── articles │ │ │ ├── css │ │ │ └── highlight-theme.css │ │ │ └── js │ │ │ ├── jquery.localScroll.min.js │ │ │ ├── jquery.scrollTo.min.js │ │ │ └── pym.min.js │ ├── templates │ │ └── articles │ │ │ ├── _article_author_list.html │ │ │ ├── _article_category_and_tags_overline.html │ │ │ ├── _article_link_list.html │ │ │ ├── _article_list_item.html │ │ │ ├── _article_list_section_overline.html │ │ │ ├── _base_articles.html │ │ │ ├── article_detail.html │ │ │ ├── article_list.html │ │ │ └── article_list_with_promos.html │ ├── urls.py │ └── views.py ├── base │ ├── __init__.py │ ├── context_processors.py │ ├── feeds.py │ ├── fixtures │ │ └── taggit_test_data.json │ ├── helpers.py │ ├── models.py │ ├── static │ │ └── base │ │ │ ├── css │ │ │ ├── app.css │ │ │ ├── bootstrap.css │ │ │ ├── font-awesome-ie7.min.css │ │ │ ├── font-awesome.css │ │ │ ├── font-awesome.min.css │ │ │ └── warnr.css │ │ │ ├── font │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ └── fontawesome-webfont.woff │ │ │ ├── img │ │ │ ├── favicon.ico │ │ │ ├── source_logo.png │ │ │ ├── source_mini.png │ │ │ ├── source_retina.png │ │ │ ├── source_retina_top.png │ │ │ ├── source_retina_top_invert.png │ │ │ └── srccon_flag.png │ │ │ └── js │ │ │ ├── app.js │ │ │ ├── gridfilter.js │ │ │ ├── libs │ │ │ ├── hogan-2.0.0.js │ │ │ ├── jquery-1.7.2.min.js │ │ │ ├── modernizr-2.0.6.min.js │ │ │ ├── overthrow.min.js │ │ │ ├── snap.min.js │ │ │ ├── typeahead.js │ │ │ └── typeahead.min.js │ │ │ └── listfilter.js │ ├── templates │ │ ├── .gitignore │ │ ├── 404.html │ │ ├── 500.html │ │ ├── admin │ │ │ ├── base_site.html │ │ │ └── index.html │ │ ├── base.html │ │ ├── feeds │ │ │ └── article_description.html │ │ ├── flatpages │ │ │ └── default.html │ │ ├── homepage.html │ │ ├── search │ │ │ ├── includes │ │ │ │ ├── _article_list_item.html │ │ │ │ ├── _code_list_item.html │ │ │ │ ├── _organization_list_item.html │ │ │ │ └── _person_list_item.html │ │ │ ├── indexes │ │ │ │ ├── articles │ │ │ │ │ └── article_text.txt │ │ │ │ ├── code │ │ │ │ │ └── code_text.txt │ │ │ │ └── people │ │ │ │ │ ├── organization_text.txt │ │ │ │ │ └── person_text.txt │ │ │ └── search.html │ │ └── utils │ │ │ ├── _basic_link_list.html │ │ │ └── _paginate.html │ ├── urls.py │ ├── utils.py │ ├── views.py │ └── widgets.py ├── code │ ├── __init__.py │ ├── admin.py │ ├── fixtures │ │ └── test_data.json │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── update_code_github_stats.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── search_indexes.py │ ├── static │ │ └── code │ │ │ └── js │ │ │ ├── date.js │ │ │ └── repo.js │ ├── templates │ │ └── code │ │ │ ├── _base_code.html │ │ │ ├── _code_category_and_tags_overline.html │ │ │ ├── _code_link_list.html │ │ │ ├── _code_list_item.html │ │ │ ├── code_detail.html │ │ │ ├── code_list.html │ │ │ └── code_list.json │ ├── urls.py │ └── views.py ├── guides │ ├── __init__.py │ ├── admin.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto__add_field_guide_cover_color.py │ │ ├── 0003_auto__add_field_guidearticle_external_url__chg_field_guidearticle_arti.py │ │ ├── 0004_auto__add_field_guidearticle_external_title.py │ │ └── __init__.py │ ├── models.py │ ├── templates │ │ └── guides │ │ │ ├── _base_guides.html │ │ │ ├── _guide_article_list_item.html │ │ │ ├── _guide_link_list.html │ │ │ ├── _guide_list_item.html │ │ │ ├── guide_detail.html │ │ │ └── guide_list.html │ ├── urls.py │ └── views.py ├── jobs │ ├── __init__.py │ ├── admin.py │ ├── forms.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ ├── job_post_reminders.py │ │ │ └── tweet_new_jobs.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto__add_field_job_tweeted_at.py │ │ ├── 0003_auto__add_field_job_email.py │ │ ├── 0004_auto__add_field_job_description__add_field_job_contact_name__add_field.py │ │ └── __init__.py │ ├── models.py │ ├── templates │ │ └── jobs │ │ │ ├── _base_jobs.html │ │ │ ├── _job_list_as_grouped_list.html │ │ │ ├── _job_list_as_list.html │ │ │ ├── emails │ │ │ ├── job_post_reminder.html │ │ │ └── job_post_reminder.txt │ │ │ └── job_list.html │ ├── urls.py │ └── views.py ├── locale │ ├── en_US │ │ └── LC_MESSAGES │ │ │ └── messages.po │ └── fr │ │ └── LC_MESSAGES │ │ └── messages.po ├── people │ ├── __init__.py │ ├── admin.py │ ├── fixtures │ │ └── test_data.json │ ├── forms.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ ├── bulk_update_twitter_bios.py │ │ │ ├── update_organization_github_stats.py │ │ │ └── update_person_github_stats.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── search_indexes.py │ ├── templates │ │ └── people │ │ │ ├── _base_organizations.html │ │ │ ├── _base_people.html │ │ │ ├── _organization_link_list.html │ │ │ ├── _organization_link_list_inline.html │ │ │ ├── _person_link_list.html │ │ │ ├── _person_link_list_inline.html │ │ │ ├── organization_detail.html │ │ │ ├── organization_list.html │ │ │ ├── organization_update.html │ │ │ ├── person_detail.html │ │ │ └── person_list.html │ ├── urls │ │ ├── __init__.py │ │ ├── organizations.py │ │ └── people.py │ ├── utils.py │ └── views.py ├── settings │ ├── __init__.py │ ├── base.py │ └── local.py-dist ├── tags │ ├── __init__.py │ ├── admin.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── utils.py ├── urls.py └── utils │ ├── __init__.py │ ├── caching.py │ ├── email.py │ ├── json.py │ └── pagination.py ├── vagrantconfig.yaml ├── vagrantconfig_local.yaml-dist ├── vendor-local ├── lib │ └── python │ │ ├── dateutil │ │ ├── __init__.py │ │ ├── easter.py │ │ ├── parser.py │ │ ├── relativedelta.py │ │ ├── rrule.py │ │ ├── tz.py │ │ ├── tzwin.py │ │ └── zoneinfo │ │ │ ├── __init__.py │ │ │ └── zoneinfo--latest.tar.gz │ │ ├── httplib2 │ │ ├── __init__.py │ │ ├── cacerts.txt │ │ ├── iri2uri.py │ │ └── socks.py │ │ ├── memcache.py │ │ ├── python_dateutil-2.2-py2.7.egg-info │ │ ├── PKG-INFO │ │ ├── SOURCES.txt │ │ ├── dependency_links.txt │ │ ├── installed-files.txt │ │ ├── not-zip-safe │ │ ├── requires.txt │ │ └── top_level.txt │ │ ├── six-1.6.1-py2.7.egg-info │ │ ├── PKG-INFO │ │ ├── SOURCES.txt │ │ ├── dependency_links.txt │ │ ├── installed-files.txt │ │ └── top_level.txt │ │ ├── six.py │ │ └── smartypants.py └── vendor.pth └── wsgi └── playdoh.wsgi /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.pot 3 | *.pyc 4 | source/settings/local.py 5 | source_demo_db 6 | venv/* 7 | static/* 8 | media/cache 9 | media/img/uploads 10 | .DS_Store -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor"] 2 | path = vendor 3 | url = git://github.com/mozilla/playdoh-lib.git 4 | [submodule "vendor-local/src/django-browserid"] 5 | path = vendor-local/src/django-browserid 6 | url = https://github.com/mozilla/django-browserid.git 7 | [submodule "vendor-local/src/requests"] 8 | path = vendor-local/src/requests 9 | url = https://github.com/kennethreitz/requests.git 10 | [submodule "vendor-local/src/django-south"] 11 | path = vendor-local/src/django-south 12 | url = git://github.com/dmishe/django-south.git 13 | [submodule "vendor-local/src/django-cache-machine"] 14 | path = vendor-local/src/django-cache-machine 15 | url = git://github.com/jbalogh/django-cache-machine.git 16 | [submodule "vendor-local/src/django-haystack"] 17 | path = vendor-local/src/django-haystack 18 | url = git://github.com/toastdriven/django-haystack.git 19 | [submodule "vendor-local/src/pyelasticsearch"] 20 | path = vendor-local/src/pyelasticsearch 21 | url = git://github.com/toastdriven/pyelasticsearch.git 22 | [submodule "vendor-local/src/sorl-thumbnail"] 23 | path = vendor-local/src/sorl-thumbnail 24 | url = git://github.com/mariocesar/sorl-thumbnail.git 25 | [submodule "vendor-local/src/django-taggit"] 26 | path = vendor-local/src/django-taggit 27 | url = https://github.com/ryanpitts/django-taggit.git 28 | [submodule "vendor-local/src/typogrify"] 29 | path = vendor-local/src/typogrify 30 | url = git://github.com/mintchaos/typogrify.git 31 | [submodule "vendor-local/src/python-twitter"] 32 | path = vendor-local/src/python-twitter 33 | url = git://github.com/bear/python-twitter.git 34 | [submodule "vendor-local/src/python-oauth2"] 35 | path = vendor-local/src/python-oauth2 36 | url = git://github.com/simplegeo/python-oauth2.git 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Mozilla 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | * Neither the name of the copyright owner nor the names of its contributors 13 | may be used to endorse or promote products derived from this software 14 | without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include */*/templates *.* 2 | recursive-include */locale *.* 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | source 2 | ====== 3 | 4 | Source is a website [dedicated to][sinker-explain] "advocating for, shining a spotlight on, and helping to generate community around the code that's being written in journalism." It's built with [Django][django], using Mozilla's [Playdoh web app template][gh-playdoh]. 5 | 6 | [sinker-explain]: http://sinker.tumblr.com/post/12203160394/journalism-in-the-open-hard-coding-community 7 | [django]: http://www.djangoproject.com/ 8 | [gh-playdoh]: https://github.com/mozilla/playdoh 9 | 10 | 11 | Installation 12 | ------------ 13 | 14 | ### Requirements 15 | 16 | You need Python 2.6 or 2.7, Mozilla's [funfactory][funfactory], MySQL, git, virtualenv, and a Unix-like OS. 17 | 18 | [funfactory]: https://github.com/mozilla/funfactory 19 | 20 | ### Setup 21 | 22 | First, make sure you've got funfactory installed, because the Playdoh app template will need it. 23 | 24 | `pip install funfactory` 25 | 26 | Then: 27 | 28 | 1. Fork and/or clone this Source repository from GitHub 29 | 2. Set up a virtual environment for your new project 30 | 3. Activate your virtualenv and cd into the project directory 31 | 4. Fetch the submodule dependancies 32 | 33 | `git submodule update --init --recursive` 34 | 35 | And make sure you have all the development requirements 36 | 37 | `pip install -r requirements/dev.txt` 38 | 39 | ### Configuration 40 | 41 | The app has a base settings file that can be found at source/settings/base.py, you can override any of the values there inside a local.py file. 42 | 43 | `cp source/settings/local.py-dist source/settings/local.py` 44 | 45 | Please ensure that you create your own SECRET_KEY and HMAC_KEY 46 | 47 | You can point your database config to sqlite for quick testing, or if you'd rather use MySQL, you'll need to create a new database. Adjust the DATABASES dict in source/settings/local.py accordingly, and then 48 | 49 | `python manage.py syncdb` 50 | 51 | The primary content apps are managed by django-south, so next run 52 | 53 | `python manage.py migrate articles` 54 | `python manage.py migrate code` 55 | `python manage.py migrate people` 56 | 57 | This repository includes a few fixtures with test articles, people, organizations and code records for you to play with. If you'd like to add them, next run 58 | 59 | `python manage.py loaddata test_data` 60 | `python manage.py loaddata taggit_test_data` 61 | 62 | And then it's time to fire it up! 63 | 64 | `python manage.py runserver` 65 | 66 | Now you should be able view your dev server at [http://localhost:8000/][localhost] 67 | 68 | [localhost]: http://localhost:8000/ 69 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | require "yaml" 2 | 3 | # Load up our vagrant config files -- vagrantconfig.yaml first 4 | _config = YAML.load(File.open(File.join(File.dirname(__FILE__), 5 | "vagrantconfig.yaml"), File::RDONLY).read) 6 | 7 | # Local-specific/not-git-managed config -- vagrantconfig_local.yaml 8 | begin 9 | _config.merge!(YAML.load(File.open(File.join(File.dirname(__FILE__), 10 | "vagrantconfig_local.yaml"), File::RDONLY).read)) 11 | rescue Errno::ENOENT # No vagrantconfig_local.yaml found -- that's OK; just 12 | # use the defaults. 13 | end 14 | 15 | CONF = _config 16 | MOUNT_POINT = '/home/vagrant/project' 17 | 18 | Vagrant::Config.run do |config| 19 | config.vm.box = "lucid32" 20 | config.vm.box_url = "http://files.vagrantup.com/lucid32.box" 21 | 22 | config.vm.forward_port 8000, 8000 23 | 24 | # Increase vagrant's patience during hang-y CentOS bootup 25 | # see: https://github.com/jedi4ever/veewee/issues/14 26 | config.ssh.max_tries = 50 27 | config.ssh.timeout = 300 28 | 29 | # nfs needs to be explicitly enabled to run. 30 | if CONF['nfs'] == false or RUBY_PLATFORM =~ /mswin(32|64)/ 31 | config.vm.share_folder("v-root", MOUNT_POINT, ".") 32 | else 33 | config.vm.share_folder("v-root", MOUNT_POINT, ".", :nfs => true) 34 | end 35 | 36 | # Add to /etc/hosts: 33.33.33.24 dev.playdoh.org 37 | config.vm.network :hostonly, "33.33.33.24" 38 | 39 | config.vm.provision :puppet do |puppet| 40 | puppet.manifests_path = "puppet/manifests" 41 | puppet.manifest_file = "vagrant.pp" 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /bin/compile-mo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TARGET=$1 4 | LOCKFILE="/tmp/compile-mo-${2}.lock" 5 | 6 | function usage() { 7 | echo "syntax:" 8 | echo " compile-mo.sh locale-dir/ [unique]" 9 | echo "unique is an optional string that will be used as the name of the lockfile" 10 | exit 1 11 | } 12 | 13 | # check if file and dir are there 14 | if [[ ($# -gt 2) || (! -d "$TARGET") ]]; then usage; fi 15 | 16 | # check if the lockfile exists 17 | if [ -e $LOCKFILE ]; then 18 | echo "$LOCKFILE present, exiting" 19 | exit 99 20 | fi 21 | 22 | touch $LOCKFILE 23 | for lang in `find $TARGET -type f -name "*.po"`; do 24 | dir=`dirname $lang` 25 | stem=`basename $lang .po` 26 | msgfmt -o ${dir}/${stem}.mo $lang 27 | done 28 | rm $LOCKFILE 29 | -------------------------------------------------------------------------------- /bin/crontab/crontab.tpl: -------------------------------------------------------------------------------- 1 | # 2 | # {{ header }} 3 | # 4 | 5 | # MAILTO=some-email-list 6 | 7 | HOME=/tmp 8 | 9 | # Every minute! 10 | * * * * * {{ cron }} 11 | 12 | # Every hour. 13 | 42 * * * * {{ django }} cleanup 14 | 15 | # Every 2 hours. 16 | 1 */2 * * * {{ cron }} something 17 | 18 | # Etc... 19 | 20 | MAILTO=root 21 | -------------------------------------------------------------------------------- /bin/crontab/gen-crons.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | from optparse import OptionParser 4 | 5 | from jinja2 import Template 6 | 7 | 8 | HEADER = '!!AUTO-GENERATED!! Edit bin/crontab/crontab.tpl instead.' 9 | TEMPLATE = open(os.path.join(os.path.dirname(__file__), 'crontab.tpl')).read() 10 | 11 | 12 | def main(): 13 | parser = OptionParser() 14 | parser.add_option('-w', '--webapp', 15 | help='Location of web app (required)') 16 | parser.add_option('-u', '--user', 17 | help=('Prefix cron with this user. ' 18 | 'Only define for cron.d style crontabs.')) 19 | parser.add_option('-p', '--python', default='/usr/bin/python2.6', 20 | help='Python interpreter to use.') 21 | 22 | (opts, args) = parser.parse_args() 23 | 24 | if not opts.webapp: 25 | parser.error('-w must be defined') 26 | 27 | ctx = {'django': 'cd %s; %s manage.py' % (opts.webapp, opts.python)} 28 | ctx['cron'] = '%s cron' % ctx['django'] 29 | 30 | if opts.user: 31 | for k, v in ctx.iteritems(): 32 | ctx[k] = '%s %s' % (opts.user, v) 33 | 34 | # Needs to stay below the opts.user injection. 35 | ctx['python'] = opts.python 36 | ctx['header'] = HEADER 37 | 38 | print Template(TEMPLATE).render(**ctx) 39 | 40 | 41 | if __name__ == '__main__': 42 | main() 43 | -------------------------------------------------------------------------------- /bin/jenkins.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This script makes sure that Jenkins can properly run your tests against your 3 | # codebase. 4 | set -e 5 | 6 | DB_HOST="localhost" 7 | DB_USER="hudson" 8 | 9 | cd $WORKSPACE 10 | VENV=$WORKSPACE/venv 11 | 12 | echo "Starting build on executor $EXECUTOR_NUMBER..." 13 | 14 | # Make sure there's no old pyc files around. 15 | find . -name '*.pyc' -exec rm {} \; 16 | 17 | if [ ! -d "$VENV/bin" ]; then 18 | echo "No virtualenv found. Making one..." 19 | virtualenv $VENV --no-site-packages 20 | source $VENV/bin/activate 21 | pip install --upgrade pip 22 | pip install coverage 23 | fi 24 | 25 | git submodule sync -q 26 | git submodule update --init --recursive 27 | 28 | if [ ! -d "$WORKSPACE/vendor" ]; then 29 | echo "No /vendor... crap." 30 | exit 1 31 | fi 32 | 33 | source $VENV/bin/activate 34 | pip install -q -r requirements/compiled.txt 35 | pip install -q -r requirements/dev.txt 36 | 37 | cat > settings/local.py <`_. 14 | 15 | To learn more about it, step by the `playdoh project page 16 | `_. 17 | 18 | Contents 19 | -------- 20 | 21 | .. toctree:: 22 | :maxdepth: 1 23 | 24 | 25 | Indices and tables 26 | ------------------ 27 | 28 | * :ref:`genindex` 29 | * :ref:`modindex` 30 | * :ref:`search` 31 | -------------------------------------------------------------------------------- /lib/product_details_json/firefox_beta_builds.json: -------------------------------------------------------------------------------- 1 | {"ast":{"3.6.13":{"Windows":{"filesize":7.8},"OS X":{"filesize":19},"Linux":{"filesize":9.5}},"4.0b10":{"Windows":{"filesize":7.8},"OS X":{"filesize":19},"Linux":{"filesize":9.6}}},"es-CL":{"3.5.16":{"Windows":{"filesize":7.6},"OS X":{"filesize":17.4},"Linux":{"filesize":9.2}},"3.6.13":{"Windows":{"filesize":7.8},"OS X":{"filesize":19},"Linux":{"filesize":9.5}}},"es-MX":{"3.5.16":{"Windows":{"filesize":7.6},"OS X":{"filesize":17.4},"Linux":{"filesize":9.2}},"3.6.13":{"Windows":{"filesize":7.8},"OS X":{"filesize":19},"Linux":{"filesize":9.5}}},"gd":{"4.0b10":{"Windows":{"filesize":7.8},"OS X":{"filesize":19},"Linux":{"filesize":9.5}},"3.6.13":{"Windows":{"filesize":7.8},"OS X":{"filesize":19},"Linux":{"filesize":9.5}}},"kk":{"3.5.16":{"Windows":{"filesize":7.6},"OS X":{"filesize":17.4},"Linux":{"filesize":9.2}},"3.6.13":{"Windows":{"filesize":7.8},"OS X":{"filesize":19},"Linux":{"filesize":9.6}}},"ku":{"3.5.16":[],"3.6.13":{"Windows":{"filesize":7.9},"OS X":{"filesize":19},"Linux":{"filesize":9.6}}},"mn":{"3.5.16":{"Windows":{"filesize":7.6},"OS X":{"filesize":17.4},"Linux":{"filesize":9.2}},"3.6.13":[]},"or":{"3.5.16":{"Windows":{"filesize":7.6},"OS X":{"filesize":17.4},"Linux":{"filesize":9.2}},"3.6.13":{"Windows":{"filesize":8},"OS X":{"filesize":19},"Linux":{"filesize":9.8}}},"rm":{"3.5.16":{"Windows":{"filesize":7.6},"OS X":{"filesize":17.4},"Linux":{"filesize":9.2}},"4.0b10":{"Windows":{"filesize":7.8},"OS X":{"filesize":19},"Linux":{"filesize":9.5}},"3.6.13":{"Windows":{"filesize":7.8},"OS X":{"filesize":19},"Linux":{"filesize":9.6}}},"ta":{"3.5.16":{"Windows":{"filesize":7.6},"OS X":{"filesize":17.4},"Linux":{"filesize":9.2}},"3.6.13":{"Windows":{"filesize":8},"OS X":{"filesize":19},"Linux":{"filesize":9.8}}},"ta-LK":{"3.5.16":{"Windows":{"filesize":7.6},"OS X":{"filesize":17.4},"Linux":{"filesize":9.2}},"3.6.13":{"Windows":{"filesize":7.9},"OS X":{"filesize":19},"Linux":{"filesize":9.6}}}} -------------------------------------------------------------------------------- /lib/product_details_json/firefox_history_development_releases.json: -------------------------------------------------------------------------------- 1 | {"1.0rc1":"2004-10-27","1.0rc2":"2004-11-03","1.5rc1":"2005-11-01","1.5rc2":"2005-11-10","1.5rc3":"2005-11-17","2.0b1":"2006-07-12","2.0b2":"2006-08-31","2.0rc1":"2006-09-26","2.0rc2":"2006-10-06","2.0rc3":"2007-10-16","3.0b1":"2007-11-19","3.0b2":"2007-12-18","3.0b3":"2008-02-12","3.0b4":"2008-03-10","3.0b5":"2008-04-02","3.0rc1":"2008-05-16","3.0rc2":"2008-06-03","3.1b1":"2008-08-14","3.1b2":"2008-12-08","3.1b3":"2009-03-12","3.5b4":"2009-04-27","3.5rc2":"2009-06-19","3.5rc3":"2009-06-24","3.6b1":"2009-10-30","3.6b2":"2009-11-10","3.6b3":"2009-11-17","3.6b4":"2009-11-26","3.6b5":"2009-12-17","3.6rc1":"2010-01-08","3.6rc2":"2010-01-17","3.6.3plugin1":"2010-04-08","3.6.4build1":"2010-04-20","3.6.4build3":"2010-05-04","3.6.4build4":"2010-05-14","3.6.4build5":"2010-05-26","3.6.4build6":"2010-05-28","3.6.4build7":"2010-06-14","3.6.7build1":"2010-07-02","4.0b1":"2010-07-06","4.0b2":"2010-07-27","4.0b3":"2010-08-11","4.0b4":"2010-08-24","4.0b5":"2010-09-07","4.0b6":"2010-09-14","4.0b7":"2010-11-10","4.0b8":"2010-12-22","4.0b9":"2011-01-14","4.0b10":"2011-01-25"} -------------------------------------------------------------------------------- /lib/product_details_json/firefox_history_major_releases.json: -------------------------------------------------------------------------------- 1 | {"1.0":"2004-11-09","1.5":"2005-11-29","2.0":"2006-10-24","3.0":"2008-06-17","3.5":"2009-06-30","3.6":"2010-01-21"} -------------------------------------------------------------------------------- /lib/product_details_json/firefox_history_stability_releases.json: -------------------------------------------------------------------------------- 1 | {"1.0.1":"2005-02-24","1.0.2":"2005-03-23","1.0.3":"2005-04-15","1.0.4":"2005-05-11","1.0.5":"2005-07-12","1.0.6":"2005-07-19","1.0.7":"2005-09-20","1.0.8":"2006-04-13","1.5.0.1":"2006-02-01","1.5.0.2":"2006-04-13","1.5.0.3":"2006-05-02","1.5.0.4":"2006-06-01","1.5.0.5":"2006-07-26","1.5.0.6":"2006-08-02","1.5.0.7":"2006-09-14","1.5.0.8":"2006-11-07","1.5.0.9":"2006-12-19","1.5.0.10":"2007-02-23","1.5.0.11":"2007-03-20","1.5.0.12":"2007-05-30","2.0.0.1":"2006-12-19","2.0.0.2":"2007-02-23","2.0.0.3":"2007-03-20","2.0.0.4":"2007-05-30","2.0.0.5":"2007-07-17","2.0.0.6":"2007-07-30","2.0.0.7":"2007-09-18","2.0.0.8":"2007-10-18","2.0.0.9":"2007-11-01","2.0.0.10":"2007-11-26","2.0.0.11":"2007-11-30","2.0.0.12":"2008-02-07","2.0.0.13":"2008-03-25","2.0.0.14":"2008-04-16","2.0.0.15":"2008-07-01","2.0.0.16":"2008-07-15","2.0.0.17":"2008-09-23","2.0.0.18":"2008-11-12","2.0.0.19":"2008-12-16","2.0.0.20":"2008-12-18","3.0.1":"2008-07-16","3.0.2":"2008-09-23","3.0.3":"2008-09-26","3.0.4":"2008-11-12","3.0.5":"2008-12-16","3.0.6":"2009-02-03","3.0.7":"2009-03-04","3.0.8":"2009-03-27","3.0.9":"2009-04-21","3.0.10":"2009-04-27","3.0.11":"2009-06-11","3.0.12":"2009-07-21","3.0.13":"2009-08-03","3.0.14":"2009-09-09","3.0.15":"2009-10-27","3.0.16":"2009-12-15","3.0.17":"2010-01-05","3.0.18":"2010-02-17","3.0.19":"2010-03-30","3.5.1":"2009-07-17","3.5.2":"2009-08-03","3.5.3":"2009-09-09","3.5.4":"2009-10-27","3.5.5":"2009-11-05","3.5.6":"2009-12-15","3.5.7":"2010-01-05","3.5.8":"2010-02-17","3.5.9":"2010-03-30","3.5.10":"2010-06-22","3.5.11":"2010-07-20","3.5.12":"2010-09-07","3.5.13":"2010-09-15","3.5.14":"2010-10-19","3.5.15":"2010-10-27","3.6.16":"2010-12-09","3.6.2":"2010-03-22","3.6.3":"2010-04-01","3.6.4":"2010-06-22","3.6.6":"2010-06-26","3.6.7":"2010-07-20","3.6.8":"2010-07-23","3.6.9":"2010-09-07","3.6.10":"2010-09-15","3.6.11":"2010-10-19","3.6.12":"2010-10-27","3.6.13":"2010-12-09"} -------------------------------------------------------------------------------- /lib/product_details_json/firefox_versions.json: -------------------------------------------------------------------------------- 1 | {"LATEST_FIREFOX_VERSION":"3.6.13","LATEST_FIREFOX_DEVEL_VERSION":"4.0b10","LATEST_FIREFOX_RELEASED_DEVEL_VERSION":"4.0b10","LATEST_FIREFOX_OLDER_VERSION":"3.5.16"} -------------------------------------------------------------------------------- /lib/product_details_json/mobile_history_development_releases.json: -------------------------------------------------------------------------------- 1 | {"1.1b1":"2010-04-28","1.1rc1":"2010-06-16","4.0b1":"2010-10-06","4.0b2":"2010-11-04","4.0b3":"2010-12-22"} -------------------------------------------------------------------------------- /lib/product_details_json/mobile_history_major_releases.json: -------------------------------------------------------------------------------- 1 | {"1.0":"2010-01-28","1.1":"2010-07-01"} -------------------------------------------------------------------------------- /lib/product_details_json/mobile_history_stability_releases.json: -------------------------------------------------------------------------------- 1 | {"1.0.1":"2010-04-13"} -------------------------------------------------------------------------------- /lib/product_details_json/thunderbird_beta_builds.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /lib/product_details_json/thunderbird_history_development_releases.json: -------------------------------------------------------------------------------- 1 | {"1.0rc1":"2004-12-01","1.5b1":"2005-09-09","1.5b2":"2005-10-07","1.5rc1":"2005-11-05","1.5rc2":"2005-12-21","2.0b1":"2006-12-12","2.0b2":"2007-01-23","2.0rc1":"2007-04-06","3.0a1":"2008-05-12","3.0a2":"2008-07-13","3.0a3":"2008-10-14","3.0b1":"2008-12-09","3.0b2":"2009-02-26","3.0b3":"2009-07-21","3.0b4":"2009-10-22","3.0rc1":"2009-11-24","3.0rc2":"2009-12-01","3.1a1":"2010-02-03","3.1b1":"2010-03-10","3.1rc1":"2010-05-27","3.1rc2":"2010-06-09"} -------------------------------------------------------------------------------- /lib/product_details_json/thunderbird_history_major_releases.json: -------------------------------------------------------------------------------- 1 | {"1.0":"2004-12-07","1.5":"2006-01-11","2.0":"2007-04-18","3.0":"2009-12-08","3.1":"2010-06-24"} -------------------------------------------------------------------------------- /lib/product_details_json/thunderbird_history_stability_releases.json: -------------------------------------------------------------------------------- 1 | {"1.0.2":"2005-03-21","1.0.5":"2005-07-13","1.0.6":"2005-07-19","1.0.7":"2005-09-29","1.0.8":"2006-04-21","1.5.0.2":"2006-04-21","1.5.0.4":"2006-06-01","1.5.0.5":"2006-07-27","1.5.0.7":"2006-09-14","1.5.0.8":"2006-11-08","1.5.0.9":"2006-12-19","1.5.0.10":"2007-03-01","1.5.0.12":"2007-05-30","1.5.0.13":"2007-08-23","2.0.0.4":"2007-06-14","2.0.0.5":"2007-07-19","2.0.0.6":"2007-08-01","2.0.0.9":"2007-11-14","2.0.0.12":"2008-02-26","2.0.0.14":"2008-05-01","2.0.0.16":"2008-07-23","2.0.0.17":"2008-09-25","2.0.0.18":"2008-11-19","2.0.0.19":"2008-12-30","2.0.0.21":"2009-03-18","2.0.0.22":"2009-06-22","2.0.0.23":"2009-08-20","2.0.0.24":"2010-03-16","3.0.1":"2010-01-20","3.0.2":"2010-02-25","3.0.3":"2010-03-01","3.0.4":"2010-03-30","3.0.5":"2010-06-17","3.0.6":"2010-07-20","3.0.7":"2010-09-07","3.0.8":"2010-09-16","3.0.9":"2010-10-19","3.0.10":"2010-10-27","3.1.1":"2010-07-20","3.1.2":"2010-08-05","3.1.3":"2010-09-07","3.1.4":"2010-09-16","3.1.5":"2010-10-19","3.1.6":"2010-10-27"} -------------------------------------------------------------------------------- /lib/product_details_json/thunderbird_versions.json: -------------------------------------------------------------------------------- 1 | {"LATEST_THUNDERBIRD_VERSION":"3.1.7","LATEST_THUNDERBIRD__OLDER_VERSION":"3.0.11"} -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | # Edit this if necessary or override the variable in your environment. 6 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'source.settings') 7 | 8 | try: 9 | # For local development in a virtualenv: 10 | from funfactory import manage 11 | except ImportError: 12 | # Production: 13 | # Add a temporary path so that we can import the funfactory 14 | tmp_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 15 | 'vendor', 'src', 'funfactory') 16 | sys.path.append(tmp_path) 17 | 18 | from funfactory import manage 19 | 20 | # Let the path magic happen in setup_environ() ! 21 | sys.path.remove(tmp_path) 22 | 23 | 24 | manage.setup_environ(__file__, more_pythonic=True) 25 | 26 | if __name__ == "__main__": 27 | manage.main() 28 | -------------------------------------------------------------------------------- /media/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 | } -------------------------------------------------------------------------------- /media/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 | } -------------------------------------------------------------------------------- /media/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 | -------------------------------------------------------------------------------- /media/admin/img/admin/arrow-down.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/arrow-down.gif -------------------------------------------------------------------------------- /media/admin/img/admin/arrow-up.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/arrow-up.gif -------------------------------------------------------------------------------- /media/admin/img/admin/changelist-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/changelist-bg.gif -------------------------------------------------------------------------------- /media/admin/img/admin/changelist-bg_rtl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/changelist-bg_rtl.gif -------------------------------------------------------------------------------- /media/admin/img/admin/chooser-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/chooser-bg.gif -------------------------------------------------------------------------------- /media/admin/img/admin/chooser_stacked-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/chooser_stacked-bg.gif -------------------------------------------------------------------------------- /media/admin/img/admin/default-bg-reverse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/default-bg-reverse.gif -------------------------------------------------------------------------------- /media/admin/img/admin/default-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/default-bg.gif -------------------------------------------------------------------------------- /media/admin/img/admin/deleted-overlay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/deleted-overlay.gif -------------------------------------------------------------------------------- /media/admin/img/admin/icon-no.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/icon-no.gif -------------------------------------------------------------------------------- /media/admin/img/admin/icon-unknown.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/icon-unknown.gif -------------------------------------------------------------------------------- /media/admin/img/admin/icon-yes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/icon-yes.gif -------------------------------------------------------------------------------- /media/admin/img/admin/icon_addlink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/icon_addlink.gif -------------------------------------------------------------------------------- /media/admin/img/admin/icon_alert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/icon_alert.gif -------------------------------------------------------------------------------- /media/admin/img/admin/icon_calendar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/icon_calendar.gif -------------------------------------------------------------------------------- /media/admin/img/admin/icon_changelink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/icon_changelink.gif -------------------------------------------------------------------------------- /media/admin/img/admin/icon_clock.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/icon_clock.gif -------------------------------------------------------------------------------- /media/admin/img/admin/icon_deletelink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/icon_deletelink.gif -------------------------------------------------------------------------------- /media/admin/img/admin/icon_error.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/icon_error.gif -------------------------------------------------------------------------------- /media/admin/img/admin/icon_searchbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/icon_searchbox.png -------------------------------------------------------------------------------- /media/admin/img/admin/icon_success.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/icon_success.gif -------------------------------------------------------------------------------- /media/admin/img/admin/inline-delete-8bit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/inline-delete-8bit.png -------------------------------------------------------------------------------- /media/admin/img/admin/inline-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/inline-delete.png -------------------------------------------------------------------------------- /media/admin/img/admin/inline-restore-8bit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/inline-restore-8bit.png -------------------------------------------------------------------------------- /media/admin/img/admin/inline-restore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/inline-restore.png -------------------------------------------------------------------------------- /media/admin/img/admin/inline-splitter-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/inline-splitter-bg.gif -------------------------------------------------------------------------------- /media/admin/img/admin/nav-bg-grabber.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/nav-bg-grabber.gif -------------------------------------------------------------------------------- /media/admin/img/admin/nav-bg-reverse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/nav-bg-reverse.gif -------------------------------------------------------------------------------- /media/admin/img/admin/nav-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/nav-bg.gif -------------------------------------------------------------------------------- /media/admin/img/admin/selector-add.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/selector-add.gif -------------------------------------------------------------------------------- /media/admin/img/admin/selector-addall.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/selector-addall.gif -------------------------------------------------------------------------------- /media/admin/img/admin/selector-remove.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/selector-remove.gif -------------------------------------------------------------------------------- /media/admin/img/admin/selector-removeall.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/selector-removeall.gif -------------------------------------------------------------------------------- /media/admin/img/admin/selector-search.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/selector-search.gif -------------------------------------------------------------------------------- /media/admin/img/admin/selector_stacked-add.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/selector_stacked-add.gif -------------------------------------------------------------------------------- /media/admin/img/admin/selector_stacked-remove.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/selector_stacked-remove.gif -------------------------------------------------------------------------------- /media/admin/img/admin/tool-left.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/tool-left.gif -------------------------------------------------------------------------------- /media/admin/img/admin/tool-left_over.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/tool-left_over.gif -------------------------------------------------------------------------------- /media/admin/img/admin/tool-right.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/tool-right.gif -------------------------------------------------------------------------------- /media/admin/img/admin/tool-right_over.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/tool-right_over.gif -------------------------------------------------------------------------------- /media/admin/img/admin/tooltag-add.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/tooltag-add.gif -------------------------------------------------------------------------------- /media/admin/img/admin/tooltag-add_over.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/tooltag-add_over.gif -------------------------------------------------------------------------------- /media/admin/img/admin/tooltag-arrowright.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/tooltag-arrowright.gif -------------------------------------------------------------------------------- /media/admin/img/admin/tooltag-arrowright_over.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/admin/tooltag-arrowright_over.gif -------------------------------------------------------------------------------- /media/admin/img/changelist-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/changelist-bg.gif -------------------------------------------------------------------------------- /media/admin/img/changelist-bg_rtl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/changelist-bg_rtl.gif -------------------------------------------------------------------------------- /media/admin/img/chooser-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/chooser-bg.gif -------------------------------------------------------------------------------- /media/admin/img/chooser_stacked-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/chooser_stacked-bg.gif -------------------------------------------------------------------------------- /media/admin/img/default-bg-reverse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/default-bg-reverse.gif -------------------------------------------------------------------------------- /media/admin/img/default-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/default-bg.gif -------------------------------------------------------------------------------- /media/admin/img/deleted-overlay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/deleted-overlay.gif -------------------------------------------------------------------------------- /media/admin/img/gis/move_vertex_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/gis/move_vertex_off.png -------------------------------------------------------------------------------- /media/admin/img/gis/move_vertex_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/gis/move_vertex_on.png -------------------------------------------------------------------------------- /media/admin/img/icon-no.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/icon-no.gif -------------------------------------------------------------------------------- /media/admin/img/icon-unknown.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/icon-unknown.gif -------------------------------------------------------------------------------- /media/admin/img/icon-yes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/icon-yes.gif -------------------------------------------------------------------------------- /media/admin/img/icon_addlink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/icon_addlink.gif -------------------------------------------------------------------------------- /media/admin/img/icon_alert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/icon_alert.gif -------------------------------------------------------------------------------- /media/admin/img/icon_calendar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/icon_calendar.gif -------------------------------------------------------------------------------- /media/admin/img/icon_changelink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/icon_changelink.gif -------------------------------------------------------------------------------- /media/admin/img/icon_clock.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/icon_clock.gif -------------------------------------------------------------------------------- /media/admin/img/icon_deletelink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/icon_deletelink.gif -------------------------------------------------------------------------------- /media/admin/img/icon_error.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/icon_error.gif -------------------------------------------------------------------------------- /media/admin/img/icon_searchbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/icon_searchbox.png -------------------------------------------------------------------------------- /media/admin/img/icon_success.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/icon_success.gif -------------------------------------------------------------------------------- /media/admin/img/inline-delete-8bit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/inline-delete-8bit.png -------------------------------------------------------------------------------- /media/admin/img/inline-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/inline-delete.png -------------------------------------------------------------------------------- /media/admin/img/inline-restore-8bit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/inline-restore-8bit.png -------------------------------------------------------------------------------- /media/admin/img/inline-restore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/inline-restore.png -------------------------------------------------------------------------------- /media/admin/img/inline-splitter-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/inline-splitter-bg.gif -------------------------------------------------------------------------------- /media/admin/img/nav-bg-grabber.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/nav-bg-grabber.gif -------------------------------------------------------------------------------- /media/admin/img/nav-bg-reverse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/nav-bg-reverse.gif -------------------------------------------------------------------------------- /media/admin/img/nav-bg-selected.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/nav-bg-selected.gif -------------------------------------------------------------------------------- /media/admin/img/nav-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/nav-bg.gif -------------------------------------------------------------------------------- /media/admin/img/selector-icons.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/selector-icons.gif -------------------------------------------------------------------------------- /media/admin/img/selector-search.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/selector-search.gif -------------------------------------------------------------------------------- /media/admin/img/sorting-icons.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/sorting-icons.gif -------------------------------------------------------------------------------- /media/admin/img/tool-left.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/tool-left.gif -------------------------------------------------------------------------------- /media/admin/img/tool-left_over.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/tool-left_over.gif -------------------------------------------------------------------------------- /media/admin/img/tool-right.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/tool-right.gif -------------------------------------------------------------------------------- /media/admin/img/tool-right_over.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/tool-right_over.gif -------------------------------------------------------------------------------- /media/admin/img/tooltag-add.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/tooltag-add.gif -------------------------------------------------------------------------------- /media/admin/img/tooltag-add_over.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/tooltag-add_over.gif -------------------------------------------------------------------------------- /media/admin/img/tooltag-arrowright.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/tooltag-arrowright.gif -------------------------------------------------------------------------------- /media/admin/img/tooltag-arrowright_over.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/media/admin/img/tooltag-arrowright_over.gif -------------------------------------------------------------------------------- /media/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. -------------------------------------------------------------------------------- /media/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 | -------------------------------------------------------------------------------- /media/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 | -------------------------------------------------------------------------------- /media/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 | -------------------------------------------------------------------------------- /media/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('
'+a.deleteText+"
"):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 | -------------------------------------------------------------------------------- /media/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 7 | ServerName dev.playdoh.org 8 | 9 | DirectoryIndex index.php index.html 10 | Options -Indexes 11 | 12 | RewriteEngine On 13 | 14 | DocumentRoot "/var/www/html/" 15 | 16 | Alias /media/ "/home/vagrant/project/media/" 17 | Alias /admin-media/ "/home/vagrant/project/vendor/src/django/django/contrib/admin/media/" 18 | 19 | WSGIDaemonProcess playdoh processes=1 threads=1 maximum-requests=1 20 | WSGIProcessGroup playdoh 21 | 22 | WSGIScriptAlias / "/home/vagrant/project/wsgi/playdoh.wsgi" 23 | 24 | 25 | AddDefaultCharset off 26 | Order deny,allow 27 | Deny from all 28 | Allow from all 29 | 30 | 31 | -------------------------------------------------------------------------------- /puppet/manifests/classes/apache.pp: -------------------------------------------------------------------------------- 1 | # Red Hat, CentOS, and Fedora think Apache is the only web server 2 | # ever, so we have to use a different package on CentOS than Ubuntu. 3 | class apache { 4 | case $operatingsystem { 5 | centos: { 6 | package { "httpd-devel": 7 | ensure => present, 8 | before => File['/etc/httpd/conf.d/playdoh.conf']; 9 | } 10 | 11 | file { "/etc/httpd/conf.d/playdoh.conf": 12 | source => "$PROJ_DIR/puppet/files/etc/httpd/conf.d/playdoh.conf", 13 | owner => "root", group => "root", mode => 0644, 14 | require => [ 15 | Package['httpd-devel'] 16 | ]; 17 | } 18 | 19 | service { "httpd": 20 | ensure => running, 21 | enable => true, 22 | require => [ 23 | Package['httpd-devel'], 24 | File['/etc/httpd/conf.d/playdoh.conf'] 25 | ]; 26 | } 27 | 28 | } 29 | ubuntu: { 30 | package { "apache2-dev": 31 | ensure => present, 32 | before => File['/etc/apache2/sites-enabled/playdoh.conf']; 33 | } 34 | 35 | file { "/etc/apache2/sites-enabled/playdoh.conf": 36 | source => "$PROJ_DIR/puppet/files/etc/httpd/conf.d/playdoh.conf", 37 | owner => "root", group => "root", mode => 0644, 38 | require => [ 39 | Package['apache2-dev'] 40 | ]; 41 | } 42 | 43 | exec { 44 | 'a2enmod rewrite': 45 | onlyif => 'test ! -e /etc/apache2/mods-enabled/rewrite.load'; 46 | 'a2enmod proxy': 47 | onlyif => 'test ! -e /etc/apache2/mods-enabled/proxy.load'; 48 | } 49 | 50 | service { "apache2": 51 | ensure => running, 52 | enable => true, 53 | require => [ 54 | Package['apache2-dev'], 55 | File['/etc/apache2/sites-enabled/playdoh.conf'] 56 | ]; 57 | } 58 | 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /puppet/manifests/classes/custom.pp: -------------------------------------------------------------------------------- 1 | # You can add custom puppet manifests for your app here. 2 | class custom { 3 | } 4 | -------------------------------------------------------------------------------- /puppet/manifests/classes/init.pp: -------------------------------------------------------------------------------- 1 | # stage {"pre": before => Stage["main"]} class {'apt': stage => 'pre'} 2 | 3 | # Commands to run before all others in puppet. 4 | class init { 5 | group { "puppet": 6 | ensure => "present", 7 | } 8 | 9 | case $operatingsystem { 10 | ubuntu: { 11 | exec { "update_apt": 12 | command => "sudo apt-get update", 13 | } 14 | 15 | # Provides "add-apt-repository" command, useful if you need 16 | # to install software from other apt repositories. 17 | package { "python-software-properties": 18 | ensure => present, 19 | require => [ 20 | Exec['update_apt'], 21 | ]; 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /puppet/manifests/classes/memcached.pp: -------------------------------------------------------------------------------- 1 | # We use memcached in production, so we _should_ use it while 2 | # we develop as well. That said, playdoh shouldn't *rely* on it 3 | # entirely; it should work with any non-null cache store in Django. 4 | class memcached { 5 | package { "memcached": 6 | ensure => installed; 7 | } 8 | 9 | service { "memcached": 10 | ensure => running, 11 | enable => true, 12 | require => Package['memcached']; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /puppet/manifests/classes/mysql.pp: -------------------------------------------------------------------------------- 1 | # Get mysql up and running 2 | class mysql { 3 | package { "mysql-server": 4 | ensure => installed; 5 | } 6 | 7 | case $operatingsystem { 8 | centos: { 9 | package { "mysql-devel": 10 | ensure => installed; 11 | } 12 | } 13 | 14 | ubuntu: { 15 | package { "libmysqld-dev": 16 | ensure => installed; 17 | } 18 | } 19 | } 20 | 21 | service { "mysql": 22 | ensure => running, 23 | enable => true, 24 | require => Package['mysql-server']; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /puppet/manifests/classes/playdoh.pp: -------------------------------------------------------------------------------- 1 | # playdoh-specific commands that get playdoh all going so you don't 2 | # have to. 3 | 4 | # TODO: Make this rely on things that are not straight-up exec. 5 | class playdoh { 6 | file { "$PROJ_DIR/project/settings/local.py": 7 | ensure => file, 8 | source => "$PROJ_DIR/project/settings/local.py-dist", 9 | replace => false; 10 | } 11 | 12 | exec { "create_mysql_database": 13 | command => "mysql -uroot -B -e'CREATE DATABASE $DB_NAME CHARACTER SET utf8;'", 14 | unless => "mysql -uroot -B --skip-column-names -e 'show databases' | /bin/grep '$DB_NAME'", 15 | require => File["$PROJ_DIR/project/settings/local.py"] 16 | } 17 | 18 | exec { "grant_mysql_database": 19 | command => "mysql -uroot -B -e'GRANT ALL PRIVILEGES ON $DB_NAME.* TO $DB_USER@localhost # IDENTIFIED BY \"$DB_PASS\"'", 20 | unless => "mysql -uroot -B --skip-column-names mysql -e 'select user from user' | grep '$DB_USER'", 21 | require => Exec["create_mysql_database"]; 22 | } 23 | 24 | exec { "syncdb": 25 | cwd => "$PROJ_DIR", 26 | command => "python ./manage.py syncdb --noinput", 27 | require => Exec["grant_mysql_database"]; 28 | } 29 | 30 | exec { "sql_migrate": 31 | cwd => "$PROJ_DIR", 32 | command => "python ./vendor/src/schematic/schematic migrations/", 33 | require => [ 34 | Service["mysql"], 35 | Package["python2.6-dev", "libapache2-mod-wsgi", "python-wsgi-intercept" ], 36 | Exec["syncdb"] 37 | ]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /puppet/manifests/classes/python.pp: -------------------------------------------------------------------------------- 1 | # Install python and compiled modules for project 2 | class python { 3 | case $operatingsystem { 4 | centos: { 5 | package { 6 | ["python26-devel", "python26-libs", "python26-distribute", "python26-mod_wsgi"]: 7 | ensure => installed; 8 | } 9 | 10 | exec { "pip-install": 11 | command => "easy_install -U pip", 12 | creates => "pip", 13 | require => Package["python26-devel", "python26-distribute"] 14 | } 15 | 16 | exec { "pip-install-compiled": 17 | command => "pip install -r $PROJ_DIR/requirements/compiled.txt", 18 | require => Exec['pip-install'] 19 | } 20 | } 21 | 22 | ubuntu: { 23 | package { 24 | ["python2.6-dev", "python2.6", "libapache2-mod-wsgi", "python-wsgi-intercept", "python-pip"]: 25 | ensure => installed; 26 | } 27 | 28 | exec { "pip-install-compiled": 29 | command => "pip install -r $PROJ_DIR/requirements/compiled.txt", 30 | require => Package['python-pip'] 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /puppet/manifests/vagrant.pp: -------------------------------------------------------------------------------- 1 | # 2 | # Playdoh puppet magic for dev boxes 3 | # 4 | import "classes/*.pp" 5 | 6 | $PROJ_DIR = "/home/vagrant/project" 7 | 8 | # You can make these less generic if you like, but these are box-specific 9 | # so it's not required. 10 | $DB_NAME = "playdoh_app" 11 | $DB_USER = "root" 12 | 13 | Exec { 14 | path => "/usr/local/bin:/usr/bin:/usr/sbin:/sbin:/bin", 15 | } 16 | 17 | class dev { 18 | class { 19 | init: before => Class[mysql]; 20 | mysql: before => Class[python]; 21 | python: before => Class[apache]; 22 | apache: before => Class[playdoh]; 23 | memcached: ; 24 | playdoh: ; 25 | custom: ; 26 | } 27 | } 28 | 29 | include dev 30 | -------------------------------------------------------------------------------- /requirements/compiled.txt: -------------------------------------------------------------------------------- 1 | -r ../vendor/src/funfactory/funfactory/requirements/compiled.txt 2 | -------------------------------------------------------------------------------- /requirements/dev.txt: -------------------------------------------------------------------------------- 1 | # This file pulls in everything a developer needs. If it's a basic package 2 | # needed to run the site, it belongs in requirements/prod.txt. If it's a 3 | # package for developers (testing, docs, etc.), it goes in this file. 4 | 5 | -r ../vendor/src/funfactory/funfactory/requirements/compiled.txt 6 | -r ../vendor/src/funfactory/funfactory/requirements/dev.txt 7 | -------------------------------------------------------------------------------- /requirements/prod.txt: -------------------------------------------------------------------------------- 1 | -r ../vendor/src/funfactory/funfactory/requirements/prod.txt 2 | django_browserid 3 | requests 4 | newrelic==1.11.0.55 5 | python-dateutil==2.2 6 | six==1.6.1 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from setuptools import setup, find_packages 4 | 5 | 6 | setup(name='source', 7 | version='1.0', 8 | description='Django application.', 9 | long_description='', 10 | author='', 11 | author_email='', 12 | license='', 13 | url='', 14 | include_package_data=True, 15 | classifiers = [], 16 | packages=find_packages(exclude=['tests']), 17 | install_requires=[]) 18 | -------------------------------------------------------------------------------- /source/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/__init__.py -------------------------------------------------------------------------------- /source/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/api/__init__.py -------------------------------------------------------------------------------- /source/api/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls.defaults import * 3 | from django.views.decorators.cache import cache_page 4 | 5 | from .views import ContributorCount 6 | 7 | FEED_CACHE_TIME = getattr(settings, 'FEED_CACHE_SECONDS', 60*15) 8 | 9 | urlpatterns = patterns('', 10 | url( 11 | regex = '^contributor-count/$', 12 | view = cache_page(ContributorCount.as_view(), FEED_CACHE_TIME), 13 | kwargs = {}, 14 | name = 'api_v1_contributor_count', 15 | ), 16 | ) 17 | -------------------------------------------------------------------------------- /source/articles/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/articles/__init__.py -------------------------------------------------------------------------------- /source/articles/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/articles/management/__init__.py -------------------------------------------------------------------------------- /source/articles/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/articles/management/commands/__init__.py -------------------------------------------------------------------------------- /source/articles/management/commands/migrate_article_categories.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from source.articles.models import Article, Category 3 | 4 | class Command(BaseCommand): 5 | help = "One-time command to migrate articles to new FK relationship with Category model" 6 | def handle(self, *args, **options): 7 | article_set = Article.objects.all() 8 | category_set = list(Category.objects.all()) 9 | failed_articles = [] 10 | for article in article_set: 11 | print "Migrating " + article.title 12 | try: 13 | print " looking for " + article.article_type + "..." 14 | category = Category.objects.get(slug=article.article_type) 15 | article.category = category 16 | article.save() 17 | print " found a match!" 18 | except: 19 | print " category not found :(" 20 | failed_articles.append(article.title) 21 | 22 | print "Failed to match: " + str(failed_articles) -------------------------------------------------------------------------------- /source/articles/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/articles/migrations/__init__.py -------------------------------------------------------------------------------- /source/articles/search_indexes.py: -------------------------------------------------------------------------------- 1 | from haystack import indexes 2 | from .models import Article 3 | 4 | 5 | class ArticleIndex(indexes.SearchIndex, indexes.Indexable): 6 | title = indexes.CharField(model_attr='title', boost=1.2) 7 | text = indexes.CharField(document=True, use_template=True) 8 | pubdate = indexes.DateTimeField(model_attr='pubdate') 9 | 10 | def get_model(self): 11 | return Article 12 | 13 | def get_updated_field(self): 14 | return 'modified' 15 | 16 | def index_queryset(self): 17 | """Used when the entire index for model is updated.""" 18 | return self.get_model().live_objects.filter(show_in_lists=True) 19 | -------------------------------------------------------------------------------- /source/articles/static/articles/css/highlight-theme.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Night Theme */ 2 | /* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ 3 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 4 | /* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ 5 | 6 | /* Tomorrow Comment */ 7 | .hljs-comment { 8 | color: #969896; 9 | } 10 | 11 | /* Tomorrow Red */ 12 | .hljs-variable, 13 | .hljs-attribute, 14 | .hljs-tag, 15 | .hljs-regexp, 16 | .ruby .hljs-constant, 17 | .xml .hljs-tag .hljs-title, 18 | .xml .hljs-pi, 19 | .xml .hljs-doctype, 20 | .html .hljs-doctype, 21 | .css .hljs-id, 22 | .css .hljs-class, 23 | .css .hljs-pseudo { 24 | color: #cc6666; 25 | } 26 | 27 | /* Tomorrow Orange */ 28 | .hljs-number, 29 | .hljs-preprocessor, 30 | .hljs-pragma, 31 | .hljs-built_in, 32 | .hljs-literal, 33 | .hljs-params, 34 | .hljs-constant { 35 | color: #de935f; 36 | } 37 | 38 | /* Tomorrow Yellow */ 39 | .ruby .hljs-class .hljs-title, 40 | .css .hljs-rule .hljs-attribute { 41 | color: #f0c674; 42 | } 43 | 44 | /* Tomorrow Green */ 45 | .hljs-string, 46 | .hljs-value, 47 | .hljs-inheritance, 48 | .hljs-header, 49 | .hljs-name, 50 | .ruby .hljs-symbol, 51 | .xml .hljs-cdata { 52 | color: #b5bd68; 53 | } 54 | 55 | /* Tomorrow Aqua */ 56 | .hljs-title, 57 | .css .hljs-hexcolor { 58 | color: #8abeb7; 59 | } 60 | 61 | /* Tomorrow Blue */ 62 | .hljs-function, 63 | .python .hljs-decorator, 64 | .python .hljs-title, 65 | .ruby .hljs-function .hljs-title, 66 | .ruby .hljs-title .hljs-keyword, 67 | .perl .hljs-sub, 68 | .javascript .hljs-title, 69 | .coffeescript .hljs-title { 70 | color: #81a2be; 71 | } 72 | 73 | /* Tomorrow Purple */ 74 | .hljs-keyword, 75 | .javascript .hljs-function { 76 | color: #b294bb; 77 | } 78 | 79 | .hljs { 80 | display: block; 81 | overflow-x: auto; 82 | background: #1d1f21; 83 | color: #c5c8c6; 84 | padding: 0.5em; 85 | -webkit-text-size-adjust: none; 86 | } 87 | 88 | .coffeescript .javascript, 89 | .javascript .xml, 90 | .tex .hljs-formula, 91 | .xml .javascript, 92 | .xml .vbscript, 93 | .xml .css, 94 | .xml .hljs-cdata { 95 | opacity: 0.5; 96 | } 97 | -------------------------------------------------------------------------------- /source/articles/static/articles/js/jquery.localScroll.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2007-2010 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com 3 | * Dual licensed under MIT and GPL. 4 | * @author Ariel Flesler 5 | * @version 1.2.8b 6 | */ 7 | ;(function($){var g=location.href.replace(/#.*/,'');var h=$.localScroll=function(a){$('body').localScroll(a)};h.defaults={duration:1000,axis:'y',event:'click',stop:true,target:window,reset:true};h.hash=function(a){if(location.hash){a=$.extend({},h.defaults,a);a.hash=false;if(a.reset){var d=a.duration;delete a.duration;$(a.target).scrollTo(0,a);a.duration=d}scroll(0,location,a)}};$.fn.localScroll=function(b){b=$.extend({},h.defaults,b);return b.lazy?this.bind(b.event,function(e){var a=$([e.target,e.target.parentNode]).filter(filter)[0];if(a)scroll(e,a,b)}):this.find('a,area').filter(filter).bind(b.event,function(e){scroll(e,this,b)}).end().end();function filter(){return!!this.href&&!!this.hash&&this.href.replace(this.hash,'')==g&&(!b.filter||$(this).is(b.filter))}};function scroll(e,a,b){var c=a.hash.slice(1),elem=document.getElementById(c)||document.getElementsByName(c)[0];if(!elem)return;if(e)e.preventDefault();var d=$(b.target);if(b.lock&&d.is(':animated')||b.onBefore&&b.onBefore(e,elem,d)===false)return;if(b.stop)d._scrollable().stop(true);if(b.hash){var f=elem.id==c?'id':'name',$a=$(' ').attr(f,c).css({position:'absolute',top:$(window).scrollTop(),left:$(window).scrollLeft()});elem[f]='';$('body').prepend($a);location=a.hash;$a.remove();elem[f]=c}d.scrollTo(elem,b).trigger('notify.serialScroll',[elem])}})(jQuery); -------------------------------------------------------------------------------- /source/articles/static/articles/js/jquery.scrollTo.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2007-2012 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com 3 | * Dual licensed under MIT and GPL. 4 | * @author Ariel Flesler 5 | * @version 1.4.5 BETA 6 | */ 7 | ;(function($){var h=$.scrollTo=function(a,b,c){$(window).scrollTo(a,b,c)};h.defaults={axis:'xy',duration:parseFloat($.fn.jquery)>=1.3?0:1,limit:true};h.window=function(a){return $(window)._scrollable()};$.fn._scrollable=function(){return this.map(function(){var a=this,isWin=!a.nodeName||$.inArray(a.nodeName.toLowerCase(),['iframe','#document','html','body'])!=-1;if(!isWin)return a;var b=(a.contentWindow||a).document||a.ownerDocument||a;return/webkit/i.test(navigator.userAgent)||b.compatMode=='BackCompat'?b.body:b.documentElement})};$.fn.scrollTo=function(e,f,g){if(typeof f=='object'){g=f;f=0}if(typeof g=='function')g={onAfter:g};if(e=='max')e=9e9;g=$.extend({},h.defaults,g);f=f||g.duration;g.queue=g.queue&&g.axis.length>1;if(g.queue)f/=2;g.offset=both(g.offset);g.over=both(g.over);return this._scrollable().each(function(){if(e==null)return;var d=this,$elem=$(d),targ=e,toff,attr={},win=$elem.is('html,body');switch(typeof targ){case'number':case'string':if(/^([+-]=?)?\d+(\.\d+)?(px|%)?$/.test(targ)){targ=both(targ);break}targ=$(targ,this);if(!targ.length)return;case'object':if(targ.is||targ.style)toff=(targ=$(targ)).offset()}$.each(g.axis.split(''),function(i,a){var b=a=='x'?'Left':'Top',pos=b.toLowerCase(),key='scroll'+b,old=d[key],max=h.max(d,a);if(toff){attr[key]=toff[pos]+(win?0:old-$elem.offset()[pos]);if(g.margin){attr[key]-=parseInt(targ.css('margin'+b))||0;attr[key]-=parseInt(targ.css('border'+b+'Width'))||0}attr[key]+=g.offset[pos]||0;if(g.over[pos])attr[key]+=targ[a=='x'?'width':'height']()*g.over[pos]}else{var c=targ[pos];attr[key]=c.slice&&c.slice(-1)=='%'?parseFloat(c)/100*max:c}if(g.limit&&/^\d+$/.test(attr[key]))attr[key]=attr[key]<=0?0:Math.min(attr[key],max);if(!i&&g.queue){if(old!=attr[key])animate(g.onAfterFirst);delete attr[key]}});animate(g.onAfter);function animate(a){$elem.animate(attr,f,g.easing,a&&function(){a.call(this,e,g)})}}).end()};h.max=function(a,b){var c=b=='x'?'Width':'Height',scroll='scroll'+c;if(!$(a).is('html,body'))return a[scroll]-$(a)[c.toLowerCase()]();var d='client'+c,html=a.ownerDocument.documentElement,body=a.ownerDocument.body;return Math.max(html[scroll],body[scroll])-Math.min(html[d],body[d])};function both(a){return typeof a=='object'?a:{top:a,left:a}}})(jQuery); -------------------------------------------------------------------------------- /source/articles/templates/articles/_article_author_list.html: -------------------------------------------------------------------------------- 1 | {% for author in author_list %}{% if loop.first %}By {% endif %}{{ author.name()|smartypants }}{% if not loop.last %}, {% endif %}{% endfor %} -------------------------------------------------------------------------------- /source/articles/templates/articles/_article_category_and_tags_overline.html: -------------------------------------------------------------------------------- 1 | {% if article.category %}{{ article.category.name }}{% endif %}{% if article.tags.all().exists() %} {% for tag in article.tags.all() %}{{ tag.name|smartypants }}{% if not loop.last %} {% endif %}{% endfor %}{% endif %} 2 | 3 | {# 4 | 5 | If we ever completely drop the original `tags` field, the code below will 6 | generate the proper list of tags for an article object 7 | 8 | {% if article.category %}{{ article.category.name }}{% endif %}{% if article.merged_tag_list %} {% for tag in article.merged_tag_list %}{{ tag }}{% if not loop.last %} {% endif %}{% endfor %}{% endif %} 9 | #} 10 | -------------------------------------------------------------------------------- /source/articles/templates/articles/_article_link_list.html: -------------------------------------------------------------------------------- 1 | {% for article in article_link_list %} 2 | {% if loop.first %}{% if not hide_link_list_title %}

    {% if override_list_title %}{{ override_list_title|smartypants }} {% else %}Articles{% endif %}

    {% endif %} 3 | {% endif %}{% endfor %} 6 | -------------------------------------------------------------------------------- /source/articles/templates/articles/_article_list_item.html: -------------------------------------------------------------------------------- 1 | {# standard presentation block for an article in list and search results pages #} 2 |
    3 |

    {% include "articles/_article_category_and_tags_overline.html" %}{{ article.title|typogrify }}

    4 | {% if article.image %}{{ article.title }}{% endif %} 5 | 12 |

    {{ article.summary|typogrify|safe }}

    13 |
    14 | -------------------------------------------------------------------------------- /source/articles/templates/articles/_article_list_section_overline.html: -------------------------------------------------------------------------------- 1 |

    {{ section.name }}{% if category %} / {{ category.name }}{% endif %}{% if tags %} / {% for tag in tags %}{{ tag.name|smartypants }}{% if not loop.last %}, {% endif %}{% endfor %}{% endif %}

    2 | -------------------------------------------------------------------------------- /source/articles/templates/articles/_base_articles.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% if section %} 4 | {% set active_nav = section.slug %} 5 | {% endif %} 6 | 7 | {% block page_title %}{% if section %}{{ section.name }}{% else %}Articles{% endif %}{% if tags %} tagged: {% for tag in tags %}{{ tag.name|smartypants }}{% if not loop.last %}, {% endif %}{% endfor %}{% endif %} - {{ super() }}{% endblock %} 8 | -------------------------------------------------------------------------------- /source/articles/templates/articles/article_list.html: -------------------------------------------------------------------------------- 1 | {% extends "articles/_base_articles.html" %} 2 | 3 | {% block content %} 4 | {% if section %} 5 | {% include "articles/_article_list_section_overline.html" %} 6 | {% endif %} 7 | 8 | {% for article in page.object_list %} 9 | {% include "articles/_article_list_item.html" %} 10 | {% endfor %} 11 | 12 | {% include "utils/_paginate.html" %} 13 | {% endblock content %} 14 | 15 | {% block site_js_extra %} 16 | 25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /source/articles/templates/articles/article_list_with_promos.html: -------------------------------------------------------------------------------- 1 | {% extends "articles/_base_articles.html" %} 2 | 3 | {% block article_class %}section-with-promos{% endblock %} 4 | 5 | {% block content %} 6 | {% if section %} 7 | {% include "articles/_article_list_section_overline.html" %} 8 | {% endif %} 9 | 10 | {% if section.description %}

    {{ section.description|safe }}

    {% endif %} 11 | 12 | {% if lead_promo %} 13 | {% with article=lead_promo %} 14 | 15 | {% if article.image %}{{ article.title }}{% endif %} 16 |

    17 | {% if article.category %}{{ article.category.name }}{% endif %}{% if article.tags.all().exists() %} / {% for tag in article.tags.all() %}{{ tag.name|smartypants }}{% if not loop.last %} {% endif %}{% endfor %}{% endif %} 18 | {{ article.title|typogrify }} 19 |

    20 |

    {{ article.safe_summary|typogrify|safe }}

    21 |
    22 | {% endwith %} 23 | 24 | 37 | {% endif %} 38 | 39 | {% if lead_promo and page.object_list|length > 3 %} 40 |

    More case studies:

    41 | {% endif %} 42 | 43 | {% for article in page.object_list %} 44 | {# if we have promos on this page, don't include those articles in list #} 45 | {% if article.pk not in articles_to_exclude_from_list %} 46 | {% include "articles/_article_list_item.html" %} 47 | {% endif %} 48 | {% endfor %} 49 | 50 | {% include "utils/_paginate.html" %} 51 | {% endblock content %} 52 | 53 | {% block site_js_extra %} 54 | 63 | {% endblock %} 64 | -------------------------------------------------------------------------------- /source/articles/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls.defaults import * 3 | from django.views.decorators.cache import cache_page 4 | from django.views.generic.simple import redirect_to 5 | 6 | from .views import ArticleList 7 | from source.base.feeds import ArticleFeed 8 | 9 | STANDARD_CACHE_TIME = getattr(settings, 'CACHE_MIDDLEWARE_SECONDS', 60*15) 10 | FEED_CACHE_TIME = getattr(settings, 'FEED_CACHE_SECONDS', 60*15) 11 | 12 | urlpatterns = patterns('', 13 | # /articles/ is matched as a section in base.urls 14 | #url( 15 | # regex = '^$', 16 | # view = ArticleList.as_view(), 17 | # kwargs = {}, 18 | # name = 'article_list', 19 | #, 20 | url( 21 | regex = '^tags/(?P[-\w\+]+)/$', 22 | view = cache_page(ArticleList.as_view(), STANDARD_CACHE_TIME), 23 | kwargs = {}, 24 | name = 'article_list_by_tag', 25 | ), 26 | url( 27 | regex = '^tags/(?P[-\w\+]+)/rss/$', 28 | view = cache_page(ArticleFeed(), FEED_CACHE_TIME), 29 | kwargs = {}, 30 | name = 'article_list_by_tag_feed', 31 | ), 32 | url( 33 | regex = '^tags/$', 34 | view = redirect_to, 35 | kwargs = {'url': '/articles/'}, 36 | name = 'article_list_tags', 37 | ), 38 | ) 39 | -------------------------------------------------------------------------------- /source/base/__init__.py: -------------------------------------------------------------------------------- 1 | """Application base, containing global templates.""" 2 | -------------------------------------------------------------------------------- /source/base/context_processors.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | 4 | def http_protocol(request): 5 | """ 6 | To stop disqus going bonkers we need to use the same protocal as the domain 7 | """ 8 | protocol = getattr(settings, 'HTTP_PROTOCOL', False) 9 | 10 | return { 11 | 'HTTP_PROTOCOL': protocol, 12 | } 13 | 14 | 15 | def warnr(request): 16 | """ 17 | As we're using an external service to resize images we need to pipe through 18 | the FULL domain - https:// and everything 19 | """ 20 | 21 | stage = getattr(settings, 'APP_STAGE', False) 22 | message = getattr(settings, 'APP_MESSAGE', False) 23 | 24 | return { 25 | 'APP_STAGE': stage, 26 | 'APP_MSG': message 27 | } 28 | -------------------------------------------------------------------------------- /source/base/models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/base/models.py -------------------------------------------------------------------------------- /source/base/static/base/css/warnr.css: -------------------------------------------------------------------------------- 1 | #app_stage_box { 2 | background: #FFFF00; 3 | padding: 0.5em 20px; 4 | color: 827600; 5 | } 6 | #app_stage_box a { 7 | font-weight: bold; 8 | text-decoration: underline; 9 | } -------------------------------------------------------------------------------- /source/base/static/base/font/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/base/static/base/font/fontawesome-webfont.eot -------------------------------------------------------------------------------- /source/base/static/base/font/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/base/static/base/font/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /source/base/static/base/font/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/base/static/base/font/fontawesome-webfont.woff -------------------------------------------------------------------------------- /source/base/static/base/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/base/static/base/img/favicon.ico -------------------------------------------------------------------------------- /source/base/static/base/img/source_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/base/static/base/img/source_logo.png -------------------------------------------------------------------------------- /source/base/static/base/img/source_mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/base/static/base/img/source_mini.png -------------------------------------------------------------------------------- /source/base/static/base/img/source_retina.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/base/static/base/img/source_retina.png -------------------------------------------------------------------------------- /source/base/static/base/img/source_retina_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/base/static/base/img/source_retina_top.png -------------------------------------------------------------------------------- /source/base/static/base/img/source_retina_top_invert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/base/static/base/img/source_retina_top_invert.png -------------------------------------------------------------------------------- /source/base/static/base/img/srccon_flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/base/static/base/img/srccon_flag.png -------------------------------------------------------------------------------- /source/base/static/base/js/gridfilter.js: -------------------------------------------------------------------------------- 1 | // same as listfilter.js, but serves pages that use .grid-box items instead 2 | 3 | // custom jQuery filter selector `icontains` for text matching 4 | // http://answers.oreilly.com/topic/1055-creating-a-custom-filter-selector-with-jquery/ 5 | $.expr[':'].icontains = function(element, index, match) { 6 | return (element.textContent || element.innerText || "").toUpperCase().indexOf(match[3].toUpperCase()) >= 0; 7 | }; 8 | 9 | $(document).ready(function() { 10 | // set up initial vars 11 | var filterForm = '
    \ 12 | \ 13 | \ 14 |
    '; 15 | var filteredList = $('#filterable-list'); 16 | 17 | // insert filter form because we know we have js 18 | $(filterForm).insertBefore(filteredList); 19 | 20 | // after each keystroke in #list-filter input, do a case-insensitive 21 | // search against all the `.grid-box` elements inside #filterable-list 22 | $('#list-filter').change(function() { 23 | var filterVal = $(this).val(); 24 | if (filterVal) { 25 | // hide the primary container to avoid potential repaints 26 | filteredList.css('display','none'); 27 | // hide grid-boxes that don't have matching text, 28 | // and filter-blocks that don't have visible grid-boxes 29 | filteredList.find('.grid-box:not(:icontains(' + filterVal + '))').css('display','none'); 30 | filteredList.find('.filter-block:not(:has(.grid-box:visible))').css('display','none'); 31 | // show blocks/boxes that contain matching text 32 | filteredList.find('.filter-block:has(.grid-box:icontains(' + filterVal + '))').css('display','block'); 33 | filteredList.find('.grid-box:icontains(' + filterVal + ')').css('display','inline-block'); 34 | // show the primary container again 35 | filteredList.css('display','block'); 36 | } else { 37 | // nothing in filter form, so make sure everything is visible 38 | filteredList.find('.filter-block').css('display','block'); 39 | filteredList.find('.grid-box').css('display','inline-block'); 40 | } 41 | 42 | // show 'no results' message if we've removed all the items 43 | if ($('.filter-block > div:visible').length == 0) { 44 | $('#no-results').remove(); 45 | $('#js-filter-form').after('

    No matching results found.

    '); 46 | } else { 47 | $('#no-results').remove(); 48 | } 49 | return false; 50 | }).keyup(function() { 51 | $(this).change(); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /source/base/templates/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/base/templates/.gitignore -------------------------------------------------------------------------------- /source/base/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block page_title %}{{ _('Page not found') }}{% endblock %} 4 | 5 | {% block content %} 6 |

    {{ _('Page not found') }}

    7 |

    8 | {% trans %} 9 | Sorry, but we couldn't find the page you're looking for. 10 | {% endtrans %} 11 |

    12 | {% endblock content %} 13 | -------------------------------------------------------------------------------- /source/base/templates/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ _('Something went wrong!') }} 6 | 7 | 8 |

    {{ _('Page not found') }}

    9 |

    10 | {% trans %} 11 | Sorry, but something went wrong. 12 | {% endtrans %} 13 |

    14 | 15 | 16 | -------------------------------------------------------------------------------- /source/base/templates/admin/base_site.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/base.html" %}{% load i18n %} 2 | 3 | {% block title %}{{ title }} | {% trans 'Source admin' %}{% endblock %} 4 | 5 | {% block branding %} 6 |

    {% trans 'Source administration' %}{% block appname %}{% endblock%}

    7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /source/base/templates/feeds/article_description.html: -------------------------------------------------------------------------------- 1 | {% if obj.get_live_author_set().exists() %}

    {% for author in obj.get_live_author_set() %}{% if loop.first %}By {% endif %}{{ author.name() }}{% if not loop.last %}, {% endif %}{% endfor %}

    {% endif %} 2 | 3 | {% if obj.image %} 4 | {{ obj.title }} 5 | {% if obj.pretty_caption %}

    {{ obj.pretty_caption|safe }}

    {% endif %} 6 | {% endif %} 7 | 8 | {{ obj.pretty_body_text|safe }} 9 | 10 | {% for articleblock in obj.articleblock_set.all() %} 11 |

    {{ articleblock.title }}

    12 | {% if articleblock.image %} 13 | {{ articleblock.title }} 14 | {% endif %} 15 | {{ articleblock.pretty_body_text|safe }} 16 | {% endfor %} 17 | -------------------------------------------------------------------------------- /source/base/templates/flatpages/default.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block page_title %}{{ flatpage.title }} - {{ super() }}{% endblock %} 3 | 4 | {% block content %} 5 |

    {{ flatpage.title|typogrify }}

    6 | {{ flatpage.content|linebreaks|typogrify|safe }} 7 | {% endblock content %} 8 | -------------------------------------------------------------------------------- /source/base/templates/homepage.html: -------------------------------------------------------------------------------- 1 | {% extends "articles/article_list.html" %} 2 | {% block page_title %}Source - Journalism Code, Context & Community - A project by Knight-Mozilla OpenNews{% endblock %} 3 | 4 | {% block article_class %}homepage{% endblock %} 5 | 6 | {% block base_tagline %} 7 |

    Journalism code and the people who make it

    8 | {% endblock %} 9 | 10 | {% block base_aside %} 11 | 39 | {% endblock %} -------------------------------------------------------------------------------- /source/base/templates/search/includes/_article_list_item.html: -------------------------------------------------------------------------------- 1 | {# slim presentation block for an article item in search results #} 2 |
    3 |

    {% if not hide_list_item_categories %}Article: {% endif %}{{ object.title|typogrify }}

    4 |

    {{ object.summary|typogrify|safe }}

    5 |
    6 | -------------------------------------------------------------------------------- /source/base/templates/search/includes/_code_list_item.html: -------------------------------------------------------------------------------- 1 | {# slim presentation block for a code item in search results #} 2 |
    3 |

    {% if not hide_list_item_categories %}Code: {% endif %}{{ object.name|typogrify }}

    4 | {% if object.summary_or_description %}

    {{ object.summary_or_description|typogrify|safe }}

    {% endif %} 5 |
    6 | -------------------------------------------------------------------------------- /source/base/templates/search/includes/_organization_list_item.html: -------------------------------------------------------------------------------- 1 | {# slim presentation block for an organization item in search results #} 2 |
    3 |

    {% if not hide_list_item_categories %}Organization: {% endif %}{{ object.name|typogrify }}

    4 | {% if object.description %}

    {{ object.description|typogrify|safe }}

    {% endif %} 5 |
    6 | -------------------------------------------------------------------------------- /source/base/templates/search/includes/_person_list_item.html: -------------------------------------------------------------------------------- 1 | {# slim presentation block for a person item in search results #} 2 |
    3 |

    {% if not hide_list_item_categories %}Person: {% endif %}{{ object.name()|typogrify }}

    4 | {% if object.description %}

    {{ object.description|typogrify|safe }}

    {% endif %} 5 |
    6 | -------------------------------------------------------------------------------- /source/base/templates/search/indexes/articles/article_text.txt: -------------------------------------------------------------------------------- 1 | {{ object.title }} 2 | {{ object.subhead }} 3 | {{ object.body }} 4 | 5 | {% for author in object.get_live_author_set() %} 6 | {{ author.first_name }} {{ author.last_name }} 7 | {% endfor %} 8 | 9 | {% for person in object.get_live_people_set() %} 10 | {{ person.first_name }} {{ person.last_name }} 11 | {% endfor %} 12 | 13 | {% for organization in object.get_live_organization_set() %} 14 | {{ organization.name }} 15 | {% endfor %} 16 | 17 | {% for code in object.get_live_code_set() %} 18 | {{ code.name }} 19 | {% endfor %} 20 | 21 | {% for tag in object.tags.all() %} 22 | {{ tag }} 23 | {% endfor %} 24 | -------------------------------------------------------------------------------- /source/base/templates/search/indexes/code/code_text.txt: -------------------------------------------------------------------------------- 1 | {{ object.name }} 2 | {{ object.description }} 3 | 4 | {% for person in object.get_live_people_set() %} 5 | {{ person.first_name }} {{ person.last_name }} 6 | {% endfor %} 7 | 8 | {% for organization in object.get_live_organization_set() %} 9 | {{ organization.name }} 10 | {% endfor %} 11 | 12 | {% for tag in object.tags.all() %} 13 | {{ tag }} 14 | {% endfor %} 15 | -------------------------------------------------------------------------------- /source/base/templates/search/indexes/people/organization_text.txt: -------------------------------------------------------------------------------- 1 | {{ object.name }} 2 | {{ object.description }} 3 | {{ object.homepage }} 4 | {{ object.twitter_username }} 5 | {{ object.github_username }} 6 | {{ object.address }}, {{ object.city }} {{ object.state }}{% if object.country %}, {{ object.country }}{% endif %} 7 | -------------------------------------------------------------------------------- /source/base/templates/search/indexes/people/person_text.txt: -------------------------------------------------------------------------------- 1 | {{ object.first_name }} {{ object.last_name }} 2 | {{ object.description }} 3 | {{ object.email }} 4 | {{ object.twitter_username }} 5 | {{ object.github_username }} 6 | 7 | {% for organization in object.get_live_organization_set() %} 8 | {{ organization.name }} 9 | {% endfor %} 10 | -------------------------------------------------------------------------------- /source/base/templates/search/search.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% set active_nav = "search" %} 3 | {% block page_title %}Search - {{ super() }}{% endblock %} 4 | 5 | {% block article_class %}search-results{% if page.object_list|length <= 2 or not (person_results or organization_results) %} search-results-single-col{% endif %}{% endblock %} 6 | 7 | {% block base_above_article %} 8 |

    Search

    9 | 10 |
    11 | {{ form.q }} 12 | 13 |
    14 | {% endblock %} 15 | 16 | {% block content %} 17 | {% if query %} 18 | {#} 19 | Yes, setting last_result_type_seen/result_type in the loop is a pretty 20 | ghetto way of doing `ifchanged`. Unfortunately there's no `ifchanged` 21 | support in jinja2. https://github.com/mitsuhiko/jinja2/issues/133 22 | 23 | This is essentially how the standard Django {% ifchanged %} tag 24 | operates, though. 25 | {#} 26 | {% set last_result_type_seen = '' %} 27 | {% for result in page.object_list %} 28 | {% set result_type = result.content_type().split('.')[1] %} 29 | {% if result_type != last_result_type_seen %} 30 |

    {{ content_type_map[result_type] }}

    31 | {% set last_result_type_seen = result_type %} 32 | {% endif %} 33 | {% with object = result.object, hide_list_item_categories=True %} 34 | {% include "search/includes/_%s_list_item.html" % result_type %} 35 | {% endwith %} 36 | {% else %} 37 |

    No matching articles or code index entries found.

    38 | {% endfor %} 39 | 40 | {% include "utils/_paginate.html" %} 41 | {% else %} 42 |

    Search Source’s articles, code, people and organizations.

    43 | {% endif %} 44 | {% endblock content %} 45 | 46 | {% block base_aside %} 47 | {# display secondary Person, Organization results #} 48 | {# on first page of Article, Code search results #} 49 | {% if page.number == 1 and (person_results or organization_results) %} 50 | 67 | {% endif %} 68 | {% endblock %} 69 | -------------------------------------------------------------------------------- /source/base/templates/utils/_basic_link_list.html: -------------------------------------------------------------------------------- 1 | {% for link in basic_link_list %} 2 | {% if loop.first %}{% if not hide_link_list_title %}
    Links
    {% endif %} 3 | {% endif %}{% endfor %} 6 | -------------------------------------------------------------------------------- /source/base/templates/utils/_paginate.html: -------------------------------------------------------------------------------- 1 | {% if page.has_previous() or page.has_next() %} 2 |

    3 | {% if page.has_previous() %} Previous{% endif %} 4 | {% if page.has_previous() and page.has_next() %}/{% endif %} 5 | {% if page.has_next() %}Next {% endif %} 6 |

    7 | {% endif %} 8 | -------------------------------------------------------------------------------- /source/base/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls.defaults import * 3 | from django.views.decorators.cache import cache_page 4 | 5 | from .feeds import ArticleFeed 6 | from .views import SourceSearchView, HomepageView, SlackMessageView 7 | from haystack.forms import SearchForm 8 | from haystack.query import SearchQuerySet 9 | from haystack.views import search_view_factory 10 | from source.articles.views import ArticleList, ArticleDetail 11 | from source.utils.caching import ClearCache 12 | 13 | STANDARD_CACHE_TIME = getattr(settings, 'CACHE_MIDDLEWARE_SECONDS', 60*15) 14 | FEED_CACHE_TIME = getattr(settings, 'FEED_CACHE_SECONDS', 60*15) 15 | 16 | urlpatterns = patterns('', 17 | url( 18 | regex = '^$', 19 | view = cache_page(HomepageView.as_view(template_name='homepage.html'), STANDARD_CACHE_TIME), 20 | kwargs = {}, 21 | name = 'homepage', 22 | ), 23 | (r'^articles/', include('source.articles.urls')), 24 | (r'^code/', include('source.code.urls')), 25 | (r'^guides/', include('source.guides.urls')), 26 | (r'^jobs/', include('source.jobs.urls')), 27 | (r'^organizations/', include('source.people.urls.organizations')), 28 | (r'^people/', include('source.people.urls.people')), 29 | (r'^api/1.0/', include('source.api.urls')), 30 | url( 31 | regex = '^search/$', 32 | view = search_view_factory(view_class=SourceSearchView, form_class=SearchForm, searchqueryset=SearchQuerySet().order_by('django_ct')), 33 | kwargs = {}, 34 | name = 'haystack_search', 35 | ), 36 | url( 37 | regex = '^clear-cache/$', 38 | view = ClearCache.as_view(), 39 | kwargs = {}, 40 | name = 'clear_cache', 41 | ), 42 | url( 43 | regex = '^send-to-slack/$', 44 | view = SlackMessageView.as_view(), 45 | kwargs = {}, 46 | name = 'send_to_slack', 47 | ), 48 | url( 49 | regex = '^rss/$', 50 | view = cache_page(ArticleFeed(), FEED_CACHE_TIME), 51 | kwargs = {}, 52 | name = 'homepage_feed', 53 | ), 54 | url( 55 | regex = '^category/(?P[-\w]+)/$', 56 | view = cache_page(ArticleList.as_view(), STANDARD_CACHE_TIME), 57 | kwargs = {}, 58 | name = 'article_list_by_category', 59 | ), 60 | url( 61 | regex = '^category/(?P[-\w]+)/rss/$', 62 | view = cache_page(ArticleFeed(), FEED_CACHE_TIME), 63 | kwargs = {}, 64 | name = 'article_list_by_category_feed', 65 | ), 66 | url( 67 | regex = '^(?P
    [-\w]+)/$', 68 | view = cache_page(ArticleList.as_view(), STANDARD_CACHE_TIME), 69 | kwargs = {}, 70 | name = 'article_list_by_section', 71 | ), 72 | url( 73 | regex = '^(?P
    [-\w]+)/rss/$', 74 | view = cache_page(ArticleFeed(), FEED_CACHE_TIME), 75 | kwargs = {}, 76 | name = 'article_list_by_section_feed', 77 | ), 78 | url( 79 | regex = '^(?P
    [-\w]+)/(?P[-\w]+)/$', 80 | view = cache_page(ArticleDetail.as_view(), STANDARD_CACHE_TIME), 81 | kwargs = {}, 82 | name = 'article_detail', 83 | ), 84 | ) 85 | -------------------------------------------------------------------------------- /source/base/utils.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.core.exceptions import PermissionDenied 3 | from django.core.paginator import Paginator, InvalidPage 4 | from django.http import Http404, HttpResponse 5 | from django.utils import simplejson 6 | 7 | 8 | def paginate(request, queryset, results_per_page=20): 9 | paginator = Paginator(queryset, results_per_page) 10 | 11 | try: 12 | page = paginator.page(int(request.GET.get('page', 1))) 13 | except InvalidPage: 14 | raise Http404("Sorry, that page of results does not exist.") 15 | except ValueError: 16 | raise PermissionDenied() 17 | 18 | return page, paginator 19 | 20 | def render_json_to_response(context): 21 | ''' 22 | Utility method for rendering a view's data to JSON response. 23 | ''' 24 | result = simplejson.dumps(context, sort_keys=False, indent=4) 25 | return HttpResponse(result, mimetype='application/javascript') 26 | -------------------------------------------------------------------------------- /source/base/widgets.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Original AdminImageWidget and AdminImageMixin found in 3 | sorl-thumbnail/sorl/thumbnail/admin/current.py 4 | 5 | Customizing here, however, to make them align a little 6 | more nicely in the admin forms. 7 | ''' 8 | from django import forms 9 | from django.utils.safestring import mark_safe 10 | from sorl.thumbnail.fields import ImageField 11 | from sorl.thumbnail.shortcuts import get_thumbnail 12 | 13 | 14 | class AdminImageWidget(forms.ClearableFileInput): 15 | """ 16 | An ImageField Widget for django.contrib.admin that shows 17 | a thumbnailed image and link to the current file. 18 | """ 19 | 20 | template_with_initial = u'%(clear_template)s
    %(input_text)s:
    %(input)s
    ' 21 | template_with_clear = u'%(clear)s ' 22 | 23 | def render(self, name, value, attrs=None): 24 | output = super(AdminImageWidget, self).render(name, value, attrs) 25 | if value and hasattr(value, 'url'): 26 | try: 27 | mini = get_thumbnail(value, 'x80', upscale=False) 28 | except Exception: 29 | pass 30 | else: 31 | output = ( 32 | u'
    ' 33 | u'' 34 | u'%s
    ' 35 | ) % (mini.width, value.url, mini.url, output) 36 | return mark_safe(output) 37 | 38 | 39 | class AdminImageMixin(object): 40 | """ 41 | This is a mix-in for InlineModelAdmin subclasses to make ``ImageField`` 42 | show nicer form widget 43 | """ 44 | def formfield_for_dbfield(self, db_field, **kwargs): 45 | if isinstance(db_field, ImageField): 46 | return db_field.formfield(widget=AdminImageWidget) 47 | sup = super(AdminImageMixin, self) 48 | return sup.formfield_for_dbfield(db_field, **kwargs) 49 | 50 | -------------------------------------------------------------------------------- /source/code/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/code/__init__.py -------------------------------------------------------------------------------- /source/code/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Code, CodeLink 4 | from source.base.widgets import AdminImageMixin 5 | 6 | class CodeLinkInline(admin.StackedInline): 7 | model = CodeLink 8 | extra = 1 9 | fieldsets = ( 10 | ('', {'fields': (('name', 'url'),)}), 11 | ) 12 | 13 | def formfield_for_dbfield(self, db_field, **kwargs): 14 | # More usable width in admin form field for names 15 | field = super(CodeLinkInline, self).formfield_for_dbfield(db_field, **kwargs) 16 | if db_field.name == 'name': 17 | field.widget.attrs['style'] = 'width: 30em;' 18 | return field 19 | 20 | class CodeAdmin(AdminImageMixin, admin.ModelAdmin): 21 | save_on_top = True 22 | prepopulated_fields = {'slug': ('name',)} 23 | filter_horizontal = ('people', 'organizations',) 24 | list_filter = ('is_live', 'is_active',) 25 | search_fields = ('name', 'description',) 26 | fieldsets = ( 27 | ('', {'fields': (('name', 'slug'), ('is_live', 'is_active', 'seeking_contributors'), 'url', 'tags', 'technology_tags', 'concept_tags', 'screenshot', 'description', ('repo_last_push', 'repo_forks', 'repo_watchers'), 'repo_master_branch', 'repo_description', 'summary',)}), 28 | ('Related objects', {'fields': ('people', 'organizations',)}), 29 | ) 30 | inlines = [CodeLinkInline,] 31 | readonly_fields = ('tags',) 32 | 33 | def save_model(self, request, obj, form, change): 34 | ''' 35 | Mirror split tagfield contents in primary `tags` model. 36 | See source.tags.models for further details. 37 | ''' 38 | technology_tags_list = form.cleaned_data['technology_tags'] 39 | concept_tags_list = form.cleaned_data['concept_tags'] 40 | merged_tags = technology_tags_list + concept_tags_list 41 | if merged_tags: 42 | form.cleaned_data['tags'] = merged_tags 43 | 44 | super(CodeAdmin, self).save_model(request, obj, form, change) 45 | 46 | def formfield_for_dbfield(self, db_field, **kwargs): 47 | # More usable heights and widths in admin form fields 48 | field = super(CodeAdmin, self).formfield_for_dbfield(db_field, **kwargs) 49 | if db_field.name in ['url','tags','technology_tags','concept_tags']: 50 | field.widget.attrs['style'] = 'width: 45em;' 51 | if db_field.name in ['name','slug']: 52 | field.widget.attrs['style'] = 'width: 30em;' 53 | if db_field.name == 'summary': 54 | field.widget.attrs['style'] = 'height: 4.5em;' 55 | return field 56 | 57 | admin.site.register(Code, CodeAdmin) 58 | -------------------------------------------------------------------------------- /source/code/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/code/management/__init__.py -------------------------------------------------------------------------------- /source/code/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/code/management/commands/__init__.py -------------------------------------------------------------------------------- /source/code/management/commands/update_code_github_stats.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Uses the GitHub API to update stats for Code repos. 3 | ''' 4 | from datetime import datetime 5 | import logging 6 | 7 | from django.conf import settings 8 | from django.core.management.base import BaseCommand 9 | 10 | from source.code.models import Code 11 | 12 | logging.basicConfig(filename='github_code_update.log', filemode='w', level=logging.INFO) 13 | 14 | class Command(BaseCommand): 15 | help = 'Uses GitHub API to update stats for Code records.' 16 | def handle(self, *args, **options): 17 | logging.info('Started update: %s' % datetime.now()) 18 | # get all the Code records with that have GitHub repos 19 | code_list = Code.objects.filter(url__icontains='//github.com/') 20 | 21 | for code in code_list: 22 | # attempt to fetch stats from GitHub API 23 | updated = code.update_github_stats() 24 | 25 | # save stats to database or log the error 26 | if updated: 27 | code.save() 28 | logging.info('Succesful update: %s' % code.name) 29 | else: 30 | logging.info('ERROR: %s' % code.name) 31 | 32 | logging.info('Finished update: %s' % datetime.now()) 33 | 34 | -------------------------------------------------------------------------------- /source/code/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/code/migrations/__init__.py -------------------------------------------------------------------------------- /source/code/search_indexes.py: -------------------------------------------------------------------------------- 1 | from haystack import indexes 2 | from .models import Code 3 | 4 | 5 | class CodeIndex(indexes.SearchIndex, indexes.Indexable): 6 | name = indexes.CharField(model_attr='name', boost=1.2) 7 | text = indexes.CharField(document=True, use_template=True) 8 | 9 | def get_model(self): 10 | return Code 11 | 12 | def get_updated_field(self): 13 | return 'modified' 14 | 15 | def index_queryset(self): 16 | """Used when the entire index for model is updated.""" 17 | return self.get_model().live_objects.all() 18 | -------------------------------------------------------------------------------- /source/code/templates/code/_base_code.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% set active_nav = "code" %} 3 | {% block page_title %}Code Index{% if tags %} entries tagged: {% for tag in tags %}{{ tag.name }}{% if not loop.last %}, {% endif %}{% endfor %}{% endif %} - {{ super() }}{% endblock %} 4 | -------------------------------------------------------------------------------- /source/code/templates/code/_code_category_and_tags_overline.html: -------------------------------------------------------------------------------- 1 | {% if code.tags.all().exists() %}Tags {% for tag in code.tags.all() %}{{ tag.name|smartypants }}{% if not loop.last %} {% endif %}{% endfor %} {% endif %} 2 | 3 | {# 4 | 5 | If we ever completely drop the original `tags` field, the code below will 6 | generate the proper list of tags for a code object 7 | 8 | {% if code.merged_tag_list %}Tags {% for tag in code.merged_tag_list %}{{ tag }}{% if not loop.last %} {% endif %}{% endfor %} {% endif %} 9 | 10 | #} -------------------------------------------------------------------------------- /source/code/templates/code/_code_link_list.html: -------------------------------------------------------------------------------- 1 | {% for code in code_link_list %} 2 | {% if loop.first %}{% if not hide_link_list_title %}

    Code

    {% endif %} 3 | {% endif %}{% endfor %} 6 | -------------------------------------------------------------------------------- /source/code/templates/code/_code_list_item.html: -------------------------------------------------------------------------------- 1 | {# standard presentation block for a code item in list and search results pages #} 2 |
    3 |

    {% include "code/_code_category_and_tags_overline.html" %}{{ code.name|typogrify }}

    4 | 8 | {% if code.description %}{{ code.description|linebreaks|typogrify|safe }}{% endif %} 9 |
    10 | -------------------------------------------------------------------------------- /source/code/templates/code/code_list.html: -------------------------------------------------------------------------------- 1 | {% extends "code/_base_code.html" %} 2 | 3 | {% block content %} 4 |

    Code{% if tag %} / {{ tag.name|smartypants }}{% endif %}

    5 | 6 |
    7 | {% if tags %} 8 |
    9 |

    Code index entries tagged: {% for tag in tags %}{{ tag.name }}{% if not loop.last %}, {% endif %}{% endfor %}

    10 | {% for code in object_list %} 11 |
    12 |

    {{ code.name|typogrify }}

    13 | {% if code.summary_or_description %}

    {{ code.summary_or_description|typogrify|safe }}

    {% endif %} 14 | 18 |
    19 | {% endfor %} 20 |
    21 | {% else %} 22 | {% for alpha in object_list|groupby('sort_letter') %} 23 |
    24 |

    {{ alpha.grouper }}

    25 | {% for code in alpha.list %} 26 |
    27 |

    {{ code.name|typogrify }}

    28 | {% if code.summary_or_description %}

    {{ code.summary_or_description|typogrify|safe }}

    {% endif %} 29 | 33 |
    34 | {% endfor %} 35 |
    36 | {% endfor %} 37 | {% if not object_list %}

    No matching code index entries found.

    {% endif %} 38 | {% endif %} 39 |
    40 | {% endblock content %} 41 | 42 | {% block site_js_extra %} 43 | 44 | {% endblock %} 45 | -------------------------------------------------------------------------------- /source/code/templates/code/code_list.json: -------------------------------------------------------------------------------- 1 | {% if jsonp_callback %}{{ jsonp_callback|escapejs }}({% endif %}{ "objects": [ 2 | {% for code in object_list %} 3 | { 4 | "name": "{{ code.name|e }}", 5 | "slug": "{{ code.slug }}", 6 | "source_url": "{{ HTTP_PROTOCOL }}://{{ request.get_host() }}{{ code.get_absolute_url() }}", 7 | "project_url": "{{ code.url }}", 8 | "active_project": {{ code.is_active|lower }}, 9 | "seeking_contributors": {{ code.seeking_contributors|lower }}, 10 | "summary": {% if not code.summary %}null{% else %}"{{ code.summary|trim|striptags|e }}"{% endif %}, 11 | "description": {% if not code.description %}null{% else %}"{{ code.description|trim|striptags|e }}"{% endif %}, 12 | "tags": {% if code.tags.all().exists() %}[ {% for tag in code.tags.all() %}{# TODO: remove `tags` #} 13 | 14 | { 15 | "name": "{{ tag|e }}", 16 | "source_url": "{{ HTTP_PROTOCOL }}://{{ request.get_host() }}{{ url('code_list_by_tag', tag.slug) }}" 17 | }{% if not loop.last %},{% endif %} 18 | {% endfor %} 19 | 20 | ]{% else %}null{% endif %}, 21 | "technology_tags": {% if code.technology_tags.all().exists() %}[ {% for tag in code.technology_tags.all() %} 22 | 23 | { 24 | "name": "{{ tag|e }}", 25 | "source_url": "{{ HTTP_PROTOCOL }}://{{ request.get_host() }}{{ url('code_list_by_tag', tag.slug) }}" 26 | }{% if not loop.last %},{% endif %} 27 | {% endfor %} 28 | 29 | ]{% else %}null{% endif %}, 30 | "concept_tags": {% if code.concept_tags.all().exists() %}[ {% for tag in code.concept_tags.all() %} 31 | 32 | { 33 | "name": "{{ tag|e }}", 34 | "source_url": "{{ HTTP_PROTOCOL }}://{{ request.get_host() }}{{ url('code_list_by_tag', tag.slug) }}" 35 | }{% if not loop.last %},{% endif %} 36 | {% endfor %} 37 | 38 | ]{% else %}null{% endif %}, 39 | "people": {% if code.people.all().exists() %}[ {% for person in code.people.all() %} 40 | 41 | { 42 | "name": "{{ person.name()|e }}", 43 | "source_url": "{{ HTTP_PROTOCOL }}://{{ request.get_host() }}{{ person.get_absolute_url() }}" 44 | }{% if not loop.last %},{% endif %} 45 | {% endfor %} 46 | 47 | ]{% else %}null{% endif %}, 48 | "organizations": {% if code.organizations.all().exists() %}[ {% for organization in code.organizations.all() %} 49 | 50 | { 51 | "name": "{{ organization.name|e }}", 52 | "source_url": "{{ HTTP_PROTOCOL }}://{{ request.get_host() }}{{ organization.get_absolute_url() }}" 53 | }{% if not loop.last %},{% endif %} 54 | {% endfor %} 55 | 56 | ] 57 | {% else %}null 58 | {% endif %} 59 | }{% if not loop.last %},{% endif %} 60 | 61 | {% endfor %} 62 | ]}{% if jsonp_callback %});{% endif %} 63 | -------------------------------------------------------------------------------- /source/code/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls.defaults import * 3 | from django.views.decorators.cache import cache_page 4 | from django.views.generic.simple import redirect_to 5 | 6 | from .views import CodeList, CodeDetail 7 | from source.base.feeds import CodeFeed 8 | 9 | STANDARD_CACHE_TIME = getattr(settings, 'CACHE_MIDDLEWARE_SECONDS', 60*15) 10 | FEED_CACHE_TIME = getattr(settings, 'FEED_CACHE_SECONDS', 60*15) 11 | 12 | urlpatterns = patterns('', 13 | url( 14 | regex = '^$', 15 | view = cache_page(CodeList.as_view(), STANDARD_CACHE_TIME), 16 | kwargs = {}, 17 | name = 'code_list', 18 | ), 19 | url( 20 | regex = '^rss/$', 21 | view = cache_page(CodeFeed(), FEED_CACHE_TIME), 22 | kwargs = {}, 23 | name = 'code_list_feed', 24 | ), 25 | url( 26 | regex = '^json/$', 27 | view = cache_page(CodeList.as_view(), FEED_CACHE_TIME), 28 | kwargs = {'render_json': True}, 29 | name = 'code_list_feed_json', 30 | ), 31 | url( 32 | regex = '^tags/(?P[-\w\+]+)/$', 33 | view = cache_page(CodeList.as_view(), STANDARD_CACHE_TIME), 34 | kwargs = {}, 35 | name = 'code_list_by_tag', 36 | ), 37 | url( 38 | regex = '^tags/(?P[-\w\+]+)/rss/$', 39 | view = cache_page(CodeFeed(), FEED_CACHE_TIME), 40 | kwargs = {}, 41 | name = 'code_list_by_tag_feed', 42 | ), 43 | url( 44 | regex = '^tags/(?P[-\w\+]+)/json/$', 45 | view = cache_page(CodeList.as_view(), FEED_CACHE_TIME), 46 | kwargs = {'render_json': True}, 47 | name = 'code_list_by_tag_feed_json', 48 | ), 49 | url( 50 | regex = '^tags/$', 51 | view = redirect_to, 52 | kwargs = {'url': '/code/'}, 53 | name = 'code_list_tags', 54 | ), 55 | url( 56 | regex = '^(?P[-\w]+)/$', 57 | view = cache_page(CodeDetail.as_view(), STANDARD_CACHE_TIME), 58 | kwargs = {}, 59 | name = 'code_detail', 60 | ), 61 | ) 62 | -------------------------------------------------------------------------------- /source/code/views.py: -------------------------------------------------------------------------------- 1 | from django.core.urlresolvers import reverse 2 | from django.shortcuts import get_object_or_404, render_to_response 3 | from django.template import RequestContext 4 | from django.views.generic import ListView, DetailView 5 | 6 | from .models import Code 7 | from source.tags.utils import filter_queryset_by_tags 8 | from source.utils.pagination import paginate 9 | 10 | 11 | class CodeList(ListView): 12 | model = Code 13 | 14 | def dispatch(self, *args, **kwargs): 15 | self.render_json = kwargs.get('render_json', False) 16 | self.tag_slugs = kwargs.get('tag_slugs', None) 17 | self.tags = [] 18 | return super(CodeList, self).dispatch(*args, **kwargs) 19 | 20 | def get_queryset(self): 21 | queryset = Code.live_objects.prefetch_related('organizations') 22 | 23 | if self.tag_slugs: 24 | queryset, self.tags = filter_queryset_by_tags(queryset, self.tag_slugs, self.tags) 25 | 26 | return queryset 27 | 28 | def get_context_data(self, **kwargs): 29 | context = super(CodeList, self).get_context_data(**kwargs) 30 | context['active_nav'] = 'Code' 31 | 32 | if self.tags: 33 | context['tags'] = self.tags 34 | context['rss_link'] = reverse('code_list_by_tag_feed', kwargs={'tag_slugs': self.tag_slugs}) 35 | context['json_link'] = reverse('code_list_by_tag_feed_json', kwargs={'tag_slugs': self.tag_slugs}) 36 | else: 37 | context['rss_link'] = reverse('code_list_feed') 38 | context['json_link'] = reverse('code_list_feed_json') 39 | 40 | # No pagination required for current alpha list display 41 | #page, paginator = paginate(self.request, self.object_list, 50) 42 | #context.update({ 43 | # 'page': page, 44 | # 'paginator': paginator 45 | #}) 46 | 47 | return context 48 | 49 | def render_to_response(self, context): 50 | if self.render_json: 51 | ''' 52 | JSON export runs through a hand-rolled template for now, so we can 53 | attach things like related names and urls. If we start doing more 54 | with providing JSON, we should definitly go full django-tastypie. 55 | ''' 56 | if 'callback' in self.request.GET: 57 | # provide jsonp support for requests 58 | # with ?callback=foo paramater 59 | context['jsonp_callback'] = self.request.GET['callback'] 60 | return render_to_response( 61 | 'code/code_list.json', 62 | context, 63 | context_instance = RequestContext(self.request), 64 | mimetype='application/json' 65 | ) 66 | return super(CodeList, self).render_to_response(context) 67 | 68 | 69 | class CodeDetail(DetailView): 70 | model = Code 71 | 72 | def get_queryset(self): 73 | queryset = Code.live_objects.prefetch_related('codelink_set', 'people', 'organizations', 'article_set') 74 | 75 | return queryset 76 | -------------------------------------------------------------------------------- /source/guides/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/guides/__init__.py -------------------------------------------------------------------------------- /source/guides/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Guide, GuideArticle 4 | from source.base.widgets import AdminImageMixin 5 | 6 | class GuideArticleInline(admin.StackedInline): 7 | model = GuideArticle 8 | extra = 1 9 | raw_id_fields = ('article',) 10 | fieldsets = ( 11 | ('', {'fields': ('order', 'article', 'external_url', 'external_title', 'article_notes')}), 12 | ) 13 | 14 | def formfield_for_dbfield(self, db_field, **kwargs): 15 | # More usable heights and widths in admin form fields 16 | field = super(GuideArticleInline, self).formfield_for_dbfield(db_field, **kwargs) 17 | if db_field.name in ['external_url','external_title']: 18 | field.widget.attrs['style'] = 'width: 30em;' 19 | return field 20 | 21 | class GuideAdmin(AdminImageMixin, admin.ModelAdmin): 22 | save_on_top = True 23 | prepopulated_fields = {'slug': ('title',)} 24 | list_filter = ('is_live', 'show_in_lists',) 25 | search_fields = ('title', 'summary', 'description',) 26 | fieldsets = ( 27 | ('', {'fields': (('pubdate', 'is_live', 'show_in_lists'), ('title', 'slug'), 'description', 'summary')}), 28 | ('', {'fields': ('cover_color', 'image')}), 29 | ) 30 | inlines = [GuideArticleInline,] 31 | 32 | def formfield_for_dbfield(self, db_field, **kwargs): 33 | # More usable heights and widths in admin form fields 34 | field = super(GuideAdmin, self).formfield_for_dbfield(db_field, **kwargs) 35 | if db_field.name in ['title','slug']: 36 | field.widget.attrs['style'] = 'width: 30em;' 37 | if db_field.name == 'summary': 38 | field.widget.attrs['style'] = 'height: 4.5em;' 39 | return field 40 | 41 | admin.site.register(Guide, GuideAdmin) 42 | -------------------------------------------------------------------------------- /source/guides/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/guides/migrations/__init__.py -------------------------------------------------------------------------------- /source/guides/templates/guides/_base_guides.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% set active_nav = "guides" %} 3 | {% block page_title %}Guides - {{ super() }}{% endblock %} 4 | -------------------------------------------------------------------------------- /source/guides/templates/guides/_guide_article_list_item.html: -------------------------------------------------------------------------------- 1 | {# standard presentation block for an article on a guide detail page #} 2 |
    3 | {% if guidearticle.external_url and guidearticle.external_title %} 4 |

    {{ guidearticle.external_title|typogrify }}

    5 | {% else %} 6 |

    {{ article.title|typogrify }}

    7 | 12 | {% endif %} 13 | 14 | {% if guidearticle.article_notes %} 15 |

    {{ guidearticle.article_notes|typogrify|safe }}

    16 | {% elif article %} 17 |

    {{ article.summary|typogrify|safe }}

    18 | {% endif %} 19 |
    20 | -------------------------------------------------------------------------------- /source/guides/templates/guides/_guide_link_list.html: -------------------------------------------------------------------------------- 1 | {% for guide in guide_link_list %} 2 | {% if loop.first %}{% if not hide_link_list_title %}

    Part of a Guide

    {% endif %} 3 | {% endif %}{% endfor %} 6 | -------------------------------------------------------------------------------- /source/guides/templates/guides/_guide_list_item.html: -------------------------------------------------------------------------------- 1 | {# standard presentation block for an guide on list pages #} 2 | 3 | {% if guide.image %}
    {{ guide.title }}
    {% endif %} 4 | {{ guide.title|typogrify }} 5 |
    6 | -------------------------------------------------------------------------------- /source/guides/templates/guides/guide_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "guides/_base_guides.html" %} 2 | 3 | {% block page_title %}{{ guide.title }} - {{ super() }}{% endblock %} 4 | 5 | {% block article_class %}guide-detail{% endblock %} 6 | {% block content %} 7 |

    Guides

    8 | 9 |
    10 |
    11 |
    12 | {% if guide.image %}
    {{ guide.title }}
    {% endif %} 13 | {{ guide.title|typogrify }} 14 |
    15 |
    16 | {{ guide.description_or_summary|linebreaks|typogrify|safe }} 17 |
    18 | 19 |
    20 | {% for guidearticle in guide.get_live_article_set() %} 21 | {% with article=guidearticle.article %} 22 | {% include "guides/_guide_article_list_item.html" %} 23 | {% endwith %} 24 | {% endfor %} 25 |
    26 | {% endblock content %} -------------------------------------------------------------------------------- /source/guides/templates/guides/guide_list.html: -------------------------------------------------------------------------------- 1 | {% extends "guides/_base_guides.html" %} 2 | 3 | {% block content %} 4 |

    Guides

    5 | 6 |
    7 |

    Source Guides are collections of tutorials, project discussions, and advice on topics of interest to developers and interactive designers in newsrooms. Is there a Guide topic missing that you’d like to see here? Let us know.

    8 |
    9 | 10 |
    11 | {% for guide in object_list %} 12 | {% include "guides/_guide_list_item.html" %} 13 | {% endfor %} 14 |
    15 | 16 | {% endblock content %} 17 | -------------------------------------------------------------------------------- /source/guides/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls.defaults import * 3 | from django.views.decorators.cache import cache_page 4 | 5 | from .views import GuideList, GuideDetail 6 | from source.base.feeds import GuideFeed 7 | 8 | STANDARD_CACHE_TIME = getattr(settings, 'CACHE_MIDDLEWARE_SECONDS', 60*15) 9 | FEED_CACHE_TIME = getattr(settings, 'FEED_CACHE_SECONDS', 60*15) 10 | 11 | urlpatterns = patterns('', 12 | url( 13 | regex = '^$', 14 | view = cache_page(GuideList.as_view(), STANDARD_CACHE_TIME), 15 | kwargs = {}, 16 | name = 'guide_list', 17 | ), 18 | url( 19 | regex = '^rss/$', 20 | view = cache_page(GuideFeed(), FEED_CACHE_TIME), 21 | kwargs = {}, 22 | name = 'guide_list_feed', 23 | ), 24 | url( 25 | regex = '^(?P[-\w]+)/$', 26 | view = cache_page(GuideDetail.as_view(), STANDARD_CACHE_TIME), 27 | kwargs = {}, 28 | name = 'guide_detail', 29 | ), 30 | ) 31 | -------------------------------------------------------------------------------- /source/guides/views.py: -------------------------------------------------------------------------------- 1 | from django.core.urlresolvers import reverse 2 | from django.shortcuts import render_to_response 3 | from django.views.generic import ListView, DetailView 4 | 5 | from .models import Guide 6 | from source.utils.json import render_json_to_response 7 | 8 | 9 | class GuideList(ListView): 10 | model = Guide 11 | 12 | def dispatch(self, *args, **kwargs): 13 | self.render_json = kwargs.get('render_json', False) 14 | return super(GuideList, self).dispatch(*args, **kwargs) 15 | 16 | def get_queryset(self): 17 | queryset = Guide.live_objects.all() 18 | 19 | return queryset 20 | 21 | def get_context_data(self, **kwargs): 22 | context = super(GuideList, self).get_context_data(**kwargs) 23 | context['active_nav'] = 'Guides' 24 | context['rss_link'] = reverse('guide_list_feed') 25 | 26 | return context 27 | 28 | 29 | class GuideDetail(DetailView): 30 | model = Guide 31 | 32 | def get_queryset(self): 33 | if self.request.user.is_staff: 34 | # allow preview for logged-in editors 35 | queryset = Guide.objects.prefetch_related('guidearticle_set') 36 | else: 37 | queryset = Guide.live_objects.prefetch_related('guidearticle_set') 38 | 39 | return queryset 40 | -------------------------------------------------------------------------------- /source/jobs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/jobs/__init__.py -------------------------------------------------------------------------------- /source/jobs/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Job 4 | 5 | class JobAdmin(admin.ModelAdmin): 6 | save_on_top = True 7 | prepopulated_fields = {'slug': ('name',)} 8 | list_filter = ('is_live', 'organization',) 9 | list_display = ('name', 'organization', 'will_show_on_site', 'listing_start_date', 'listing_end_date', 'tweeted_at') 10 | search_fields = ('name', 'organization__name',) 11 | fieldsets = ( 12 | ('', {'fields': (('name', 'slug'), 'description', 'organization', 'location', 'url', 'contact_name', 'email', 'tweeted_at', 'listing_start_date', 'listing_end_date', 'is_live',)}), 13 | ) 14 | 15 | def formfield_for_dbfield(self, db_field, **kwargs): 16 | # More usable heights and widths in admin form fields 17 | field = super(JobAdmin, self).formfield_for_dbfield(db_field, **kwargs) 18 | if db_field.name == 'description': 19 | field.widget.attrs['style'] = 'height: 3em;' 20 | return field 21 | 22 | 23 | admin.site.register(Job, JobAdmin) 24 | -------------------------------------------------------------------------------- /source/jobs/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.forms import ModelForm 3 | 4 | from .models import Job 5 | 6 | class JobUpdateForm(ModelForm): 7 | class Meta: 8 | model = Job 9 | fields = ( 10 | 'name', 11 | 'description', 12 | 'location', 13 | 'url', 14 | 'contact_name', 15 | 'email', 16 | 'listing_end_date', 17 | ) 18 | -------------------------------------------------------------------------------- /source/jobs/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/jobs/management/__init__.py -------------------------------------------------------------------------------- /source/jobs/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/jobs/management/commands/__init__.py -------------------------------------------------------------------------------- /source/jobs/management/commands/job_post_reminders.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Testing the Amazon email hookup. 3 | ''' 4 | import logging 5 | from datetime import datetime 6 | from time import sleep 7 | 8 | from django.conf import settings 9 | from django.core.management.base import BaseCommand 10 | from django.template.loader import render_to_string 11 | 12 | from source.jobs.models import Job 13 | from source.people.models import Organization 14 | from source.utils.email import send_multipart_email 15 | 16 | logging.basicConfig(filename='email_job_reminders.log', filemode='w', level=logging.INFO) 17 | 18 | 19 | class Command(BaseCommand): 20 | help = 'Testing email via Amazon SMTP.' 21 | def handle(self, *args, **options): 22 | logging.info('Started job: %s' % datetime.now()) 23 | 24 | organizations_with_jobs_ids = set(Job.live_objects.values_list('organization', flat=True)) 25 | organizations_with_jobs = Organization.objects.filter(id__in=organizations_with_jobs_ids) 26 | 27 | for organization in organizations_with_jobs: 28 | jobs = Job.live_objects.filter(organization=organization) 29 | 30 | 31 | 32 | # add context for rendering personalized emails 33 | subject = '[Source] Monthly update from Source Jobs' 34 | email_context = { 35 | 'site_url': settings.BASE_SITE_URL, 36 | 'organization': organization, 37 | 'jobs': jobs, 38 | } 39 | 40 | # render text and html versions of email body 41 | text_content = render_to_string( 42 | 'jobs/emails/job_post_reminder.txt', 43 | email_context, 44 | ) 45 | html_content = render_to_string( 46 | 'jobs/emails/job_post_reminder.html', 47 | email_context 48 | ) 49 | 50 | send_multipart_email( 51 | subject = subject, 52 | from_email = settings.DEFAULT_FROM_EMAIL, 53 | to = organization.email, 54 | text_content = text_content, 55 | html_content = html_content 56 | ) 57 | 58 | # avoid rate limit 59 | sleep(1) 60 | 61 | 62 | logging.info('Finished job: %s' % datetime.now()) 63 | 64 | -------------------------------------------------------------------------------- /source/jobs/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/jobs/migrations/__init__.py -------------------------------------------------------------------------------- /source/jobs/templates/jobs/_base_jobs.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% set active_nav = "jobs" %} 3 | {% block page_title %}Jobs - {{ super() }}{% endblock %} 4 | -------------------------------------------------------------------------------- /source/jobs/templates/jobs/_job_list_as_grouped_list.html: -------------------------------------------------------------------------------- 1 | {% for group in job_list %} 2 |
    3 |

    {% if grouper == 'organization' %}{{ group.list[0].organization.name }}{% else %}{{ group.grouper }}{% endif %}

    4 | {% for job in group.list %} 5 |
    6 | 7 |

    {{ job.wrapped_job_name }} {% if not sort_by_organization %}at {{ job.wrapped_organization_name }} {% endif %}

    8 | {% if job.description %}

    {{ job.description }}

    {% endif %} 9 |
    10 | {{ job.wrapped_organization_name }} 11 | {% if job.location %}Location: {{ job.location }}{% endif %} 12 | {% if job.wrapped_contact_name %}Contact: {{ job.wrapped_contact_name }}{% endif %} 13 |
    14 |
    Posted {{ job.listing_start_date|simple_datesince }}
    15 |
    16 | {% endfor %} 17 |
    18 | {% endfor %} 19 | -------------------------------------------------------------------------------- /source/jobs/templates/jobs/_job_list_as_list.html: -------------------------------------------------------------------------------- 1 | {% for job in job_list %} 2 |
    3 | 4 |

    {{ job.wrapped_job_name }} at {{ job.wrapped_organization_name }}

    5 | {% if job.description %}

    {{ job.description }}

    {% endif %} 6 |
    7 | {{ job.wrapped_organization_name }} 8 | {% if job.location %}Location: {{ job.location }}{% endif %} 9 | {% if job.wrapped_contact_name %}Contact: {{ job.wrapped_contact_name }}{% endif %} 10 |
    11 |
    Posted {{ job.listing_start_date|simple_datesince }}
    12 |
    13 | {% endfor %} 14 | -------------------------------------------------------------------------------- /source/jobs/templates/jobs/emails/job_post_reminder.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Monthly update from Source Jobs 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 |
    14 | Source 15 |
    18 | 19 | 20 | 21 | 43 | 44 |
    22 | 23 |

    Hi! This is just a quick monthly update from the Source Jobs page, to remind you about any listings you have on the site and when they expire.

    24 | 25 |

    Here’s what we have posted for {{ organization.name }} right now:

    26 | 27 | 28 | 29 | 30 | 31 | 32 | {% for job in jobs %} 33 | 34 | 35 | {% endfor %} 36 |
    JobExpires
    {{ job.name }}{{ job.pretty_expiration_date }}
    37 | 38 |

    If you need to update any of these job listings, or if you’re ready to add a new one, you can log in right here.

    39 | 40 |

    — The Source team

    41 | 42 |
    45 | 46 | -------------------------------------------------------------------------------- /source/jobs/templates/jobs/emails/job_post_reminder.txt: -------------------------------------------------------------------------------- 1 | Hi! This is just a quick monthly update from the Source Jobs page, to remind you about any listings you have on the site and when they expire. 2 | 3 | Here's what we have posted for {{ organization.name }} right now: 4 | 5 | {% for job in jobs %} 6 | * {{ job.name }} (expires {{ job.pretty_expiration_date }}) 7 | {% endfor %} 8 | 9 | If you need to update any of these job listings, or if you're ready to add a new one, you can log in right here: 10 | 11 | {{ site_url }}/organizations/update/ 12 | 13 | -- The Source team -------------------------------------------------------------------------------- /source/jobs/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls.defaults import * 3 | from django.views.decorators.cache import cache_page 4 | 5 | from .views import JobList, JobUpdate 6 | from source.base.feeds import JobFeed 7 | 8 | STANDARD_CACHE_TIME = getattr(settings, 'CACHE_MIDDLEWARE_SECONDS', 60*15) 9 | FEED_CACHE_TIME = getattr(settings, 'FEED_CACHE_SECONDS', 60*15) 10 | 11 | urlpatterns = patterns('', 12 | url( 13 | regex = '^$', 14 | view = cache_page(JobList.as_view(), STANDARD_CACHE_TIME), 15 | kwargs = {}, 16 | name = 'job_list', 17 | ), 18 | url( 19 | regex = '^rss/$', 20 | view = cache_page(JobFeed(), FEED_CACHE_TIME), 21 | kwargs = {}, 22 | name = 'job_list_feed', 23 | ), 24 | url( 25 | regex = '^json/$', 26 | view = cache_page(JobList.as_view(), FEED_CACHE_TIME), 27 | kwargs = {'render_json': True}, 28 | name = 'job_list_feed_json', 29 | ), 30 | url( 31 | regex = '^update/$', 32 | view = JobUpdate.as_view(), 33 | kwargs = {}, 34 | name = 'job_update', 35 | ), 36 | ) 37 | -------------------------------------------------------------------------------- /source/locale/en_US/LC_MESSAGES/messages.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: PACKAGE VERSION\n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "POT-Creation-Date: 2011-05-26 18:11-0700\n" 6 | "PO-Revision-Date: 2011-05-26 18:11-0700\n" 7 | "Last-Translator: Automatically generated\n" 8 | "Language-Team: none\n" 9 | "Language: en_US\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "X-Generator: Translate Toolkit 1.8.0\n" 14 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 15 | 16 | #: apps/examples/templates/examples/home.html:5 17 | msgid "Hello world" 18 | msgstr "Hello world" 19 | 20 | #. This is a localizer comment 21 | #: apps/examples/templates/examples/home.html:9 22 | msgid "This is a test view." 23 | msgstr "This is a test view." 24 | 25 | #: apps/examples/templates/examples/home.html:11 26 | msgid "Learn you some Playdoh and then go build something awesome." 27 | msgstr "Learn you some Playdoh and then go build something awesome." 28 | 29 | #: apps/examples/templates/examples/home.html:17 30 | msgid "Current locale: %(LANG)s.
    Available locales: %(langs)s." 31 | msgstr "Current locale: %(LANG)s.
    Available locales: %(langs)s." 32 | -------------------------------------------------------------------------------- /source/locale/fr/LC_MESSAGES/messages.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: PACKAGE VERSION\n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "POT-Creation-Date: 2011-06-03 19:07-0700\n" 6 | "Last-Translator: Automatically generated\n" 7 | "Language-Team: none\n" 8 | "Language: fr\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 13 | 14 | #: apps/examples/templates/examples/home.html:5 15 | msgid "Hello world" 16 | msgstr "Bonjour le monde" 17 | 18 | #. This is a localizer comment 19 | #: apps/examples/templates/examples/home.html:9 20 | msgid "This is a test view." 21 | msgstr "Ceci est une vue de test." 22 | 23 | #: apps/examples/templates/examples/home.html:11 24 | msgid "Learn you some Playdoh and then go build something awesome." 25 | msgstr "Apprends à jouer avec Playdoh et construis quelque chose de génial." 26 | 27 | #: apps/examples/templates/examples/home.html:17 28 | msgid "Current locale: %(LANG)s.
    Available locales: %(langs)s." 29 | msgstr "Langue active : %(LANG)s.
    Langues disponibles : %(langs)s." 30 | -------------------------------------------------------------------------------- /source/people/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/people/__init__.py -------------------------------------------------------------------------------- /source/people/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Person, PersonLink, Organization, OrganizationLink 4 | from source.base.widgets import AdminImageMixin 5 | 6 | class PersonLinkInline(admin.StackedInline): 7 | model = PersonLink 8 | extra = 1 9 | fieldsets = ( 10 | ('', {'fields': (('name', 'url'),)}), 11 | ) 12 | 13 | def formfield_for_dbfield(self, db_field, **kwargs): 14 | # More usable width in admin form field for names 15 | field = super(PersonLinkInline, self).formfield_for_dbfield(db_field, **kwargs) 16 | if db_field.name == 'name': 17 | field.widget.attrs['style'] = 'width: 30em;' 18 | return field 19 | 20 | class PersonAdmin(admin.ModelAdmin): 21 | save_on_top = True 22 | prepopulated_fields = {'slug': ('first_name', 'last_name')} 23 | list_filter = ('is_live', 'show_in_lists',) 24 | filter_horizontal = ('organizations',) 25 | search_fields = ('first_name', 'last_name', 'description',) 26 | fieldsets = ( 27 | ('', {'fields': (('first_name', 'last_name', 'slug'), ('is_live', 'show_in_lists'), 'email', 'twitter_username', 'twitter_bio', 'twitter_profile_image_url', 'github_username', ('github_repos_num', 'github_gists_num'), 'description',)}), 28 | ('Related objects', {'fields': ('organizations',)}), 29 | ) 30 | inlines = [PersonLinkInline,] 31 | 32 | class OrganizationLinkInline(admin.StackedInline): 33 | model = OrganizationLink 34 | extra = 1 35 | fieldsets = ( 36 | ('', {'fields': (('name', 'url'),)}), 37 | ) 38 | 39 | def formfield_for_dbfield(self, db_field, **kwargs): 40 | # More usable width in admin form field for names 41 | field = super(OrganizationLinkInline, self).formfield_for_dbfield(db_field, **kwargs) 42 | if db_field.name == 'name': 43 | field.widget.attrs['style'] = 'width: 30em;' 44 | return field 45 | 46 | class OrganizationAdmin(AdminImageMixin, admin.ModelAdmin): 47 | save_on_top = True 48 | prepopulated_fields = {'slug': ('name',)} 49 | list_filter = ('is_live',) 50 | search_fields = ('name', 'description',) 51 | fieldsets = ( 52 | ('', {'fields': (('name', 'slug'), ('is_live', 'show_in_lists'), 'email', 'twitter_username', 'github_username', ('github_repos_num', 'github_gists_num'), 'homepage', 'logo', 'description',)}), 53 | ('Location', {'fields': ('address', ('city', 'state',), 'country',)}), 54 | ) 55 | inlines = [OrganizationLinkInline,] 56 | 57 | admin.site.register(Person, PersonAdmin) 58 | admin.site.register(Organization, OrganizationAdmin) 59 | -------------------------------------------------------------------------------- /source/people/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.forms import ModelForm 3 | 4 | from .models import Organization, Person 5 | 6 | class OrganizationUpdateForm(ModelForm): 7 | class Meta: 8 | model = Organization 9 | fields = ( 10 | 'twitter_username', 11 | 'github_username', 12 | 'homepage', 13 | 'description', 14 | 'address', 15 | 'city', 16 | 'state', 17 | ) 18 | widgets = { 19 | 'description': forms.Textarea(attrs={'rows': 4}), 20 | } 21 | 22 | class PersonUpdateForm(ModelForm): 23 | class Meta: 24 | model = Person 25 | fields = ( 26 | 'first_name', 27 | 'last_name', 28 | 'email', 29 | 'twitter_username', 30 | 'github_username', 31 | 'description', 32 | ) 33 | widgets = { 34 | 'description': forms.Textarea(attrs={'rows': 4}), 35 | } 36 | -------------------------------------------------------------------------------- /source/people/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/people/management/__init__.py -------------------------------------------------------------------------------- /source/people/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/people/management/commands/__init__.py -------------------------------------------------------------------------------- /source/people/management/commands/update_organization_github_stats.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Uses the GitHub API to update stats for Organization records. 3 | ''' 4 | from datetime import datetime 5 | import logging 6 | import requests 7 | 8 | from django.conf import settings 9 | from django.core.management.base import BaseCommand 10 | 11 | from source.people.models import Organization 12 | 13 | CLIENT_ID=settings.GITHUB_CLIENT_ID 14 | CLIENT_SECRET=settings.GITHUB_CLIENT_SECRET 15 | 16 | logging.basicConfig(filename='github_org_update.log', filemode='w', level=logging.INFO) 17 | 18 | class Command(BaseCommand): 19 | help = 'Uses GitHub API to update stats for Organization records.' 20 | def handle(self, *args, **options): 21 | logging.info('Started update: %s' % datetime.now()) 22 | # get all the Person records with Twitter usernames 23 | organization_list = Organization.objects.exclude(github_username='') 24 | 25 | for organization in organization_list: 26 | github_username = organization.github_username 27 | github_api_url = 'https://api.github.com/orgs/%s?client_id=%s&client_secret=%s' % ( 28 | github_username.lower(), CLIENT_ID, CLIENT_SECRET 29 | ) 30 | r = requests.get(github_api_url) 31 | data = r.json 32 | try: 33 | organization.github_repos_num = data['public_repos'] 34 | organization.github_gists_num = data['public_gists'] 35 | organization.save() 36 | logging.info('Succesful update: %s' % github_username) 37 | except: 38 | logging.info('ERROR: %s' % github_username) 39 | pass 40 | 41 | logging.info('Finished update: %s' % datetime.now()) 42 | 43 | -------------------------------------------------------------------------------- /source/people/management/commands/update_person_github_stats.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Uses the GitHub API to update stats for Person records. 3 | ''' 4 | from datetime import datetime 5 | import logging 6 | import requests 7 | 8 | from django.conf import settings 9 | from django.core.management.base import BaseCommand 10 | 11 | from source.people.models import Person 12 | 13 | CLIENT_ID=settings.GITHUB_CLIENT_ID 14 | CLIENT_SECRET=settings.GITHUB_CLIENT_SECRET 15 | 16 | logging.basicConfig(filename='github_person_update.log', filemode='w', level=logging.INFO) 17 | 18 | class Command(BaseCommand): 19 | help = 'Uses GitHub API to update stats for Person records.' 20 | def handle(self, *args, **options): 21 | logging.info('Started update: %s' % datetime.now()) 22 | # get all the Person records with Twitter usernames 23 | person_list = Person.objects.exclude(github_username='') 24 | 25 | for person in person_list: 26 | github_username = person.github_username 27 | github_api_url = 'https://api.github.com/users/%s?client_id=%s&client_secret=%s' % ( 28 | github_username.lower(), CLIENT_ID, CLIENT_SECRET 29 | ) 30 | r = requests.get(github_api_url) 31 | data = r.json 32 | try: 33 | person.github_repos_num = data['public_repos'] 34 | person.github_gists_num = data['public_gists'] 35 | person.save() 36 | logging.info('Succesful update: %s' % github_username) 37 | except: 38 | logging.info('ERROR: %s' % github_username) 39 | pass 40 | 41 | logging.info('Finished update: %s' % datetime.now()) 42 | 43 | -------------------------------------------------------------------------------- /source/people/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/people/migrations/__init__.py -------------------------------------------------------------------------------- /source/people/search_indexes.py: -------------------------------------------------------------------------------- 1 | from haystack import indexes 2 | from .models import Person, Organization 3 | 4 | 5 | class PersonIndex(indexes.SearchIndex, indexes.Indexable): 6 | name = indexes.CharField(model_attr='name', boost=1.2) 7 | text = indexes.CharField(document=True, use_template=True) 8 | 9 | def get_model(self): 10 | return Person 11 | 12 | def get_updated_field(self): 13 | return 'modified' 14 | 15 | def index_queryset(self): 16 | """Used when the entire index for model is updated.""" 17 | return self.get_model().live_objects.all() 18 | 19 | 20 | class OrganizationIndex(indexes.SearchIndex, indexes.Indexable): 21 | name = indexes.CharField(model_attr='name', boost=1.2) 22 | text = indexes.CharField(document=True, use_template=True) 23 | 24 | def get_model(self): 25 | return Organization 26 | 27 | def get_updated_field(self): 28 | return 'modified' 29 | 30 | def index_queryset(self): 31 | """Used when the entire index for model is updated.""" 32 | return self.get_model().live_objects.all() 33 | -------------------------------------------------------------------------------- /source/people/templates/people/_base_organizations.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% set active_nav = "community" %} 3 | {% block page_title %}Organizations - {{ super() }}{% endblock %} 4 | -------------------------------------------------------------------------------- /source/people/templates/people/_base_people.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% set active_nav = "community" %} 3 | {% block page_title %}People - {{ super() }}{% endblock %} 4 | -------------------------------------------------------------------------------- /source/people/templates/people/_organization_link_list.html: -------------------------------------------------------------------------------- 1 | {% for organization in organization_link_list %} 2 | {% if loop.first %}{% if not hide_link_list_title %}

    Organizations

    {% endif %} 3 | {% endif %}{% endfor %} 6 | -------------------------------------------------------------------------------- /source/people/templates/people/_organization_link_list_inline.html: -------------------------------------------------------------------------------- 1 | {% if organization_link_list %}
  • {% for organization in organization_link_list %}{{ organization.name|smartypants }}{% if not loop.last %}, {% endif %}{% endfor %}
  • {% endif %} 2 | -------------------------------------------------------------------------------- /source/people/templates/people/_person_link_list.html: -------------------------------------------------------------------------------- 1 | {% for person in person_link_list %} 2 | {% if loop.first %}{% if not hide_link_list_title %}

    People

    {% endif %} 3 | {% endif %}{% endfor %} 6 | -------------------------------------------------------------------------------- /source/people/templates/people/_person_link_list_inline.html: -------------------------------------------------------------------------------- 1 | {% if person_link_list %}
  • {% for person in person_link_list %}{{ person.name()|smartypants }}{% if not loop.last %}, {% endif %}{% endfor %}
  • {% endif %} 2 | -------------------------------------------------------------------------------- /source/people/templates/people/organization_list.html: -------------------------------------------------------------------------------- 1 | {% extends "people/_base_organizations.html" %} 2 | 3 | {% block content %} 4 |

    5 | People / Organizations 6 |

    7 | 8 |
    9 | {% for alpha in object_list|groupby('sort_letter') %} 10 |
    11 |

    {{ alpha.grouper }}

    12 | {% for organization in alpha.list %} 13 | 16 | {% endfor %} 17 |
    18 | {% endfor %} 19 |
    20 | {% endblock content %} 21 | 22 | {% block site_js_extra %} 23 | 24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /source/people/templates/people/organization_update.html: -------------------------------------------------------------------------------- 1 | {% extends "people/_base_organizations.html" %} 2 | 3 | {% block page_title %}Update Your Organization - {{ super() }}{% endblock %} 4 | 5 | {% block content %} 6 |

    NOTE: We’re updating the code that runs Source and transitioning away from the current login system, which is being shuttered in December 2016. The revised site will be ready in January, but there will be a short gap between the day logins are shut off and the day we’re ready to relaunch. During that time, we’d love to post your jobs for you! Please just email us the details, and we’ll post them. We’ll share details about updated logins as soon as possible.

    7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /source/people/templates/people/person_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "people/_base_people.html" %} 2 | 3 | {% block page_title %}{{ person.name() }} - {{ super() }}{% endblock %} 4 | 5 | {% block content %} 6 |

    People

    7 | 8 |
    9 |

    {{ person.name()|typogrify }}

    10 | {% if person.twitter_bio %}{% endif %} 11 | {% if person.description %}{{ person.description|linebreaks|typogrify|safe }}{% endif %} 12 | 13 | 41 |
    42 | 43 |
    44 | {% with code_link_list = person.get_live_code_set() %} 45 | {% include "code/_code_link_list.html" %} 46 | {% endwith %} 47 | 48 | {% with article_link_list = person.get_live_article_set(), override_list_title = 'Projects' %} 49 | {% include "articles/_article_link_list.html" %} 50 | {% endwith %} 51 | 52 | {% with article_link_list = person.get_live_article_authored_set(), override_list_title = 'Articles by %s' % person.first_name %} 53 | {% include "articles/_article_link_list.html" %} 54 | {% endwith %} 55 |
    56 | {% endblock content %} 57 | 58 | -------------------------------------------------------------------------------- /source/people/templates/people/person_list.html: -------------------------------------------------------------------------------- 1 | {% extends "people/_base_people.html" %} 2 | 3 | {% block content %} 4 |

    5 | People / Organizations 6 |

    7 | 8 |
    9 | {% for alpha in object_list|groupby('sort_letter') %} 10 |
    11 |

    {{ alpha.grouper }}

    12 | {% for person in alpha.list %} 13 |
    14 |

    {{ person.name()|smartypants }}

    15 | {% if person.get_live_organization_set().exists() %} 16 | {% endif %} 19 |
    20 | {% endfor %} 21 |
    22 | {% endfor %} 23 |
    24 | {% endblock content %} 25 | 26 | {% block site_js_extra %} 27 | 28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /source/people/urls/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/people/urls/__init__.py -------------------------------------------------------------------------------- /source/people/urls/organizations.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls.defaults import * 3 | from django.views.decorators.cache import cache_page 4 | 5 | from source.people.views import OrganizationList, OrganizationDetail, OrganizationUpdate 6 | 7 | STANDARD_CACHE_TIME = getattr(settings, 'CACHE_MIDDLEWARE_SECONDS', 60*15) 8 | 9 | urlpatterns = patterns('', 10 | url( 11 | regex = '^$', 12 | view = cache_page(OrganizationList.as_view(), STANDARD_CACHE_TIME), 13 | kwargs = {}, 14 | name = 'organization_list', 15 | ), 16 | url( 17 | regex = '^update/$', 18 | view = OrganizationUpdate.as_view(), 19 | kwargs = {}, 20 | name = 'organization_update', 21 | ), 22 | url( 23 | regex = '^(?P[-\w]+)/$', 24 | view = cache_page(OrganizationDetail.as_view(), STANDARD_CACHE_TIME), 25 | kwargs = {}, 26 | name = 'organization_detail', 27 | ), 28 | ) 29 | -------------------------------------------------------------------------------- /source/people/urls/people.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls.defaults import * 3 | from django.views.decorators.cache import cache_page 4 | 5 | from source.people.views import PersonList, PersonDetail, PersonUpdate, PersonSearchJson 6 | 7 | STANDARD_CACHE_TIME = getattr(settings, 'CACHE_MIDDLEWARE_SECONDS', 60*15) 8 | 9 | urlpatterns = patterns('', 10 | url( 11 | regex = '^$', 12 | view = cache_page(PersonList.as_view(), STANDARD_CACHE_TIME), 13 | kwargs = {}, 14 | name = 'person_list', 15 | ), 16 | url( 17 | regex = '^update/$', 18 | view = PersonUpdate.as_view(), 19 | kwargs = {}, 20 | name = 'person_update', 21 | ), 22 | url( 23 | regex = '^json/$', 24 | view = PersonSearchJson.as_view(), 25 | kwargs = {}, 26 | name = 'person_search_json', 27 | ), 28 | url( 29 | regex = '^(?P[-\w]+)/$', 30 | view = cache_page(PersonDetail.as_view(), STANDARD_CACHE_TIME), 31 | kwargs = {}, 32 | name = 'person_detail', 33 | ), 34 | ) 35 | -------------------------------------------------------------------------------- /source/people/utils.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User, Group 2 | 3 | from .models import Organization, Person 4 | 5 | def create_auth_user(email): 6 | new_user = None 7 | # seek matching organization for new user 8 | try: 9 | matching_organization = Organization.objects.get(email__iexact=email) 10 | organization_admin_group, created = Group.objects.get_or_create(name='Organization Admins') 11 | 12 | # find matching organization before creating user record so we don't have orphans 13 | new_user = User.objects.create_user(email, email) 14 | new_user.groups.add(organization_admin_group) 15 | except: 16 | pass 17 | 18 | return new_user 19 | -------------------------------------------------------------------------------- /source/settings/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import * 2 | try: 3 | from .local import * 4 | except ImportError, exc: 5 | exc.args = tuple(['%s (did you rename settings/local.py-dist?)' % exc.args[0]]) 6 | raise exc 7 | -------------------------------------------------------------------------------- /source/settings/local.py-dist: -------------------------------------------------------------------------------- 1 | # This is an example settings/local.py file. 2 | # These settings overrides what's in settings/base.py 3 | 4 | # To extend any settings from settings/base.py here's an example: 5 | #from . import base 6 | #INSTALLED_APPS = base.INSTALLED_APPS + ['debug_toolbar'] 7 | 8 | DATABASES = { 9 | 'default': { 10 | 'ENGINE': 'django.db.backends.mysql', 11 | 'NAME': 'playdoh_app', 12 | 'USER': 'root', 13 | 'PASSWORD': '', 14 | 'HOST': '', 15 | 'PORT': '', 16 | 'OPTIONS': { 17 | 'init_command': 'SET storage_engine=InnoDB', 18 | 'charset' : 'utf8', 19 | 'use_unicode' : True, 20 | }, 21 | 'TEST_CHARSET': 'utf8', 22 | 'TEST_COLLATION': 'utf8_general_ci', 23 | }, 24 | # 'slave': { 25 | # ... 26 | # }, 27 | } 28 | 29 | # Uncomment this and set to all slave DBs in use on the site. 30 | # SLAVE_DATABASES = ['slave'] 31 | 32 | # Recipients of traceback emails and other notifications. 33 | ADMINS = ( 34 | # ('Your Name', 'your_email@domain.com'), 35 | ) 36 | MANAGERS = ADMINS 37 | 38 | # Debugging displays nice error messages, but leaks memory. Set this to False 39 | # on all server instances and True only for development. 40 | DEBUG = TEMPLATE_DEBUG = True 41 | 42 | # Is this a development instance? Set this to True on development/master 43 | # instances and False on stage/prod. 44 | DEV = True 45 | 46 | # # Playdoh ships with sha512 password hashing by default. Bcrypt+HMAC is safer, 47 | # # so it is recommended. Please read , 48 | # # then switch this to bcrypt and pick a secret HMAC key for your application. 49 | # PWD_ALGORITHM = 'bcrypt' 50 | # HMAC_KEYS = { # for bcrypt only 51 | # '2011-01-01': 'cheesecake', 52 | # } 53 | 54 | # Make this unique, and don't share it with anybody. It cannot be blank. 55 | SECRET_KEY = '' 56 | 57 | # Uncomment these to activate and customize Celery: 58 | # CELERY_ALWAYS_EAGER = False # required to activate celeryd 59 | # BROKER_HOST = 'localhost' 60 | # BROKER_PORT = 5672 61 | # BROKER_USER = 'playdoh' 62 | # BROKER_PASSWORD = 'playdoh' 63 | # BROKER_VHOST = 'playdoh' 64 | # CELERY_RESULT_BACKEND = 'amqp' 65 | 66 | ## Log settings 67 | 68 | # SYSLOG_TAG = "http_app_playdoh" # Make this unique to your project. 69 | # LOGGING = dict(loggers=dict(playdoh={'level': logging.DEBUG})) 70 | 71 | # Common Event Format logging parameters 72 | #CEF_PRODUCT = 'Playdoh' 73 | #CEF_VENDOR = 'Mozilla' 74 | 75 | # Should robots.txt allow web crawlers? Set this to True for production 76 | ENGAGE_ROBOTS = True 77 | 78 | if DEV: 79 | SESSION_COOKIE_SECURE = False 80 | -------------------------------------------------------------------------------- /source/tags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/tags/__init__.py -------------------------------------------------------------------------------- /source/tags/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import TechnologyTag, TechnologyTaggedItem, ConceptTag, ConceptTaggedItem 4 | 5 | 6 | class TechnologyTaggedItemInline(admin.StackedInline): 7 | model = TechnologyTaggedItem 8 | 9 | class TechnologyTagAdmin(admin.ModelAdmin): 10 | list_display = ['name'] 11 | inlines = [ 12 | TechnologyTaggedItemInline 13 | ] 14 | 15 | class ConceptTaggedItemInline(admin.StackedInline): 16 | model = ConceptTaggedItem 17 | 18 | class ConceptTagAdmin(admin.ModelAdmin): 19 | list_display = ['name'] 20 | inlines = [ 21 | ConceptTaggedItemInline 22 | ] 23 | 24 | admin.site.register(TechnologyTag, TechnologyTagAdmin) 25 | admin.site.register(ConceptTag, ConceptTagAdmin) 26 | -------------------------------------------------------------------------------- /source/tags/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/tags/migrations/__init__.py -------------------------------------------------------------------------------- /source/tags/models.py: -------------------------------------------------------------------------------- 1 | ''' 2 | We currently use tags for filtering on these content models: 3 | 4 | - Article 5 | - Code 6 | 7 | Originally there was but one `tags` field on each model, and it was good. 8 | But lo and verily, @slifty asked for split tagfields, to differentiate 9 | between technologies and concepts. And this seemed beautiful to our eyes. 10 | 11 | It also seemed so very simple to implement. But as I have discovered, 12 | django-taggit is in no way designed to accommodate multiple TaggableManagers 13 | on a given model. This app gives us a way to work around this limitation. 14 | But beautiful it is not. 15 | 16 | To implement split tagfields, a model should keep its original `tags` field, 17 | and then add `technology_tags` and `concept_tags` fields as TaggableManagers 18 | with `through` properties pointing at this app's FooTaggedItem fields. 19 | 20 | Additionally, that model's admin class should set the original `tags` field 21 | to readonly, and implement a `save_model` that automatically populates `tags` 22 | with a concatenated list of `technology_tags` and `concept_tags`. 23 | 24 | Yes, this denormalizes things somewhat. But it enables much simpler code for 25 | displaying lists of tags, and because of the way django-taggit operates, 26 | having *something* in that `tags` field turns out to be critical for proper 27 | filtering of querysets, *even against the two split tagfields*. You would 28 | think that a filter like so ... 29 | 30 | queryset.filter( 31 | Q(tags__slug=tag_slug) | 32 | Q(technology_tags__slug=tag_slug) | 33 | Q(concept_tags__slug=tag_slug) 34 | ) 35 | 36 | ... would work just fine, but because of something deep down inside 37 | django-taggit, this filter will *not* return an object that has a null 38 | value in `tags`. Even if you entirely remove the `tags` field from 39 | the filter, this problem still exists. Even if you remove the `tags` field 40 | from the model itself, the problem still exists; it just migrates to 41 | `technology_tags` (or whichever TaggableManager appears first in the model 42 | code.) 43 | 44 | Patches against django-taggit have thus far failed to solve the problem. And 45 | "problem" might not be the right way to put it; that app was never designed 46 | to support a use case with multiple kinds of tags. Because the denormalized 47 | version that keeps `tags` around *does* offer some benefits, however, this is 48 | where we stand for now. 49 | 50 | TODO: Figure out that filter bug wtf 51 | ''' 52 | from django.db import models 53 | 54 | from taggit.models import GenericTaggedItemBase, TagBase 55 | 56 | 57 | class TechnologyTag(TagBase): 58 | pass 59 | 60 | class TechnologyTaggedItem(GenericTaggedItemBase): 61 | tag = models.ForeignKey(TechnologyTag, related_name="%(app_label)s_%(class)s_techtag_items") 62 | 63 | class ConceptTag(TagBase): 64 | pass 65 | 66 | class ConceptTaggedItem(GenericTaggedItemBase): 67 | tag = models.ForeignKey(ConceptTag, related_name="%(app_label)s_%(class)s_concepttag_items") 68 | -------------------------------------------------------------------------------- /source/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls.defaults import patterns, include 3 | from django.contrib.staticfiles.urls import staticfiles_urlpatterns 4 | from django.http import HttpResponse 5 | 6 | from .base import urls 7 | 8 | from funfactory.monkeypatches import patch 9 | patch() 10 | 11 | # Uncomment the next two lines to enable the admin: 12 | from django.contrib import admin 13 | admin.autodiscover() 14 | 15 | urlpatterns = patterns('', 16 | (r'^admin/', include(admin.site.urls)), 17 | (r'^browserid/', include('django_browserid.urls')), 18 | # Generate a robots.txt 19 | (r'^robots.txt$', 20 | lambda r: HttpResponse( 21 | "User-agent: *\n%s: /" % ('Allow' if settings.ENGAGE_ROBOTS else 'Disallow') , 22 | mimetype="text/plain" 23 | ) 24 | ), 25 | (r'', include(urls)), 26 | ) 27 | 28 | ## In DEBUG mode, serve media files through Django. 29 | if settings.DEBUG: 30 | # static files 31 | urlpatterns += staticfiles_urlpatterns() 32 | # uploaded images 33 | media_url = settings.MEDIA_URL.lstrip('/').rstrip('/') 34 | urlpatterns += patterns('', 35 | (r'^%s/(?P.*)$' % media_url, 'django.views.static.serve', 36 | {'document_root': settings.MEDIA_ROOT}), 37 | ) -------------------------------------------------------------------------------- /source/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/source/utils/__init__.py -------------------------------------------------------------------------------- /source/utils/caching.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | from django.conf import settings 4 | from django.contrib.auth.decorators import login_required 5 | from django.core.cache import cache 6 | from django.core.urlresolvers import resolve 7 | from django.http import HttpRequest, HttpResponse, HttpResponseForbidden, Http404 8 | from django.utils import simplejson 9 | from django.utils.cache import get_cache_key 10 | from django.utils.decorators import method_decorator 11 | from django.utils.encoding import iri_to_uri 12 | from django.utils.translation import get_language 13 | from django.views.generic import View 14 | 15 | from funfactory import urlresolvers 16 | from .json import LazyEncoder 17 | 18 | 19 | def expire_page_cache(path, key_prefix=None): 20 | # pass the path through funfactory resolver in order to get locale 21 | resolved_path = resolve(path) 22 | path_with_locale = urlresolvers.reverse( 23 | resolved_path.func, 24 | args = resolved_path.args, 25 | kwargs = resolved_path.kwargs 26 | ) 27 | try: 28 | language = urlresolvers.split_path(path_with_locale)[0].lower() 29 | except: 30 | language = None 31 | 32 | # get cache key, expire if the cached item exists 33 | key = get_url_cache_key( 34 | path_with_locale, language=language, key_prefix=key_prefix 35 | ) 36 | 37 | if key: 38 | if cache.get(key): 39 | cache.set(key, None, 0) 40 | return True 41 | return False 42 | 43 | 44 | def get_url_cache_key(url, language=None, key_prefix=None): 45 | ''' 46 | modified version of http://djangosnippets.org/snippets/2595/ 47 | ''' 48 | if key_prefix is None: 49 | key_prefix = getattr(settings, 'CACHE_MIDDLEWARE_KEY_PREFIX', None) 50 | ctx = hashlib.md5() 51 | path = hashlib.md5(iri_to_uri(url)) 52 | cache_key = 'views.decorators.cache.cache_page.%s.%s.%s.%s' % ( 53 | key_prefix, 'GET', path.hexdigest(), ctx.hexdigest() 54 | ) 55 | if language: 56 | cache_key += '.%s' % language 57 | return cache_key 58 | 59 | 60 | class ClearCache(View): 61 | def render_json_to_response(self, context): 62 | result = simplejson.dumps(context, cls=LazyEncoder) 63 | return HttpResponse(result, mimetype='application/javascript') 64 | 65 | @method_decorator(login_required) 66 | def get(self, request, *args, **kwargs): 67 | path = request.GET.get('path', None) 68 | 69 | try: 70 | resolved_path = resolve(path) 71 | expire_page_cache(path) 72 | except: 73 | raise Http404 74 | if self.request.is_ajax(): 75 | result = {'success': 'True'} 76 | return self.render_json_to_response(result) 77 | else: 78 | return HttpResponse('Cache cleared for "%s"!' % path) 79 | 80 | -------------------------------------------------------------------------------- /source/utils/email.py: -------------------------------------------------------------------------------- 1 | from django.core.mail import EmailMultiAlternatives 2 | from django.utils.encoding import smart_str 3 | 4 | 5 | def send_multipart_email(subject, from_email, to, text_content, html_content): 6 | text_content = smart_str(text_content) 7 | html_content = smart_str(html_content) 8 | 9 | msg = EmailMultiAlternatives(subject, text_content, from_email, [to,]) 10 | msg.attach_alternative(html_content, "text/html") 11 | msg.send() 12 | -------------------------------------------------------------------------------- /source/utils/json.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | from django.utils import simplejson 3 | from django.utils.functional import Promise 4 | from django.utils.encoding import force_unicode 5 | from django.core.serializers.json import DjangoJSONEncoder 6 | 7 | class LazyEncoder(DjangoJSONEncoder): 8 | def default(self, obj): 9 | if isinstance(obj, Promise): 10 | return force_unicode(obj) 11 | return super(LazyEncoder, self).default(obj) 12 | 13 | def render_json_to_response(context): 14 | ''' 15 | Utility method for rendering a view's data to JSON response. 16 | ''' 17 | result = simplejson.dumps(context, sort_keys=False, indent=4, cls=LazyEncoder) 18 | return HttpResponse(result, mimetype='application/javascript') 19 | -------------------------------------------------------------------------------- /source/utils/pagination.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.core.exceptions import PermissionDenied 3 | from django.core.paginator import Paginator, InvalidPage 4 | from django.http import Http404 5 | 6 | 7 | def paginate(request, queryset, results_per_page=20): 8 | paginator = Paginator(queryset, results_per_page) 9 | 10 | try: 11 | page = paginator.page(int(request.GET.get('page', 1))) 12 | except InvalidPage: 13 | raise Http404("Sorry, that page of results does not exist.") 14 | except ValueError: 15 | raise PermissionDenied() 16 | 17 | return page, paginator 18 | -------------------------------------------------------------------------------- /vagrantconfig.yaml: -------------------------------------------------------------------------------- 1 | # Default config for Vagrant 2 | 3 | # Don't change this; use vagrantconfig_local.yaml to override these 4 | # settings instead. 5 | nfs: false 6 | -------------------------------------------------------------------------------- /vagrantconfig_local.yaml-dist: -------------------------------------------------------------------------------- 1 | # Configuration for Vagrant 2 | 3 | # Change to true if you can use nfs; using nfs significantly 4 | # improves performance. 5 | nfs: false 6 | -------------------------------------------------------------------------------- /vendor-local/lib/python/dateutil/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Copyright (c) 2003-2010 Gustavo Niemeyer 4 | 5 | This module offers extensions to the standard Python 6 | datetime module. 7 | """ 8 | __author__ = "Tomi Pieviläinen " 9 | __license__ = "Simplified BSD" 10 | __version__ = "2.2" 11 | -------------------------------------------------------------------------------- /vendor-local/lib/python/dateutil/easter.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2003-2007 Gustavo Niemeyer 3 | 4 | This module offers extensions to the standard Python 5 | datetime module. 6 | """ 7 | __license__ = "Simplified BSD" 8 | 9 | import datetime 10 | 11 | __all__ = ["easter", "EASTER_JULIAN", "EASTER_ORTHODOX", "EASTER_WESTERN"] 12 | 13 | EASTER_JULIAN = 1 14 | EASTER_ORTHODOX = 2 15 | EASTER_WESTERN = 3 16 | 17 | def easter(year, method=EASTER_WESTERN): 18 | """ 19 | This method was ported from the work done by GM Arts, 20 | on top of the algorithm by Claus Tondering, which was 21 | based in part on the algorithm of Ouding (1940), as 22 | quoted in "Explanatory Supplement to the Astronomical 23 | Almanac", P. Kenneth Seidelmann, editor. 24 | 25 | This algorithm implements three different easter 26 | calculation methods: 27 | 28 | 1 - Original calculation in Julian calendar, valid in 29 | dates after 326 AD 30 | 2 - Original method, with date converted to Gregorian 31 | calendar, valid in years 1583 to 4099 32 | 3 - Revised method, in Gregorian calendar, valid in 33 | years 1583 to 4099 as well 34 | 35 | These methods are represented by the constants: 36 | 37 | EASTER_JULIAN = 1 38 | EASTER_ORTHODOX = 2 39 | EASTER_WESTERN = 3 40 | 41 | The default method is method 3. 42 | 43 | More about the algorithm may be found at: 44 | 45 | http://users.chariot.net.au/~gmarts/eastalg.htm 46 | 47 | and 48 | 49 | http://www.tondering.dk/claus/calendar.html 50 | 51 | """ 52 | 53 | if not (1 <= method <= 3): 54 | raise ValueError("invalid method") 55 | 56 | # g - Golden year - 1 57 | # c - Century 58 | # h - (23 - Epact) mod 30 59 | # i - Number of days from March 21 to Paschal Full Moon 60 | # j - Weekday for PFM (0=Sunday, etc) 61 | # p - Number of days from March 21 to Sunday on or before PFM 62 | # (-6 to 28 methods 1 & 3, to 56 for method 2) 63 | # e - Extra days to add for method 2 (converting Julian 64 | # date to Gregorian date) 65 | 66 | y = year 67 | g = y % 19 68 | e = 0 69 | if method < 3: 70 | # Old method 71 | i = (19*g+15)%30 72 | j = (y+y//4+i)%7 73 | if method == 2: 74 | # Extra dates to convert Julian to Gregorian date 75 | e = 10 76 | if y > 1600: 77 | e = e+y//100-16-(y//100-16)//4 78 | else: 79 | # New method 80 | c = y//100 81 | h = (c-c//4-(8*c+13)//25+19*g+15)%30 82 | i = h-(h//28)*(1-(h//28)*(29//(h+1))*((21-g)//11)) 83 | j = (y+y//4+i+2-c+c//4)%7 84 | 85 | # p can be from -6 to 56 corresponding to dates 22 March to 23 May 86 | # (later dates apply to method 2, although 23 May never actually occurs) 87 | p = i-j+e 88 | d = 1+(p+27+(p+6)//40)%31 89 | m = 3+(p+26)//30 90 | return datetime.date(int(y), int(m), int(d)) 91 | 92 | -------------------------------------------------------------------------------- /vendor-local/lib/python/dateutil/parser.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/vendor-local/lib/python/dateutil/parser.py -------------------------------------------------------------------------------- /vendor-local/lib/python/dateutil/zoneinfo/zoneinfo--latest.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source/7e547abdb5c5b41d72eb47521c9c1dd5ba488731/vendor-local/lib/python/dateutil/zoneinfo/zoneinfo--latest.tar.gz -------------------------------------------------------------------------------- /vendor-local/lib/python/python_dateutil-2.2-py2.7.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.1 2 | Name: python-dateutil 3 | Version: 2.2 4 | Summary: Extensions to the standard Python datetime module 5 | Home-page: http://labix.org/python-dateutil 6 | Author: Tomi Pievilaeinen 7 | Author-email: tomi.pievilainen@iki.fi 8 | License: Simplified BSD 9 | Description: The dateutil module provides powerful extensions to the 10 | datetime module available in the Python standard library. 11 | 12 | Platform: UNKNOWN 13 | Classifier: Development Status :: 5 - Production/Stable 14 | Classifier: Intended Audience :: Developers 15 | Classifier: License :: OSI Approved :: BSD License 16 | Classifier: Programming Language :: Python 17 | Classifier: Programming Language :: Python :: 2 18 | Classifier: Programming Language :: Python :: 2.6 19 | Classifier: Programming Language :: Python :: 2.7 20 | Classifier: Programming Language :: Python :: 3 21 | Classifier: Programming Language :: Python :: 3.2 22 | Classifier: Programming Language :: Python :: 3.3 23 | Classifier: Topic :: Software Development :: Libraries 24 | Requires: six 25 | -------------------------------------------------------------------------------- /vendor-local/lib/python/python_dateutil-2.2-py2.7.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | LICENSE 2 | MANIFEST.in 3 | Makefile 4 | NEWS 5 | README 6 | example.py 7 | setup.cfg 8 | setup.py 9 | test.py 10 | dateutil/__init__.py 11 | dateutil/easter.py 12 | dateutil/parser.py 13 | dateutil/relativedelta.py 14 | dateutil/rrule.py 15 | dateutil/tz.py 16 | dateutil/tzwin.py 17 | dateutil/zoneinfo/__init__.py 18 | dateutil/zoneinfo/zoneinfo--latest.tar.gz 19 | python_dateutil.egg-info/PKG-INFO 20 | python_dateutil.egg-info/SOURCES.txt 21 | python_dateutil.egg-info/dependency_links.txt 22 | python_dateutil.egg-info/not-zip-safe 23 | python_dateutil.egg-info/requires.txt 24 | python_dateutil.egg-info/top_level.txt 25 | sandbox/rrulewrapper.py 26 | sandbox/scheduler.py -------------------------------------------------------------------------------- /vendor-local/lib/python/python_dateutil-2.2-py2.7.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /vendor-local/lib/python/python_dateutil-2.2-py2.7.egg-info/installed-files.txt: -------------------------------------------------------------------------------- 1 | ../dateutil/__init__.py 2 | ../dateutil/easter.py 3 | ../dateutil/parser.py 4 | ../dateutil/relativedelta.py 5 | ../dateutil/rrule.py 6 | ../dateutil/tz.py 7 | ../dateutil/tzwin.py 8 | ../dateutil/zoneinfo/__init__.py 9 | ../dateutil/zoneinfo/zoneinfo--latest.tar.gz 10 | ../dateutil/__init__.pyc 11 | ../dateutil/easter.pyc 12 | ../dateutil/parser.pyc 13 | ../dateutil/relativedelta.pyc 14 | ../dateutil/rrule.pyc 15 | ../dateutil/tz.pyc 16 | ../dateutil/tzwin.pyc 17 | ../dateutil/zoneinfo/__init__.pyc 18 | ./ 19 | dependency_links.txt 20 | not-zip-safe 21 | PKG-INFO 22 | requires.txt 23 | SOURCES.txt 24 | top_level.txt 25 | -------------------------------------------------------------------------------- /vendor-local/lib/python/python_dateutil-2.2-py2.7.egg-info/not-zip-safe: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /vendor-local/lib/python/python_dateutil-2.2-py2.7.egg-info/requires.txt: -------------------------------------------------------------------------------- 1 | six -------------------------------------------------------------------------------- /vendor-local/lib/python/python_dateutil-2.2-py2.7.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | dateutil 2 | -------------------------------------------------------------------------------- /vendor-local/lib/python/six-1.6.1-py2.7.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.0 2 | Name: six 3 | Version: 1.6.1 4 | Summary: Python 2 and 3 compatibility utilities 5 | Home-page: http://pypi.python.org/pypi/six/ 6 | Author: Benjamin Peterson 7 | Author-email: benjamin@python.org 8 | License: MIT 9 | Description: Six is a Python 2 and 3 compatibility library. It provides utility functions 10 | for smoothing over the differences between the Python versions with the goal of 11 | writing Python code that is compatible on both Python versions. See the 12 | documentation for more information on what is provided. 13 | 14 | Six supports every Python version since 2.5. It is contained in only one Python 15 | file, so it can be easily copied into your project. (The copyright and license 16 | notice must be retained.) 17 | 18 | Online documentation is at http://pythonhosted.org/six/. 19 | 20 | Bugs can be reported to http://bitbucket.org/gutworth/six. The code can also be 21 | found there. 22 | 23 | For questions about six or porting in general, email the python-porting mailing 24 | list: http://mail.python.org/mailman/listinfo/python-porting 25 | 26 | Platform: UNKNOWN 27 | Classifier: Programming Language :: Python :: 2 28 | Classifier: Programming Language :: Python :: 3 29 | Classifier: Intended Audience :: Developers 30 | Classifier: License :: OSI Approved :: MIT License 31 | Classifier: Topic :: Software Development :: Libraries 32 | Classifier: Topic :: Utilities 33 | -------------------------------------------------------------------------------- /vendor-local/lib/python/six-1.6.1-py2.7.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | CHANGES 2 | LICENSE 3 | MANIFEST.in 4 | README 5 | setup.cfg 6 | setup.py 7 | six.py 8 | test_six.py 9 | documentation/Makefile 10 | documentation/conf.py 11 | documentation/index.rst 12 | six.egg-info/PKG-INFO 13 | six.egg-info/SOURCES.txt 14 | six.egg-info/dependency_links.txt 15 | six.egg-info/top_level.txt -------------------------------------------------------------------------------- /vendor-local/lib/python/six-1.6.1-py2.7.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /vendor-local/lib/python/six-1.6.1-py2.7.egg-info/installed-files.txt: -------------------------------------------------------------------------------- 1 | ../six.py 2 | ../six.pyc 3 | ./ 4 | dependency_links.txt 5 | PKG-INFO 6 | SOURCES.txt 7 | top_level.txt 8 | -------------------------------------------------------------------------------- /vendor-local/lib/python/six-1.6.1-py2.7.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | six 2 | -------------------------------------------------------------------------------- /vendor-local/vendor.pth: -------------------------------------------------------------------------------- 1 | src/django-browserid 2 | src/requests 3 | src/django-south 4 | src/django-taggit 5 | src/django-cache-machine 6 | src/django-haystack 7 | src/pyelasticsearch 8 | src/sorl-thumbnail 9 | src/python-twitter 10 | src/typogrify 11 | src/python-oauth2 12 | -------------------------------------------------------------------------------- /wsgi/playdoh.wsgi: -------------------------------------------------------------------------------- 1 | import os 2 | import site 3 | import sys 4 | 5 | # make sure we have the right DJANGO_SETTINGS_MODULE 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "source.settings") 7 | 8 | os.environ.setdefault('CELERY_LOADER', 'django') 9 | 10 | # Add the app dir to the python path so we can import manage. 11 | wsgidir = os.path.dirname(__file__) 12 | site.addsitedir(os.path.abspath(os.path.join(wsgidir, '../'))) 13 | 14 | # manage adds /apps, /lib, and /vendor to the Python path. 15 | import manage 16 | from django.conf import settings 17 | 18 | # attempt to engage with New Relic... 19 | NEW_RELIC_CONFIG_FILE = getattr(settings, 'NEW_RELIC_CONFIG_FILE', None) 20 | if NEW_RELIC_CONFIG_FILE: 21 | import newrelic.agent 22 | newrelic.agent.initialize(NEW_RELIC_CONFIG_FILE) 23 | 24 | # load the app 25 | from django.core.wsgi import get_wsgi_application 26 | application = get_wsgi_application() 27 | 28 | # vim: ft=python --------------------------------------------------------------------------------