'+a.addText+" "),h=b(this).parent().find("tr:last a")):(b(this).filter(":last").after('"),h=b(this).filter(":last").next().find("a"));h.click(function(){var c=b("#id_"+a.prefix+
3 | "-TOTAL_FORMS"),e=b("#"+a.prefix+"-empty"),d=e.clone(!0);d.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+g);d.is("tr")?d.children(":last").append('"):d.is("ul")||d.is("ol")?d.append(''+a.deleteText+" "):d.children(":first").append(''+a.deleteText+
4 | " ");d.find("*").each(function(){j(this,a.prefix,c.val())});d.insertBefore(b(e));b(c).val(parseInt(c.val())+1);g+=1;""!=f.val()&&0>=f.val()-c.val()&&h.parent().hide();d.find("a."+a.deleteCssClass).click(function(){var c=b(this).parents("."+a.formCssClass);c.remove();g-=1;a.removed&&a.removed(c);c=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(c.length);(""==f.val()||0 0) {
25 | values.push($(field).val());
26 | }
27 | })
28 | field.val(URLify(values.join(' '), maxLength));
29 | };
30 |
31 | $(dependencies.join(',')).keyup(populate).change(populate).focus(populate);
32 | });
33 | };
34 | })(django.jQuery);
35 |
--------------------------------------------------------------------------------
/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 %}{% 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 %}
{% endif %}
5 |
6 | {% if article.get_live_author_set().exists() %}{% with author_list = article.get_live_author_set() %}{% include "articles/_article_author_list.html" %}{% endwith %} {% endif %}
7 | {% with person_link_list = article.get_live_people_set() %}
8 | {% include "people/_person_link_list_inline.html" %}{% endwith %}
9 | {% with organization_link_list = article.get_live_organization_set() %}
10 | {% include "people/_organization_link_list_inline.html" %}{% endwith %}
11 |
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 %} {% 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 | Start typing to filter list \
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 |
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 |
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 |
12 | Journalism Code
13 |
14 | Recently added
15 | {# for toggle switch once we being storing github data in db #}{#}
16 | New
17 | Updated {#}
18 |
19 |
20 | {% for code in homepage_code_list %}
21 |
22 | {{ code.name|smartypants }}
23 | {% with person_link_list = code.get_live_people_set() %}
24 | {% if person_link_list %}
25 |
26 | {% for person in person_link_list %}{{ person.name()|smartypants }} {% if not loop.last %}, {% endif %}{% endfor %}
27 | {% endif %}
28 | {% endwith %}
29 | {% with organization_link_list = code.get_live_organization_set() %}
30 | {% if organization_link_list %}
31 |
32 | {% for organization in organization_link_list %}{{ organization.name|smartypants }} {% if not loop.last %}, {% endif %}{% endfor %}
33 | {% endif %}
34 | {% endwith %}
35 |
36 | {% endfor %}
37 |
38 |
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 |
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 |
9 |
10 |
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 |
51 | {# People #}
52 | {% if person_results %}
53 | People
54 |
57 | {% endif %}
58 |
59 | {# Organizations #}
60 | {% if organization_results %}
61 | Organizations
62 |
65 | {% endif %}
66 |
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 %}{% endif %}
3 | {% endif %}{% endfor %}
6 |
--------------------------------------------------------------------------------
/source/base/templates/utils/_paginate.html:
--------------------------------------------------------------------------------
1 | {% if page.has_previous() or page.has_next() %}
2 |
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 %(clear_checkbox_label)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''
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 %}{% 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 |
5 | {% with organization_link_list = code.get_live_organization_set() %}
6 | {% include "people/_organization_link_list_inline.html" %}{% endwith %}
7 |
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 |
10 | {% for code in object_list %}
11 |
12 |
13 | {% if code.summary_or_description %}
{{ code.summary_or_description|typogrify|safe }}
{% endif %}
14 |
15 | {% if code.get_live_organization_set().exists() %} {% for organization in code.get_live_organization_set() %}{{ organization.name|typogrify }} {% if not loop.last %}, {% endif %}{% endfor %} {% endif %}
16 | {% if code.tags.all().exists() %} {% for tag in code.tags.all() %}{{ tag.name|smartypants }} {% if not loop.last %}, {% endif %}{% endfor %} {% endif %}
17 |
18 |
19 | {% endfor %}
20 |
21 | {% else %}
22 | {% for alpha in object_list|groupby('sort_letter') %}
23 |
24 |
25 | {% for code in alpha.list %}
26 |
27 |
28 | {% if code.summary_or_description %}
{{ code.summary_or_description|typogrify|safe }}
{% endif %}
29 |
30 | {% if code.get_live_organization_set().exists() %} {% for organization in code.get_live_organization_set() %}{{ organization.name|typogrify }} {% if not loop.last %}, {% endif %}{% endfor %} {% endif %}
31 | {% if code.tags.all().exists() %} {% for tag in code.tags.all() %}{{ tag.name|smartypants }} {% if not loop.last %}, {% endif %}{% endfor %} {% endif %}
32 |
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 |
5 | {% else %}
6 |
7 |
8 | {% if article.get_live_author_set().exists() %}{% with author_list = article.get_live_author_set() %}{% include "articles/_article_author_list.html" %}{% endwith %} {% endif %}
9 | {% with organization_link_list = article.get_live_organization_set() %}
10 | {% include "people/_organization_link_list_inline.html" %}{% endwith %}
11 |
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 %}{% 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 %}{% 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 |
8 |
9 |
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 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
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 | Job
30 | Expires
31 |
32 | {% for job in jobs %}
33 | {{ job.name }}
34 | {{ job.pretty_expiration_date }}
35 | {% endfor %}
36 |
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 |
43 |
44 |
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 %}{% 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 %}{% 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 |
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 |
7 |
8 |
9 |
{{ person.name()|typogrify }}
10 | {% if person.twitter_bio %}{% endif %}
11 | {% if person.description %}{{ person.description|linebreaks|typogrify|safe }}{% endif %}
12 |
13 |
14 | {% with organization_link_list = person.get_live_organization_set() %}
15 | {% include "people/_organization_link_list_inline.html" %}
16 | {% endwith %}
17 |
18 | {% if person.email %}
19 |
20 |
21 | {{ person.email }}
22 |
23 | {% endif %}
24 | {% if person.twitter_username %}
25 |
29 | {% endif %}
30 | {% if person.github_username %}
31 |
32 |
33 | {{ person.github_username }}
34 | {% if person.github_repos_num or person.github_gists_num %}
35 | ({{ person.github_repos_num }} repo{{ person.github_repos_num|dj_pluralize }} / {{ person.github_gists_num }} gist{{ person.github_gists_num|dj_pluralize }})
36 | {% endif %}
37 |
38 | {% endif %}
39 | {% for link in person.personlink_set.all() %}{{ link.name|typogrify }} {% endfor %}
40 |
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 |
7 |
8 |
9 | {% for alpha in object_list|groupby('sort_letter') %}
10 |
11 |
12 | {% for person in alpha.list %}
13 |
14 |
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
--------------------------------------------------------------------------------