├── .coveragerc ├── .dockerignore ├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README.md ├── bin ├── execweb ├── jenkins │ ├── fig.yml │ ├── makemessages.sh │ ├── postbuild.sh │ ├── push2deis.sh │ ├── push2dockerhub.sh │ ├── runacceptancetests.sh │ └── runtests.sh ├── peep.py ├── run-common.sh ├── run-docker.sh ├── run-fig.sh └── unit_test_coverage ├── conftest.py ├── docs ├── Makefile ├── _static │ └── .gitignore ├── _templates │ └── .gitignore ├── authoring_content.rst ├── conf.py ├── gulp.rst ├── homepage.rst ├── images.rst ├── images │ ├── copy-tree-done.png │ ├── copy-tree.png │ ├── hp-blob.png │ └── slug.png ├── importing_legacy_data.rst ├── index.rst ├── key_concepts.rst ├── make.bat ├── pages.rst ├── quiz.rst ├── translation_using_verbatim.rst ├── versions_and_locales.rst └── videos.rst ├── fig.yml ├── gulpfile.js ├── legacy ├── __init__.py └── json │ ├── 1.1 │ ├── cs.json │ ├── de.json │ ├── el.json │ ├── en.json │ ├── es.json │ ├── hr.json │ ├── hu.json │ ├── it.json │ ├── pl.json │ ├── pt.json │ └── sr.json │ ├── 1.3T │ ├── en.json │ ├── hi.json │ └── ta.json │ ├── 1.4 │ └── bangladesh.json │ ├── README │ ├── spanish_LA.json │ └── video_path_to_youtube_id.json ├── manage.py ├── masterfirefoxos ├── __init__.py ├── base │ ├── __init__.py │ ├── admin.py │ ├── app.py │ ├── context_processors.py │ ├── forms.py │ ├── middleware.py │ ├── migrate │ │ ├── __init__.py │ │ ├── medialibrary │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_auto_20150128_1239.py │ │ │ ├── 0003_auto_20150211_1120.py │ │ │ └── __init__.py │ │ └── page │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_auto_20150116_1205.py │ │ │ ├── 0003_richtextentry.py │ │ │ ├── 0004_auto_20150116_1254.py │ │ │ ├── 0005_auto_20150127_1425.py │ │ │ ├── 0006_auto_20150128_0414.py │ │ │ ├── 0007_auto_20150128_1239.py │ │ │ ├── 0008_remove_quizquestion_image.py │ │ │ ├── 0009_quizquestion_image.py │ │ │ ├── 0010_auto_20150211_0911.py │ │ │ ├── 0011_auto_20150211_0919.py │ │ │ ├── 0012_auto_20150211_1114.py │ │ │ ├── 0013_auto_20150211_1119.py │ │ │ └── __init__.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20150218_1128.py │ │ └── __init__.py │ ├── models.py │ ├── static │ │ ├── css │ │ │ ├── README.md │ │ │ ├── base.css │ │ │ ├── base.css.map │ │ │ ├── home.css │ │ │ ├── home.css.map │ │ │ ├── oldIE.css │ │ │ └── oldIE.css.map │ │ ├── img │ │ │ ├── favicon.ico │ │ │ ├── footer-mozilla.png │ │ │ ├── fox-swoop-hr.png │ │ │ ├── fox-swoop.png │ │ │ ├── fxos-logo-hr.png │ │ │ ├── fxos-logo.png │ │ │ ├── menu-icon.svg │ │ │ └── tab.png │ │ ├── js │ │ │ ├── ga.js │ │ │ ├── init_tinymce.js │ │ │ ├── libs │ │ │ │ └── html5shiv.js │ │ │ └── main.js │ │ ├── revision.txt │ │ └── stylus │ │ │ ├── base.styl │ │ │ ├── faq.styl │ │ │ ├── fonts-ar-fa.styl │ │ │ ├── fonts.styl │ │ │ ├── home.styl │ │ │ ├── lib.styl │ │ │ ├── oldIE.styl │ │ │ ├── quiz.styl │ │ │ └── reset.styl │ ├── templates │ │ ├── base.jinja │ │ ├── content.jinja │ │ ├── home.jinja │ │ └── includes │ │ │ ├── faqentry.jinja │ │ │ ├── imageparagraph.jinja │ │ │ ├── nav-information.jinja │ │ │ ├── quizanswer.jinja │ │ │ ├── quizquestion.jinja │ │ │ ├── richtextentry.jinja │ │ │ ├── version-language-switcher.jinja │ │ │ └── videoparagraph.jinja │ ├── templatetags │ │ ├── __init__.py │ │ └── helpers.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_acceptance.py │ │ ├── test_admin.py │ │ ├── test_helpers.py │ │ ├── test_middleware.py │ │ ├── test_models.py │ │ ├── test_utils.py │ │ └── test_views.py │ ├── utils.py │ └── views.py ├── settings │ ├── __init__.py │ └── base.py ├── urls.py └── wsgi.py ├── newrelic.ini ├── package.json ├── pytest.ini ├── requirements.txt ├── scripts ├── cleanup_po.py ├── create_db_locales.py ├── db_strings.py ├── dehtmlify.py ├── import_legacy_pages_and_translations.py ├── makemessages_all_locales.py └── test_cleanup_po.py └── setup.cfg /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = masterfirefoxos 3 | 4 | [report] 5 | omit = 6 | */migrate/* 7 | */test_acceptance.py 8 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | venv 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | *.pyc 3 | .DS_Store 4 | docs/_build 5 | /media/* 6 | /static 7 | /locale 8 | node_modules 9 | db-strings.txt 10 | *~ 11 | \#*\# 12 | .coverage 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.4" 4 | addons: 5 | postgresql: "9.3" 6 | before_script: 7 | - psql -c 'create database masterfirefoxos_db;' -U postgres 8 | install: 9 | - pip install -r requirements.txt 10 | - pip install coveralls 11 | env: 12 | - DEBUG=False SECRET_KEY='FOO' ALLOWED_HOSTS=localhost, DATABASE_URL=postgres://postgres@localhost/masterfirefoxos_db 13 | script: 14 | flake8 masterfirefoxos && coverage run manage.py test 15 | after_success: 16 | coveralls 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:jessie 2 | ENV LC_ALL=C.UTF-8 3 | ENV LANG=C.UTF-8 4 | ENV PYTHONDONTWRITEBYTECODE=1 5 | 6 | WORKDIR /app 7 | 8 | EXPOSE 8000 9 | CMD ["./bin/run-docker.sh"] 10 | 11 | RUN adduser --uid 1000 --disabled-password --gecos '' --no-create-home webdev 12 | 13 | RUN apt-get update && \ 14 | apt-get install -y --no-install-recommends build-essential python3 python3-dev python3-pip libpq-dev gettext libjpeg62-turbo-dev postgresql-client sharutils libjpeg62-turbo 15 | RUN update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 10 16 | RUN update-alternatives --install /usr/bin/python python /usr/bin/python3 10 17 | 18 | # Pin a known to work with peep pip version. 19 | RUN pip install pip==6.0.0 20 | 21 | # Install app 22 | COPY ./requirements.txt /app/requirements.txt 23 | COPY ./bin/peep.py /app/bin/peep.py 24 | RUN ./bin/peep.py install --no-cache-dir -r requirements.txt 25 | 26 | ADD https://github.com/mozilla/masterfirefoxos-l10n/archive/master.tar.gz /tmp/locale.tar.gz 27 | RUN mkdir -p /app/locale && tar zxf /tmp/locale.tar.gz -C /app/locale --strip-components 1 28 | COPY . /app 29 | 30 | # Cleanup 31 | RUN apt-get purge -y python-dev build-essential libpq-dev libjpeg62-turbo-dev 32 | RUN apt-get autoremove -y 33 | RUN rm -rf /var/lib/{apt,dpkg,cache,log} /usr/share/doc /usr/share/man /tmp/* 34 | 35 | # Change User 36 | RUN chown webdev.webdev -R . 37 | USER webdev 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | -= This project has been archived =- 2 | ------------------------------------ 3 | 4 | masterfirefoxos 5 | =============== 6 | 7 | The Master Firefox OS Website https://wiki.mozilla.org/Websites/Master_Firefox_OS 8 | 9 | [![Docs Status](https://readthedocs.org/projects/masterfirefoxos/badge/?version=latest&style=)](http://masterfirefoxos.mozilla.org/) 10 | [![Build Status](https://ci.masterfirefoxos.com/job/mozilla_masterfirefoxos/badge/icon)](https://ci.masterfirefoxos.com/job/mozilla_masterfirefoxos/) 11 | 12 | Docs 13 | ---- 14 | 15 | Documentation can be found at http://masterfirefoxos.rtfd.org/. 16 | 17 | 18 | Building Documentation Locally 19 | ------------------------------ 20 | The instructions below assume you have Python and 21 | [pip](https://pip.pypa.io/) installed. It is also 22 | strongly recommended that you create and activate a 23 | [virtualenv](https://virtualenv.pypa.io/) first. 24 | 25 | If you'd like to build the documentation locally: 26 | 27 | ```sh 28 | pip install -r requirements.txt 29 | cd docs 30 | make html 31 | ``` 32 | 33 | The resulting docs can be located under the ``_build/html`` directory. 34 | 35 | You can also run ``make livehtml`` to launch a webserver on 36 | http://127.0.0.1:8000 that auto-rebuild the documentation when any files are 37 | changed. 38 | 39 | Docker for development 40 | ---------------------- 41 | 42 | 0. Make sure you have [docker](https://docker.io) and [fig](https://pypi.python.org/pypi/fig) 43 | 1. fig up 44 | 45 | Note that this will probably not work with 46 | [boot2docker](https://github.com/boot2docker/boot2docker), as the 47 | volumes will not get mounted. 48 | 49 | 50 | Docker for deploying to production 51 | ----------------------------------- 52 | 53 | 1. Add your project in [Docker Registry](https://registry.hub.docker.com/) as [Automated Build](http://docs.docker.com/docker-hub/builds/) 54 | 2. Prepare a 'env' file with all the variables needed by dev, stage or production. 55 | 3. Run the image: 56 | 57 | docker run --env-file env -p 80:80 mozilla/masterfirefoxos 58 | 59 | 60 | NewRelic Monitoring 61 | ------------------- 62 | 63 | A newrelic.ini file is already included. To enable NewRelic monitoring 64 | add two enviroment variables: 65 | 66 | - NEW_RELIC_LICENSE_KEY 67 | - NEW_RELIC_APP_NAME 68 | 69 | See the [full list of supported environment variables](https://docs.newrelic.com/docs/agents/python-agent/installation-configuration/python-agent-configuration#environment-variables). 70 | -------------------------------------------------------------------------------- /bin/execweb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker exec -it $(docker ps | grep masterfirefoxos_web | awk '{ print $1 }') $1 -------------------------------------------------------------------------------- /bin/jenkins/fig.yml: -------------------------------------------------------------------------------- 1 | db: 2 | image: postgres:9.3 3 | web: 4 | build: . 5 | ports: 6 | - "8000:8000" 7 | links: 8 | - db 9 | environment: 10 | - PYTHONDONTWRITEBYTECODE=1 11 | - DATABASE_URL=postgres://postgres@db/postgres 12 | - DEBUG=True 13 | - ALLOWED_HOSTS=localhost,127.0.0.1, 14 | - SECRET_KEY=59114b6a-2858-4caf-8878-482a24ee9542 15 | command: 16 | ./bin/run-fig.sh 17 | -------------------------------------------------------------------------------- /bin/jenkins/makemessages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Fetches the latest strings from the database and codebase and 4 | # updates the locale repository. 5 | # 6 | # Needs DEIS_USER, DEIS_PASSWORD, DEIS_CONTROLLER, DEIS_APP and 7 | # LOCALE_REPOSITORY environment variables. 8 | # 9 | # To set them go to Job -> Configure -> Build Environment -> Inject 10 | # passwords and Inject env variables 11 | # 12 | 13 | set -ex 14 | 15 | TDIR=`mktemp -d` 16 | virtualenv $TDIR 17 | . $TDIR/bin/activate 18 | pip install fig 19 | 20 | rm -rf locale db-strings.txt run-output 21 | git clone $LOCALE_REPOSITORY locale 22 | 23 | deis login $DEIS_CONTROLLER --username $DEIS_USERNAME --password $DEIS_PASSWORD 24 | deis run -a $DEIS_APP -- "./manage.py runscript db_strings && echo CUTHERE && cat db-strings.txt | gzip -9 | uuencode -" > run-output 25 | awk '{if (nowprint) {print;}}/CUTHERE/ {nowprint = 1}' run-output | uudecode | gunzip > db-strings.txt 26 | fig --project-name jenkins${JOB_NAME}${BUILD_NUMBER} run -T web ./manage.py runscript makemessages_all_locales 27 | fig --project-name jenkins${JOB_NAME}${BUILD_NUMBER} run -T web ./manage.py runscript cleanup_po 28 | fig --project-name jenkins${JOB_NAME}${BUILD_NUMBER} run -T web chmod a+wx -R locale 29 | 30 | cd locale 31 | git add . 32 | git commit -m "Update strings." 33 | git push origin master 34 | -------------------------------------------------------------------------------- /bin/jenkins/postbuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | # Fig does not remove the running database instace. We cannot use fig 5 | # rm here because we re-tagged the image while uploading to DockerHub. 6 | docker rm -f `echo jenkins${JOB_NAME}${BUILD_NUMBER}| sed s/_//g`_db_1 7 | -------------------------------------------------------------------------------- /bin/jenkins/push2deis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Needs DEIS_CONTROLLER URL, DEIS_USERNAME, DEIS_PASSWORD, 3 | # DOCKER_REPOSITORY and DEIS_APPLICATION environment variables. 4 | # 5 | # To set them go to Job -> Configure -> Build Environment -> Inject 6 | # passwords and Inject env variables 7 | # 8 | 9 | set -ex 10 | 11 | deis login $DEIS_CONTROLLER --username $DEIS_USERNAME --password $DEIS_PASSWORD 12 | deis pull $DOCKER_REPOSITORY:`git rev-parse HEAD` -a $DEIS_APPLICATION 13 | -------------------------------------------------------------------------------- /bin/jenkins/push2dockerhub.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Needs DOCKER_USERNAME, DOCKER_PASSWORD, DOCKER_REPOSITORY 3 | # environment variables. 4 | # 5 | # To set them go to Job -> Configure -> Build Environment -> Inject 6 | # passwords and Inject env variables 7 | # 8 | set -ex 9 | 10 | docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD -e $DOCKER_USERNAME@example.com 11 | 12 | # If pull request use $ghprbActualCommit otherwise use $GIT_COMMIT 13 | COMMIT="${ghprbActualCommit:=$GIT_COMMIT}" 14 | 15 | # Tag using git hash 16 | docker tag `echo jenkins${JOB_NAME}${BUILD_NUMBER}| sed s/_//g`_web $DOCKER_REPOSITORY:$COMMIT 17 | docker push $DOCKER_REPOSITORY:$COMMIT 18 | 19 | # Tag as latest 20 | docker tag `echo jenkins${JOB_NAME}${BUILD_NUMBER}| sed s/_//g`_web $DOCKER_REPOSITORY:latest 21 | docker push $DOCKER_REPOSITORY:latest 22 | -------------------------------------------------------------------------------- /bin/jenkins/runacceptancetests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Runs acceptance tests. 4 | # 5 | # Needs DOCKER_REPOSITORY, BASE_URL and optionally NUM_PROCS 6 | # 7 | # To set them go to Job -> Configure -> Build Environment -> Inject 8 | # passwords and Inject env variables 9 | # 10 | set -ex 11 | 12 | REVISION=$(curl $BASE_URL/static/revision.txt) 13 | echo REVISION=$REVISION 14 | ENV_FILE=`mktemp` 15 | echo "DATABASE_URL=sqlite://" >> $ENV_FILE 16 | echo "DEBUG=False" >> $ENV_FILE 17 | echo "ALLOWED_HOSTS=localhost" >> $ENV_FILE 18 | echo "SECRET_KEY=test" >> $ENV_FILE 19 | 20 | # Run Tests 21 | docker run --env-file=$ENV_FILE $DOCKER_REPOSITORY:$REVISION py.test -m acceptance --acceptance --baseurl=$BASE_URL -n ${NUM_PROCS:-7} 22 | 23 | # Delete temp file. 24 | rm -rf $ENV_FILE 25 | -------------------------------------------------------------------------------- /bin/jenkins/runtests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Runs unit_tests 4 | # 5 | set -ex 6 | 7 | # Create a temporary virtualenv to install fig 8 | TDIR=`mktemp -d` 9 | virtualenv $TDIR 10 | . $TDIR/bin/activate 11 | pip install fig 12 | 13 | FIG_CMD="fig --project-name jenkins${JOB_NAME}${BUILD_NUMBER} -f ./bin/jenkins/fig.yml" 14 | 15 | $FIG_CMD build 16 | 17 | docker-squash -t `echo jenkins${JOB_NAME}${BUILD_NUMBER}| sed s/_//g`_web `echo jenkins${JOB_NAME}${BUILD_NUMBER}| sed s/_//g`_web 18 | 19 | $FIG_CMD run -T web flake8 20 | 21 | # Lint translations 22 | $FIG_CMD run -T web dennis-cmd lint --errorsonly locale/ 23 | 24 | # Run Tests 25 | $FIG_CMD run -T web py.test --assert plain --cov masterfirefoxos --cov-report=term-missing 26 | 27 | # Delete virtualenv 28 | rm -rf $TDIR 29 | -------------------------------------------------------------------------------- /bin/run-common.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./manage.py syncdb --noinput 4 | ./manage.py compilemessages 5 | ./manage.py runscript create_db_locales > /dev/null 2>&1 & 6 | -------------------------------------------------------------------------------- /bin/run-docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | if [ -e ./.git/HEAD ] 5 | then 6 | cp ./.git/HEAD /app/masterfirefoxos/base/static/revision.txt 7 | else 8 | echo $GIT_SHA > /app/masterfirefoxos/base/static/revision.txt 9 | fi 10 | 11 | ./bin/run-common.sh 12 | ./manage.py collectstatic --noinput 13 | gunicorn masterfirefoxos.wsgi:application -b 0.0.0.0:8000 -w 2 --log-file - 14 | -------------------------------------------------------------------------------- /bin/run-fig.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ./bin/run-common.sh 4 | ./manage.py runserver 0.0.0.0:8000 5 | -------------------------------------------------------------------------------- /bin/unit_test_coverage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export DJANGO_SETTINGS_MODULE=masterfirefoxos.settings 3 | py.test --cov masterfirefoxos --cov-report=term-missing masterfirefoxos 4 | -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.conf import settings 3 | 4 | 5 | def pytest_addoption(parser): 6 | parser.addoption('--acceptance', action="store_true", 7 | help="run acceptance tests") 8 | parser.addoption('--baseurl', action='store', dest='base_url', 9 | default='http://localhost:8000', metavar='url', 10 | help='base url test against for acceptance tests') 11 | 12 | 13 | def pytest_runtest_setup(item): 14 | if 'acceptance' in item.keywords and not item.config.getoption( 15 | "--acceptance"): 16 | pytest.skip("need --acceptance option to run") 17 | 18 | 19 | @pytest.fixture 20 | def base_url(request): 21 | return request.config.getoption('--baseurl') 22 | 23 | 24 | @pytest.fixture 25 | def base_urls(base_url): 26 | return ['/'.join([base_url, locale, data['slug'], '']) 27 | for version, data in settings.VERSIONS_LOCALE_MAP.items() 28 | for locale in data['locales']] 29 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/masterfirefoxos.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/masterfirefoxos.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/masterfirefoxos" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/masterfirefoxos" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | 179 | livehtml: 180 | sphinx-autobuild -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 181 | -------------------------------------------------------------------------------- /docs/_static/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/masterfirefoxos/32eb1f6ce0ed357de286e64839570e73e86ae136/docs/_static/.gitignore -------------------------------------------------------------------------------- /docs/_templates/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/masterfirefoxos/32eb1f6ce0ed357de286e64839570e73e86ae136/docs/_templates/.gitignore -------------------------------------------------------------------------------- /docs/authoring_content.rst: -------------------------------------------------------------------------------- 1 | .. This Source Code Form is subject to the terms of the Mozilla Public 2 | .. License, v. 2.0. If a copy of the MPL was not distributed with this 3 | .. file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | .. _authoring-content: 6 | 7 | ================= 8 | Authoring Content 9 | ================= 10 | 11 | Below you will find basic content authoring instructions for the MasterFirefoxOS (MFOS) project. 12 | 13 | .. toctree:: 14 | :maxdepth: 1 15 | 16 | key_concepts 17 | homepage 18 | pages 19 | images 20 | videos 21 | quiz 22 | 23 | 24 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # masterfirefoxos documentation build configuration file, created by 4 | # sphinx-quickstart on Thu Nov 6 19:39:08 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | 18 | on_rtd = os.environ.get('READTHEDOCS') == 'True' 19 | if not on_rtd: 20 | import sphinx_rtd_theme 21 | html_theme = 'sphinx_rtd_theme' 22 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 23 | 24 | 25 | # If extensions (or modules to document with autodoc) are in another directory, 26 | # add these directories to sys.path here. If the directory is relative to the 27 | # documentation root, use os.path.abspath to make it absolute, like shown here. 28 | #sys.path.insert(0, os.path.abspath('.')) 29 | 30 | # -- General configuration ------------------------------------------------ 31 | 32 | # If your documentation needs a minimal Sphinx version, state it here. 33 | #needs_sphinx = '1.0' 34 | 35 | # Add any Sphinx extension module names here, as strings. They can be 36 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 37 | # ones. 38 | extensions = [] 39 | 40 | # Add any paths that contain templates here, relative to this directory. 41 | templates_path = ['_templates'] 42 | 43 | # The suffix of source filenames. 44 | source_suffix = '.rst' 45 | 46 | # The encoding of source files. 47 | #source_encoding = 'utf-8-sig' 48 | 49 | # The master toctree document. 50 | master_doc = 'index' 51 | 52 | # General information about the project. 53 | project = u'masterfirefoxos' 54 | copyright = u'2014, Mozilla WebProd' 55 | 56 | # The version info for the project you're documenting, acts as replacement for 57 | # |version| and |release|, also used in various other places throughout the 58 | # built documents. 59 | # 60 | # The short X.Y version. 61 | version = '0.1' 62 | # The full version, including alpha/beta/rc tags. 63 | release = '0.1' 64 | 65 | # The language for content autogenerated by Sphinx. Refer to documentation 66 | # for a list of supported languages. 67 | #language = None 68 | 69 | # There are two options for replacing |today|: either, you set today to some 70 | # non-false value, then it is used: 71 | #today = '' 72 | # Else, today_fmt is used as the format for a strftime call. 73 | #today_fmt = '%B %d, %Y' 74 | 75 | # List of patterns, relative to source directory, that match files and 76 | # directories to ignore when looking for source files. 77 | exclude_patterns = ['_build'] 78 | 79 | # The reST default role (used for this markup: `text`) to use for all 80 | # documents. 81 | #default_role = None 82 | 83 | # If true, '()' will be appended to :func: etc. cross-reference text. 84 | #add_function_parentheses = True 85 | 86 | # If true, the current module name will be prepended to all description 87 | # unit titles (such as .. function::). 88 | #add_module_names = True 89 | 90 | # If true, sectionauthor and moduleauthor directives will be shown in the 91 | # output. They are ignored by default. 92 | #show_authors = False 93 | 94 | # The name of the Pygments (syntax highlighting) style to use. 95 | pygments_style = 'sphinx' 96 | 97 | # A list of ignored prefixes for module index sorting. 98 | #modindex_common_prefix = [] 99 | 100 | # If true, keep warnings as "system message" paragraphs in the built documents. 101 | #keep_warnings = False 102 | 103 | 104 | # -- Options for HTML output ---------------------------------------------- 105 | 106 | # The theme to use for HTML and HTML Help pages. See the documentation for 107 | # a list of builtin themes. 108 | html_theme = 'sphinx_rtd_theme' 109 | 110 | # Theme options are theme-specific and customize the look and feel of a theme 111 | # further. For a list of options available for each theme, see the 112 | # documentation. 113 | #html_theme_options = {} 114 | 115 | # Add any paths that contain custom themes here, relative to this directory. 116 | #html_theme_path = [] 117 | 118 | # The name for this set of Sphinx documents. If None, it defaults to 119 | # " v documentation". 120 | #html_title = None 121 | 122 | # A shorter title for the navigation bar. Default is the same as html_title. 123 | #html_short_title = None 124 | 125 | # The name of an image file (relative to this directory) to place at the top 126 | # of the sidebar. 127 | #html_logo = None 128 | 129 | # The name of an image file (within the static path) to use as favicon of the 130 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 131 | # pixels large. 132 | #html_favicon = None 133 | 134 | # Add any paths that contain custom static files (such as style sheets) here, 135 | # relative to this directory. They are copied after the builtin static files, 136 | # so a file named "default.css" will overwrite the builtin "default.css". 137 | html_static_path = ['_static'] 138 | 139 | # Add any extra paths that contain custom files (such as robots.txt or 140 | # .htaccess) here, relative to this directory. These files are copied 141 | # directly to the root of the documentation. 142 | #html_extra_path = [] 143 | 144 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 145 | # using the given strftime format. 146 | #html_last_updated_fmt = '%b %d, %Y' 147 | 148 | # If true, SmartyPants will be used to convert quotes and dashes to 149 | # typographically correct entities. 150 | #html_use_smartypants = True 151 | 152 | # Custom sidebar templates, maps document names to template names. 153 | #html_sidebars = {} 154 | 155 | # Additional templates that should be rendered to pages, maps page names to 156 | # template names. 157 | #html_additional_pages = {} 158 | 159 | # If false, no module index is generated. 160 | #html_domain_indices = True 161 | 162 | # If false, no index is generated. 163 | #html_use_index = True 164 | 165 | # If true, the index is split into individual pages for each letter. 166 | #html_split_index = False 167 | 168 | # If true, links to the reST sources are added to the pages. 169 | #html_show_sourcelink = True 170 | 171 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 172 | #html_show_sphinx = True 173 | 174 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 175 | #html_show_copyright = True 176 | 177 | # If true, an OpenSearch description file will be output, and all pages will 178 | # contain a tag referring to it. The value of this option must be the 179 | # base URL from which the finished HTML is served. 180 | #html_use_opensearch = '' 181 | 182 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 183 | #html_file_suffix = None 184 | 185 | # Output file base name for HTML help builder. 186 | htmlhelp_basename = 'masterfirefoxosdoc' 187 | 188 | 189 | # -- Options for LaTeX output --------------------------------------------- 190 | 191 | latex_elements = { 192 | # The paper size ('letterpaper' or 'a4paper'). 193 | #'papersize': 'letterpaper', 194 | 195 | # The font size ('10pt', '11pt' or '12pt'). 196 | #'pointsize': '10pt', 197 | 198 | # Additional stuff for the LaTeX preamble. 199 | #'preamble': '', 200 | } 201 | 202 | # Grouping the document tree into LaTeX files. List of tuples 203 | # (source start file, target name, title, 204 | # author, documentclass [howto, manual, or own class]). 205 | latex_documents = [ 206 | ('index', 'masterfirefoxos.tex', u'masterfirefoxos Documentation', 207 | u'Mozilla WebProd', 'manual'), 208 | ] 209 | 210 | # The name of an image file (relative to this directory) to place at the top of 211 | # the title page. 212 | #latex_logo = None 213 | 214 | # For "manual" documents, if this is true, then toplevel headings are parts, 215 | # not chapters. 216 | #latex_use_parts = False 217 | 218 | # If true, show page references after internal links. 219 | #latex_show_pagerefs = False 220 | 221 | # If true, show URL addresses after external links. 222 | #latex_show_urls = False 223 | 224 | # Documents to append as an appendix to all manuals. 225 | #latex_appendices = [] 226 | 227 | # If false, no module index is generated. 228 | #latex_domain_indices = True 229 | 230 | 231 | # -- Options for manual page output --------------------------------------- 232 | 233 | # One entry per manual page. List of tuples 234 | # (source start file, name, description, authors, manual section). 235 | man_pages = [ 236 | ('index', 'masterfirefoxos', u'masterfirefoxos Documentation', 237 | [u'Mozilla WebProd'], 1) 238 | ] 239 | 240 | # If true, show URL addresses after external links. 241 | #man_show_urls = False 242 | 243 | 244 | # -- Options for Texinfo output ------------------------------------------- 245 | 246 | # Grouping the document tree into Texinfo files. List of tuples 247 | # (source start file, target name, title, author, 248 | # dir menu entry, description, category) 249 | texinfo_documents = [ 250 | ('index', 'masterfirefoxos', u'masterfirefoxos Documentation', 251 | u'Mozilla WebProd', 'masterfirefoxos', 'One line description of project.', 252 | 'Miscellaneous'), 253 | ] 254 | 255 | # Documents to append as an appendix to all manuals. 256 | #texinfo_appendices = [] 257 | 258 | # If false, no module index is generated. 259 | #texinfo_domain_indices = True 260 | 261 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 262 | #texinfo_show_urls = 'footnote' 263 | 264 | # If true, do not generate a @detailmenu in the "Top" node's menu. 265 | #texinfo_no_detailmenu = False 266 | -------------------------------------------------------------------------------- /docs/gulp.rst: -------------------------------------------------------------------------------- 1 | .. This Source Code Form is subject to the terms of the Mozilla Public 2 | .. License, v. 2.0. If a copy of the MPL was not distributed with this 3 | .. file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | .. _gulp: 6 | 7 | =========== 8 | Using Gulp 9 | =========== 10 | 11 | Introduction 12 | ------------ 13 | Gulp is used to compile the stylus files into CSS. 14 | 15 | 16 | Pre-Requisites 17 | -------------- 18 | 19 | * `Node `_ 20 | * `NPM `_ 21 | 22 | 23 | Installation 24 | ------------ 25 | If not installed already, install gulp globally. You may need to run this command using sudo:: 26 | 27 | npm install -g gulp 28 | 29 | Install the project specific node modules:: 30 | 31 | npm install 32 | 33 | 34 | Tasks 35 | ----- 36 | 37 | Compile all stylus files to css:: 38 | 39 | gulp 40 | 41 | Watch stylus directory, compile to css on change:: 42 | 43 | gulp watch:css 44 | -------------------------------------------------------------------------------- /docs/homepage.rst: -------------------------------------------------------------------------------- 1 | .. This Source Code Form is subject to the terms of the Mozilla Public 2 | .. License, v. 2.0. If a copy of the MPL was not distributed with this 3 | .. file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | 6 | ============ 7 | Homepage 8 | ============ 9 | 10 | 11 | Each version of FFOS supported on the site will have a unique HP. 12 | 13 | The majority of the content for the HP comes from the pages that live beneath it in the tree. 14 | 15 | The homepage takes the title and the **homepage navigation** copy for each page. 16 | 17 | .. image:: images/hp-blob.png 18 | 19 | -------------------------------------------------------------------------------- /docs/images.rst: -------------------------------------------------------------------------------- 1 | .. This Source Code Form is subject to the terms of the Mozilla Public 2 | .. License, v. 2.0. If a copy of the MPL was not distributed with this 3 | .. file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | 6 | =============== 7 | Images 8 | =============== 9 | 10 | Images can be uploaded by navigating to the ``media files`` section of the admin. 11 | 12 | Naming 13 | ------ 14 | 15 | Please name your videos according to the following pattern, **section_thing**.png. 16 | 17 | For example: intro_adaptive.png 18 | 19 | 20 | Categories & Translations 21 | ------------------------- 22 | 23 | When you upload an image you must add categories denoting: 24 | 1. What version of FFOS the image is associated with. 25 | 2. What language uses the image. 26 | 27 | Localized verions of the same image should be given the exact same name. 28 | 29 | 30 | .. Important:: 31 | Only en images will appear in the CMS. The localized versions will be inserted automatically 32 | when the site is viewed. The names must be an exact match for this to work. 33 | -------------------------------------------------------------------------------- /docs/images/copy-tree-done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/masterfirefoxos/32eb1f6ce0ed357de286e64839570e73e86ae136/docs/images/copy-tree-done.png -------------------------------------------------------------------------------- /docs/images/copy-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/masterfirefoxos/32eb1f6ce0ed357de286e64839570e73e86ae136/docs/images/copy-tree.png -------------------------------------------------------------------------------- /docs/images/hp-blob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/masterfirefoxos/32eb1f6ce0ed357de286e64839570e73e86ae136/docs/images/hp-blob.png -------------------------------------------------------------------------------- /docs/images/slug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/masterfirefoxos/32eb1f6ce0ed357de286e64839570e73e86ae136/docs/images/slug.png -------------------------------------------------------------------------------- /docs/importing_legacy_data.rst: -------------------------------------------------------------------------------- 1 | .. This Source Code Form is subject to the terms of the Mozilla Public 2 | .. License, v. 2.0. If a copy of the MPL was not distributed with this 3 | .. file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | Importing Legacy Content 6 | ======================== 7 | 8 | Content from the first version of the website is included in the 9 | `legacy` folder as json files. To import these pages and translations 10 | into the new website run:: 11 | 12 | ./manage.py runscript import_legacy_pages_and_translations 13 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. masterfirefoxos documentation master file, created by 2 | sphinx-quickstart on Thu Nov 6 19:39:08 2014. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to masterfirefoxos's documentation! 7 | =========================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | authoring_content 15 | versions_and_locales 16 | translation_using_verbatim 17 | gulp 18 | importing_legacy_data 19 | 20 | 21 | 22 | Indices and tables 23 | ================== 24 | 25 | * :ref:`genindex` 26 | * :ref:`modindex` 27 | * :ref:`search` 28 | -------------------------------------------------------------------------------- /docs/key_concepts.rst: -------------------------------------------------------------------------------- 1 | .. This Source Code Form is subject to the terms of the Mozilla Public 2 | .. License, v. 2.0. If a copy of the MPL was not distributed with this 3 | .. file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | 6 | ============ 7 | Key Concepts 8 | ============ 9 | 10 | Authentication 11 | -------------- 12 | 13 | In order to author content in the MFOS site, you will need to login: 14 | 15 | 1. Navigate to: **https://masterfirefoxos.mozilla.org/admin**) 16 | 2. Supply the **username** (email) and **password** 17 | 3. Press **Login** 18 | 19 | FFOS Versions 20 | ------------- 21 | 22 | All pages on masterfirefoxos (MFOSS) are associated with a version of Firefox OS. In the CMS this 23 | is accomplished by having a seperate page tree for each version. The version of FFOS is denoted by 24 | the slug, as shown below: 25 | 26 | .. image:: images/slug.png 27 | 28 | The slug will also appear in urls: 29 | 30 | ``https://www.masterfirefoxos.com/en/1-1/introduction/`` 31 | 32 | When adding a new FFOS version or locale changes must also be made by a developer to the settings 33 | file. This is a once per version or locale change. 34 | 35 | 36 | TinyMCE Editor 37 | -------------- 38 | 39 | The TinyMCE editor is disabled for content entry. Unfortunately this editor 40 | adds HTML formatting that causes problems for L10N. 41 | 42 | .. warning:: 43 | 44 | Generally you should not manually add html tags. If you do need to add html tags pleas 45 | restrict usage to ,
and basic formatting. Do not use tags like

or

. 46 | 47 | 48 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\masterfirefoxos.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\masterfirefoxos.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /docs/pages.rst: -------------------------------------------------------------------------------- 1 | .. This Source Code Form is subject to the terms of the Mozilla Public 2 | .. License, v. 2.0. If a copy of the MPL was not distributed with this 3 | .. file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | 6 | =============== 7 | Pages 8 | =============== 9 | 10 | 11 | New Pages 12 | ------------------------ 13 | 14 | To create a new page on the site, click **add page** in the upper right. The form presented to you 15 | is explained below. 16 | 17 | 18 | Meta Information 19 | ------------------------ 20 | 21 | * **Title** The title of the page. 22 | * **Slug** Used to build the URL. 23 | * **Active / In Navigation** Pages that are marked active appear on the site. This setting allows you to save a page without having it appear live. Navigation toggles if the page appears in the left nav. 24 | 25 | 26 | Other Options 27 | ------------------------ 28 | 29 | * **Template Type** Selects the layout of the page, Home Template is for the Homepage, all other Pages should be Content Template 30 | * **Override URL** If for some reason the URL needs to be overriden, specify the URL here (rarely used). 31 | * **Redirect** If needed, a redirect (rarely used). 32 | 33 | 34 | Main Content Area 35 | ------------------------ 36 | This is where all the content displayed on the page goes. Pages are made up of **sections**. Sections are one of 37 | 6 types: 38 | 39 | * Rich Text 40 | * Image Paragaph 41 | * FAQ Entry 42 | * YouTube Paragraph 43 | * Quiz Question 44 | * Quiz Answer 45 | 46 | 47 | Rich Text 48 | ------------------------ 49 | Essentially a big blob of content. Usefull for intro paragraphs. 50 | 51 | 52 | Image Paragraph 53 | ------------------------ 54 | Pages such as the customer guide primarily consist of images and paragraphs of text. 55 | Use this option to create these sections. 56 | 57 | 58 | FAQ Entry 59 | ------------------------ 60 | Used on the FAQ, contains fields for questions and corresponding answers. 61 | 62 | 63 | YouTube Paragraph 64 | ------------------------ 65 | Pages such as Key Features consist of embedded videos and paragraphs of text. Use this option to 66 | create these sections. More details can be found on the youtube doc page. 67 | 68 | Quiz Question & Answer 69 | ------------------------ 70 | Used to create the quiz. More details can be found on the quiz doc page. 71 | 72 | Headings 73 | ------------------------ 74 | In addition to the title field there are 2 additional fields for headings: 75 | 76 | * Subheader 2 77 | * Subheader 3 78 | 79 | These map to the headings

and

(title is

). 80 | -------------------------------------------------------------------------------- /docs/quiz.rst: -------------------------------------------------------------------------------- 1 | .. This Source Code Form is subject to the terms of the Mozilla Public 2 | .. License, v. 2.0. If a copy of the MPL was not distributed with this 3 | .. file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | 6 | =============== 7 | Quiz 8 | =============== 9 | 10 | A quiz is a collection of questions where each question consists of the following: 11 | 12 | 1. A question. 13 | 2. Text to display when the answer is wrong or right. 14 | 3. A list of possible answers where one or more is correct. 15 | 16 | 17 | To create a question: 18 | 19 | 1. Select **Quiz Question** from the dropdown. 20 | 2. Populate the 4 rich text areas: 21 | 1. Question Text 22 | 2. Correct Answer Text 23 | 3. Partially Correct Answer Text 24 | 4. Incorrect Answer Text 25 | 3. Select **Quiz Answer** from the dropdown. 26 | 1. Populate the copy for the answer. 27 | 2. Check **Correct** if this is a correct answer. 28 | 29 | .. Note:: 30 | You repeat the above for each question and answer set. The CMS assumes all answers are attached 31 | to a question until another question is reached. This means the order of items in the CMS is 32 | used to determine which answers go with which questions. 33 | -------------------------------------------------------------------------------- /docs/translation_using_verbatim.rst: -------------------------------------------------------------------------------- 1 | .. This Source Code Form is subject to the terms of the Mozilla Public 2 | .. License, v. 2.0. If a copy of the MPL was not distributed with this 3 | .. file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | .. _translation-using-verbatim: 6 | 7 | ========================== 8 | Translation Using Verbatim 9 | ========================== 10 | 11 | 12 | Important URLS 13 | ------------------------ 14 | 15 | * Verbatim Project https://localize.mozilla.org/projects/masterfirefoxos/ 16 | * L10N Preview Site http://l10n.masterfirefoxos.com/ 17 | * L10N Repository https://github.com/mozilla/masterfirefoxos-l10n 18 | 19 | 20 | Verbatim 21 | ------------------------ 22 | 23 | 1. Strings are updated from masterfirefox to our L10N repository daily or on request. 24 | 2. Verbatim must be manually updated by pinging mathjaz. 25 | 3. Localizers when they commit changes also update the github repository. 26 | 4. The l10n preview site updates a few minutes after each commit to the github repository. 27 | 28 | 29 | L10N Preview Site 30 | ------------------------ 31 | 32 | The production website only displays finalized fully translated locales. To preview in-progress 33 | translations, you may view http://l10n.masterfirefoxos.com/ . 34 | -------------------------------------------------------------------------------- /docs/versions_and_locales.rst: -------------------------------------------------------------------------------- 1 | Versions and Locales 2 | ==================== 3 | 4 | Adding new Versions of the Documentation 5 | ---------------------------------------- 6 | 7 | To add more Versions of MasterFirefoxOS Documentation you need to create 8 | the corresponding CMS pages and edit settings. For example to add 9 | version 2.0 based on version 1.3T:: 10 | 11 | 1. Go to /admin/page/page 12 | 2. Select the root page of version 1.3T 13 | 3. Select `Copy tree` from `Action` drop-down and click `Go`. 14 | 15 | .. image:: images/copy-tree.png 16 | 17 | Step three will copy all pages of version 1.3T into a new tree 18 | 19 | .. image:: images/copy-tree-done.png 20 | 21 | 4. Edit the copied root page and set `title` to `Master Firefox OS` 22 | and `slug` to `2-0`. 23 | 24 | .. note:: 25 | 26 | For slug use dashes instead of dots. 27 | 28 | 5. Add in `masterfirefoxos.settings.base`:: 29 | 30 | VERSIONS_LOCALE_MAP['2.0'] = { 31 | 'slug': '2-0', 32 | 'locales': [ 33 | ]} 34 | 35 | And you're done. Edit the content and once you're ready activate 36 | locales for translation. Read `Managing the Versions of Documentation 37 | per locale`_. 38 | 39 | 40 | Adding new locales 41 | ------------------ 42 | 43 | To add more locales append a tuple with locale code and locale name in 44 | `masterfirefox.settings.base.LANGUAGES`. For example to add German 45 | change `LANGUAGES` to:: 46 | 47 | LANGUAGES = ( 48 | ('en', ugettext_lazy('English')), 49 | ('el', ugettext_lazy('Greek')), 50 | ('de', ugettext_lazy('German')), 51 | ) 52 | 53 | Then you need to generate messages for the first time for the added locale:: 54 | 55 | ./manage.py makemessages --locale de 56 | 57 | .. note:: 58 | 59 | It's important to explicitly define the new locale using the 60 | `--locale` flag when executing the `makemessages` command. Using 61 | the `-a` flag to make messages for all locales will ignore the new 62 | locales. 63 | 64 | 65 | Managing the Versions of Documentation per locale 66 | ------------------------------------------------ 67 | 68 | A locale gets activated once it gets linked with a Version of the 69 | Documentation. For example to link locale `foo` with version `1.1` edit 70 | `masterfirefoxos.settings.base.VERSIONS_LOCALE_MAP`:: 71 | 72 | VERSIONS_LOCALE_MAP['1.1'] = { 73 | 'slug': '1-1', 74 | 'locales': [ 75 | 'en', 'foo' 76 | ]} 77 | 78 | .. note:: 79 | 80 | If you need to add a new version, read the `Adding new Versions of 81 | the Documentation`_. 82 | 83 | .. note:: 84 | 85 | Check if the locale is also in the `pending_locales` list and 86 | remove it. 87 | 88 | If locale `foo` is still work in progress (e.g. translations are not 89 | complete yet) instead of appending the `locales` list, add it to 90 | `pending_locales` list:: 91 | 92 | VERSIONS_LOCALE_MAP['1.1'] = { 93 | 'slug': '1-1', 94 | 'locales': [ 95 | 'en' 96 | ], 97 | 'pending_locales': [ 98 | 'foo' 99 | ]} 100 | 101 | This will ensure that localization strings get generated but it will 102 | not display on the site. 103 | 104 | 105 | Extracting strings 106 | ------------------ 107 | 108 | MasterFirefoxOS stores strings for localization in three different places: 109 | 110 | * Python files (.py) 111 | * HTML files (.html) 112 | * In the database 113 | 114 | Python and HTML files are automatically handled by Django's 115 | `makemessages` command. The database strings must first get extracted 116 | into a text file be parsable by `makemessages` command. To extract the 117 | database strings run:: 118 | 119 | ./manage.py runscript db_strings 120 | 121 | .. note:: 122 | 123 | Extracted database strings are stored in `db-strings.txt` file. This 124 | file should *not* be edited manually. 125 | 126 | Now all strings are in Python, HTML and Text files that `makemessages` 127 | command can parse. To generate `.po` files for all supported languages 128 | run:: 129 | 130 | ./manage.py makemessages -a 131 | 132 | .. warning:: 133 | 134 | Always extract database strings before running 135 | `makemessages`. Failing to do so may remove all database strings 136 | from `.po` files. 137 | 138 | 139 | Now you can distributed your `.po` files to the translators. 140 | 141 | The generated po files contain strings for all versions of the CMS 142 | content. The final step is to keep only the strings from the versions 143 | of the CMS content activated per locale:: 144 | 145 | ./manage.py runscript cleanup_po 146 | 147 | 148 | Compile strings 149 | --------------- 150 | 151 | Given that you have translated `.po` files you need to compile them 152 | into `.mo` files. To do this run the `compilemessages` command:: 153 | 154 | ./manage.py compilemessages 155 | 156 | 157 | This is required step for translations to work. 158 | 159 | 160 | How does database localization work? 161 | ------------------------------------ 162 | 163 | The following command will iterate through all FeinCMS Pages and 164 | through all Content Types defined in each Page, and extract strings 165 | from fields named in each Content Type model's `_l10n_fields` 166 | attribute, and output to a template text file: 167 | 168 | ./manage.py db_strings 169 | 170 | By default, the command outputs to `db-strings.txt` but accepts an 171 | optional `filename` argument. 172 | 173 | This text file can be parsed with `./manage.py makemessages` command 174 | to generate a `.po` file. 175 | 176 | We use a custom `render` method that calls `ugettext` on 177 | each localizable field:: 178 | 179 | from django.utils.translation import ugettext as _ 180 | 181 | class FAQEntry(models.Model): 182 | 183 | question = fields.CharField(max_length=255) 184 | answer = fields.TextField(max_length=255) 185 | _l10n_fields = ['question', 'answer'] 186 | 187 | class Meta: 188 | abstract = True 189 | 190 | def render(self, **kwargs): 191 | return render_to_string( 192 | 'faqentry.html', 193 | { 194 | 'question': _(self.question), 195 | 'answer': _(self.answer), 196 | } 197 | ) 198 | -------------------------------------------------------------------------------- /docs/videos.rst: -------------------------------------------------------------------------------- 1 | .. This Source Code Form is subject to the terms of the Mozilla Public 2 | .. License, v. 2.0. If a copy of the MPL was not distributed with this 3 | .. file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | 6 | =============== 7 | Videos 8 | =============== 9 | 10 | Videos are hosted on youtube. To upload a new video you may use the credentials on the masterfirefox 11 | mana page 12 | 13 | https://mana.mozilla.org/wiki/display/websites/masterfirefoxos.mozilla.org 14 | 15 | When uploading videos please do the following: 16 | 17 | 1. Add videos to the Firefox OS playlist. 18 | 2. Use consistent naming among localized versions. 19 | 3. Tag videos with ``MasterFirefoxOS`` and the language, example ``Srpski`` 20 | 21 | Videos are localized via the PO files and not within the CMS. 22 | -------------------------------------------------------------------------------- /fig.yml: -------------------------------------------------------------------------------- 1 | db: 2 | image: postgres:9.3 3 | web: 4 | build: . 5 | ports: 6 | - "8000:8000" 7 | volumes: 8 | - .:/app 9 | links: 10 | - db 11 | environment: 12 | - PYTHONDONTWRITEBYTECODE=1 13 | - DATABASE_URL=postgres://postgres@db/postgres 14 | - DEBUG=True 15 | - ALLOWED_HOSTS=localhost,127.0.0.1, 16 | - SECRET_KEY=59114b6a-2858-4caf-8878-482a24ee9542 17 | command: 18 | ./bin/run-fig.sh 19 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var gulpFilter = require('gulp-filter'); 3 | var concat = require('gulp-concat'); 4 | var stylus = require('gulp-stylus'); 5 | var sourcemaps = require('gulp-sourcemaps'); 6 | var del = require('del'); 7 | 8 | var paths = { 9 | stylus: './masterfirefoxos/base/static/stylus', 10 | css: './masterfirefoxos/base/static/css/' 11 | }; 12 | 13 | gulp.task('compress:base', function () { 14 | return gulp.src(paths.stylus + '/base.styl') 15 | .pipe(sourcemaps.init()) 16 | .pipe(stylus({compress: true})) 17 | .pipe(concat('base.css')) 18 | .pipe(sourcemaps.write('.')) 19 | .pipe(gulp.dest(paths.css)); 20 | }); 21 | 22 | gulp.task('compress:home', function () { 23 | return gulp.src(paths.stylus + '/home.styl') 24 | .pipe(sourcemaps.init()) 25 | .pipe(stylus({compress: true})) 26 | .pipe(concat('home.css')) 27 | .pipe(sourcemaps.write('.')) 28 | .pipe(gulp.dest(paths.css)); 29 | }); 30 | 31 | gulp.task('compress:ie', function () { 32 | return gulp.src(paths.stylus + '/oldIE.styl') 33 | .pipe(sourcemaps.init()) 34 | .pipe(stylus({compress: true})) 35 | .pipe(sourcemaps.write('.')) 36 | .pipe(gulp.dest(paths.css)); 37 | }); 38 | 39 | gulp.task('clean:css', function (cb) { 40 | return del([ 41 | paths.css + '/*', 42 | '!' + paths.css + '/README.md' 43 | ], cb); 44 | }); 45 | 46 | gulp.task('watch:css', function() { 47 | gulp.watch(paths.stylus + '/**/*.styl', ['default']); 48 | }); 49 | 50 | gulp.task('default', ['clean:css', 'compress:base', 'compress:home', 'compress:ie']); 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /legacy/json/README: -------------------------------------------------------------------------------- 1 | Fetched from http://masterfirefoxos.mozilla.org/course/ on 2014-12-26 2 | -------------------------------------------------------------------------------- /legacy/json/video_path_to_youtube_id.json: -------------------------------------------------------------------------------- 1 | { 2 | "course/assets/m05/t05/p10/portuguese/m05-t05-p10-a05-b40-c10.mp4": "gqwl1xiLEkg", 3 | "course/assets/m05/t05/p10/german/m05-t05-p10-a05-b10-c10.ogv": "9bNfgtMa-H0", 4 | "course/assets/m05/t05/p10/croatian/m05-t05-p10-a05-b40-c10.ogv": "aH6y26VxnlM", 5 | "course/assets/m05/t05/p10/italian/m05-t05-p10-a05-b20-c05.ogv": "0PiFn-2byP0", 6 | "course/assets/m05/t05/p10/italian/m05-t05-p10-a05-b10-c05.ogv": "9U6qXAqx9qw", 7 | "course/assets/m05/t05/p10/czech/m05-t05-p10-a05-b10-c10.ogv": "EBM2J9U6u1E", 8 | "course/assets/m05/t05/p10/polish/m05-p10-a05-b20-c10.ogv": "DFQUPO2Zbr4", 9 | "course/assets/m05/t05/p10/czech/m05-t05-p10-a05-b10-c05.ogv": "v1tLRWtCkEs", 10 | "course/assets/m05/t05/p10/tamil/m05-t05-p10-a05-b20-c10.ogv": "YjEAgE3IGdo", 11 | "course/assets/m05/t05/p10/polish/m05-t05-p10-a05-b40-c05.ogv": "oU6bX-blsVo", 12 | "course/assets/m05/t05/p10/greek/m05-t05-p10-a05-b20-c10.ogv": "nkaAYhpvJ6o", 13 | "course/assets/m05/t05/p10/czech/m05-t05-p10-a05-b30-c05.ogv": "OAIWl4ZU-Fs", 14 | "course/assets/m05/t05/p10/english/m05-t05-p10-a05-b30-c10.mp4": "nwAUQvNuyTw", 15 | "course/assets/m05/t05/p10/english/m05-t05-p10-a05-b30-c05.mp4": "46dngYXUcFg", 16 | "course/assets/m05/t05/p10/greek/m05-t05-p10-a05-b10-c05.ogv": "B9fwnibymws", 17 | "course/assets/m05/t05/p10/tamil/m05-t05-p10-a05-b20-c05.ogv": "ityctnM6VEQ", 18 | "course/assets/m05/t05/p10/croatian/m05-t05-p10-a05-b10-c05.ogv": "QcDS5tfALy4", 19 | "course/assets/m05/t05/p10/tamil/m05-t05-p10-a05-b15-c10.ogv": "UGJo0wcU2cM", 20 | "course/assets/m05/t05/p10/tamil/m05-t05-p10-a05-b15-c05.ogv": "TrjhifzgXso", 21 | "course/assets/m05/t05/p10/english/m05-t05-p10-a05-b10-c05.ogv": "yPRIgHARJsA", 22 | "course/assets/m05/t05/p10/english/m05-t05-p10-a05-b40-c10.mp4": "kGb4OLORKPA", 23 | "course/assets/m05/t05/p10/german/m05-t05-p10-a05-b40-c05.ogv": "5HnQ6o9HTns", 24 | "course/assets/m05/t05/p10/greek/m05-t05-p10-a05-b40-c10.ogv": "g8k8MjX9ZjM", 25 | "course/assets/m05/t05/p10/polish/m05-t05-p10-a05-b10-c05.ogv": "IUfYEg2FGn4", 26 | "course/assets/m05/t05/p10/spanish/m05-t05-p10-a05-b10-c10.ogv": "hRPXmLOx1-w", 27 | "course/assets/m05/t05/p10/serbian/m05-t05-p10-a05-b30-c05.ogv": "Y04bxc3kVgE", 28 | "course/assets/m05/t05/p10/greek/m05-t05-p10-a05-b30-c05.ogv": "JGCQ6lsOEO4", 29 | "course/assets/m05/t05/p10/portuguese/m05-t05-p10-a05-b30-c10.ogv": "g7xGYUl_Odc", 30 | "course/assets/m05/t05/p10/german/m05-t05-p10-a05-b30-c05.ogv": "LXCZrsX0DRc", 31 | "course/assets/m05/t05/p10/portuguese/m05-t05-p10-a05-b20-c10.ogv": "WGdJ_cL3LNc", 32 | "course/assets/m05/t05/p10/portuguese/m05-t05-p10-a05-b10-c05.ogv": "6mUYqNted2U", 33 | "course/assets/m05/t05/p10/portuguese/m05-t05-p10-a05-b30-c05.ogv": "zivYtU2lip0", 34 | "course/assets/m05/t05/p10/portuguese/m05-t05-p10-a05-b10-c10.ogv": "wXlmBxaoLi8", 35 | "course/assets/m05/t05/p10/english/m05-t05-p10-a05-b40-c05.mp4": "ikbujo0l7dI", 36 | "course/assets/m05/t05/p10/croatian/m05-t05-p10-a05-b30-c05.ogv": "wPX9GTkMj8E", 37 | "course/assets/m05/t05/p10/german/m05-t05-p10-a05-b10-c05.ogv": "zu2a0FXVh8c", 38 | "course/assets/m05/t05/p10/czech/m05-t05-p10-a05-b20-c10.ogv": "6jSEGncQTs4", 39 | "course/assets/m05/t05/p10/spanish/m05-t05-p10-a05-b40-c05.ogv": "N89xDJyG15E", 40 | "course/assets/m05/t05/p10/german/m05-t05-p10-a05-b40-c10.ogv": "pRTWg1Yv_ko", 41 | "course/assets/m05/t05/p10/hindi/m05-t05-p10-a05-b10-c05.ogv": "akv0ylpKGbk", 42 | "course/assets/m05/t05/p10/hindi/m05-t05-p10-a05-b15-c10.ogv": "s64Wtw4e_8o", 43 | "course/assets/m05/t05/p10/english/m05-t05-p10-a05-b20-c05.ogv": "YarD9ZluQJU", 44 | "course/assets/m05/t05/p10/czech/m05-t05-p10-a05-b40-c05.ogv": "X2zr6dITLWo", 45 | "course/assets/m05/t05/p10/serbian/m05-t05-p10-a05-b30-c10.ogv": "c0XSya6I39M", 46 | "course/assets/m05/t05/p10/portuguese/m05-t05-p10-a05-b40-c05.ogv": "c_A92GmAN94", 47 | "course/assets/m05/t05/p10/croatian/m05-t05-p10-a05-b30-c10.ogv": "1fzu0Lf2XLo", 48 | "course/assets/m05/t05/p10/greek/m05-t05-p10-a05-b20-c05.ogv": "hcSxACM2Zoc", 49 | "course/assets/m05/t05/p10/croatian/m05-t05-p10-a05-b20-c05.ogv": "Jk26OjisREE", 50 | "course/assets/m05/t05/p10/serbian/m05-t05-p10-a05-b40-c05.ogv": "XRawOINb-1k", 51 | "course/assets/m05/t05/p10/italian/m05-t05-p10-a05-b40-c05.ogv": "CsnZIFzdoNM", 52 | "course/assets/m05/t05/p10/spanish/m05-t05-p10-a05-b20-c05.ogv": "ZrXfURfNK18", 53 | "course/assets/m05/t05/p10/english/m05-t05-p10-a05-b10-c10.mp4": "SijuLONPI9U", 54 | "course/assets/m05/t05/p10/italian/m05-t05-p10-a05-b10-c10.ogv": "cBUADOIP4ks", 55 | "course/assets/m05/t05/p10/italian/m05-t05-p10-a05-b20-c10.ogv": "-qmurnSReFE", 56 | "course/assets/m05/t05/p10/croatian/m05-t05-p10-a05-b10-c10.ogv": "Ha3TerLNPQI", 57 | "course/assets/m05/t05/p10/spanish/m05-t05-p10-a05-b40-c10.ogv": "mzOLVnujiJ0", 58 | "course/assets/m05/t05/p10/czech/m05-t05-p10-a05-b20-c05.ogv": "PNedT1D6BR0", 59 | "course/assets/m05/t05/p10/spanish/m05-t05-p10-a05-b20-c10.mp4": "rMX9JtfYoBg", 60 | "course/assets/m05/t05/p30/m05-t05-p30-a05-b10.ogv": "CqhwzZycdFU", 61 | "course/assets/m05/t05/p10/italian/m05-t05-p10-a05-b40-c10.ogv": "Z8ELi8wBR7o", 62 | "course/assets/m05/t05/p10/serbian/m05-t05-p10-a05-b10-c10.ogv": "Wur2jVusiDI", 63 | "course/assets/m05/t05/p10/hindi/m05-t05-p10-a05-b15-c05.ogv": "zNGaHn3dSm4", 64 | "course/assets/m05/t05/p10/croatian/m05-t05-p10-a05-b40-c05.ogv": "oZazouOvw8s", 65 | "course/assets/m05/t05/p10/spanish/m05-t05-p10-a05-b30-c10.ogv": "kAvoXC0XkPw", 66 | "course/assets/m05/t05/p05/m05-p05-a05-b05-c10.ogv": "z8IAfoqxOPI", 67 | "course/assets/m05/t05/p10/english/m05-t05-p10-a05-b15-c05.ogv": "eBDX7k0gkJg", 68 | "course/assets/m05/t05/p10/czech/m05-t05-p10-a05-b30-c10.ogv": "GoCINKDdAA0", 69 | "course/assets/m05/t05/p10/german/m05-t05-p10-a05-b20-c10.ogv": "c7t5vc16sck", 70 | "course/assets/m05/t05/p10/polish/m05-p10-a05-b20-c05.ogv": "R3yT3mLzW3E", 71 | "course/assets/m05/t05/p10/english/m05-t05-p10-a05-b10-c05.mp4": "ODCBbEbcCGM", 72 | "course/assets/m05/t05/p10/tamil/m05-t05-p10-a05-b10-c05.ogv": "vcSts5T-va4", 73 | "course/assets/m05/t05/p10/croatian/m05-t05-p10-a05-b20-c10.ogv": "Lc5AmjbdA-8", 74 | "course/assets/m05/t05/p10/serbian/m05-t05-p10-a05-b20-c10.ogv": "TM9BUmlD6co", 75 | "course/assets/m05/t05/p10/german/m05-t05-p10-a05-b30-c10.ogv": "JNVcmXmkF5M", 76 | "course/assets/m05/t05/p10/english/m05-t05-p10-a05-b20-c05.mp4": "-2V-hbYu9WQ", 77 | "course/assets/m05/t05/p10/english/m05-t05-p10-a05-b20-c10.mp4": "Dvne9wMwV6E", 78 | "course/assets/m05/t05/p10/greek/m05-t05-p10-a05-b40-c05.ogv": "ZmL0pLwhqqQ", 79 | "course/assets/m05/t05/p10/spanish/m05-t05-p10-a05-b30-c05.ogv": "RhOLzXfqeBw", 80 | "course/assets/m05/t05/p10/english/m05-t05-p10-a05-b15-c10.ogv": "t_yNwJDAgKw", 81 | "course/assets/m05/t05/p10/greek/m05-t05-p10-a05-b10-c10.ogv": "dBYWqVOxf8Q", 82 | "course/assets/m05/t05/p10/serbian/m05-t05-p10-a05-b20-c05.ogv": "dv4O2ju1vpA", 83 | "course/assets/m05/t05/p10/polish/m05-t05-p10-a05-b40-c10.ogv": "vE1MxioVBBM", 84 | "course/assets/m05/t05/p10/italian/m05-t05-p10-a05-b30-c10.ogv": "Oe9was4FQHA", 85 | "course/assets/m05/t05/p10/polish/m05-t05-p10-a05-b10-c10.ogv": "XYfESrBUkK8", 86 | "course/assets/m05/t05/p10/italian/m05-t05-p10-a05-b30-c05.ogv": "Xqkgb6-CHpM", 87 | "course/assets/m05/t05/p10/serbian/m05-t05-p10-a05-b40-c10.ogv": "XDwIdH4hrgI", 88 | "course/assets/m05/t05/p10/german/m05-t05-p10-a05-b20-c05.ogv": "0HWsDe_xfsM", 89 | "course/assets/m05/t05/p10/polish/m05-t05-p10-a05-b30-c05.ogv": "lodw2TvOOgU", 90 | "course/assets/m05/t05/p10/greek/m05-t05-p10-a05-b30-c10.ogv": "J--N_B5_Grk", 91 | "course/assets/m05/t05/p10/czech/m05-t05-p10-a05-b40-c10.ogv": "hEluAdC02mI", 92 | "course/assets/m05/t05/p10/hindi/m05-t05-p10-a05-b20-c05.ogv": "G5m4e0BR0JA", 93 | "course/assets/m05/t05/p10/polish/m05-t05-p10-a05-b30-c10.ogv": "jxN8ETP1c84", 94 | "course/assets/m05/t05/p10/serbian/m05-t05-p10-a05-b10-c05.ogv": "-8-yc1KUBMs", 95 | "course/assets/m05/t05/p10/english/m05-t05-p10-a05-b20-c10.ogv": "hGvNjO82y-4", 96 | "course/assets/m05/t05/p10/hindi/m05-t05-p10-a05-b20-c10.ogv": "rSOC6IAxyPk", 97 | "course/assets/m05/t05/p10/portuguese/m05-t05-p10-a05-b20-c05.ogv": "PCekhjtIxcs", 98 | "course/assets/m05/t05/p10/spanish/m05-t05-p10-a05-b10-c05.ogv": "whcfcevIexg" 99 | } 100 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "masterfirefoxos.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /masterfirefoxos/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/masterfirefoxos/32eb1f6ce0ed357de286e64839570e73e86ae136/masterfirefoxos/__init__.py -------------------------------------------------------------------------------- /masterfirefoxos/base/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = 'masterfirefoxos.base.app.BaseAppConfig' 2 | -------------------------------------------------------------------------------- /masterfirefoxos/base/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin, messages 2 | from django.forms import ValidationError 3 | 4 | from feincms.module.medialibrary.admin import MediaFileAdmin as MediaFileAdminOld 5 | from feincms.module.medialibrary.forms import MediaFileAdminForm as MediaFileAdminFormOld 6 | 7 | from feincms.module.medialibrary.models import MediaFile 8 | from feincms.module.page.admin import PageAdmin as PageAdminOld 9 | from feincms.module.page.models import Page 10 | 11 | from .utils import copy_tree 12 | 13 | 14 | class PageAdmin(PageAdminOld): 15 | save_on_top = True 16 | actions = ['copy_tree_admin_action'] 17 | list_editable = ['in_navigation'] 18 | list_display = ['short_title', 'slug', 'is_visible_admin', 'in_navigation', 19 | 'template'] 20 | 21 | def copy_tree_admin_action(self, request, queryset): 22 | if len(queryset) != 1: 23 | self.message_user(request, 'Select only one page to copy', level=messages.ERROR) 24 | return 25 | copy_tree(queryset[0]) 26 | copy_tree_admin_action.short_description = 'Copy tree' 27 | 28 | 29 | admin.site.unregister(Page) 30 | admin.site.register(Page, PageAdmin) 31 | 32 | 33 | class MediaFileAdminForm(MediaFileAdminFormOld): 34 | def clean(self, *args, **kwargs): 35 | cleaned_data = super(MediaFileAdminForm, self).clean(*args, **kwargs) 36 | 37 | if not cleaned_data.get('categories'): 38 | raise ValidationError('You must select at least one category.') 39 | 40 | return cleaned_data 41 | 42 | 43 | class MediaFileAdmin(MediaFileAdminOld): 44 | form = MediaFileAdminForm 45 | inlines = [] 46 | list_display = ['admin_thumbnail', '__str__', 'list_categories', 'formatted_created'] 47 | 48 | fieldsets = ( 49 | (None, {'fields': ('file', 'categories')}), 50 | ) 51 | 52 | def list_categories(self, obj): 53 | return ', '.join([category.title for category in obj.categories.all()]) 54 | 55 | 56 | admin.site.unregister(MediaFile) 57 | admin.site.register(MediaFile, MediaFileAdmin) 58 | -------------------------------------------------------------------------------- /masterfirefoxos/base/app.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | from .models import Page 4 | 5 | 6 | class BaseAppConfig(AppConfig): 7 | name = 'masterfirefoxos.base' 8 | 9 | def ready(self): 10 | Page.__str__ = lambda x: x.slug 11 | -------------------------------------------------------------------------------- /masterfirefoxos/base/context_processors.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings as django_settings 2 | from django.utils import translation 3 | 4 | 5 | def l18n(request): 6 | return { 7 | 'LANG': translation.get_language(), 8 | 'DIR': 'rtl' if translation.get_language_bidi() else 'ltr', 9 | } 10 | 11 | 12 | def settings(request): 13 | return { 14 | 'settings': django_settings, 15 | } 16 | -------------------------------------------------------------------------------- /masterfirefoxos/base/forms.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.forms import CharField, ModelChoiceField, Textarea 4 | from django.utils.safestring import mark_safe 5 | 6 | from feincms.admin.item_editor import FeinCMSInline 7 | from feincms.module.medialibrary.fields import MediaFileForeignKey 8 | from feincms.module.medialibrary.models import MediaFile 9 | 10 | from .utils import unmangle 11 | 12 | 13 | class CustomMediaFileTypeChoiceField(ModelChoiceField): 14 | def label_from_instance(self, obj): 15 | basename = os.path.basename(obj.file.name) 16 | basename = basename.rsplit('.')[0] 17 | return basename 18 | 19 | 20 | class TinyMCETextArea(Textarea): 21 | def render(self, name, value, attrs=None): 22 | widget_code = super(TinyMCETextArea, self).render( 23 | name, value, attrs) 24 | return mark_safe( 25 | '{}
Activate TinyMCE'.format(widget_code)) 26 | 27 | class Media: 28 | js = [ 29 | '//tinymce.cachefly.net/4.1/tinymce.min.js', 30 | 'js/init_tinymce.js' 31 | ] 32 | 33 | 34 | class Rodenticide(CharField): 35 | """ 36 | Undo the text mangling done by TinyMCE 37 | """ 38 | def to_python(self, value): 39 | return unmangle(super(Rodenticide, self).to_python(value)) 40 | 41 | 42 | class MediaFileInline(FeinCMSInline): 43 | 44 | def formfield_for_foreignkey(self, db_field, request, **kwargs): 45 | if isinstance(db_field, MediaFileForeignKey): 46 | return CustomMediaFileTypeChoiceField( 47 | MediaFile.objects.filter(type='image', categories__title='en'), 48 | required=not db_field.blank, **kwargs) 49 | return super(MediaFileInline, self).formfield_for_foreignkey(db_field, request, **kwargs) 50 | -------------------------------------------------------------------------------- /masterfirefoxos/base/middleware.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.http import HttpResponseRedirect 3 | 4 | 5 | class NonExistentLocaleRedirectionMiddleware(object): 6 | """Redirect to the 'en' version of a page if translation does not exist. 7 | 8 | This middleware redirects requests to pages with locale other than 9 | 'en' to their 'en' version. The map of available locales and 10 | documentation versions is maintained in 11 | settings.VERSIONS_LOCALE_MAP. 12 | 13 | settings.ENABLE_ALL_LANGUAGES will disable this middleware. 14 | """ 15 | 16 | def process_request(self, request): 17 | if settings.ENABLE_ALL_LANGUAGES or request.path.startswith('/en/'): 18 | return 19 | 20 | url_breakdown = request.path.split('/') 21 | try: 22 | version_slug = url_breakdown[2] 23 | locale = url_breakdown[1] 24 | except IndexError: 25 | # Normally we would never need handle this exception 26 | # because this middleware comes after LocaleMiddleware, 27 | # which because it adds locale in the url it makes the 28 | # url_breakdown to have a least 3 parts when split. But 29 | # let's play it safe and just return if that ever happens. 30 | return 31 | 32 | for version, data in settings.VERSIONS_LOCALE_MAP.items(): 33 | if data.get('slug') == version_slug: 34 | if locale not in data.get('locales'): 35 | url_breakdown[1] = 'en' 36 | new_path = '/'.join(url_breakdown) 37 | params = request.GET.copy() 38 | params['from-lang'] = locale 39 | latest = settings.LOCALE_LATEST_VERSION.get(locale) 40 | if latest: 41 | params['latest-version'] = latest['name'] 42 | return HttpResponseRedirect( 43 | '?'.join([new_path, params.urlencode()])) 44 | return 45 | -------------------------------------------------------------------------------- /masterfirefoxos/base/migrate/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/masterfirefoxos/32eb1f6ce0ed357de286e64839570e73e86ae136/masterfirefoxos/base/migrate/__init__.py -------------------------------------------------------------------------------- /masterfirefoxos/base/migrate/medialibrary/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django.utils.timezone 6 | import feincms.translations 7 | import feincms.extensions 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='Category', 18 | fields=[ 19 | ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), 20 | ('title', models.CharField(max_length=200, verbose_name='title')), 21 | ('slug', models.SlugField(max_length=150, verbose_name='slug')), 22 | ('parent', models.ForeignKey(related_name='children', to='medialibrary.Category', verbose_name='parent', null=True, blank=True)), 23 | ], 24 | options={ 25 | 'ordering': ['parent__title', 'title'], 26 | 'verbose_name_plural': 'categories', 27 | 'verbose_name': 'category', 28 | }, 29 | bases=(models.Model,), 30 | ), 31 | migrations.CreateModel( 32 | name='MediaFile', 33 | fields=[ 34 | ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), 35 | ('file', models.FileField(max_length=255, verbose_name='file', upload_to='medialibrary/%Y/%m/')), 36 | ('type', models.CharField(max_length=12, editable=False, choices=[('image', 'Image'), ('video', 'Video'), ('audio', 'Audio'), ('pdf', 'PDF document'), ('swf', 'Flash'), ('txt', 'Text'), ('rtf', 'Rich Text'), ('zip', 'Zip archive'), ('doc', 'Microsoft Word'), ('xls', 'Microsoft Excel'), ('ppt', 'Microsoft PowerPoint'), ('other', 'Binary')], verbose_name='file type')), 37 | ('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='created')), 38 | ('copyright', models.CharField(max_length=200, blank=True, verbose_name='copyright')), 39 | ('file_size', models.IntegerField(editable=False, null=True, blank=True, verbose_name='file size')), 40 | ('categories', models.ManyToManyField(to='medialibrary.Category', null=True, blank=True, verbose_name='categories')), 41 | ], 42 | options={ 43 | 'abstract': False, 44 | 'ordering': ['-created'], 45 | 'verbose_name_plural': 'media files', 46 | 'verbose_name': 'media file', 47 | }, 48 | bases=(models.Model, feincms.extensions.ExtensionsMixin, feincms.translations.TranslatedObjectMixin), 49 | ), 50 | migrations.CreateModel( 51 | name='MediaFileTranslation', 52 | fields=[ 53 | ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), 54 | ('language_code', models.CharField(default='bn', max_length=10, choices=[('bn', 'Bengali'), ('hr', 'Croatian'), ('cz', 'Czech'), ('en', 'English'), ('de', 'German'), ('el', 'Greek'), ('hi', 'Hindi'), ('hu', 'Hungarian'), ('it', 'Italian'), ('pl', 'Polish'), ('pt', 'Portuguese'), ('sr', 'Serbian'), ('es', 'Spanish'), ('ta', 'Tamil')], verbose_name='language')), 55 | ('caption', models.CharField(max_length=200, verbose_name='caption')), 56 | ('description', models.TextField(blank=True, verbose_name='description')), 57 | ('parent', models.ForeignKey(related_name='translations', to='medialibrary.MediaFile')), 58 | ], 59 | options={ 60 | 'verbose_name': 'media file translation', 61 | 'verbose_name_plural': 'media file translations', 62 | }, 63 | bases=(models.Model,), 64 | ), 65 | migrations.AlterUniqueTogether( 66 | name='mediafiletranslation', 67 | unique_together=set([('parent', 'language_code')]), 68 | ), 69 | ] 70 | -------------------------------------------------------------------------------- /masterfirefoxos/base/migrate/medialibrary/0002_auto_20150128_1239.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import masterfirefoxos.settings.base 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('medialibrary', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='mediafile', 17 | name='file', 18 | field=models.FileField(upload_to=masterfirefoxos.settings.base.media_files_unique_path, max_length=255, verbose_name='file'), 19 | preserve_default=True, 20 | ), 21 | migrations.AlterField( 22 | model_name='mediafiletranslation', 23 | name='language_code', 24 | field=models.CharField(default='bn', max_length=10, verbose_name='language', choices=[('bn', 'Bengali'), ('hr', 'Croatian'), ('cs', 'Czech'), ('en', 'English'), ('de', 'German'), ('el', 'Greek'), ('hi', 'Hindi'), ('hu', 'Hungarian'), ('it', 'Italian'), ('pl', 'Polish'), ('pt', 'Portuguese'), ('sr', 'Serbian'), ('es', 'Spanish'), ('ta', 'Tamil'), ('xx', 'Pirate')]), 25 | preserve_default=True, 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /masterfirefoxos/base/migrate/medialibrary/0003_auto_20150211_1120.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('medialibrary', '0002_auto_20150128_1239'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='mediafiletranslation', 16 | name='language_code', 17 | field=models.CharField(verbose_name='language', choices=[('bn', 'Bengali'), ('hr', 'Croatian'), ('cs', 'Czech'), ('en', 'English'), ('de', 'German'), ('el', 'Greek'), ('hi', 'Hindi'), ('hu', 'Hungarian'), ('it', 'Italian'), ('ja', 'Japanese'), ('pl', 'Polish'), ('pt', 'Portuguese'), ('sr', 'Serbian'), ('es', 'Spanish'), ('ta', 'Tamil'), ('xx', 'Pirate')], max_length=10, default='bn'), 18 | preserve_default=True, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /masterfirefoxos/base/migrate/medialibrary/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/masterfirefoxos/32eb1f6ce0ed357de286e64839570e73e86ae136/masterfirefoxos/base/migrate/medialibrary/__init__.py -------------------------------------------------------------------------------- /masterfirefoxos/base/migrate/page/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import feincms.extensions 6 | import feincms.module.medialibrary.fields 7 | import feincms.contrib.richtext 8 | import feincms.module.mixins 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ('medialibrary', '0001_initial'), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='FAQEntry', 20 | fields=[ 21 | ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), 22 | ('question', models.CharField(max_length=255)), 23 | ('answer', models.CharField(max_length=255)), 24 | ('region', models.CharField(max_length=255)), 25 | ('ordering', models.IntegerField(default=0, verbose_name='ordering')), 26 | ], 27 | options={ 28 | 'verbose_name': 'faq entry', 29 | 'abstract': False, 30 | 'ordering': ['ordering'], 31 | 'verbose_name_plural': 'faq entrys', 32 | 'db_table': 'page_page_faqentry', 33 | 'permissions': [], 34 | }, 35 | bases=(models.Model,), 36 | ), 37 | migrations.CreateModel( 38 | name='MediaParagraphEntry', 39 | fields=[ 40 | ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), 41 | ('title', models.CharField(max_length=255)), 42 | ('text', models.TextField()), 43 | ('region', models.CharField(max_length=255)), 44 | ('ordering', models.IntegerField(default=0, verbose_name='ordering')), 45 | ('type', models.CharField(default='default', verbose_name='type', choices=[('default', 'default')], max_length=20)), 46 | ('mediafile', feincms.module.medialibrary.fields.MediaFileForeignKey(verbose_name='media file', to='medialibrary.MediaFile', related_name='+')), 47 | ], 48 | options={ 49 | 'verbose_name': 'media paragraph entry', 50 | 'abstract': False, 51 | 'ordering': ['ordering'], 52 | 'verbose_name_plural': 'media paragraph entrys', 53 | 'db_table': 'page_page_mediaparagraphentry', 54 | 'permissions': [], 55 | }, 56 | bases=(models.Model,), 57 | ), 58 | migrations.CreateModel( 59 | name='Page', 60 | fields=[ 61 | ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), 62 | ('lft', models.PositiveIntegerField(editable=False, db_index=True)), 63 | ('rght', models.PositiveIntegerField(editable=False, db_index=True)), 64 | ('tree_id', models.PositiveIntegerField(editable=False, db_index=True)), 65 | ('level', models.PositiveIntegerField(editable=False, db_index=True)), 66 | ('active', models.BooleanField(default=True, verbose_name='active')), 67 | ('title', models.CharField(help_text='This title is also used for navigation menu items.', verbose_name='title', max_length=200)), 68 | ('slug', models.SlugField(help_text='This is used to build the URL for this page', verbose_name='slug', max_length=150)), 69 | ('in_navigation', models.BooleanField(default=False, verbose_name='in navigation')), 70 | ('override_url', models.CharField(blank=True, help_text="Override the target URL. Be sure to include slashes at the beginning and at the end if it is a local URL. This affects both the navigation and subpages' URLs.", verbose_name='override URL', max_length=255)), 71 | ('redirect_to', models.CharField(blank=True, help_text='Target URL for automatic redirects or the primary key of a page.', verbose_name='redirect to', max_length=255)), 72 | ('_cached_url', models.CharField(blank=True, default='', verbose_name='Cached URL', db_index=True, editable=False, max_length=255)), 73 | ('template_key', models.CharField(default='content.html', verbose_name='template', choices=[('content.html', 'Content template'), ('home.html', 'Home template')], max_length=255)), 74 | ('parent', models.ForeignKey(blank=True, verbose_name='Parent', to='page.Page', null=True, related_name='children')), 75 | ], 76 | options={ 77 | 'verbose_name': 'page', 78 | 'verbose_name_plural': 'pages', 79 | 'ordering': ['tree_id', 'lft'], 80 | }, 81 | bases=(models.Model, feincms.extensions.ExtensionsMixin, feincms.module.mixins.ContentModelMixin), 82 | ), 83 | migrations.CreateModel( 84 | name='RichTextEntry', 85 | fields=[ 86 | ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), 87 | ('text', feincms.contrib.richtext.RichTextField(blank=True, verbose_name='text')), 88 | ('region', models.CharField(max_length=255)), 89 | ('ordering', models.IntegerField(default=0, verbose_name='ordering')), 90 | ('parent', models.ForeignKey(to='page.Page', related_name='richtextentry_set')), 91 | ], 92 | options={ 93 | 'verbose_name': 'rich text entry', 94 | 'abstract': False, 95 | 'ordering': ['ordering'], 96 | 'verbose_name_plural': 'rich text entrys', 97 | 'db_table': 'page_page_richtextentry', 98 | 'permissions': [], 99 | }, 100 | bases=(models.Model,), 101 | ), 102 | migrations.CreateModel( 103 | name='YouTubeParagraphEntry', 104 | fields=[ 105 | ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), 106 | ('title', models.CharField(max_length=255)), 107 | ('text', models.TextField()), 108 | ('youtube_id', models.CharField(max_length=100)), 109 | ('region', models.CharField(max_length=255)), 110 | ('ordering', models.IntegerField(default=0, verbose_name='ordering')), 111 | ('parent', models.ForeignKey(to='page.Page', related_name='youtubeparagraphentry_set')), 112 | ], 113 | options={ 114 | 'verbose_name': 'you tube paragraph entry', 115 | 'abstract': False, 116 | 'ordering': ['ordering'], 117 | 'verbose_name_plural': 'you tube paragraph entrys', 118 | 'db_table': 'page_page_youtubeparagraphentry', 119 | 'permissions': [], 120 | }, 121 | bases=(models.Model,), 122 | ), 123 | migrations.AddField( 124 | model_name='mediaparagraphentry', 125 | name='parent', 126 | field=models.ForeignKey(to='page.Page', related_name='mediaparagraphentry_set'), 127 | preserve_default=True, 128 | ), 129 | migrations.AddField( 130 | model_name='faqentry', 131 | name='parent', 132 | field=models.ForeignKey(to='page.Page', related_name='faqentry_set'), 133 | preserve_default=True, 134 | ), 135 | ] 136 | -------------------------------------------------------------------------------- /masterfirefoxos/base/migrate/page/0002_auto_20150116_1205.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('page', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='richtextentry', 16 | name='parent', 17 | ), 18 | migrations.DeleteModel( 19 | name='RichTextEntry', 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /masterfirefoxos/base/migrate/page/0003_richtextentry.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('page', '0002_auto_20150116_1205'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='RichTextEntry', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, verbose_name='ID', serialize=False)), 18 | ('text', models.TextField()), 19 | ('region', models.CharField(max_length=255)), 20 | ('ordering', models.IntegerField(default=0, verbose_name='ordering')), 21 | ('parent', models.ForeignKey(related_name='richtextentry_set', to='page.Page')), 22 | ], 23 | options={ 24 | 'permissions': [], 25 | 'verbose_name': 'rich text entry', 26 | 'abstract': False, 27 | 'ordering': ['ordering'], 28 | 'verbose_name_plural': 'rich text entrys', 29 | 'db_table': 'page_page_richtextentry', 30 | }, 31 | bases=(models.Model,), 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /masterfirefoxos/base/migrate/page/0004_auto_20150116_1254.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('page', '0003_richtextentry'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='faqentry', 16 | name='answer', 17 | field=models.TextField(), 18 | preserve_default=True, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /masterfirefoxos/base/migrate/page/0005_auto_20150127_1425.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import sorl.thumbnail.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('page', '0004_auto_20150116_1254'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='mediaparagraphentry', 17 | name='mediafile', 18 | ), 19 | migrations.RemoveField( 20 | model_name='mediaparagraphentry', 21 | name='type', 22 | ), 23 | migrations.AddField( 24 | model_name='mediaparagraphentry', 25 | name='image', 26 | field=sorl.thumbnail.fields.ImageField(upload_to='', null=True), 27 | preserve_default=True, 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /masterfirefoxos/base/migrate/page/0006_auto_20150128_0414.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import sorl.thumbnail.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('page', '0005_auto_20150127_1425'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='QuizAnswer', 17 | fields=[ 18 | ('id', models.AutoField(serialize=False, primary_key=True, auto_created=True, verbose_name='ID')), 19 | ('answer', models.TextField()), 20 | ('correct', models.BooleanField(default=False)), 21 | ('region', models.CharField(max_length=255)), 22 | ('ordering', models.IntegerField(default=0, verbose_name='ordering')), 23 | ('parent', models.ForeignKey(related_name='quizanswer_set', to='page.Page')), 24 | ], 25 | options={ 26 | 'ordering': ['ordering'], 27 | 'verbose_name': 'quiz answer', 28 | 'verbose_name_plural': 'quiz answers', 29 | 'permissions': [], 30 | 'db_table': 'page_page_quizanswer', 31 | 'abstract': False, 32 | }, 33 | bases=(models.Model,), 34 | ), 35 | migrations.CreateModel( 36 | name='QuizQuestion', 37 | fields=[ 38 | ('id', models.AutoField(serialize=False, primary_key=True, auto_created=True, verbose_name='ID')), 39 | ('image', sorl.thumbnail.fields.ImageField(null=True, upload_to='', blank=True)), 40 | ('question', models.TextField()), 41 | ('correct_feedback', models.TextField()), 42 | ('incorrect_feedback', models.TextField()), 43 | ('partly_correct_feedback', models.TextField()), 44 | ('region', models.CharField(max_length=255)), 45 | ('ordering', models.IntegerField(default=0, verbose_name='ordering')), 46 | ('parent', models.ForeignKey(related_name='quizquestion_set', to='page.Page')), 47 | ], 48 | options={ 49 | 'ordering': ['ordering'], 50 | 'verbose_name': 'quiz question', 51 | 'verbose_name_plural': 'quiz questions', 52 | 'permissions': [], 53 | 'db_table': 'page_page_quizquestion', 54 | 'abstract': False, 55 | }, 56 | bases=(models.Model,), 57 | ), 58 | migrations.AddField( 59 | model_name='mediaparagraphentry', 60 | name='alt', 61 | field=models.CharField(max_length=255, default='', blank=True), 62 | preserve_default=True, 63 | ), 64 | ] 65 | -------------------------------------------------------------------------------- /masterfirefoxos/base/migrate/page/0007_auto_20150128_1239.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import feincms.module.medialibrary.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('medialibrary', '0002_auto_20150128_1239'), 12 | ('page', '0006_auto_20150128_0414'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='ImageParagraphEntry', 18 | fields=[ 19 | ('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)), 20 | ('alt', models.CharField(blank=True, max_length=255, default='')), 21 | ('title', models.CharField(max_length=255)), 22 | ('text', models.TextField()), 23 | ('region', models.CharField(max_length=255)), 24 | ('ordering', models.IntegerField(default=0, verbose_name='ordering')), 25 | ('image', feincms.module.medialibrary.fields.MediaFileForeignKey(to='medialibrary.MediaFile')), 26 | ('parent', models.ForeignKey(to='page.Page', related_name='imageparagraphentry_set')), 27 | ], 28 | options={ 29 | 'db_table': 'page_page_imageparagraphentry', 30 | 'abstract': False, 31 | 'ordering': ['ordering'], 32 | 'permissions': [], 33 | 'verbose_name': 'image paragraph entry', 34 | 'verbose_name_plural': 'image paragraph entrys', 35 | }, 36 | bases=(models.Model,), 37 | ), 38 | migrations.RemoveField( 39 | model_name='mediaparagraphentry', 40 | name='parent', 41 | ), 42 | migrations.DeleteModel( 43 | name='MediaParagraphEntry', 44 | ), 45 | ] 46 | -------------------------------------------------------------------------------- /masterfirefoxos/base/migrate/page/0008_remove_quizquestion_image.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('page', '0007_auto_20150128_1239'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='quizquestion', 16 | name='image', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /masterfirefoxos/base/migrate/page/0009_quizquestion_image.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import feincms.module.medialibrary.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('medialibrary', '0002_auto_20150128_1239'), 12 | ('page', '0008_remove_quizquestion_image'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='quizquestion', 18 | name='image', 19 | field=feincms.module.medialibrary.fields.MediaFileForeignKey(blank=True, to='medialibrary.MediaFile', null=True), 20 | preserve_default=True, 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /masterfirefoxos/base/migrate/page/0010_auto_20150211_0911.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('page', '0009_quizquestion_image'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='imageparagraphentry', 16 | name='subheader_2', 17 | field=models.CharField(max_length=255, blank=True), 18 | preserve_default=True, 19 | ), 20 | migrations.AddField( 21 | model_name='imageparagraphentry', 22 | name='subheader_3', 23 | field=models.CharField(max_length=255, blank=True), 24 | preserve_default=True, 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /masterfirefoxos/base/migrate/page/0011_auto_20150211_0919.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('page', '0010_auto_20150211_0911'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='youtubeparagraphentry', 16 | name='subheader_2', 17 | field=models.CharField(blank=True, max_length=255), 18 | preserve_default=True, 19 | ), 20 | migrations.AddField( 21 | model_name='youtubeparagraphentry', 22 | name='subheader_3', 23 | field=models.CharField(blank=True, max_length=255), 24 | preserve_default=True, 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /masterfirefoxos/base/migrate/page/0012_auto_20150211_1114.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('page', '0011_auto_20150211_0919'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='richtextentry', 16 | name='subheader_2', 17 | field=models.CharField(max_length=255, blank=True), 18 | preserve_default=True, 19 | ), 20 | migrations.AddField( 21 | model_name='richtextentry', 22 | name='subheader_3', 23 | field=models.CharField(max_length=255, blank=True), 24 | preserve_default=True, 25 | ), 26 | migrations.AddField( 27 | model_name='richtextentry', 28 | name='title', 29 | field=models.CharField(max_length=255, blank=True), 30 | preserve_default=True, 31 | ), 32 | migrations.AlterField( 33 | model_name='richtextentry', 34 | name='text', 35 | field=models.TextField(help_text='Yo'), 36 | preserve_default=True, 37 | ), 38 | ] 39 | -------------------------------------------------------------------------------- /masterfirefoxos/base/migrate/page/0013_auto_20150211_1119.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('page', '0012_auto_20150211_1114'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='faqentry', 16 | name='answer', 17 | field=models.TextField(help_text='HTML is allowed.'), 18 | preserve_default=True, 19 | ), 20 | migrations.AlterField( 21 | model_name='imageparagraphentry', 22 | name='text', 23 | field=models.TextField(help_text='HTML is allowed.'), 24 | preserve_default=True, 25 | ), 26 | migrations.AlterField( 27 | model_name='quizanswer', 28 | name='answer', 29 | field=models.TextField(help_text='HTML is allowed.'), 30 | preserve_default=True, 31 | ), 32 | migrations.AlterField( 33 | model_name='quizquestion', 34 | name='correct_feedback', 35 | field=models.TextField(help_text='HTML is allowed.'), 36 | preserve_default=True, 37 | ), 38 | migrations.AlterField( 39 | model_name='quizquestion', 40 | name='incorrect_feedback', 41 | field=models.TextField(help_text='HTML is allowed.'), 42 | preserve_default=True, 43 | ), 44 | migrations.AlterField( 45 | model_name='quizquestion', 46 | name='partly_correct_feedback', 47 | field=models.TextField(help_text='HTML is allowed.'), 48 | preserve_default=True, 49 | ), 50 | migrations.AlterField( 51 | model_name='quizquestion', 52 | name='question', 53 | field=models.TextField(help_text='HTML is allowed.'), 54 | preserve_default=True, 55 | ), 56 | migrations.AlterField( 57 | model_name='richtextentry', 58 | name='text', 59 | field=models.TextField(help_text='HTML is allowed.'), 60 | preserve_default=True, 61 | ), 62 | migrations.AlterField( 63 | model_name='youtubeparagraphentry', 64 | name='text', 65 | field=models.TextField(help_text='HTML is allowed.'), 66 | preserve_default=True, 67 | ), 68 | ] 69 | -------------------------------------------------------------------------------- /masterfirefoxos/base/migrate/page/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/masterfirefoxos/32eb1f6ce0ed357de286e64839570e73e86ae136/masterfirefoxos/base/migrate/page/__init__.py -------------------------------------------------------------------------------- /masterfirefoxos/base/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('page', '0009_quizquestion_image'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Locale', 16 | fields=[ 17 | ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), # noqa 18 | ('code', models.CharField(max_length=10, unique=True, choices=[('bn', 'Bengali'), ('hr', 'Croatian'), ('cs', 'Czech'), ('en', 'English'), ('de', 'German'), ('el', 'Greek'), ('hi', 'Hindi'), ('hu', 'Hungarian'), ('it', 'Italian'), ('ja', 'Japanese'), ('pl', 'Polish'), ('pt', 'Portuguese'), ('sr', 'Serbian'), ('es', 'Spanish'), ('ta', 'Tamil'), ('xx', 'Pirate')])), # noqa 19 | ('latest_version', models.ForeignKey(blank=True, null=True, to='page.Page')), # noqa 20 | ('pending_versions', models.ManyToManyField(blank=True, to='page.Page', related_name='pending_locales', null=True)), # noqa 21 | ('versions', models.ManyToManyField(blank=True, to='page.Page', related_name='locales', null=True)), # noqa 22 | ], 23 | options={ 24 | }, 25 | bases=(models.Model,), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /masterfirefoxos/base/migrations/0002_auto_20150218_1128.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('base', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterModelOptions( 15 | name='locale', 16 | options={'ordering': ('code',)}, 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /masterfirefoxos/base/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/masterfirefoxos/32eb1f6ce0ed357de286e64839570e73e86ae136/masterfirefoxos/base/migrations/__init__.py -------------------------------------------------------------------------------- /masterfirefoxos/base/models.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.db import models 3 | from django.db.models.signals import pre_save 4 | from django.dispatch import receiver 5 | from django.template.loader import render_to_string 6 | from django.utils.translation import ugettext as _ 7 | 8 | from jinja2 import Markup 9 | from feincms.module.medialibrary.fields import MediaFileForeignKey 10 | from feincms.module.medialibrary.models import MediaFile 11 | from feincms.module.page.models import Page 12 | 13 | from .forms import FeinCMSInline, MediaFileInline 14 | from .utils import youtube_embed_url 15 | 16 | 17 | Page.register_templates( 18 | { 19 | 'title': 'Content template', 20 | 'path': 'content.jinja', 21 | 'regions': ( 22 | ('main', 'Main content area'), 23 | ('homepage', 'Homepage navigation'), 24 | ), 25 | }, 26 | { 27 | 'title': 'Home template', 28 | 'path': 'home.jinja', 29 | 'regions': ( 30 | ('main', 'Main content area'), 31 | ), 32 | } 33 | ) 34 | 35 | 36 | class YouTubeParagraphEntry(models.Model): 37 | feincms_item_editor_inline = FeinCMSInline 38 | title = models.CharField(max_length=255) 39 | subheader_2 = models.CharField(max_length=255, blank=True) 40 | subheader_3 = models.CharField(max_length=255, blank=True) 41 | text = models.TextField(help_text='HTML is allowed.') 42 | youtube_id = models.CharField(max_length=100) 43 | _l10n_fields = ['title', 'text', 'subheader_2', 'subheader_3'] 44 | 45 | class Meta: 46 | abstract = True 47 | 48 | def render(self, **kwargs): 49 | return render_to_string( 50 | 'includes/videoparagraph.jinja', 51 | { 52 | 'title': _(self.title), 53 | 'subheader_2': _(self.subheader_2) if self.subheader_2 else '', 54 | 'subheader_3': _(self.subheader_3) if self.subheader_3 else '', 55 | 'text': Markup(_(self.text)), 56 | 'video': youtube_embed_url( 57 | kwargs.get('request'), self.youtube_id) 58 | } 59 | ) 60 | 61 | 62 | class ImageParagraphEntry(models.Model): 63 | feincms_item_editor_inline = MediaFileInline 64 | image = MediaFileForeignKey(MediaFile) 65 | alt = models.CharField(max_length=255, blank=True, default='') 66 | title = models.CharField(max_length=255) 67 | subheader_2 = models.CharField(max_length=255, blank=True) 68 | subheader_3 = models.CharField(max_length=255, blank=True) 69 | text = models.TextField(help_text='HTML is allowed.') 70 | _l10n_fields = ['alt', 'title', 'text', 'subheader_2', 'subheader_3'] 71 | 72 | class Meta: 73 | abstract = True 74 | 75 | def render(self, **kwargs): 76 | return render_to_string( 77 | 'includes/imageparagraph.jinja', 78 | { 79 | 'alt': _(self.alt) if self.alt else '', 80 | 'title': _(self.title), 81 | 'text': Markup(_(self.text)), 82 | 'subheader_2': _(self.subheader_2) if self.subheader_2 else '', 83 | 'subheader_3': _(self.subheader_3) if self.subheader_3 else '', 84 | 'image': self.image, 85 | } 86 | ) 87 | 88 | 89 | class FAQEntry(models.Model): 90 | feincms_item_editor_inline = FeinCMSInline 91 | question = models.CharField(max_length=255) 92 | answer = models.TextField(help_text='HTML is allowed.') 93 | _l10n_fields = ['question', 'answer'] 94 | 95 | class Meta: 96 | abstract = True 97 | 98 | def render(self, **kwargs): 99 | return render_to_string( 100 | 'includes/faqentry.jinja', 101 | { 102 | 'question': _(self.question), 103 | 'answer': Markup(_(self.answer)), 104 | } 105 | ) 106 | 107 | 108 | class RichTextEntry(models.Model): 109 | feincms_item_editor_inline = FeinCMSInline 110 | title = models.CharField(max_length=255, blank=True) 111 | subheader_2 = models.CharField(max_length=255, blank=True) 112 | subheader_3 = models.CharField(max_length=255, blank=True) 113 | text = models.TextField(help_text='HTML is allowed.') 114 | _l10n_fields = ['text', 'title', 'subheader_2', 'subheader_3'] 115 | 116 | class Meta: 117 | abstract = True 118 | 119 | def render(self, **kwargs): 120 | return render_to_string( 121 | 'includes/richtextentry.jinja', 122 | { 123 | 'title': _(self.title) if self.title else '', 124 | 'subheader_2': _(self.subheader_2) if self.subheader_2 else '', 125 | 'subheader_3': _(self.subheader_3) if self.subheader_3 else '', 126 | 'text': Markup(_(self.text)), 127 | } 128 | ) 129 | 130 | 131 | class QuizQuestion(models.Model): 132 | feincms_item_editor_inline = MediaFileInline 133 | image = MediaFileForeignKey( 134 | MediaFile, 135 | limit_choices_to=models.Q(type='image', categories__title='en'), 136 | blank=True, null=True) 137 | question = models.TextField(help_text='HTML is allowed.') 138 | correct_feedback = models.TextField(help_text='HTML is allowed.') 139 | incorrect_feedback = models.TextField(help_text='HTML is allowed.') 140 | partly_correct_feedback = models.TextField(help_text='HTML is allowed.') 141 | _l10n_fields = ['question', 'correct_feedback', 'incorrect_feedback', 142 | 'partly_correct_feedback'] 143 | 144 | class Meta: 145 | abstract = True 146 | 147 | def render(self, **kwargs): 148 | return render_to_string( 149 | 'includes/quizquestion.jinja', {'question': self}) 150 | 151 | 152 | class QuizAnswer(models.Model): 153 | feincms_item_editor_inline = FeinCMSInline 154 | answer = models.TextField(help_text='HTML is allowed.') 155 | correct = models.BooleanField(default=False) 156 | _l10n_fields = ['answer'] 157 | 158 | class Meta: 159 | abstract = True 160 | 161 | def render(self, **kwargs): 162 | return render_to_string( 163 | 'includes/quizanswer.jinja', {'answer': self}) 164 | 165 | 166 | Page.create_content_type(RichTextEntry) 167 | Page.create_content_type(ImageParagraphEntry) 168 | Page.create_content_type(FAQEntry) 169 | Page.create_content_type(YouTubeParagraphEntry) 170 | Page.create_content_type(QuizQuestion) 171 | Page.create_content_type(QuizAnswer) 172 | 173 | 174 | @receiver(pre_save, dispatch_uid='trim_content_signal') 175 | def trim_content(sender, instance, **kwargs): 176 | """Trim spaces from TextFields and CharFields before saving.""" 177 | if sender in Page._feincms_content_types: 178 | for field in instance._meta.fields: 179 | if (isinstance(field, models.TextField) or 180 | isinstance(field, models.CharField)): 181 | value = getattr(instance, field.name, '') 182 | setattr(instance, field.name, value.strip()) 183 | 184 | 185 | class Locale(models.Model): 186 | code = models.CharField(max_length=10, choices=settings.LANGUAGES, unique=True) 187 | latest_version = models.ForeignKey( 188 | Page, blank=True, null=True, 189 | limit_choices_to={'parent__isnull': True}) 190 | versions = models.ManyToManyField( 191 | Page, blank=True, null=True, 192 | limit_choices_to={'parent__isnull': True}, 193 | related_name='locales') 194 | pending_versions = models.ManyToManyField( 195 | Page, blank=True, null=True, 196 | limit_choices_to={'parent__isnull': True}, 197 | related_name='pending_locales') 198 | 199 | def __str__(self): 200 | return '{} ({})'.format(settings.LANGUAGE_NAMES[self.code], self.code) 201 | 202 | class Meta: 203 | ordering = ('code',) 204 | -------------------------------------------------------------------------------- /masterfirefoxos/base/static/css/README.md: -------------------------------------------------------------------------------- 1 | Editing CSS 2 | =========== 3 | 4 | The Master Firefox OS site uses [Stylus](http://learnboost.github.io/stylus/) 5 | for CSS preprocessing. This folder contains the processed CSS files. 6 | 7 | **Do not edit these CSS files directly.** Any changes will be overwritten 8 | the next time the Stylus files are compiled. 9 | 10 | To modify the site's styling, edit the Stylus files in 11 | ``/masterfirefoxos/base/static/stylus/`` and compile to this folder. 12 | -------------------------------------------------------------------------------- /masterfirefoxos/base/static/css/home.css: -------------------------------------------------------------------------------- 1 | .home-entry .link{margin:1.5em 0}.home .masthead{background-image:none;}.home .masthead .contain{padding-bottom:3em}.home .masthead .nav-info{position:absolute;top:auto;bottom:20px}.home .page-intro{margin-top:20px;}.home .page-intro h2{font-size:36px;font-size:2.25rem}@media screen and (min-width:480px){.home .masthead .contain{padding-bottom:0}.home .masthead .nav-info{top:10px;bottom:auto}}@media screen and (min-width:760px){.home .masthead{min-height:420px;margin-bottom:20px;position:relative;overflow:hidden;}.home .masthead:before{content:'';display:block;background:url("/static/img/fox-swoop.png") no-repeat;position:absolute;left:calc(50% + 40px);right:auto;transform:none;top:145px;width:590px;height:670px}html[dir='rtl'] .home .masthead:before{left:auto}html[dir='rtl'] .home .masthead:before{right:calc(50% + 40px)}html[dir='rtl'] .home .masthead:before{transform:scaleX(-1)}.home .page-intro{float:left;width:340px;margin-left:10px;margin-right:10px;clear:left;}html[dir='rtl'] .home .page-intro{float:right}.home .page-intro h2{font-size:48px;font-size:3rem}.home .main{float:none;width:auto;margin-left:10px;margin-right:10px;margin:0}.home-entry{float:left;width:280px;margin-left:10px;margin-right:10px;margin-bottom:2em;}html[dir='rtl'] .home-entry{float:right}.home-entry:nth-child(even){margin-left:70px;}html[dir='rtl'] .home-entry:nth-child(even){margin-left:10px;margin-right:70px}.home-entry:nth-child(odd){clear:left;}html[dir='rtl'] .home-entry:nth-child(odd){clear:right}}@media screen and (min-width:1000px){.home .page-intro{float:left;width:460px;margin-left:10px;margin-right:10px;}html[dir='rtl'] .home .page-intro{float:right}.home .page-intro h2{font-size:64px;font-size:4rem}.home-entry{float:left;width:380px;margin-left:10px;margin-right:10px;}html[dir='rtl'] .home-entry{float:right}.home-entry:nth-child(even){margin-left:90px;}html[dir='rtl'] .home-entry:nth-child(even){margin-left:10px;margin-right:90px}}@media only screen and (-webkit-min-device-pixel-ratio:1.5) and (min-width:760px),only screen and (min-resolution:1.5dppx) and (min-width:760px),only screen and (min-resolution:144dpi) and (min-width:760px){.home .masthead:before{background-image:url("/static/img/fox-swoop-hr.png");-webkit-background-size:590px 670px;background-size:590px 670px}} 2 | /*# sourceMappingURL=home.css.map */ -------------------------------------------------------------------------------- /masterfirefoxos/base/static/css/oldIE.css: -------------------------------------------------------------------------------- 1 | .section,.contain,.footer,.main,.aside{zoom:1}.contain{width:720px}.main{float:left;width:460px;margin-left:10px;margin-right:10px;margin-left:70px;}html[dir='rtl'] .main{float:right}html[dir='rtl'] .main{margin-left:10px;margin-right:70px}.aside{float:left;width:160px;margin-left:10px;margin-right:10px}html[dir='rtl'] .aside{float:right}.site-logo{width:225px;float:left}.site-switcher{padding:0;width:300px;float:right;text-align:right}.nav-info{position:absolute;top:10px;bottom:auto;right:170px;font-size:16px;font-size:1rem;}.nav-info ul{margin:0}.nav-main .menu-icon{display:none}.nav-main ul{height:auto;overflow:visible;max-height:auto;transition:none}.video-frame iframe{width:460px;height:259px}.media-image{padding-left:180px;padding-right:0;}html[dir='rtl'] .media-image{padding-left:0}html[dir='rtl'] .media-image{padding-right:180px}.media-image img{float:left;margin-left:-185px;margin-right:0;width:160px}html[dir='rtl'] .media-image img{float:right}html[dir='rtl'] .media-image img{margin-left:0}html[dir='rtl'] .media-image img{margin-right:-185px}.footer-license,.footer-lang{float:left;width:160px;margin-left:10px;margin-right:10px}html[dir='rtl'] .footer-license,html[dir='rtl'] .footer-lang{float:right}.footer-logo,.footer-nav{float:left;width:100px;margin-left:10px;margin-right:10px}html[dir='rtl'] .footer-logo,html[dir='rtl'] .footer-nav{float:right}.footer-license{margin-right:60px}.quiz-answer input{left:10px;top:10px;background:transparent none;border:none;padding:0}.quiz-answer label{background:#e9edf0;zoom:1;}.quiz-answer label:hover{background:#eef1f4}.quiz-answer input:checked + label:before{content:''}.home .masthead{background-image:none;min-height:1px;margin-bottom:20px;}.home .masthead .contain{min-height:0}.home .page-intro{clear:both;}.home .page-intro h2{font-size:48px;font-size:3rem}.home .main{float:none;width:auto;margin-left:10px;margin-right:10px;margin:0} 2 | /*# sourceMappingURL=oldIE.css.map */ -------------------------------------------------------------------------------- /masterfirefoxos/base/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/masterfirefoxos/32eb1f6ce0ed357de286e64839570e73e86ae136/masterfirefoxos/base/static/img/favicon.ico -------------------------------------------------------------------------------- /masterfirefoxos/base/static/img/footer-mozilla.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/masterfirefoxos/32eb1f6ce0ed357de286e64839570e73e86ae136/masterfirefoxos/base/static/img/footer-mozilla.png -------------------------------------------------------------------------------- /masterfirefoxos/base/static/img/fox-swoop-hr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/masterfirefoxos/32eb1f6ce0ed357de286e64839570e73e86ae136/masterfirefoxos/base/static/img/fox-swoop-hr.png -------------------------------------------------------------------------------- /masterfirefoxos/base/static/img/fox-swoop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/masterfirefoxos/32eb1f6ce0ed357de286e64839570e73e86ae136/masterfirefoxos/base/static/img/fox-swoop.png -------------------------------------------------------------------------------- /masterfirefoxos/base/static/img/fxos-logo-hr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/masterfirefoxos/32eb1f6ce0ed357de286e64839570e73e86ae136/masterfirefoxos/base/static/img/fxos-logo-hr.png -------------------------------------------------------------------------------- /masterfirefoxos/base/static/img/fxos-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/masterfirefoxos/32eb1f6ce0ed357de286e64839570e73e86ae136/masterfirefoxos/base/static/img/fxos-logo.png -------------------------------------------------------------------------------- /masterfirefoxos/base/static/img/menu-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | ]> 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /masterfirefoxos/base/static/img/tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/masterfirefoxos/32eb1f6ce0ed357de286e64839570e73e86ae136/masterfirefoxos/base/static/img/tab.png -------------------------------------------------------------------------------- /masterfirefoxos/base/static/js/ga.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 6 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 7 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 8 | })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); 9 | 10 | ga('create', 'UA-35433268-38', 'auto'); 11 | ga('send', 'pageview'); 12 | -------------------------------------------------------------------------------- /masterfirefoxos/base/static/js/init_tinymce.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var tinymce_added = {}; 3 | 4 | django.jQuery(document).ready(function() { 5 | tinyMCE.init({ 6 | height: '300', 7 | paste_auto_cleanup_on_paste: false, 8 | relative_urls: false, 9 | invalid_elements: 'script', 10 | entity_encoding : "raw", 11 | extended_valid_elements : "-p", 12 | plugins: [ 13 | "code", "link" 14 | ], 15 | toolbar: "undo redo | styleselect | bold italic underline | bullist numlist | link | code", 16 | statusbar: false, 17 | menubar : false, 18 | style_formats: [ 19 | { 20 | title: "Headers", 21 | items: [ 22 | {title: "Header 1",format: "h1"}, 23 | {title: "Header 2",format: "h2"}, 24 | {title: "Header 3",format: "h3"}, 25 | {title: "Header 4",format: "h4"}, 26 | {title: "Header 5",format: "h5"}, 27 | {title: "Header 6",format: "h6"} 28 | ] 29 | }, 30 | { 31 | title: "Inline", 32 | items: [ 33 | {title: "Bold", icon: "bold", format: "bold"}, 34 | {title: "Italic", icon: "italic", format: "italic"}, 35 | {title: "Underline", icon: "underline", format: "underline"}, 36 | {title: "Strikethrough", icon: "strikethrough", format: "strikethrough"}, 37 | {title: "Superscript", icon: "superscript", format: "superscript"}, 38 | {title: "Subscript", icon: "subscript", format: "subscript"}, 39 | {title: "Code", icon: "code", format: "code"} 40 | ] 41 | }, 42 | { 43 | title: "Blocks", 44 | items: [ 45 | {title: "Paragraph", format: "p"}, 46 | {title: "Blockquote", format: "blockquote"}, 47 | {title: "Div", format: "div"}, 48 | {title: "Pre", format: "pre"} 49 | ] 50 | } 51 | ] 52 | }); 53 | // contentblock_init_handlers.push(richtext_init_fn); 54 | 55 | django.jQuery('.activate-tinymce').on('click', function(event) { 56 | event.preventDefault(); 57 | var textfield = django.jQuery(this).siblings()[1] 58 | tinyMCE.execCommand('mceAddEditor', false, textfield.id); 59 | django.jQuery(this).remove() 60 | }); 61 | }); 62 | })(); 63 | -------------------------------------------------------------------------------- /masterfirefoxos/base/static/js/libs/html5shiv.js: -------------------------------------------------------------------------------- 1 | (function(g,b){function k(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function l(a){var c={},f=a.createElement,b=a.createDocumentFragment,d=b();a.createElement=function(a){if(!e.shivMethods)return f(a);var b;b=c[a]?c[a].cloneNode():m.test(a)?(c[a]=f(a)).cloneNode():f(a);return b.canHaveChildren&&!n.test(a)?d.appendChild(b):b};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+k().join().replace(/\w+/g,function(a){f(a); 2 | d.createElement(a);return'c("'+a+'")'})+");return n}")(e,d)}function h(a){var c;if(a.documentShived)return a;if(e.shivCSS&&!i){c=a.createElement("p");var b=a.getElementsByTagName("head")[0]||a.documentElement;c.innerHTML="x";c=!!b.insertBefore(c.lastChild,b.firstChild)}j||(c=!l(a));if(c)a.documentShived=c;return a}var d=g.html5||{},n=/^<|^(?:button|form|map|select|textarea|object|iframe|option|optgroup)$/i, 3 | m=/^<|^(?:a|b|button|code|div|fieldset|form|h1|h2|h3|h4|h5|h6|i|iframe|img|input|label|li|link|ol|option|p|param|q|script|select|span|strong|style|table|tbody|td|textarea|tfoot|th|thead|tr|ul)$/i,i,j;(function(){var a=b.createElement("a");a.innerHTML="";i="hidden"in a;if(!(a=1==a.childNodes.length))a:{try{b.createElement("a")}catch(c){a=!0;break a}a=b.createDocumentFragment();a="undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}j= 4 | a})();var e={elements:d.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup main mark meter nav output progress section summary time video",shivCSS:!1!==d.shivCSS,shivMethods:!1!==d.shivMethods,type:"default",shivDocument:h};g.html5=e;h(b)})(this,document); 5 | -------------------------------------------------------------------------------- /masterfirefoxos/base/static/js/main.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | ;(function() { 6 | 'use strict'; 7 | 8 | // Add class to reflect javascript availability for CSS 9 | document.documentElement.className = document.documentElement.className.replace(/\bno-js\b/, 'js'); 10 | 11 | // A container for text strings to use in scripts 12 | var strings = document.getElementById('strings'); 13 | 14 | // override clicks on the hamburger menu icon 15 | var menu_icon = document.querySelector('.menu-icon'); 16 | if (menu_icon) { 17 | menu_icon.onclick = function() { 18 | return false; 19 | }; 20 | } 21 | 22 | // for the language and version switcher 23 | function selectChangeLocation(id) { 24 | var select = document.getElementById(id); 25 | var selected = select.options[select.selectedIndex]; 26 | select.addEventListener('change', function(event) { 27 | window.location = window.location.pathname.replace( 28 | selected.value, event.target.value); 29 | }); 30 | } 31 | selectChangeLocation('language'); 32 | selectChangeLocation('version'); 33 | 34 | // get the next sibling element 35 | function nextElementSiblingMatching(element, selector) { 36 | while (element && element.nextElementSibling && 37 | !element.nextElementSibling.matches(selector)) { 38 | element = element.nextElementSibling; 39 | } 40 | if (element && element.nextElementSibling && 41 | element.nextElementSibling.matches(selector)) { 42 | return element.nextElementSibling; 43 | } 44 | } 45 | 46 | // get the previous sibling element 47 | function previousElementSiblingMatching(element, selector) { 48 | while (element && element.previousElementSibling && 49 | !element.previousElementSibling.matches(selector)) { 50 | element = element.previousElementSibling; 51 | } 52 | if (element && element.previousElementSibling && 53 | element.previousElementSibling.matches(selector)) { 54 | return element.previousElementSibling; 55 | } 56 | } 57 | 58 | // find the last sibling element in a matched set 59 | function lastContiguousElementSiblingMatching(element, selector) { 60 | while (element && element.nextElementSibling && 61 | element.nextElementSibling.matches(selector)) { 62 | element = element.nextElementSibling; 63 | } 64 | if (element && element.matches(selector)) { 65 | return element; 66 | } 67 | } 68 | 69 | // get the last answer in a set of sibling answers 70 | function lastContiguousAnswer(element) { 71 | return lastContiguousElementSiblingMatching( 72 | nextElementSiblingMatching(element, '.quiz-answer'), '.quiz-answer'); 73 | } 74 | 75 | // determine if the selected answer is correct 76 | // (tip: you can totally cheat by viewing source!) 77 | function isCorrect(answer) { 78 | var input = answer.querySelector('input'); 79 | if (answer.matches('.correct')) { 80 | return input.checked; 81 | } 82 | return !input.checked; 83 | } 84 | 85 | // show the appropriate feedback for the selected answer 86 | function showFeedback(submitElement) { 87 | var correct = 0; 88 | var incorrect = 0; 89 | var question = previousElementSiblingMatching(submitElement.parentElement, '.quiz-question'); 90 | var correctFeedback = nextElementSiblingMatching(question, '.feedback-correct'); 91 | var incorrectFeedback = nextElementSiblingMatching(question, '.feedback-incorrect'); 92 | var partlyFeedback = nextElementSiblingMatching(question, '.feedback-partial'); 93 | var answer = nextElementSiblingMatching(question, '.quiz-answer'); 94 | 95 | while (answer && answer.matches('.quiz-answer')) { 96 | if (isCorrect(answer)) { 97 | correct++; 98 | } else { 99 | incorrect++; 100 | } 101 | answer = answer.nextElementSibling; 102 | } 103 | 104 | if (correct) { 105 | if (incorrect) { 106 | partlyFeedback.style.display = 'block'; 107 | correctFeedback.style.display = 'none'; 108 | incorrectFeedback.style.display = 'none'; 109 | } else { 110 | correctFeedback.style.display = 'block'; 111 | incorrectFeedback.style.display = 'none'; 112 | partlyFeedback.style.display = 'none'; 113 | } 114 | } else { 115 | incorrectFeedback.style.display = 'block'; 116 | correctFeedback.style.display = 'none'; 117 | partlyFeedback.style.display = 'none'; 118 | } 119 | } 120 | 121 | // add a submit button after the last answer 122 | function addSubmitToEachLastAnswer() { 123 | var answer = document.querySelector('.quiz-answer'); 124 | 125 | while (answer) { 126 | answer = lastContiguousAnswer(answer); 127 | if (answer) { 128 | var submit = document.createElement('p'); 129 | submit.classList.add('quiz-submit'); 130 | var submitBtn = submit.appendChild(document.createElement('button')); 131 | submitBtn.appendChild(document.createTextNode(strings.dataset.submit)); 132 | answer.parentNode.insertBefore(submit, answer.nextSibling); 133 | submitBtn.addEventListener('click', function(event) { 134 | showFeedback(event.target); 135 | }); 136 | } 137 | } 138 | } 139 | addSubmitToEachLastAnswer(); 140 | 141 | // Show/hide FAQ answers 142 | function faqToggle(event) { 143 | if (event.target.parentNode.classList.contains('open')) { 144 | event.target.parentNode.classList.remove('open'); 145 | } else { 146 | event.target.parentNode.classList.add('open'); 147 | } 148 | } 149 | 150 | var faqQuestions = document.getElementsByClassName('faq-question'); 151 | if (faqQuestions) { 152 | for (var i = 0; i < faqQuestions.length; i++) { 153 | faqQuestions[i].addEventListener('click', faqToggle); 154 | } 155 | } 156 | 157 | 158 | })(); 159 | -------------------------------------------------------------------------------- /masterfirefoxos/base/static/revision.txt: -------------------------------------------------------------------------------- 1 | # Placeholder. Will be replaced by ./bin/run-docker.sh 2 | -------------------------------------------------------------------------------- /masterfirefoxos/base/static/stylus/faq.styl: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | // FAQ pages 6 | 7 | @require 'lib'; 8 | 9 | 10 | .faq { 11 | margin-bottom: .25em; 12 | } 13 | 14 | .faq-question { 15 | background: rgba(255, 255, 255, .25); 16 | cursor: pointer; 17 | display: block; 18 | font-size: 20px; 19 | margin: 0; 20 | bidi-value(padding, .5em 10px .5em 45px, .5em 45px .5em 10px); 21 | position: relative; 22 | 23 | &:hover { 24 | background: rgba(255, 255, 255, .5); 25 | } 26 | 27 | &:before { 28 | color: $dark-gray; 29 | display: block; 30 | font-size: 35px; 31 | text-align: center; 32 | content: '\25b8'; // right triangle 33 | width: 25px; 34 | height: 100%; 35 | line-height: 1.2; 36 | position: absolute; 37 | top: 0; 38 | bidi-value(left, 5px, auto); 39 | bidi-value(right, auto, 5px); 40 | bidi-value(transform, none, scaleX(-1)); 41 | } 42 | } 43 | 44 | .faq-answer { 45 | padding: 1em 10px 2em 45px; 46 | } 47 | 48 | .js .faq-answer { 49 | display: none; 50 | } 51 | 52 | .open { 53 | .faq-question:before { 54 | content: '\25BE'; // down triangle 55 | } 56 | 57 | .faq-answer { 58 | display: block; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /masterfirefoxos/base/static/stylus/fonts-ar-fa.styl: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | 6 | // Declare webfonts for Arabic and Persian (Farsi) 7 | 8 | @font-face { 9 | font-family: 'Droid Naskh'; 10 | src: local('DroidArabicNaskh'), url('https://mozorg.cdn.mozilla.net/media/fonts/l10n/DroidNaskh-Regular.woff') format('woff'); 11 | font-weight: normal; 12 | } 13 | 14 | @font-face { 15 | font-family: 'Droid Naskh'; 16 | src: local('DroidArabicNaskh'), url('https://mozorg.cdn.mozilla.net/media/fonts/l10n/DroidNaskh-Bold.woff') format('woff'); 17 | font-weight: bold; 18 | } 19 | 20 | body { 21 | font-family: 'Fira Sans', 'Droid Naskh', Calibri, sans-serif; 22 | } 23 | 24 | h1, h2, h3, h4, h5, h6, legend, dl.faq dt { 25 | font-family: 'Fira Sans Light', 'Droid Naskh', Calibri, sans-serif; 26 | } 27 | -------------------------------------------------------------------------------- /masterfirefoxos/base/static/stylus/fonts.styl: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | 6 | // Declare webfonts. 7 | // We're omitting EOT files since this site delivers a separate stylesheet 8 | // to old versions of IE (IE9 and up support WOFF). 9 | 10 | // Regular 11 | @font-face { 12 | font-family: 'Fira Sans'; 13 | src: local('Fira Sans Regular'), url('https://mozorg.cdn.mozilla.net/media/fonts/FiraSans-Regular.woff') format('woff'); 14 | font-weight: normal; 15 | font-style: normal; 16 | } 17 | 18 | // Bold 19 | @font-face { 20 | font-family: 'Fira Sans'; 21 | src: local('Fira Sans Bold'), url('https://mozorg.cdn.mozilla.net/media/fonts/FiraSans-Bold.woff') format('woff'); 22 | font-weight: bold; 23 | font-style: normal; 24 | } 25 | 26 | // Italic 27 | @font-face { 28 | font-family: 'Fira Sans'; 29 | src: local('Fira Sans Italic'), url('https://mozorg.cdn.mozilla.net/media/fonts/FiraSans-Italic.woff') format('woff'); 30 | font-weight: normal; 31 | font-style: italic; 32 | } 33 | 34 | // Bold Italic 35 | @font-face { 36 | font-family: 'Fira Sans'; 37 | src: local('Fira Sans Bold Italic'), url('https://mozorg.cdn.mozilla.net/media/fonts/FiraSans-BoldItalic.woff') format('woff'); 38 | font-weight: bold; 39 | font-style: italic; 40 | } 41 | 42 | // Light 43 | @font-face { 44 | font-family: 'Fira Sans Light'; 45 | src: local('Fira Sans Light'), url('https://mozorg.cdn.mozilla.net/media/fonts/FiraSans-Light.woff') format('woff'); 46 | font-weight: 400; 47 | font-style: normal; 48 | } 49 | 50 | // Light Italic 51 | @font-face { 52 | font-family: 'Fira Sans Light'; 53 | src: local('Fira Sans Light Italic'), url('https://mozorg.cdn.mozilla.net/media/fonts/FiraSans-LightItalic.woff') format('woff'); 54 | font-weight: 400; 55 | font-style: italic; 56 | } 57 | -------------------------------------------------------------------------------- /masterfirefoxos/base/static/stylus/home.styl: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | // Home page 6 | 7 | @require 'lib'; 8 | 9 | 10 | .home-entry { 11 | .link { 12 | margin: 1.5em 0; 13 | } 14 | } 15 | 16 | .home { 17 | .masthead { 18 | background-image: none; 19 | 20 | .contain { 21 | padding-bottom: 3em; 22 | } 23 | 24 | .nav-info { 25 | position: absolute; 26 | top: auto; 27 | bottom: 20px; 28 | } 29 | } 30 | 31 | .page-intro { 32 | margin-top: 20px; 33 | 34 | h2 { 35 | font-size: 36px; 36 | } 37 | } 38 | 39 | } 40 | 41 | 42 | 43 | //*----------------------------------------------------------------*/ 44 | // @Mobile @Landscape layout: 480px 45 | 46 | @media $media-query-mobile-wide { 47 | 48 | .home .masthead { 49 | .contain { 50 | padding-bottom: 0; 51 | } 52 | 53 | .nav-info { 54 | top: 10px; 55 | bottom: auto; 56 | } 57 | } 58 | 59 | } 60 | 61 | 62 | 63 | //*----------------------------------------------------------------*/ 64 | // @Tablet layout: 760px 65 | 66 | @media $media-query-tablet { 67 | 68 | .home { 69 | 70 | .masthead { 71 | min-height: 420px; 72 | margin-bottom: 20px; 73 | position: relative; 74 | overflow: hidden; 75 | 76 | &:before { 77 | content: ''; 78 | display: block; 79 | background: url('/static/img/fox-swoop.png') no-repeat; 80 | position: absolute; 81 | bidi-value(left, calc(50% + 40px), auto); 82 | bidi-value(right, auto, calc(50% + 40px)); 83 | bidi-value(transform, none, scaleX(-1)); 84 | top: 145px; 85 | width: 590px; 86 | height: 670px; 87 | } 88 | } 89 | 90 | .page-intro { 91 | span_narrow(6); 92 | clear: left; 93 | 94 | h2 { 95 | font-size: 48px; 96 | } 97 | } 98 | 99 | .main { 100 | span_all(); 101 | margin: 0; 102 | } 103 | } 104 | 105 | .home-entry { 106 | span_narrow(5); 107 | margin-bottom: 2em; 108 | 109 | &:nth-child(even) { 110 | offset_narrow(1); 111 | } 112 | 113 | &:nth-child(odd) { 114 | bidi-value(clear, left, right); 115 | } 116 | } 117 | 118 | } 119 | 120 | 121 | 122 | //*----------------------------------------------------------------*/ 123 | // @Desktop layout: 1000px 124 | 125 | @media $media-query-desktop { 126 | 127 | .home { 128 | .page-intro { 129 | span(6); 130 | 131 | h2 { 132 | font-size: 64px; 133 | } 134 | } 135 | } 136 | 137 | .home-entry { 138 | span(5); 139 | 140 | &:nth-child(even) { 141 | offset(1); 142 | } 143 | 144 | } 145 | 146 | } 147 | 148 | @media $media-query-highres-large { 149 | 150 | .home .masthead:before { 151 | background-image: url('/static/img/fox-swoop-hr.png'); 152 | -webkit-background-size: 590px 670px; 153 | background-size: 590px 670px; 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /masterfirefoxos/base/static/stylus/lib.styl: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | 6 | // Define common variables and mixins 7 | 8 | // Typography 9 | // Respect the user's preferences by sizing with rems. 10 | $base-font-size = 100%; 11 | 12 | // Convert font-size units to rems if the unit is in pixels. 13 | // Declare your size in pixels and this function automatically adds the 14 | // equivalent size in rems while leaving the px declaration as the fallback 15 | // for old browsers. 16 | // Based on Aral Balkan's Responsive Pixels - https://github.com/aral/responsive-pixels 17 | remify(value) { 18 | u = unit(value); 19 | 20 | if value is 'inherit' or value is 'smaller' or value is null { 21 | value 22 | } else if u is 'px' { 23 | return unit(value/16, 'rem'); 24 | } else { 25 | return unit(value, u); 26 | } 27 | } 28 | 29 | // Abstract longhand for properties that take a single value 30 | abstract-longhand(prop, originalArguments) { 31 | value = originalArguments[0] 32 | remifiedValue = remify(value) 33 | 34 | {prop} value 35 | 36 | if value != remifiedValue { 37 | {prop} remify(value) 38 | } 39 | } 40 | 41 | // Remify font-size declarations 42 | font-size() { 43 | abstract-longhand('font-size', arguments) 44 | } 45 | 46 | 47 | // Allows setting of a property for LTR and RTL without having to 48 | // deal with duplicating and maintaining selectors: 49 | // example: bidi-style(left, 20px, right, auto) 50 | 51 | bidi-style(ltr-prop, value, inverse-prop, inverse-value) { 52 | {ltr-prop}: value; 53 | 54 | html[dir='rtl'] & { 55 | if (ltr-prop != inverse-prop) { 56 | {inverse-prop}: value; 57 | } 58 | {ltr-prop}: inverse-value; 59 | } 60 | } 61 | 62 | bidi-value(prop, ltr, rtl) { 63 | bidi-style(prop, ltr, prop, rtl); 64 | } 65 | 66 | 67 | // Grid units 68 | $grid-column-width = 60px; 69 | $grid-narrow-column-width = 40px; 70 | $grid-gutter-width = 20px; 71 | 72 | 73 | // Colors 74 | $primary-text-color = #484848; 75 | $secondary-text-color = darken($primary-text-color, 10%); 76 | $light-text-color = #fff; 77 | 78 | $link-color = #09c; 79 | 80 | $light-gray = #e3e8ec; 81 | $dark-gray = darken($light-gray, 20%); 82 | $light-blue = #1b9ed4; 83 | $medium-blue = #1881ba; 84 | $dark-blue = #115b83; 85 | $yellow = #fc3; 86 | 87 | 88 | 89 | // Blue backgrounds for some page sections 90 | section-blue = @block { 91 | background: $medium-blue; 92 | color: $light-text-color; 93 | 94 | h1, h2, h3, legend { 95 | color: $light-text-color; 96 | } 97 | } 98 | 99 | 100 | // Light links for dark backgrounds 101 | invert-links = @block { 102 | a { 103 | color: lighten($link-color, 60%); 104 | text-decoration: none; 105 | 106 | &:visited { 107 | color: lighten($link-color, 50%); 108 | } 109 | 110 | &:hover, 111 | &:focus, 112 | &:active { 113 | color: lighten($link-color, 70%); 114 | text-decoration: underline; 115 | } 116 | } 117 | } 118 | 119 | 120 | // White links for some occasions 121 | light-links = @block { 122 | a { 123 | color: #fff; 124 | 125 | &:visited { 126 | color: #eee; 127 | } 128 | 129 | &:hover, 130 | &:focus, 131 | &:active { 132 | color: #fff; 133 | text-decoration: underline; 134 | } 135 | } 136 | } 137 | 138 | 139 | // Basic button 140 | button = @block { 141 | border-radius: 6px; 142 | border: 0; 143 | cursor: pointer; 144 | display: inline-block; 145 | font-weight: bold; 146 | min-width: 160px; 147 | padding: .5em 20px; 148 | text-align: center; 149 | text-decoration: none; 150 | text-transform: uppercase; 151 | -webkit-transition: background-color linear .1s; 152 | -moz-transition: background-color linear .1s; 153 | transition: background-color linear .1s; 154 | } 155 | 156 | 157 | // Override base heading styles 158 | normalize-heading = @block { 159 | font-family: 'Fira Sans Light', sans-serif; 160 | letter-spacing: normal; 161 | text-shadow: none; 162 | } 163 | 164 | 165 | // Common widths 166 | $width-desktop = 1000px; 167 | $width-tablet = 760px; 168 | $width-mobile-wide = 440px; 169 | $width-mobile = 320px; 170 | 171 | 172 | // Media queries 173 | $media-query-desktop = 'screen and (min-width: 1000px)'; 174 | $media-query-tablet = 'screen and (min-width: 760px)'; 175 | $media-query-mobile-wide = 'screen and (min-width: 480px)'; 176 | $media-query-mobile = 'screen and (max-width: 480px)'; 177 | 178 | // @High resolution images for @retina-type displays 179 | $media-query-highres = 'only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 1.5dppx), only screen and (min-resolution: 144dpi)'; 180 | $media-query-highres-large = 'only screen and (-webkit-min-device-pixel-ratio: 1.5) and (min-width: 760px), only screen and (min-resolution: 1.5dppx) and (min-width: 760px), only screen and (min-resolution: 144dpi) and (min-width: 760px)'; 181 | 182 | 183 | // Column spans for standard column widths 184 | span(columns) { 185 | bidi-value(float, left, right); 186 | width: ($grid-column-width * columns) + ($grid-gutter-width * (columns - 1)); 187 | margin-left: ($grid-gutter-width / 2); 188 | margin-right: ($grid-gutter-width / 2); 189 | } 190 | 191 | span_all() { 192 | float: none; 193 | width: auto; 194 | margin-left: ($grid-gutter-width / 2); 195 | margin-right: ($grid-gutter-width / 2); 196 | } 197 | 198 | offset(columns) { 199 | margin-left: (($grid-column-width + $grid-gutter-width) * columns) + ($grid-gutter-width * 0.5); 200 | 201 | html[dir='rtl'] & { 202 | margin-left: ($grid-gutter-width * 0.5); 203 | margin-right: (($grid-column-width + $grid-gutter-width) * columns) + ($grid-gutter-width * 0.5); 204 | } 205 | } 206 | 207 | 208 | // Column spans for narrow column widths 209 | span_narrow(columns) { 210 | bidi-value(float, left, right); 211 | width: ($grid-narrow-column-width * columns) + ($grid-gutter-width * (columns - 1)); 212 | margin-left: ($grid-gutter-width / 2); 213 | margin-right: ($grid-gutter-width / 2); 214 | } 215 | 216 | offset_narrow(columns) { 217 | margin-left: (($grid-narrow-column-width + $grid-gutter-width) * columns) + ($grid-gutter-width * 0.5); 218 | 219 | html[dir='rtl'] & { 220 | margin-left: ($grid-gutter-width * 0.5); 221 | margin-right: (($grid-narrow-column-width + $grid-gutter-width) * columns) + ($grid-gutter-width * 0.5); 222 | } 223 | } 224 | 225 | 226 | // Mixins 227 | clearfix() { 228 | &:before, &:after { 229 | content: ' '; 230 | display: table; 231 | } 232 | 233 | &:after { 234 | clear: both; 235 | } 236 | } 237 | 238 | a11y-hidden() { 239 | position: absolute !important; 240 | height: 1px; 241 | width: 1px; 242 | margin: -1px; 243 | overflow: hidden; 244 | clip: rect(0 0 0 0); 245 | padding: 0; 246 | border: 0; 247 | } 248 | -------------------------------------------------------------------------------- /masterfirefoxos/base/static/stylus/oldIE.styl: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | 6 | // This basic style sheet is served to IE 8 and lower. It provides 7 | // a minimal layout with some purposeful design but leaves out 8 | // the more advanced styling older versions of IE don't support. 9 | 10 | @require 'lib'; 11 | 12 | 13 | .section, 14 | .contain, 15 | .footer, 16 | .main, 17 | .aside { 18 | zoom: 1; 19 | } 20 | 21 | // IE before v9 doesn't support media queries so it doesn't get the 22 | // rules for the wide layout, but IE is also unlikely to have a small 23 | // viewport so we'll assume IE8 and lower are wide. 24 | .contain { 25 | width: $width-tablet - ($grid-gutter-width * 2); 26 | } 27 | 28 | .main { 29 | span_narrow(8); 30 | offset_narrow(1); 31 | } 32 | 33 | .aside { 34 | span_narrow(3); 35 | } 36 | 37 | .site-logo { 38 | width: 225px; 39 | float: left; 40 | } 41 | 42 | .site-switcher { 43 | padding: 0; 44 | width: 300px; 45 | float: right; 46 | text-align: right; 47 | } 48 | 49 | .nav-info { 50 | position: absolute; 51 | top: 10px; 52 | bottom: auto; 53 | right: 170px; 54 | font-size: 16px; 55 | 56 | ul { 57 | margin: 0; 58 | } 59 | } 60 | 61 | .nav-main { 62 | .menu-icon { 63 | display: none; 64 | } 65 | 66 | ul { 67 | height: auto; 68 | overflow: visible; 69 | max-height: auto; 70 | transition: none; 71 | } 72 | } 73 | 74 | .video-frame iframe { 75 | width: 460px; 76 | height: 259px; 77 | } 78 | 79 | .media-image { 80 | bidi-value(padding-left, ($grid-narrow-column-width * 3) + ($grid-gutter-width * 3), 0); 81 | bidi-value(padding-right, 0, ($grid-narrow-column-width * 3) + ($grid-gutter-width * 3)); 82 | 83 | img { 84 | bidi-value(float, left, right); 85 | bidi-value(margin-left, -(($grid-narrow-column-width * 3) + ($grid-gutter-width * 3) + 5), 0); 86 | bidi-value(margin-right, 0, -(($grid-narrow-column-width * 3) + ($grid-gutter-width * 3) + 5)); 87 | width: ($grid-narrow-column-width * 3) + ($grid-gutter-width * 2); 88 | } 89 | } 90 | 91 | .footer-license, 92 | .footer-lang { 93 | span_narrow(3); 94 | } 95 | 96 | .footer-logo, 97 | .footer-nav { 98 | span_narrow(2); 99 | } 100 | 101 | .footer-license { 102 | margin-right: $grid-narrow-column-width + $grid-gutter-width; 103 | } 104 | 105 | 106 | .quiz-answer { 107 | input { 108 | left: 10px; 109 | top: 10px; 110 | background: transparent none; 111 | border: none; 112 | padding: 0; 113 | } 114 | 115 | label { 116 | background: lighten($light-gray, 20%); 117 | zoom: 1; 118 | 119 | &:hover { 120 | background: lighten($light-gray, 40%); 121 | } 122 | } 123 | 124 | input:checked + label:before { 125 | content: ''; 126 | } 127 | } 128 | 129 | .home { 130 | .masthead { 131 | background-image: none; 132 | min-height: 1px; 133 | margin-bottom: 20px; 134 | 135 | .contain { 136 | min-height: 0; 137 | } 138 | } 139 | 140 | .page-intro { 141 | clear: both; 142 | 143 | h2 { 144 | font-size: 48px; 145 | 146 | } 147 | } 148 | 149 | .main { 150 | span_all(); 151 | margin: 0; 152 | } 153 | } 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /masterfirefoxos/base/static/stylus/quiz.styl: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | // Quiz pages 6 | 7 | @require 'lib'; 8 | 9 | .feedback { 10 | display: none; 11 | background: #fff; 12 | padding: 10px; 13 | border-radius: 6px; 14 | border: 3px solid #ccc; 15 | clear: both; 16 | box-shadow: 0 1px 1px rgba(0, 0, 0, .1); 17 | } 18 | 19 | .feedback-correct { 20 | border-color: #3da954; // green 21 | } 22 | 23 | .feedback-incorrect { 24 | border-color: #aa3e3e; // red 25 | } 26 | 27 | .feedback-partial { 28 | border-color: #e8be3d; // gold 29 | } 30 | 31 | .quiz-question { 32 | clear: both; 33 | font-size: 20px; 34 | padding-top: 1em; 35 | border-top: 3px groove rgba(255, 255, 255, .15); 36 | margin: 2em 0 .75em; 37 | } 38 | 39 | .quiz-answer { 40 | clear: both; 41 | margin: 0 0 5px; 42 | position: relative; 43 | overflow: hidden; 44 | 45 | input { 46 | position: absolute; 47 | bidi-value(left, -5em, auto); 48 | bidi-value(right, auto, -5em); 49 | } 50 | 51 | label { 52 | background: rgba(255, 255, 255, .25); 53 | bidi-value(padding, .5em 10px .5em 45px, .5em 45px .5em 10px); 54 | display: block; 55 | margin: 0; 56 | cursor: pointer; 57 | font-size: 16px; 58 | 59 | &:hover { 60 | background: rgba(255, 255, 255, .5); 61 | } 62 | 63 | &:before { 64 | background: $dark-gray; 65 | border-radius: 6px; 66 | content: ''; 67 | color: #fff; 68 | display: block; 69 | font-size: 25px; 70 | text-align: center; 71 | text-shadow: 0 1px 1px rgba(0, 0, 0, .1); 72 | width: 25px; 73 | height: 25px; 74 | line-height: 25px; 75 | position: absolute; 76 | top: 8px; 77 | bidi-value(left, 8px, auto); 78 | bidi-value(right, auto, 8px); 79 | } 80 | } 81 | 82 | input:checked + label { 83 | background: #fff; 84 | 85 | &:before { 86 | background: darken($dark-gray, 10%); 87 | content: '\2714'; // heavy check 88 | } 89 | } 90 | } 91 | 92 | .quiz-image { 93 | bidi-value(float, left, right); 94 | clear: both; 95 | background: #fff; 96 | box-shadow: 0 1px 1px rgba(0, 0, 0, .1); 97 | margin: 0 0 1em; 98 | padding: 5px; 99 | max-width: 100%; 100 | } 101 | 102 | .quiz-submit { 103 | clear: both; 104 | margin: 1em 0; 105 | 106 | button { 107 | {button} 108 | background: $dark-gray; 109 | color: #fff; 110 | font-size: 12px; 111 | 112 | &:hover, 113 | &:focus { 114 | background: darken($dark-gray, 10%); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /masterfirefoxos/base/static/stylus/reset.styl: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | 6 | // This overrides most browser defaults for common elements, in some cases 7 | // replacing them with our own global styling (such as bottom margins for 8 | // some elements). This isn't a complete reset and allows some browser 9 | // defaults to remain (like italic style for em, bold weight for strong, etc). 10 | 11 | // Set new HTML5 elements to block or inline-block for older browsers 12 | article, aside, details, figcaption, figure, footer, header, hgroup, main, nav, section, summary { display: block; } 13 | audio, video, canvas { display: inline-block; } 14 | 15 | html, body, form, fieldset, legend, h1, h2, h3, h4, h5, h6, dd { margin: 0; padding: 0; } 16 | p, pre, blockquote, ul, ol, dl, table, figure { margin: 0 0 1.5em; padding: 0; } 17 | blockquote:before, blockquote:after, q:before, q:after { content: ""; } 18 | blockquote, q { quotes: "" ""; } 19 | table { border-collapse: collapse; border-spacing: 0; } 20 | fieldset, iframe { border: 0; } 21 | legend { color: inherit; } /* For IE */ 22 | a img { border: 0; } 23 | input, select, textarea { font-family: inherit; font-size: 100%; } 24 | img, video, object, embed { max-width: 100%; height: auto; } 25 | -------------------------------------------------------------------------------- /masterfirefoxos/base/templates/base.jinja: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | {% if feincms_page.title %}{{ _(feincms_page.title) }} | {% endif %}{{ _('Master Firefox OS') }} 15 | 16 | {% block site_css %} 17 | 18 | {% endblock %} 19 | 20 | 21 | {% block page_css %}{% endblock %} 22 | 23 | 24 | 28 | 29 | {% block google_analytics %} 30 | 31 | {% endblock %} 32 | 33 | 34 | 35 | 36 |
37 | {% block site_header %} 38 | 59 | {% endblock %} 60 | 61 |
62 |
63 | {% block nav_content %} 64 | 72 | {% endblock %} 73 | 74 | {% block content %}{% endblock %} 75 |
76 |
77 | 78 | {% block site_footer %} 79 | 113 | {% endblock %} 114 | 115 |
{# end outer-wrapper #} 116 | 117 | 118 | {% block site_js %} 119 | 120 | {% endblock %} 121 | 122 | {% if include_pontoon(request) %} 123 | 124 | {% endif %} 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /masterfirefoxos/base/templates/content.jinja: -------------------------------------------------------------------------------- 1 | {% extends "base.jinja" %} 2 | 3 | {% block content %} 4 |
5 |

{{ _(feincms_page.title) }}

6 | 7 | {{ render_region(feincms_page, "main", request) }} 8 | 9 |
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /masterfirefoxos/base/templates/home.jinja: -------------------------------------------------------------------------------- 1 | {% extends "base.jinja" %} 2 | 3 | {% block body_class %}home{% endblock %} 4 | 5 | {% block page_css %} 6 | 7 | {% endblock %} 8 | 9 | {% block site_header %} 10 | 36 | {% endblock %} 37 | 38 | {% block nav_content %}{% endblock %} 39 | 40 | {% block content %} 41 |
42 | 43 | {% for child in feincms_page.get_children().filter(active=True, in_navigation=True) %} 44 |
45 |

{{ _(child.title) }}

46 | 47 | {{ render_region(child, "homepage", request) }} 48 | 49 | 50 |
51 | {% endfor %} 52 | 53 |
54 | {% endblock %} 55 | -------------------------------------------------------------------------------- /masterfirefoxos/base/templates/includes/faqentry.jinja: -------------------------------------------------------------------------------- 1 |
2 |

{{ question }}

3 |
4 | {{ answer }} 5 |
6 |
7 | -------------------------------------------------------------------------------- /masterfirefoxos/base/templates/includes/imageparagraph.jinja: -------------------------------------------------------------------------------- 1 |
2 | {{ alt }} 3 | 4 |

{{ title }}

5 | {% if subheader_2 %} 6 |

{{ subheader_2 }}

7 | {% endif %} 8 | {% if subheader_3 %} 9 |

{{ subheader_3 }}

10 | {% endif %} 11 | 12 |

13 | {{ text }} 14 |

15 |
16 | -------------------------------------------------------------------------------- /masterfirefoxos/base/templates/includes/nav-information.jinja: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /masterfirefoxos/base/templates/includes/quizanswer.jinja: -------------------------------------------------------------------------------- 1 |

2 | 3 | 6 |

7 | -------------------------------------------------------------------------------- /masterfirefoxos/base/templates/includes/quizquestion.jinja: -------------------------------------------------------------------------------- 1 |

2 | {{ _(question.question) | safe }} 3 |

4 | 5 | {% if question.image %} 6 |
7 | 8 |
9 | {% endif %} 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /masterfirefoxos/base/templates/includes/richtextentry.jinja: -------------------------------------------------------------------------------- 1 |
2 | {% if title %} 3 |

{{ title }}

4 | {% endif %} 5 | {% if subheader_2 %} 6 |

{{ subheader_2 }}

7 | {% endif %} 8 | {% if subheader_3 %} 9 |

{{ subheader_3 }}

10 | {% endif %} 11 |

12 | {{ text }} 13 |

14 |
15 | -------------------------------------------------------------------------------- /masterfirefoxos/base/templates/includes/version-language-switcher.jinja: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 | 13 |

14 |

15 | 16 | 23 | {{ _('Which version do I have?') }} 24 |

25 | 26 |
27 | 28 | {% if 'from-lang' in request.GET %} 29 | {% set from_lang_code = request.GET['from-lang'] %} 30 | {% if from_lang_code in settings.LANGUAGE_NAMES %} 31 | {{ activate(from_lang_code) }} 32 | {% set from_lang = settings.LANGUAGE_NAMES.get(from_lang_code)|capitalize %} 33 | {% set latest_version_from_lang = request.GET.get('latest-version') %} 34 | {% else %} 35 | {% set from_lang = 'your selected language' %} 36 | {% endif %} 37 |
38 |

39 | {{ _('Documentation for Firefox OS {version} is not yet available in {language}, so we redirected you to the English version.') | f(version=active_version(request), language=from_lang) }} 40 | {% if latest_version_from_lang %} 41 | {{ _('The latest version available in {language} is {version}.') | f(language=from_lang, version=latest_version_from_lang) }} 42 | {% endif %} 43 |

44 |
45 | {{ activate(request.LANGUAGE_CODE) }} 46 | {% endif %} 47 | -------------------------------------------------------------------------------- /masterfirefoxos/base/templates/includes/videoparagraph.jinja: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 6 |

{{ title }}

7 | {% if subheader_2 %} 8 |

{{ subheader_2 }}

9 | {% endif %} 10 | {% if subheader_3 %} 11 |

{{ subheader_3 }}

12 | {% endif %} 13 | 14 |

15 | {{ text }} 16 |

17 |
18 | -------------------------------------------------------------------------------- /masterfirefoxos/base/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/masterfirefoxos/32eb1f6ce0ed357de286e64839570e73e86ae136/masterfirefoxos/base/templatetags/__init__.py -------------------------------------------------------------------------------- /masterfirefoxos/base/templatetags/helpers.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | from datetime import datetime 4 | 5 | from django.conf import settings 6 | from django.utils.translation import activate as dj_activate, get_language 7 | 8 | import six 9 | from feincms.module.medialibrary.models import MediaFile 10 | from feincms.templatetags.feincms_tags import feincms_render_region 11 | from django_jinja import library 12 | from jinja2 import Markup 13 | from jinja2.utils import soft_unicode 14 | from sorl.thumbnail import get_thumbnail 15 | 16 | 17 | _word_beginning_split_re = re.compile(r'([-\s\(\{\[\<]+)(?u)') 18 | 19 | 20 | @library.filter 21 | def f(s, *args, **kwargs): 22 | """ 23 | Uses ``str.format`` for string interpolation. 24 | **Note**: Always converts to s to text type before interpolation. 25 | >>> {{ "{0} arguments and {x} arguments"|f('positional', x='keyword') }} 26 | "positional arguments and keyword arguments" 27 | """ 28 | s = six.text_type(s) 29 | return s.format(*args, **kwargs) 30 | 31 | 32 | @library.global_function 33 | def render_region(feincms_page, region, request): 34 | return Markup(feincms_render_region(None, feincms_page, region, request)) 35 | 36 | 37 | @library.global_function 38 | def current_year(): 39 | return datetime.now().strftime('%Y') 40 | 41 | 42 | @library.global_function 43 | def activate(language): 44 | dj_activate(language) 45 | return '' 46 | 47 | 48 | @library.global_function 49 | def active_version(request): 50 | slug = request.path.split('/')[2] 51 | for version, data in settings.VERSIONS_LOCALE_MAP.items(): 52 | if data['slug'] == slug: 53 | return version 54 | 55 | 56 | @library.global_function 57 | def get_image_url(img, geometry=None, locale=None): 58 | if not locale: 59 | locale = get_language() 60 | url = img.file.url 61 | 62 | basename = os.path.basename(img.file.name).rsplit('.')[0] 63 | 64 | query = MediaFile.objects.filter( 65 | file__startswith='medialibrary/' + basename + '.', 66 | categories__title=locale) 67 | 68 | if query.exists(): 69 | img = query.first() 70 | url = img.file.url 71 | 72 | if geometry: 73 | img = get_thumbnail(img.file, geometry, quality=90) 74 | url = img.url 75 | 76 | # AWS S3 urls contain AWS_ACCESS_KEY_ID, Expiration and other 77 | # params. We don't need them. 78 | return url.split('?')[0] 79 | 80 | 81 | @library.global_function 82 | def include_pontoon(request): 83 | return request.get_host() == getattr(settings, 'LOCALIZATION_HOST', None) 84 | 85 | 86 | @library.filter 87 | def paren_title(s): 88 | """ 89 | Fix jinja2 title filter to capitalize words inside parens 90 | see https://github.com/mitsuhiko/jinja2/pull/439 91 | """ 92 | return ''.join( 93 | [item[0].upper() + item[1:].lower() 94 | for item in _word_beginning_split_re.split(soft_unicode(s)) 95 | if item]) 96 | -------------------------------------------------------------------------------- /masterfirefoxos/base/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | from unittest.mock import Mock 3 | 4 | from feincms.module.medialibrary.models import MediaFile 5 | from sorl.thumbnail import ImageField 6 | 7 | 8 | TEST_VERSIONS_LOCALE_MAP = OrderedDict({ 9 | '9.0': { 10 | 'slug': 'version-90', 11 | 'locales': [ 12 | 'en', 13 | 'xx', 14 | 'foo', 15 | ] 16 | }, 17 | '10.0t': { 18 | 'slug': 'version-100t', 19 | 'locales': [ 20 | 'en', 21 | ], 22 | 'pending_locales': [ 23 | 'foo', 24 | ] 25 | } 26 | }) 27 | 28 | 29 | def MediaFileFactory(filename='filename.jpg', url='media_url'): 30 | media_file = MediaFile() 31 | media_file.file = Mock() 32 | media_file.file.name = filename 33 | media_file.file.url = url 34 | return media_file 35 | 36 | 37 | def ImageFieldFactory(filename='filename.jpg', url='image_url'): 38 | image_file = ImageField() 39 | image_file.name = filename 40 | image_file.url = url 41 | return image_file 42 | -------------------------------------------------------------------------------- /masterfirefoxos/base/tests/test_acceptance.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import pytest 3 | 4 | 5 | acceptance = pytest.mark.acceptance 6 | 7 | 8 | def assert_ok(url): 9 | response = requests.get(url) 10 | assert response.status_code == 200, '{} for {}'.format( 11 | response.status_code, url) 12 | 13 | 14 | def get_all_versions(base_urls, slug): 15 | for url in base_urls: 16 | assert_ok('{}{}/'.format(url, slug)) 17 | 18 | 19 | @acceptance 20 | def test_home_pages(base_urls): 21 | for url in base_urls: 22 | assert_ok(url) 23 | 24 | 25 | @acceptance 26 | def test_introduction(base_urls): 27 | get_all_versions(base_urls, 'introduction') 28 | 29 | 30 | @acceptance 31 | def test_demo_tips(base_urls): 32 | get_all_versions(base_urls, 'demo-tips') 33 | 34 | 35 | @acceptance 36 | def test_customer_guide(base_urls): 37 | get_all_versions(base_urls, 'customer-guide') 38 | 39 | 40 | @acceptance 41 | def test_key_features(base_urls): 42 | get_all_versions(base_urls, 'key-features') 43 | 44 | 45 | @acceptance 46 | def test_faq(base_urls): 47 | get_all_versions(base_urls, 'frequently-asked-questions') 48 | 49 | 50 | @acceptance 51 | def test_about(base_urls): 52 | get_all_versions(base_urls, 'about') 53 | 54 | 55 | @acceptance 56 | def test_quiz(base_urls): 57 | get_all_versions(base_urls, 'take-the-challenge') 58 | 59 | 60 | @acceptance 61 | def test_which_version(base_urls): 62 | get_all_versions(base_urls, 'which-version') 63 | -------------------------------------------------------------------------------- /masterfirefoxos/base/tests/test_admin.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import patch 2 | 3 | from feincms.module.page.models import Page 4 | 5 | from masterfirefoxos.base.admin import PageAdmin 6 | 7 | 8 | @patch('masterfirefoxos.base.admin.copy_tree') 9 | @patch('masterfirefoxos.base.admin.PageAdmin.message_user') 10 | def test_copy_tree_action_empty_queryset(mock_message_user, mock_copy_tree): 11 | page_admin = PageAdmin(Page, None) 12 | queryset = Page.objects.none() 13 | page_admin.copy_tree_admin_action('request', queryset) 14 | mock_message_user.assert_called() 15 | assert not mock_copy_tree.called 16 | 17 | 18 | @patch('masterfirefoxos.base.admin.copy_tree') 19 | @patch('masterfirefoxos.base.admin.PageAdmin.message_user') 20 | def test_copy_tree_action(mock_message_user, mock_copy_tree): 21 | page_admin = PageAdmin(Page, None) 22 | page_admin.copy_tree_admin_action('request', ['page']) 23 | assert not mock_message_user.called 24 | mock_copy_tree.assert_called_with('page') 25 | -------------------------------------------------------------------------------- /masterfirefoxos/base/tests/test_helpers.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import patch 2 | 3 | from django.conf import settings 4 | from django.test import RequestFactory 5 | from django.test.utils import override_settings 6 | 7 | from . import MediaFileFactory, ImageFieldFactory 8 | from masterfirefoxos.base.templatetags.helpers import get_image_url, include_pontoon 9 | 10 | 11 | def test_get_image_url_base(): 12 | with patch('masterfirefoxos.base.templatetags.helpers.MediaFile') as MediaFileMock: 13 | MediaFileMock.objects.filter().exists.return_value = False 14 | media_file = MediaFileFactory() 15 | url = get_image_url(media_file) 16 | assert url == 'media_url' 17 | 18 | 19 | def test_get_image_url_geometry(): 20 | with patch('masterfirefoxos.base.templatetags.helpers.MediaFile') as MediaFileMock: 21 | with patch('masterfirefoxos.base.templatetags.helpers.get_thumbnail') as get_thumbnail_mock: 22 | MediaFileMock.objects.filter().exists.return_value = False 23 | media_file = MediaFileFactory() 24 | new_media_file = ImageFieldFactory(url='new_media_url') 25 | get_thumbnail_mock.return_value = new_media_file 26 | url = get_image_url(media_file, geometry='200') 27 | get_thumbnail_mock.assert_called_with(media_file.file, '200', quality=90) 28 | assert url == 'new_media_url' 29 | 30 | 31 | def test_get_image_url_localized_file_exists(): 32 | with patch('masterfirefoxos.base.templatetags.helpers.MediaFile') as MediaFileMock: 33 | media_file = MediaFileFactory() 34 | localized_media_file = MediaFileFactory(url='localized_url') 35 | MediaFileMock.objects.filter().exists.return_value = True 36 | MediaFileMock.objects.filter().first.return_value = localized_media_file 37 | url = get_image_url(media_file) 38 | assert url == 'localized_url' 39 | 40 | 41 | @override_settings(LOCALIZATION_HOST='foo.example.com') 42 | def test_include_pontoon_valid_host(): 43 | request = RequestFactory().get('/') 44 | request.get_host = lambda: 'foo.example.com' 45 | assert include_pontoon(request) 46 | 47 | 48 | @override_settings(LOCALIZATION_HOST='bar.example.com') 49 | def test_include_pontoon_invalid_host(): 50 | request = RequestFactory().get('/') 51 | request.get_host = lambda: 'foo.example.com' 52 | assert not include_pontoon(request) 53 | 54 | 55 | @override_settings() 56 | def test_include_pontoon_no_setting(): 57 | del settings.LOCALIZATION_HOST 58 | request = RequestFactory().get('/') 59 | request.get_host = lambda: 'foo.example.com' 60 | assert not include_pontoon(request) 61 | -------------------------------------------------------------------------------- /masterfirefoxos/base/tests/test_middleware.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponseRedirect 2 | from django.test import RequestFactory 3 | from django.test.utils import override_settings 4 | 5 | from masterfirefoxos.base.tests import TEST_VERSIONS_LOCALE_MAP 6 | from masterfirefoxos.base.middleware import NonExistentLocaleRedirectionMiddleware 7 | 8 | 9 | middleware = NonExistentLocaleRedirectionMiddleware() 10 | 11 | 12 | def test_path_starts_with_en(): 13 | request = RequestFactory().get('/en/foo-bar') 14 | assert middleware.process_request(request) is None 15 | 16 | 17 | @override_settings(ENABLE_ALL_LANGUAGES=True) 18 | def test_enable_all_languages(): 19 | request = RequestFactory().get('/xx-dne/') 20 | assert middleware.process_request(request) is None 21 | 22 | 23 | @override_settings(VERSIONS_LOCALE_MAP=TEST_VERSIONS_LOCALE_MAP, 24 | ENABLE_ALL_LANGUAGES=False) 25 | def test_locale_for_version_exists(): 26 | request = RequestFactory().get('/xx/version-90') 27 | assert middleware.process_request(request) is None 28 | 29 | 30 | @override_settings(VERSIONS_LOCALE_MAP=TEST_VERSIONS_LOCALE_MAP, 31 | ENABLE_ALL_LANGUAGES=False) 32 | def test_locale_for_version_does_not_exist(): 33 | request = RequestFactory().get('/xx/version-100t/demo-tips') 34 | response = middleware.process_request(request) 35 | assert isinstance(response, HttpResponseRedirect) 36 | assert response.url == '/en/version-100t/demo-tips?from-lang=xx' 37 | 38 | 39 | @override_settings(VERSIONS_LOCALE_MAP=TEST_VERSIONS_LOCALE_MAP, 40 | ENABLE_ALL_LANGUAGES=False) 41 | def test_with_nonexistant_version(): 42 | request = RequestFactory().get('/xx/version-does-not-exist/demo-tips') 43 | assert middleware.process_request(request) is None 44 | 45 | 46 | @override_settings(VERSIONS_LOCALE_MAP=TEST_VERSIONS_LOCALE_MAP, 47 | ENABLE_ALL_LANGUAGES=False) 48 | def test_request_path_breakdown_failure(): 49 | request = RequestFactory().get('/') 50 | assert middleware.process_request(request) is None 51 | -------------------------------------------------------------------------------- /masterfirefoxos/base/tests/test_models.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import patch 2 | 3 | from . import MediaFileFactory, ImageFieldFactory 4 | from .. import models 5 | 6 | 7 | def test_youtube_paragraph_render(): 8 | test_data = {'title': 'Test Title', 'text': 'test text', 9 | 'youtube_id': 'test youtube id', 10 | 'subheader_2': 'Header 2', 'subheader_3': 'Header 3'} 11 | 12 | rendered = models.YouTubeParagraphEntry(**test_data).render() 13 | for value in test_data.values(): 14 | assert value in rendered 15 | 16 | 17 | def test_media_paragraph_render(): 18 | test_data = {'title': 'Test Title', 'text': 'test text', 19 | 'subheader_2': 'Header 2', 'subheader_3': 'Header 3'} 20 | entry = models.ImageParagraphEntry(**test_data) 21 | entry.image = MediaFileFactory() 22 | with patch('masterfirefoxos.base.templatetags.helpers.MediaFile') as MediaFileMock: 23 | with patch('masterfirefoxos.base.templatetags.helpers.get_thumbnail') as get_thumbnail_mock: 24 | get_thumbnail_mock.return_value = ImageFieldFactory(url='new_media_url') 25 | MediaFileMock.objects.filter().exists.return_value = False 26 | rendered = entry.render() 27 | assert 'new_media_url' in rendered 28 | for value in test_data.values(): 29 | assert value in rendered 30 | 31 | 32 | def test_faq_render(): 33 | test_data = {'question': 'test question', 'answer': 'test answer'} 34 | rendered = models.FAQEntry(**test_data).render() 35 | for value in test_data.values(): 36 | assert value in rendered 37 | -------------------------------------------------------------------------------- /masterfirefoxos/base/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import Mock, patch 2 | 3 | from django.test import override_settings, RequestFactory 4 | 5 | from feincms.module.page.models import Page 6 | 7 | from .. import models 8 | from .. import utils 9 | 10 | 11 | def test_entry_strings(): 12 | rich_text_entry = models.RichTextEntry( 13 | title='title', subheader_2='sub 2', subheader_3='sub 3', text='test text') 14 | assert utils.entry_strings(rich_text_entry) == ['title', 'sub 2', 'sub 3', 'test text'] 15 | 16 | image_paragraph_entry = models.ImageParagraphEntry( 17 | alt='alt', title='test title', text='test text', 18 | subheader_2='sub 2', subheader_3='sub 3') 19 | assert set(utils.entry_strings(image_paragraph_entry)) == set([ 20 | 'alt', 'test title', 'test text', 'sub 2', 'sub 3']) 21 | 22 | faq_entry = models.FAQEntry( 23 | question='test question', answer='test answer') 24 | assert utils.entry_strings(faq_entry) == [ 25 | 'test question', 'test answer'] 26 | 27 | youtube_entry = models.YouTubeParagraphEntry( 28 | title='test title', text='test text', youtube_id='test id', 29 | subheader_2='sub 2', subheader_3='sub 3') 30 | assert set(utils.entry_strings(youtube_entry)) == set([ 31 | 'test title', 'test text', 'sub 2', 'sub 3']) 32 | 33 | 34 | def test_pages_l10n_template(): 35 | parent = Page(title='Parent Title', slug='parent-slug') 36 | page = Page(title='Page Title', slug='page-slug') 37 | page.parent = parent 38 | parent_entry = models.RichTextEntry(text='

Parent Text

') 39 | entry = models.RichTextEntry(text='

Page Text

') 40 | parent.content.all_of_type = lambda content_type: [parent_entry] 41 | page.content.all_of_type = lambda content_type: [entry] 42 | l10n_template = utils.pages_l10n_template([parent, page]) 43 | assert 'Page path: /parent-slug/\n' in l10n_template 44 | assert 'Page path: /parent-slug/page-slug/\n' in l10n_template 45 | assert ('{% blocktrans trimmed %}\nParent Title\n{% endblocktrans %}' 46 | in l10n_template) 47 | assert ('{% blocktrans trimmed %}\nPage Title\n{% endblocktrans %}' 48 | in l10n_template) 49 | assert ('{% blocktrans trimmed %}\n

Parent Text

\n{% endblocktrans %}' 50 | in l10n_template) 51 | assert ('{% blocktrans trimmed %}\n

Page Text

\n{% endblocktrans %}' 52 | in l10n_template) 53 | 54 | 55 | @patch('masterfirefoxos.base.utils.copy_content_and_children') 56 | @patch('masterfirefoxos.base.utils.Page.objects') 57 | @patch('masterfirefoxos.base.utils.datetime') 58 | def test_copy_tree(mock_datetime, mock_page_objects, 59 | mock_copy_content_and_children): 60 | mock_datetime.now().strftime.return_value = 'date' 61 | parent = Page() 62 | page = Page(title='foo bar', slug='sl-ug', active=True, parent=parent) 63 | assert (utils.copy_tree(page) == 64 | mock_copy_content_and_children.return_value) 65 | mock_page_objects.create.assert_called_with( 66 | title='Copy of foo bar on date', slug='copy-of-sl-ug-on-date', 67 | parent=parent, active=False) 68 | mock_copy_content_and_children.assert_called_with( 69 | page, mock_page_objects.create.return_value) 70 | 71 | 72 | @patch('masterfirefoxos.base.utils.copy_content_and_children') 73 | @patch('masterfirefoxos.base.utils.Page.objects') 74 | def test_copy_page_with_parent(mock_page_objects, 75 | mock_copy_content_and_children): 76 | page = Page(title='title', slug='slug', active=False) 77 | assert (utils.copy_page_with_parent(page, 'parent') == 78 | mock_copy_content_and_children.return_value) 79 | mock_page_objects.create.assert_called_with( 80 | title='title', slug='slug', parent='parent', active=False) 81 | mock_copy_content_and_children.assert_called_with( 82 | page, mock_page_objects.create.return_value) 83 | 84 | 85 | @patch('masterfirefoxos.base.utils.copy_page_with_parent') 86 | def test_copy_content_and_children(mock_copy_page_with_parent): 87 | page = Mock() 88 | page.get_children.return_value = ['child'] 89 | new_page = Mock() 90 | assert utils.copy_content_and_children(page, new_page) == new_page 91 | new_page.copy_content_from.assert_called_with(page) 92 | mock_copy_page_with_parent.assert_called_with('child', new_page) 93 | 94 | 95 | @override_settings(LOCALIZED_YOUTUBE_ID={ 96 | 'en-youtube-id': {'xx': 'xx-youtube-id'}}) 97 | def test_youtube_embed_url_translated_id(): 98 | request = RequestFactory().get('/xx/introduction/') 99 | expected = 'https://www.youtube.com/embed/xx-youtube-id' 100 | assert utils.youtube_embed_url(request, 'en-youtube-id') == expected 101 | 102 | 103 | def test_youtube_embed_url_subtitle_querystring(): 104 | request = RequestFactory().get('/xx/introduction/') 105 | expected = ('https://www.youtube.com/embed/en-youtube-id' + 106 | '?hl=xx&cc_lang_pref=xx&cc_load_policy=1') 107 | assert utils.youtube_embed_url(request, 'en-youtube-id') == expected 108 | 109 | 110 | def test_youtube_embed_url_en(): 111 | request = RequestFactory().get('/en/introduction/') 112 | expected = 'https://www.youtube.com/embed/en-youtube-id' 113 | assert utils.youtube_embed_url(request, 'en-youtube-id') == expected 114 | -------------------------------------------------------------------------------- /masterfirefoxos/base/tests/test_views.py: -------------------------------------------------------------------------------- 1 | from django.test import RequestFactory 2 | from django.test.utils import override_settings 3 | 4 | from .. import views 5 | 6 | 7 | @override_settings( 8 | LOCALE_LATEST_VERSION={ 9 | 'de': {'slug': '1-1', 'name': '1.1'}, 10 | 'en': {'slug': '1-3T', 'name': '1.3T'} 11 | }) 12 | def test_home_redirect_de(): 13 | request = RequestFactory().get('/de/') 14 | request.LANGUAGE_CODE = 'de' # normally added by LocaleMiddleware 15 | response = views.home_redirect(request) 16 | assert response.status_code == 302 17 | assert response['location'] == '1-1/' 18 | 19 | 20 | @override_settings( 21 | LOCALE_LATEST_VERSION={}, 22 | LOCALE_LATEST_PENDING_VERSION={'xx': {'slug': '4-2', 'name': '4.2'}}, 23 | ENABLE_ALL_LANGUAGES=True) 24 | def test_home_redirect_pending_version(): 25 | request = RequestFactory().get('/xx/') 26 | request.LANGUAGE_CODE = 'xx' # normally added by LocaleMiddleware 27 | response = views.home_redirect(request) 28 | assert response.status_code == 302 29 | assert response['location'] == '4-2/' 30 | 31 | 32 | @override_settings(LOCALE_LATEST_VERSION={'en': {'slug': '1-3T', 'name': '1.3T'}}, 33 | ENABLE_ALL_LANGUAGES=False) 34 | def test_home_redirect_english_default(): 35 | request = RequestFactory().get('/de/') 36 | request.LANGUAGE_CODE = 'de' # normally added by LocaleMiddleware 37 | response = views.home_redirect(request) 38 | assert response.status_code == 302 39 | assert response['location'] == '/en/1-3T/' 40 | -------------------------------------------------------------------------------- /masterfirefoxos/base/utils.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from itertools import chain 3 | 4 | from django.db.models.fields import TextField 5 | from django.conf import settings 6 | 7 | from feincms.module.page.models import Page 8 | 9 | 10 | page_template_template = ''' 11 | {{% comment %}} 12 | Translators: 13 | Page path: {parent_slug}/{page_slug}/ 14 | {{% endcomment %}} 15 | {{% blocktrans trimmed %}} 16 | {string} 17 | {{% endblocktrans %}} 18 | ''' 19 | 20 | 21 | def entry_strings(entry): 22 | return [getattr(entry, field.name) for field in entry._meta.fields 23 | if field.name in getattr(entry, '_l10n_fields', []) 24 | and getattr(entry, field.name)] 25 | 26 | 27 | def page_template_generator(page): 28 | yield page_template_template.format( 29 | page_slug=page.slug, 30 | parent_slug='/' + page.parent.slug if page.parent else '', 31 | string=page.title) 32 | 33 | for content_type in page._feincms_content_types: 34 | for entry in page.content.all_of_type(content_type): 35 | for entry_string in entry_strings(entry): 36 | yield page_template_template.format( 37 | page_slug=page.slug, 38 | parent_slug='/' + page.parent.slug if page.parent else '', 39 | string=entry_string) 40 | 41 | 42 | def pages_l10n_template(pages=None): 43 | return '\n'.join( 44 | chain(*[page_template_generator(page) for page in 45 | pages or Page.objects.all()])) 46 | 47 | 48 | def copy_content_and_children(page, new_page): 49 | new_page.copy_content_from(page) 50 | for child in page.get_children(): 51 | copy_page_with_parent(child, new_page) 52 | return new_page 53 | 54 | 55 | def copy_page_with_parent(page, parent): 56 | new_page = Page.objects.create( 57 | title=page.title, slug=page.slug, parent=parent, active=False) 58 | return copy_content_and_children(page, new_page) 59 | 60 | 61 | def copy_tree(page): 62 | now = datetime.now().strftime('%Y-%m-%d-%H-%M-%S') 63 | new_page = Page.objects.create( 64 | title='Copy of {title} on {now}'.format(title=page.title, now=now), 65 | slug='copy-of-{slug}-on-{now}'.format(slug=page.slug, now=now), 66 | parent=page.parent, active=False) 67 | return copy_content_and_children(page, new_page) 68 | 69 | 70 | def youtube_embed_url(request, en_youtube_id): 71 | embed = 'https://www.youtube.com/embed/' 72 | if request and not request.path.startswith('/en/'): 73 | lang = request.path.split('/')[1] # validity ensured by middleware 74 | youtube_id = settings.LOCALIZED_YOUTUBE_ID.get(en_youtube_id, {}).get( 75 | lang, en_youtube_id) 76 | if youtube_id == en_youtube_id: 77 | query_template = '?hl={lang}&cc_lang_pref={lang}&cc_load_policy=1' 78 | return embed + youtube_id + query_template.format(lang=lang) 79 | else: 80 | youtube_id = en_youtube_id 81 | return embed + youtube_id 82 | 83 | 84 | def unmangle(text): 85 | return text.replace( 86 | '\r\n', ' ').replace( 87 | '’', '’').replace( 88 | '“', '“').replace( 89 | '”', '”').replace( 90 | '—', '—').replace( 91 | '
', '
').replace( 92 | '

 

', '').replace( 93 | '

', '').replace( 94 | '

', '').replace( 95 | '

', ' ').replace( 96 | '
', ' ').strip() 97 | 98 | 99 | def unmangle_pages(pages=None): 100 | for page in pages or Page.objects.all(): 101 | for content_type in page._feincms_content_types: 102 | for entry in page.content.all_of_type(content_type): 103 | for field in entry._meta.fields: 104 | if isinstance(field, TextField): 105 | text = getattr(entry, field.name) 106 | unmangled = unmangle(text) 107 | if text != unmangled: 108 | setattr(entry, field.name, unmangled) 109 | entry.save(update_fields=[field.name]) 110 | -------------------------------------------------------------------------------- /masterfirefoxos/base/views.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.http import HttpResponseRedirect 3 | 4 | 5 | def home_redirect(request): 6 | version = settings.LOCALE_LATEST_VERSION.get(request.LANGUAGE_CODE) 7 | if not version and settings.ENABLE_ALL_LANGUAGES: 8 | version = settings.LOCALE_LATEST_PENDING_VERSION.get( 9 | request.LANGUAGE_CODE) 10 | if version: 11 | return HttpResponseRedirect(version['slug'] + '/') 12 | return HttpResponseRedirect( 13 | '/{}/{}/'.format('en', settings.LOCALE_LATEST_VERSION['en']['slug'])) 14 | -------------------------------------------------------------------------------- /masterfirefoxos/settings/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import decouple 4 | 5 | 6 | try: 7 | from .base import * # noqa 8 | except decouple.UndefinedValueError as exp: 9 | print('ERROR:', exp.message) 10 | sys.exit(1) 11 | -------------------------------------------------------------------------------- /masterfirefoxos/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | from django.contrib import admin 3 | from django.conf.urls.i18n import i18n_patterns 4 | 5 | 6 | urlpatterns = patterns('', 7 | url(r'^admin/', include(admin.site.urls)), 8 | ) 9 | 10 | urlpatterns += i18n_patterns('', 11 | url(r'^$', 'masterfirefoxos.base.views.home_redirect'), 12 | url(r'', include('feincms.urls')), 13 | ) 14 | -------------------------------------------------------------------------------- /masterfirefoxos/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for masterfirefoxos project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'masterfirefoxos.settings') 12 | 13 | from django.conf import settings 14 | from django.core.wsgi import get_wsgi_application 15 | 16 | import newrelic.agent 17 | from decouple import config 18 | from whitenoise.django import DjangoWhiteNoise 19 | 20 | application = get_wsgi_application() 21 | application = DjangoWhiteNoise(application) 22 | 23 | # Add media files 24 | if settings.MEDIA_ROOT and settings.MEDIA_URL: 25 | application.add_files(settings.MEDIA_ROOT, prefix=settings.MEDIA_URL) 26 | 27 | # Add NewRelic 28 | newrelic_ini = config('NEW_RELIC_CONFIG_FILE', default='newrelic.ini') 29 | newrelic_license_key = config('NEW_RELIC_LICENSE_KEY', default=None) 30 | if newrelic_ini and newrelic_license_key: 31 | newrelic.agent.initialize(newrelic_ini) 32 | application = newrelic.agent.wsgi_application()(application) 33 | -------------------------------------------------------------------------------- /newrelic.ini: -------------------------------------------------------------------------------- 1 | # --------------------------------------------------------------------------- 2 | 3 | # 4 | # This file configures the New Relic Python Agent. 5 | # 6 | # The path to the configuration file should be supplied to the function 7 | # newrelic.agent.initialize() when the agent is being initialized. 8 | # 9 | # The configuration file follows a structure similar to what you would 10 | # find for Microsoft Windows INI files. For further information on the 11 | # configuration file format see the Python ConfigParser documentation at: 12 | # 13 | # http://docs.python.org/library/configparser.html 14 | # 15 | # For further discussion on the behaviour of the Python agent that can 16 | # be configured via this configuration file see: 17 | # 18 | # http://newrelic.com/docs/python/python-agent-configuration 19 | # 20 | 21 | # --------------------------------------------------------------------------- 22 | 23 | # Here are the settings that are common to all environments. 24 | 25 | [newrelic] 26 | 27 | # You must specify the license key associated with your New 28 | # Relic account. This key binds the Python Agent's data to your 29 | # account in the New Relic service. 30 | # license_key = use NEW_RELIC_LICENSE_KEY environment variable 31 | 32 | # The appplication name. Set this to be the name of your 33 | # application as you would like it to show up in New Relic UI. 34 | # The UI will then auto-map instances of your application into a 35 | # entry on your home dashboard page. 36 | # app_name = use NEW_RELIC_APP_NAME environment variable 37 | 38 | # When "true", the agent collects performance data about your 39 | # application and reports this data to the New Relic UI at 40 | # newrelic.com. This global switch is normally overridden for 41 | # each environment below. 42 | monitor_mode = true 43 | 44 | # Sets the name of a file to log agent messages to. Useful for 45 | # debugging any issues with the agent. This is not set by 46 | # default as it is not known in advance what user your web 47 | # application processes will run as and where they have 48 | # permission to write to. Whatever you set this to you must 49 | # ensure that the permissions for the containing directory and 50 | # the file itself are correct, and that the user that your web 51 | # application runs as can write to the file. If not able to 52 | # write out a log file, it is also possible to say "stderr" and 53 | # output to standard error output. This would normally result in 54 | # output appearing in your web server log. 55 | #log_file = /tmp/newrelic-python-agent.log 56 | 57 | # Sets the level of detail of messages sent to the log file, if 58 | # a log file location has been provided. Possible values, in 59 | # increasing order of detail, are: "critical", "error", "warning", 60 | # "info" and "debug". When reporting any agent issues to New 61 | # Relic technical support, the most useful setting for the 62 | # support engineers is "debug". However, this can generate a lot 63 | # of information very quickly, so it is best not to keep the 64 | # agent at this level for longer than it takes to reproduce the 65 | # problem you are experiencing. 66 | log_level = info 67 | 68 | # The Python Agent communicates with the New Relic service using 69 | # SSL by default. Note that this does result in an increase in 70 | # CPU overhead, over and above what would occur for a non SSL 71 | # connection, to perform the encryption involved in the SSL 72 | # communication. This work is though done in a distinct thread 73 | # to those handling your web requests, so it should not impact 74 | # response times. You can if you wish revert to using a non SSL 75 | # connection, but this will result in information being sent 76 | # over a plain socket connection and will not be as secure. 77 | ssl = true 78 | 79 | # High Security Mode enforces certain security settings, and 80 | # prevents them from being overridden, so that no sensitive data 81 | # is sent to New Relic. Enabling High Security Mode means that 82 | # SSL is turned on, request parameters are not collected, and SQL 83 | # can not be sent to New Relic in its raw form. To activate High 84 | # Security Mode, it must be set to 'true' in this local .ini 85 | # configuration file AND be set to 'true' in the server-side 86 | # configuration in the New Relic user interface. For details, see 87 | # https://docs.newrelic.com/docs/subscriptions/high-security 88 | high_security = true 89 | 90 | # The Python Agent will attempt to connect directly to the New 91 | # Relic service. If there is an intermediate firewall between 92 | # your host and the New Relic service that requires you to use a 93 | # HTTP proxy, then you should set both the "proxy_host" and 94 | # "proxy_port" settings to the required values for the HTTP 95 | # proxy. The "proxy_user" and "proxy_pass" settings should 96 | # additionally be set if proxy authentication is implemented by 97 | # the HTTP proxy. The "proxy_scheme" setting dictates what 98 | # protocol scheme is used in talking to the HTTP protocol. This 99 | # would normally always be set as "http" which will result in the 100 | # agent then using a SSL tunnel through the HTTP proxy for end to 101 | # end encryption. 102 | # proxy_scheme = http 103 | # proxy_host = hostname 104 | # proxy_port = 8080 105 | # proxy_user = 106 | # proxy_pass = 107 | 108 | # Tells the transaction tracer and error collector (when 109 | # enabled) whether or not to capture the query string for the 110 | # URL and send it as the request parameters for display in the 111 | # UI. When "true", it is still possible to exclude specific 112 | # values from being captured using the "ignored_params" setting. 113 | capture_params = false 114 | 115 | # Space separated list of variables that should be removed from 116 | # the query string captured for display as the request 117 | # parameters in the UI. 118 | ignored_params = 119 | 120 | # The transaction tracer captures deep information about slow 121 | # transactions and sends this to the UI on a periodic basis. The 122 | # transaction tracer is enabled by default. Set this to "false" 123 | # to turn it off. 124 | transaction_tracer.enabled = true 125 | 126 | # Threshold in seconds for when to collect a transaction trace. 127 | # When the response time of a controller action exceeds this 128 | # threshold, a transaction trace will be recorded and sent to 129 | # the UI. Valid values are any positive float value, or (default) 130 | # "apdex_f", which will use the threshold for a dissatisfying 131 | # Apdex controller action - four times the Apdex T value. 132 | transaction_tracer.transaction_threshold = apdex_f 133 | 134 | # When the transaction tracer is on, SQL statements can 135 | # optionally be recorded. The recorder has three modes, "off" 136 | # which sends no SQL, "raw" which sends the SQL statement in its 137 | # original form, and "obfuscated", which strips out numeric and 138 | # string literals. 139 | transaction_tracer.record_sql = obfuscated 140 | 141 | # Threshold in seconds for when to collect stack trace for a SQL 142 | # call. In other words, when SQL statements exceed this 143 | # threshold, then capture and send to the UI the current stack 144 | # trace. This is helpful for pinpointing where long SQL calls 145 | # originate from in an application. 146 | transaction_tracer.stack_trace_threshold = 0.5 147 | 148 | # Determines whether the agent will capture query plans for slow 149 | # SQL queries. Only supported in MySQL and PostgreSQL. Set this 150 | # to "false" to turn it off. 151 | transaction_tracer.explain_enabled = true 152 | 153 | # Threshold for query execution time below which query plans 154 | # will not not be captured. Relevant only when "explain_enabled" 155 | # is true. 156 | transaction_tracer.explain_threshold = 0.5 157 | 158 | # Space separated list of function or method names in form 159 | # 'module:function' or 'module:class.function' for which 160 | # additional function timing instrumentation will be added. 161 | transaction_tracer.function_trace = 162 | 163 | # The error collector captures information about uncaught 164 | # exceptions or logged exceptions and sends them to UI for 165 | # viewing. The error collector is enabled by default. Set this 166 | # to "false" to turn it off. 167 | error_collector.enabled = true 168 | 169 | # To stop specific errors from reporting to the UI, set this to 170 | # a space separated list of the Python exception type names to 171 | # ignore. The exception name should be of the form 'module:class'. 172 | error_collector.ignore_errors = 173 | 174 | # Browser monitoring is the Real User Monitoring feature of the UI. 175 | # For those Python web frameworks that are supported, this 176 | # setting enables the auto-insertion of the browser monitoring 177 | # JavaScript fragments. 178 | browser_monitoring.auto_instrument = true 179 | 180 | # A thread profiling session can be scheduled via the UI when 181 | # this option is enabled. The thread profiler will periodically 182 | # capture a snapshot of the call stack for each active thread in 183 | # the application to construct a statistically representative 184 | # call tree. 185 | thread_profiler.enabled = true 186 | 187 | # --------------------------------------------------------------------------- 188 | 189 | # 190 | # The application environments. These are specific settings which 191 | # override the common environment settings. The settings related to a 192 | # specific environment will be used when the environment argument to the 193 | # newrelic.agent.initialize() function has been defined to be either 194 | # "development", "test", "staging" or "production". 195 | # 196 | 197 | [newrelic:development] 198 | monitor_mode = false 199 | 200 | [newrelic:test] 201 | monitor_mode = false 202 | 203 | [newrelic:staging] 204 | app_name = Python Application (Staging) 205 | monitor_mode = true 206 | 207 | [newrelic:production] 208 | monitor_mode = true 209 | 210 | # --------------------------------------------------------------------------- 211 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "masterfirefoxos", 3 | "version": "0.1.0", 4 | "description": "", 5 | "private": true, 6 | "dependencies": {}, 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/mozilla/masterfirefoxos.git" 10 | }, 11 | "author": "Mozilla", 12 | "license": "MPL", 13 | "bugs": { 14 | "url": "https://bugzilla.mozilla.org/enter_bug.cgi?product=Websites&component=Master%20Firefox%20OS" 15 | }, 16 | "devDependencies": { 17 | "gulp": "~3.8.10", 18 | "gulp-filter": "~2.0.0", 19 | "gulp-concat": "~2.4.3", 20 | "gulp-sourcemaps": "~1.3.0", 21 | "del": "~1.1.1", 22 | "gulp-stylus": "~2.0.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | DJANGO_SETTINGS_MODULE = masterfirefoxos.settings -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Adding peep so we can track it on requires.io. For packages 2 | # installation we use peep.py shipped in ./bin 3 | # sha256: PU6v8vYN6py7rTKqZEHGVCiY_9o2REixOYI0al7WRXA 4 | peep==2.2 5 | 6 | # sha256: 8uJz7TSsu1YJYtXPEpF5NtjfAil98JvTCJuFRtRYQTg 7 | # sha256: ygF2j97N4TQwHzFwdDIm9g7f9cOTXxJDc3jr2RFQY1M 8 | dj-database-url==0.3.0 9 | 10 | # sha256: JLo_Moq6CNjVBbwqj2ipTr4kqF6Eg2QLZnIhvA79Ox4 11 | psycopg2==2.5.4 12 | 13 | # sha256: ufv3R_qOcR0zDy9Da5suz17tnrZ6Y3yoGrzRHCq-3sg 14 | python-decouple==2.3 15 | 16 | # sha256: LiSsXQBNtXFJdqBKwOgMbfbkfpjDVMssDYL4h51Pj9s 17 | Jinja2==2.7.3 18 | 19 | # sha256: pOwa_1m5WhS0XrLiN2GgF56YMZ2lp-t2tW6ozce4ccM 20 | MarkupSafe==0.23 21 | 22 | # sha256: gnFVEftiRvrUumbYEuuTQWroNxtGT6iL84Z8nBd9qhQ 23 | # sha256: _7QRdX3jdNBcdZySmQjSOXxm47z2813uQabvnrhRSXs 24 | gunicorn==19.1.1 25 | 26 | # sha256: 2slBnbPs4nu1PHQzJD_CLtQs9o3p1sQSk5Dh2a7-YxA 27 | # sha256: Pk2AGZlglZuCiVGqJMv6o2zr7RSdzXKMEehVeYsV-HA 28 | whitenoise==1.0.6 29 | 30 | # sha256: 0yV2uq13_dnmhZvpfCtqPBem9IQ2Q4nbcx8Gl7ehlmE 31 | django-csp==2.0.3 32 | 33 | # sha256: xby9Ec-YRwlq4etOg93nXRCsYu_m5zxGAPP5gJaM29I 34 | # sha256: Qxx4vPgnySoZ14Kc2a55AtUtbfyyPZiQT8gH3eoewHY 35 | # sha256: S6CrdPp_NPd_8jbuIVdFTRwqrqqiWTI2O_ob5suC29Y 36 | pytz==2014.9 37 | 38 | # sha256: J7kqegDxYuebWJb-nliXGbmF6c_fRFgmzBiFYVnObB4 39 | newrelic==2.40.0.34 40 | 41 | # sha256: KF6L1zDAtv374jwy0pNr_7pAHyPKsTLocixovoDW8YI 42 | flake8==2.2.5 43 | 44 | # Wheel Python 2.7 45 | # sha256: q409VanEtW-Xy2D21rk92UlUwWEU8K6HLCR6zoI_Fds 46 | # Wheel Python 3 47 | # sha256: jy0vup7228b_a2mm76_Yg1voKHm4rnmkM2HH1k42Pew 48 | # TarGZ 49 | # sha256: PphGaqL-VFQLy6mqbgGjn0ARDWdmjClzQMS5UUt8xJw 50 | # ZIP 51 | # sha256: MmP8nqIHC-HP-ERXhtbzGLdnaMAGg4n7OFOw0us_THI 52 | ipython==2.3.1 53 | 54 | # sha256: DUoz845megyA7BpaDkaP5Y5c3GHLj0B3sQJLhf57cRc 55 | ipdb==0.8 56 | 57 | # sha256: 0UXkOs2Dxbos77J2EH6Wb-YzXbAmWnoLSxCirbhM0m0 58 | # sha256: uwkN7xH7pRFnpYjYyklp9Jo8Q3rYrPlg39OcSwzxYAk 59 | # sha256: lJM7ZOL-CAfaBhLFdKAhwNrCjHvTxKI3I65aOeqPPQQ 60 | Sphinx==1.2.3 61 | 62 | # sha256: PYBK7mdyHzjoQO-jo65SiYMo5kcJJZNB8qiQHVvwHNQ 63 | # sha256: Dyn1RPbQN5ifoMdymp6rfk2OpQ1vDvNzY_RydWwe3KY 64 | sphinx-rtd-theme==0.1.6 65 | 66 | # sha256: XinOsvZKY-HOMBr0x0vrJ7WmYeWGPQBFT1mUC6JNc50 67 | # sha256: OQDKtkuxi2YYkSgARVAePxoFoeyZp1rrLtAmkdB4JcI 68 | sphinx-autobuild==0.3.0 69 | 70 | # sha256: 3OvUkoESYxYm9MTQ31l4fHSEBOZt2pUhEAMOqIPTuM0 71 | # sha256: x9txeBCraWX2bIzwOYqYydjfmC2jm0zX8WKRHriVlvo 72 | docutils==0.12 73 | 74 | # sha256: XgOeHUDSMpge1YkUttGsLkU6foPd6iLvnz7q3QHeRcs 75 | # sha256: ZoyOZluTX2I4WcEyE-G8mzP9Gf7sTbfC-OhVwcjFWdc 76 | # sha256: fydQ9H4cA_ffSGbTdhm4B0_8HTIYI7taNRMC_k3SC2c 77 | Pygments==2.0.1 78 | # sha256: 9uicOwJKEwYN4Zcy6C0d_WV830axXWR8Dw9aqgempBk 79 | # sha256: RsvB0Uh6nFkHCOnhNVMKyUr4Nzvx6O8Mes9sxGyE08Q 80 | # sha256: 1rKtu-fe4pIUlRkqobehcW_FTdlzmK_tGntRmMD1cko 81 | # sha256: 5FadZR1ghF6pmrFKOyvxHcPoxgAK1WHn7QsV4G4xJK4 82 | # sha256: aW8JG9A-yVTxZmgFOPvpE-ewPAYXgGXRmz_C9w7ymsg 83 | # sha256: NtFrAeD18IsmOQSTyEG27ugvVxpAr4uP6qWG5ccCuGY 84 | # sha256: w69DjlDEN55fFiftMl3AFv4tKoS-WBuHu7vZ_zPMh-4 85 | # sha256: FicQ_wHVtfj_2r65Fl2BvwBlsFW72w6cMFgp2x8BhdQ 86 | # sha256: AAVIGPcCz2sw584w15izfgkKAdLrG-S5yE-BgfGP6Dc 87 | # sha256: kiWR-7N516hBPUs9y4NR5xP4g_Zrnaghhujm6KdXYfg 88 | # sha256: YJCQdMrZZAF1nL17kK9ODOoI3zNXuX9Gm7K3GZGADC8 89 | # sha256: w6ioTWXuixS5janTubohTOp2q8VnaEq8yuNp-a8F12c 90 | # sha256: 4bjztxMJyDMvpcN-gpuETH3eKdPrD69-Oia8CnuRKcU 91 | # sha256: jAWHyFID5jTMtfnBFa86dnkjvC8HodKa8soFwgklEKI 92 | # sha256: LTKAfVGtMypEaIxvRgCEP-RdZ1roqdu_8cSqtaQD9DA 93 | # sha256: BBlBCJPEXPUj5CTa4TNYfuoxB7krdotYdrWHYMaYUr4 94 | # sha256: qCXJCzx5qY43Ui0sHjcw7PeqRB21wjs6eGKxqjfdPyk 95 | # sha256: BwgAyIkMUmUbLEWs7hBzN3OfmY4RU1MQ6FmSBW2RNek 96 | # sha256: v0eZxZ9Wnv7CIXexGacxpnN72KSgvP0CaelHIm9EZ1Q 97 | # sha256: Wn8umRFS9kq8ouLxns5OkkHcSSFcul7VcLzkmwFjhwQ 98 | # sha256: fZt1keuL6vR3d49rFZdUgABvlpjPPomdrB2Ljcuktkk 99 | # sha256: pW56uJKLESChrnNPyHHiOgnLpS1OjY6HXm653E-68R4 100 | # sha256: 3nkxTRc060Oz-qvNGE2BeeK91Dfr1XS6GqPijMhnoRo 101 | # sha256: QB4vm3k_Qf55FNgfv4Rygot-VLadYXi4v1fykv4MKUg 102 | # sha256: WgbNO3fyH_0dpdm27INCZpGKgKwk8fD8WAVay8hW7yU 103 | # sha256: 3yqqsFnI4R6xX-ut6b9dkvDrwnPBpwJ0q7OnfgIqRsg 104 | # sha256: KWIQka95xd1BFbiLoe1IuiWpFJTDIYyQ-nKJGUuiyWY 105 | # sha256: TdbIkKtelJCtMpKbHAobay9X2cFSHVJxsdMmIuGygdE 106 | # sha256: ZLCgVyEMSArqmUBsk5EYDNhm_A_Y8LUzZ-OvIbGVeEo 107 | # sha256: kNb5u33yDpYRdLKdQpH_cWHOUn7X1_xCo15bM-zqlYo 108 | # sha256: 6qzIJq_07W6eJT6_Nyt7Q8u1neYQH-zZq0-970ws03g 109 | # sha256: P9YZRAMFuf5ZuO05Q1ad3oObWcemT916CfD4pPJvkMk 110 | # sha256: fR2Jk0LlfKTRXjgWx8NUPFaL6Emy2fy11iKqKiIqAHM 111 | # sha256: c7UEXtZlKr_FAEA9dtflECijKlwhbspq3BMLjMoGkzs 112 | # sha256: 3ogSMGcPszrQZRXITuDJuo89ZFYgovuggrr_Oz81z3o 113 | # sha256: iX-tPabqvuX6wcuoeiebkloRsC4X1mc03_Y_wIwKCio 114 | # sha256: ob5vBN1jHFEf2M74PWENr7dV6X4l-_WgP7H9XuGqR0U 115 | # sha256: KqvjwrPNVkZE4C_miYyfJSDhlTAUprLUVjeZJOycWW8 116 | # sha256: UHvcbc60ToWknGgaPCnRHWeAzOJHZNBlMSYp9zrlb4U 117 | # sha256: NXVxrXXdwnAY1YbSIDac8rM5mQSROg6uRjGwgtLTO7I 118 | # sha256: NX5AMqsb5MBQRzwIrY7iTADxRORvITyaaDpIw458VAk 119 | # sha256: zETFS0MrJ8WePQvASKWnWBsTlEwUxWyUY0z-jV6rreU 120 | Pillow==3.2.0 121 | 122 | # sha256: blfJW_PUsrmPUwcHVnT5OXuWYAbYzaP39KOv_-y5n7o 123 | # sha256: zhJNc4i0KX-HX-BPO-rcV4MkZxj18PtKwjVZpvw11lM 124 | FeinCMS==1.11.4 125 | 126 | 127 | # sha256: x03qrFi6pgMGknO5JKnf3Dbk_brFNmJsweG-4u2z3HQ 128 | https://github.com/django-mptt/django-mptt/archive/abd4f250e0690f6dc7c2938d8a864c41421b9f70.tar.gz#egg=django-mptt 129 | 130 | # sha256: f2UH1ADQft_R6oIF2jaAgAmwxTn1uKbgq1Qze5VebcM 131 | # sha256: rVQ2OeidQ2heLx07bkhxFWLuw743nmlYqSD76vTGO84 132 | # sha256: pJ7Inr20I03kc602eSv42jqGQLiiY6_aLqxRD_SQjJI 133 | feedparser==5.1.3 134 | 135 | # sha256: CJ37xCfPCbXyVzLjbQe-Nz1rL97SbK7cG4s0Qfgc6ec 136 | django-stackato==1.0 137 | 138 | # sha256: KN0LkNKbOGr7VS78TjVciJ9GOc6TZYp4cqIVDs4ou4k 139 | py==1.4.26 140 | 141 | # sha256: VQiDuYGEzvbLuYDU2QBRMwrq5BB1CS-Y7wIVcZr47zM 142 | pytest==2.6.4 143 | 144 | # sha256: zDqVGHeIYn39yUtB3pCKrfxCQfq7POrvGfS9O4nA_fc 145 | # sha256: IkqvF6KGCXB9lC3q_m0KWzgrryKm8z5OYcVsYvCQgd0 146 | Django==1.8.16 147 | 148 | # sha256: MeUZjAbykOHoE9MN3QY6BAHZ46aYEpykQmd4FnOnEoU 149 | pytest-cov==1.8.1 150 | 151 | # sha256: 0a6hxKphuDZtakLdNlBiL7-cY07STq9_N5yLlw5e1E4 152 | coverage==3.7.1 153 | 154 | # sha256: ShTGfVIP2p1CsNphNGOFeMquHTdLm7Ri2N4AWH26dkw 155 | cov-core==1.15.0 156 | 157 | # sha256: toGa2NBy2E-9uplY849sjk5D1dxDerykmBGq8MWjq64 158 | # sha256: MxnZwEi7pMyJqWkRgdDgIYei8JtDQ_0vz2zL-EtO4Yo 159 | dennis==0.6.1 160 | 161 | # sha256: seoUHVjtXkiu0mdPfIlN-4P2OcMobXsysuGfoDKltAA 162 | # sha256: INKg1YmmksEd9Um9fNqDxmXu8qg-AXuEP-zflW7brXQ 163 | polib==1.0.6 164 | 165 | # sha256: yZcvTSuAV1u5ESUAz-x1Dq9PBOB6vaSyxE5FPN5l7nc 166 | # sha256: 95yMBNfrUAcbytZ_0j88EPq2xy1WhXrfhINngGhF1uU 167 | click==3.3 168 | 169 | # sha256: QW014kelmSc9QP0Bp8pIJmbr2UZ7lYUsqUpV3bzxDuA 170 | # sha256: 49SRYStWkFJZgEtdIpJhJr7ZoMKoMXSFgrhQUAkgs3A 171 | # sha256: lZZFTHQINrALhLsuwySDga96cQXWoY_i6BO9GwW6MbE 172 | peepin==0.7 173 | 174 | # sha256: u2w2OyYqTO7TvzhLefz0108LSchZ54xnAP0BXg3iSdo 175 | django-storages-redux==1.2.1 176 | 177 | # sha256: 23aDBqF69XCWE7zCO3Cpu7dZSCOx5kHR7ZhdagPIKxA 178 | # sha256: YmBPD99oormmxr1REcguECgbSYaVFS0h9WyeSgaR1F0 179 | boto==2.35.1 180 | 181 | # sha256: -s_gx8zq_Uno9-RyERKUVmYF_f3cIwEdoGzDpGAcn30 182 | # sha256: BHu7ukG6w3xETHXd_fBXPdbi8fvYJOYke7Jvp9j6ODA 183 | six==1.8.0 184 | 185 | # sha256: rf8-36E3YtVqvHx95gnTldSwOjBBRxImBizpn8qDU3A 186 | # sha256: Mfw1XHE1TVYJWPhskeGAvivHbGHBiaqG6Gm0nbOrDfU 187 | django-extensions==1.4.9 188 | 189 | # sha256: gBjyNk71se4SXu2IoYpjXFyxztkdeyNDU9igfe8yxhM 190 | django-sslify==0.2.5 191 | 192 | # sha256: 6Cljb4OMUUa2b3QmztUhmkhGMmAWK1ZqIHMwd4pTMiY 193 | # sha256: R8ZNJ704IvGVNqxTaHgUNiz9y4L9YvnN9NEgdFFpDYA 194 | # sha256: ErI5dd22usqL64ByotYFEGAY29qgnrukuiD6z1EvRsY 195 | sorl-thumbnail==12.2 196 | 197 | # sha256: ic9r9P9kT_i5EuL6fiq7aNV6jn6Rjjk8coQNMNLrzws 198 | # sha256: PYypv2XFAU9GkYBUTR3Vu1ud9wmq1jBPnC5DcK4Ke3w 199 | mccabe==0.3 200 | 201 | # sha256: Yuh_1UU1-5MrSk2Uho21IyV6EDHIvGvTWMIBVDPmRts 202 | # sha256: FbQhMbJfN2Fl0ZX-ThewooMRGCqvkzDV61dbvtpaaYk 203 | pep8==1.5.7 204 | 205 | # sha256: rEVxaVwQzhU2vNuhopS58tPmzJ0OoXG2fVCghkzj4EI 206 | # sha256: P6gKELNtUWhr93RPXcmWIs1cmM6O1kAi5imGiq_Bd2k 207 | pyflakes==0.8.1 208 | 209 | # sha256: _bmvYNR8pXqA3wohMzYBmjT_YZLY__Nhw0nyyDmP5GA 210 | # sha256: HNvtHw4jbzXvVOkZmCx6M45P6jeGMQkz06eIegS3TXU 211 | requests==2.6.0 212 | 213 | # sha256: lRcUyqCugCN_T_wfCEUOni5fj5Ar6vGtKUAgh11vjCw 214 | execnet==1.2.0 215 | 216 | # sha256: Yx2-hD7Hza2vI5jFA0PYhweNT5N0oOMHWKNQI_hl--k 217 | pytest-xdist==1.11 218 | 219 | # sha256: Ljgtj2X4xfA3ic9v8RL1zl31umAus0yRDarxFImTv4k 220 | django-jinja==2.1.1 221 | 222 | # sha256: RORjnsBXprap3duGnU46SBj8knjsSQzOOa7whEBZKpM 223 | django-session-csrf==0.5 224 | 225 | # sha256: i-FbY3c4yMvRQipkYUZcCuq3g5z3atK10ZC28fU_rNY 226 | # sha256: dD0AVuEn70JIUOp22T1FySwxPaDlZ2WAaln8doDCWrc 227 | pytest-django==2.9.1 228 | -------------------------------------------------------------------------------- /scripts/cleanup_po.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from django.conf import settings 4 | 5 | import polib 6 | 7 | 8 | def code_string(occurrences): 9 | for occ in occurrences: 10 | if not occ[0].startswith('db-strings.txt'): 11 | return True 12 | return False 13 | 14 | 15 | def valid_version(comment, versions): 16 | for version in versions: 17 | if re.search('Page path: /{}/'.format(re.escape(version)), comment): 18 | return True 19 | return False 20 | 21 | 22 | def get_versions_for_locale(locale): 23 | versions = [] 24 | for version, data in settings.VERSIONS_LOCALE_MAP.items(): 25 | if (locale in data.get('locales', []) 26 | or locale in data.get('pending_locales', [])): 27 | versions.append(data['slug']) 28 | return versions 29 | 30 | 31 | def run(*args): 32 | for locale, lang_name in settings.LANGUAGES: 33 | if locale == 'en': 34 | continue 35 | print('Processing locale {}'.format(locale)) 36 | versions = get_versions_for_locale(locale) 37 | if '-' in locale: 38 | lang, country = locale.split('-') 39 | locale = '_'.join([lang, country.upper()]) 40 | try: 41 | po = polib.pofile('locale/{}/LC_MESSAGES/django.po'.format(locale)) 42 | except OSError: 43 | print('Cannot open po file for locale {}'.format(locale)) 44 | continue 45 | 46 | to_remove = [] 47 | for entry in po: 48 | # Currently unused string, keep. 49 | if len(entry.occurrences) == 0: 50 | continue 51 | 52 | # String that appears in code, keep. 53 | if code_string(entry.occurrences): 54 | continue 55 | 56 | # String from documenation version valid for locale, keep. 57 | if valid_version(entry.comment, versions): 58 | continue 59 | 60 | # Delete entry. 61 | to_remove.append(entry) 62 | 63 | for entry in to_remove: 64 | po.remove(entry) 65 | if to_remove: 66 | po.save() 67 | -------------------------------------------------------------------------------- /scripts/create_db_locales.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | from masterfirefoxos.base.models import Locale 4 | 5 | 6 | def run(*args): 7 | for code in settings.LANGUAGE_NAMES.keys(): 8 | Locale.objects.get_or_create(code=code) 9 | -------------------------------------------------------------------------------- /scripts/db_strings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.conf import settings 4 | 5 | from masterfirefoxos.base.utils import pages_l10n_template 6 | 7 | 8 | def run(*args): 9 | if args: 10 | filename = args[0] 11 | else: 12 | filename = os.path.join(settings.BASE_DIR, 'db-strings.txt') 13 | print(filename) 14 | template_text = pages_l10n_template() 15 | if filename == '-': 16 | print(template_text) 17 | else: 18 | with open(filename, 'w') as f: 19 | f.write(template_text) 20 | -------------------------------------------------------------------------------- /scripts/dehtmlify.py: -------------------------------------------------------------------------------- 1 | from legacy import ( 2 | fix_all_locales, fix_double_strong, split_db_h2s, split_db_h3s, 3 | strip_subheader_ems, strip_all_fields) 4 | from masterfirefoxos.base.utils import unmangle_pages 5 | 6 | 7 | def run(*args): 8 | unmangle_pages() 9 | split_db_h2s() 10 | split_db_h3s() 11 | strip_subheader_ems() 12 | fix_double_strong() 13 | strip_all_fields() 14 | fix_all_locales() 15 | -------------------------------------------------------------------------------- /scripts/import_legacy_pages_and_translations.py: -------------------------------------------------------------------------------- 1 | from legacy import create_pages_and_translations 2 | 3 | 4 | run = create_pages_and_translations 5 | -------------------------------------------------------------------------------- /scripts/makemessages_all_locales.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.core.management import call_command 3 | 4 | 5 | def locale_dirname(locale): 6 | if '-' in locale: 7 | lang, country = locale.split('-') 8 | return '_'.join([lang, country.upper()]) 9 | return locale 10 | 11 | 12 | def run(*args): 13 | all_locales = [locale_dirname(locale) for locale, _ in settings.LANGUAGES] 14 | call_command('makemessages', locale=all_locales, keep_pot=True) 15 | -------------------------------------------------------------------------------- /scripts/test_cleanup_po.py: -------------------------------------------------------------------------------- 1 | from django.test.utils import override_settings 2 | 3 | from masterfirefoxos.base.tests import TEST_VERSIONS_LOCALE_MAP 4 | from cleanup_po import code_string, get_versions_for_locale, valid_version 5 | 6 | 7 | def test_code_string(): 8 | assert code_string([('foo', 2), ('bar', 3)]) is True 9 | assert code_string([('foo', 3), ('db-strings.txt', 10)]) is True 10 | assert code_string([('db-strings.txt', 10)]) is False 11 | 12 | 13 | def test_valid_version(): 14 | comment = 'FooBar\nPage path: /1-3T/foo/bar\nLalo' 15 | assert valid_version(comment, ['1-1', '1-3T']) is True 16 | assert valid_version(comment, ['2-0', '1-3']) is False 17 | 18 | 19 | @override_settings(VERSIONS_LOCALE_MAP=TEST_VERSIONS_LOCALE_MAP) 20 | def test_get_versions_for_locale(): 21 | assert get_versions_for_locale('xx') == ['version-90'] 22 | assert set(get_versions_for_locale('en')) == set(['version-90', 'version-100t']) 23 | assert set(get_versions_for_locale('foo')) == set(['version-90', 'version-100t']) 24 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore=E121,E123,E124,E125,E126,E128,E129 3 | max-line-length=100 4 | exclude=migrate,bin/peep.py,docs/conf.py --------------------------------------------------------------------------------