Commit cc523b6d by Calen Pennington

Initial commit

parent 7ff983d9
{
"directory": "edx_course_discovery/static/bower_components",
"interactive": false
}
[run]
branch = True
data_file = .coverage
source=edx_course_discovery
omit =
edx_course_discovery/settings*
edx_course_discovery/conf*
*wsgi.py
*migrations*
*admin.py
*static*
*templates*
*.py[cod]
*.log
# C extensions
*.so
# Packages
*.egg
*.egg-info
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
lib
lib64
__pycache__
# Installer logs
pip-log.txt
# Django
default.db
# Static assets
media/cache/
assets/
# Unit test / coverage reports
.coverage
htmlcov
.tox
nosetests.xml
unittests.xml
### Internationalization artifacts
*.mo
*.po
*.prob
!django.po
!django.mo
!djangojs.po
!djangojs.mo
conf/locale/en/LC_MESSAGES/*.po
conf/locale/en/LC_MESSAGES/*.mo
conf/locale/fake*/LC_MESSAGES/*.po
conf/locale/fake*/LC_MESSAGES/*.mo
conf/locale/messages.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# QA
coverage.xml
diff_*.html
*.report
report
venv
acceptance_tests.*.log
acceptance_tests.*.png
# Override config files
override.cfg
private.py
# JetBrains
.idea
# OS X
.DS_Store
# Editor Temp Files
*.swp
*.trace
docs/_build/
[pep8]
ignore=E501
max_line_length=119
exclude=settings,migrations,edx_course_discovery/static,bower_components,edx_course_discovery/wsgi.py
language: python
python:
- "2.7"
sudo: false
# Cache the pip directory. "cache: pip" doesn't work due to install override. See https://github.com/travis-ci/travis-ci/issues/3239.
cache:
- directories:
- $HOME/.cache/pip
before_install:
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
install:
- pip install -U pip wheel coveralls
- pip install -r requirements/test.txt
script:
- make validate
branches:
only:
- master
after_success:
- coveralls
[main]
host = https://www.transifex.com
[edx-platform.edx_course_discovery]
file_filter = edx_course_discovery/conf/locale/<lang>/LC_MESSAGES/django.po
source_file = edx_course_discovery/conf/locale/en/LC_MESSAGES/django.po
source_lang = en
type = PO
[edx-platform.edx_course_discovery-js]
file_filter = edx_course_discovery/conf/locale/<lang>/LC_MESSAGES/djangojs.po
source_file = edx_course_discovery/conf/locale/en/LC_MESSAGES/djangojs.po
source_lang = en
type = PO
This diff is collapsed. Click to expand it.
.DEFAULT_GOAL := test
.PHONY: clean compile_translations dummy_translations extract_translations fake_translations help html_coverage \
migrate pull_translations push_translations quality requirements test update_translations validate
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " clean delete generated byte code and coverage reports"
@echo " compile_translations compile translation files, outputting .po files for each supported language"
@echo " dummy_translations generate dummy translation (.po) files"
@echo " extract_translations extract strings to be translated, outputting .mo files"
@echo " fake_translations generate and compile dummy translation files"
@echo " help display this help message"
@echo " html_coverage generate and view HTML coverage report"
@echo " migrate apply database migrations"
@echo " pull_translations pull translations from Transifex"
@echo " push_translations push source translation files (.po) from Transifex"
@echo " quality run PEP8 and Pylint"
@echo " requirements install requirements for local development"
@echo " test run tests and generate coverage report"
@echo " validate run tests and quality checks"
@echo ""
clean:
find . -name '*.pyc' -delete
coverage erase
rm -rf assets
requirements:
pip install -qr requirements/local.txt --exists-action w
test: clean
REUSE_DB=1 coverage run ./manage.py test edx_course_discovery --settings=edx_course_discovery.settings.test
coverage report
quality:
pep8 --config=.pep8 edx_course_discovery *.py
pylint --rcfile=pylintrc edx_course_discovery *.py
validate: test quality
migrate:
python manage.py migrate
html_coverage:
coverage html && open htmlcov/index.html
extract_translations:
python manage.py makemessages -l en -v1 -d django
python manage.py makemessages -l en -v1 -d djangojs
dummy_translations:
cd edx_course_discovery && i18n_tool dummy
compile_translations:
python manage.py compilemessages
fake_translations: extract_translations dummy_translations compile_translations
pull_translations:
tx pull -a
push_translations:
tx push -s
Part of `edX code`__.
__ http://code.edx.org/
Course Discovery Service |Travis|_ |Coveralls|_
=====================================================
.. |Travis| image:: https://travis-ci.org/edx/edx_course_discovery.svg?branch=master
.. _Travis: https://travis-ci.org/edx/edx_course_discovery
.. |Coveralls| image:: https://coveralls.io/repos/edx/edx_course_discovery/badge.svg?branch=master
.. _Coveralls: https://coveralls.io/r/edx/edx_course_discovery?branch=master
The ``README.rst`` file should start with a brief description of the repository,
which sets it in the context of other repositories under the ``edx``
organization. It should make clear where this fits in to the overall edX
codebase.
Overview (please modify)
------------------------
The ``README.rst`` file should then provide an overview of the code in this
repository, including the main components and useful entry points for starting
to understand the code in more detail.
Documentation (please modify)
-----------------------------
The ``README.rst`` should include a link to the developer documentation. This documentation should be hosted on
Read the Docs. An example statement and link are below.
The docs for Course Discovery Service are on Read the Docs: https://edx_course_discovery.readthedocs.org.
License
-------
The code in this repository is licensed under LICENSE_TYPE unless
otherwise noted.
Please see ``LICENSE.txt`` for details.
How To Contribute
-----------------
Contributions are very welcome.
Please read `How To Contribute <https://github.com/edx/edx-platform/blob/master/CONTRIBUTING.rst>`_ for details.
Even though they were written with ``edx-platform`` in mind, the guidelines
should be followed for Open edX code in general.
Reporting Security Issues
-------------------------
Please do not report security issues in public. Please email security@edx.org.
Mailing List and IRC Channel
----------------------------
You can discuss this code in the `edx-code Google Group`__ or in the ``#edx-code`` IRC channel on Freenode.
__ https://groups.google.com/forum/#!forum/edx-code
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(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/)
endif
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " applehelp to make an Apple Help Book"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo " coverage to run coverage check of the documentation (if enabled)"
clean:
rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/edx_course_discovery.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/edx_course_discovery.qhc"
applehelp:
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
@echo
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
@echo "N.B. You won't be able to view it unless you put it in" \
"~/Library/Documentation/Help or install it in your application" \
"bundle."
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/edx_course_discovery"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/edx_course_discovery"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
coverage:
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
@echo "Testing of coverage in the sources finished, look at the " \
"results in $(BUILDDIR)/coverage/python.txt."
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
# Included so Django's startproject command runs against the docs directory
/* override table width restrictions */
.wy-table-responsive table td, .wy-table-responsive table th {
/* !important prevents the common CSS stylesheets from
overriding this as on RTD they are loaded after this stylesheet */
white-space: normal !important;
}
.wy-table-responsive {
overflow: visible !important;
}
# -*- coding: utf-8 -*-
#
# Course Discovery Service documentation build configuration file, created by
# sphinx-quickstart on Sun Feb 17 11:46:20 2013.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import os
# on_rtd is whether we are on readthedocs.org
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
if not on_rtd: # only import and set the theme if we're building docs locally
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
# sys.path.insert(0, os.path.abspath('.'))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = []
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Course Discovery Service'
copyright = u'2015, edX'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '0.1'
# The full version, including alpha/beta/rc tags.
release = '0.1'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
# today = ''
# Else, today_fmt is used as the format for a strftime call.
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
# html_theme = 'sphinx_rtd_theme'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
# html_additional_pages = {}
# If false, no module index is generated.
# html_domain_indices = True
# If false, no index is generated.
# html_use_index = True
# If true, the index is split into individual pages for each letter.
# html_split_index = False
# If true, links to the reST sources are added to the pages.
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'edx_course_discoverydoc'
# -- Options for LaTeX output --------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
# 'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'edx_course_discovery.tex', u'Course Discovery Service Documentation',
u'edX', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
# latex_use_parts = False
# If true, show page references after internal links.
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
# latex_appendices = []
# If false, no module index is generated.
# latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'Course Discovery Service', u'Course Discovery Service Documentation',
[u'edX'], 1)
]
# If true, show URL addresses after external links.
# man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'Course Discovery Service', u'Course Discovery Service Documentation',
u'edX', 'Course Discovery Service', 'Course Discovery Service',
'Miscellaneous'
),
]
# Documents to append as an appendix to all manuals.
# texinfo_appendices = []
# If false, no module index is generated.
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
# texinfo_show_urls = 'footnote'
def setup(app):
app.add_stylesheet('theme_overrides.css')
Feature Toggling
================
All new features/functionality should be released behind a feature gate. This allows us to easily disable features
in the event that an issue is discovered in production. This project uses the
`Waffle <http://waffle.readthedocs.org/en/latest/>`_ library for feature gating.
Waffle supports three types of feature gates (listed below). We typically use flags and switches since samples are
random, and not ideal for our needs.
Flag
Enable a feature for specific users, groups, users meeting certain criteria (e.g. authenticated or staff),
or a certain percentage of visitors.
Switch
Simple boolean, toggling a feature for all users.
Sample
Toggle the feature for a specified percentage of the time.
For information on creating or updating features, refer to the
`Waffle documentation <http://waffle.readthedocs.org/en/latest/>`_.
Permanent Feature Rollout
-------------------------
Over time some features may become permanent and no longer need a feature gate around them. In such instances, the
relevant code and tests should be updated to remove the feature gate. Once the code is released, the feature flag/switch
should be deleted.
Getting Started
===============
If you have not already done so, create/activate a `virtualenv`_. Unless otherwise stated, assume all terminal code
below is executed within the virtualenv.
.. _virtualenv: https://virtualenvwrapper.readthedocs.org/en/latest/
Install dependencies
--------------------
Dependencies can be installed via the command below.
.. code-block:: bash
$ make requirements
Local/Private Settings
----------------------
When developing locally, it may be useful to have settings overrides that you do not wish to commit to the repository.
If you need such overrides, create a file :file:`edx_course_discovery/settings/private.py`. This file's values are
read by :file:`edx_course_discovery/settings/local.py`, but ignored by Git.
Configure edX OpenID Connect (OIDC)
-----------------------------------
This service relies on the edX OIDC (`OpenID Connect`_) authentication provider for login. Note that OIDC is built atop
OAuth 2.0, and this document may use the terms interchangeably. Under our current architecture the LMS serves as our
authentication provider.
Configuring Course Discovery Service to work with OIDC requires registering a new client with the authentication
provider and updating the Django settings for this project with the client credentials.
.. _OpenID Connect: http://openid.net/specs/openid-connect-core-1_0.html
A new OAuth 2.0 client can be created at ``http://127.0.0.1:8000/admin/oauth2/client/``.
1. Click the :guilabel:`Add client` button.
2. Leave the user field blank.
3. Specify the name of this service, ``Course Discovery Service``, as the client name.
4. Set the :guilabel:`URL` to the root path of this service: ``http://localhost:8003/``.
5. Set the :guilabel:`Redirect URL` to the OIDC client endpoint: ``https://localhost:8003/complete/edx-oidc/``.
6. Copy the :guilabel:`Client ID` and :guilabel:`Client Secret` values. They will be used later.
7. Select :guilabel:`Confidential (Web applications)` as the client type.
8. Click :guilabel:`Save`.
Now that you have the client credentials, you can update your settings (ideally in
:file:`edx_course_discovery/settings/local.py`). The table below describes the relevant settings.
+-----------------------------------------------------+----------------------------------------------------------------------------+--------------------------------------------------------------------------+
| Setting | Description | Value |
+=====================================================+============================================================================+==========================================================================+
| SOCIAL_AUTH_EDX_OIDC_KEY | OAuth 2.0 client key | (This should be set to the value generated when the client was created.) |
+-----------------------------------------------------+----------------------------------------------------------------------------+--------------------------------------------------------------------------+
| SOCIAL_AUTH_EDX_OIDC_SECRET | OAuth 2.0 client secret | (This should be set to the value generated when the client was created.) |
+-----------------------------------------------------+----------------------------------------------------------------------------+--------------------------------------------------------------------------+
| SOCIAL_AUTH_EDX_OIDC_URL_ROOT | OAuth 2.0 authentication URL | http://127.0.0.1:8000/oauth2 |
+-----------------------------------------------------+----------------------------------------------------------------------------+--------------------------------------------------------------------------+
| SOCIAL_AUTH_EDX_OIDC_ID_TOKEN_DECRYPTION_KEY | OIDC ID token decryption key. This value is used to validate the ID token. | (This should be the same value as SOCIAL_AUTH_EDX_OIDC_SECRET.) |
+-----------------------------------------------------+----------------------------------------------------------------------------+--------------------------------------------------------------------------+
Run migrations
--------------
Local installations use SQLite by default. If you choose to use another database backend, make sure you have updated
your settings and created the database (if necessary). Migrations can be run with `Django's migrate command`_.
.. code-block:: bash
$ python manage.py migrate
.. _Django's migrate command: https://docs.djangoproject.com/en/1.8/ref/django-admin/#django-admin-migrate
Run the server
--------------
The server can be run with `Django's runserver command`_. If you opt to run on a different port, make sure you update
OIDC client via LMS admin.
.. code-block:: bash
$ python manage.py runserver 8003
.. _Django's runserver command: https://docs.djangoproject.com/en/1.8/ref/django-admin/#runserver-port-or-address-port
.. edx_course_discovery documentation master file, created by
sphinx-quickstart on Sun Feb 17 11:46:20 2013.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Course Discovery Service
===============================
A service for serving course discovery and marketing information to partners, mobile, and edX.org
.. toctree::
:maxdepth: 2
getting_started
testing
features
internationalization
Internationalization
====================
All user-facing text content should be marked for translation. Even if this application is only run in English, our
open source users may choose to use another language. Marking content for translation ensures our users have
this choice.
Follow the `internationalization coding guidelines`_ in the edX Developer's Guide when developing new features.
.. _internationalization coding guidelines: http://edx.readthedocs.org/projects/edx-developer-guide/en/latest/internationalization/i18n.html
Updating Translations
~~~~~~~~~~~~~~~~~~~~~
This project uses `Transifex`_ to translate content. After new features are developed the translation source files
should be pushed to Transifex. Our translation community will translate the content, after which we can retrieve the
translations.
.. _Transifex: https://www.transifex.com/
Pushing source translation files to Transifex requires access to the edx-platform. Request access from the Open Source
Team if you will be pushing translation files. You should also `configure the Transifex client`_ if you have not done so
already.
.. _configure the Transifex client: http://docs.transifex.com/client/config/
The `make` targets listed below can be used to push or pull translations.
.. list-table::
:widths: 25 75
:header-rows: 1
* - Target
- Description
* - pull_translations
- Pull translations from Transifex
* - push_translations
- Push source translation files to Transifex
Fake Translations
~~~~~~~~~~~~~~~~~
As you develop features it may be helpful to know which strings have been marked for translation, and which are not.
Use the `fake_translations` make target for this purpose. This target will extract all strings marked for translation,
generate fake translations in the Esperanto (eo) language directory, and compile the translations.
You can trigger the display of the translations by setting your browser's language to Esperanto (eo), and navigating to
a page on the site. Instead of plain English strings, you should see specially-accented English strings that look like this:
Thé Fütüré øf Ønlïné Édüçätïøn Ⱡσяєм ι# Før änýøné, änýwhéré, änýtïmé Ⱡσяєм #
Testing
=======
The command below runs the Python tests and code quality validation—Pylint and PEP8.
.. code-block:: bash
$ make validate
Code quality validation can be run independently with:
.. code-block:: bash
$ make quality
# Models that can be shared across multiple versions of the API
# should be created here. As the API evolves, models may become more
# specific to a particular version of the API. In this case, the models
# in question should be moved to versioned sub-package.
# Serializers that can be shared across multiple versions of the API
# should be created here. As the API evolves, serializers may become more
# specific to a particular version of the API. In this case, the serializers
# in question should be moved to versioned sub-package.
# Create your tests in sub-packages prefixed with "test_" (e.g. test_models).
"""
Root API URLs.
All API URLs should be versioned, so urlpatterns should only
contain namespaces for the active versions of the API.
"""
from django.conf.urls import url, include
urlpatterns = [
url(r'^v1/', include('edx_course_discovery.apps.api.v1.urls', namespace='v1')),
]
# Create your tests in sub-packages prefixed with "test_" (e.g. test_views).
""" API v1 URLs. """
urlpatterns = []
""" Admin configuration for core models. """
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.utils.translation import ugettext_lazy as _
from edx_course_discovery.apps.core.models import User
class CustomUserAdmin(UserAdmin):
""" Admin configuration for the custom User model. """
list_display = ('username', 'email', 'full_name', 'first_name', 'last_name', 'is_staff')
fieldsets = (
(None, {'fields': ('username', 'password')}),
(_('Personal info'), {'fields': ('full_name', 'first_name', 'last_name', 'email')}),
(_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
'groups', 'user_permissions')}),
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
)
admin.site.register(User, CustomUserAdmin)
""" Constants for the core app. """
class Status(object):
"""Health statuses."""
OK = u"OK"
UNAVAILABLE = u"UNAVAILABLE"
""" Core context processors. """
from django.conf import settings
def core(_request):
""" Site-wide context processor. """
return {
'platform_name': settings.PLATFORM_NAME
}
""" Core models. """
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils.translation import ugettext_lazy as _
class User(AbstractUser):
"""Custom user model for use with OpenID Connect."""
full_name = models.CharField(_('Full Name'), max_length=255, blank=True, null=True)
@property
def access_token(self):
""" Returns an OAuth2 access token for this user, if one exists; otherwise None.
Assumes user has authenticated at least once with edX Open ID Connect.
"""
try:
return self.social_auth.first().extra_data[u'access_token'] # pylint: disable=no-member
except Exception: # pylint: disable=broad-except
return None
class Meta(object): # pylint:disable=missing-docstring
get_latest_by = 'date_joined'
def get_full_name(self):
return self.full_name or super(User, self).get_full_name()
""" Context processor tests. """
from django.test import TestCase, override_settings, RequestFactory
from edx_course_discovery.apps.core.context_processors import core
PLATFORM_NAME = 'Test Platform'
class CoreContextProcessorTests(TestCase):
""" Tests for core.context_processors.core """
@override_settings(PLATFORM_NAME=PLATFORM_NAME)
def test_core(self):
request = RequestFactory().get('/')
self.assertDictEqual(core(request), {'platform_name': PLATFORM_NAME})
""" Tests for core models. """
from django.test import TestCase
from django_dynamic_fixture import G
from social.apps.django_app.default.models import UserSocialAuth
from edx_course_discovery.apps.core.models import User
# pylint: disable=no-member
class UserTests(TestCase):
""" User model tests. """
TEST_CONTEXT = {'foo': 'bar', 'baz': None}
def test_access_token(self):
user = G(User)
self.assertIsNone(user.access_token)
social_auth = G(UserSocialAuth, user=user)
self.assertIsNone(user.access_token)
access_token = u'My voice is my passport. Verify me.'
social_auth.extra_data[u'access_token'] = access_token
social_auth.save()
self.assertEqual(user.access_token, access_token)
def test_get_full_name(self):
""" Test that the user model concatenates first and last name if the full name is not set. """
full_name = "George Costanza"
user = G(User, full_name=full_name)
self.assertEquals(user.get_full_name(), full_name)
first_name = "Jerry"
last_name = "Seinfeld"
user = G(User, full_name=None, first_name=first_name, last_name=last_name)
expected = "{first_name} {last_name}".format(first_name=first_name, last_name=last_name)
self.assertEquals(user.get_full_name(), expected)
user = G(User, full_name=full_name, first_name=first_name, last_name=last_name)
self.assertEquals(user.get_full_name(), full_name)
"""Test core.views."""
from django.db import DatabaseError
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse
from django.test import TestCase
from django.test.utils import override_settings
import mock
from edx_course_discovery.apps.core.constants import Status
User = get_user_model()
class HealthTests(TestCase):
"""Tests of the health endpoint."""
def test_all_services_available(self):
"""Test that the endpoint reports when all services are healthy."""
self._assert_health(200, Status.OK, Status.OK)
def test_database_outage(self):
"""Test that the endpoint reports when the database is unavailable."""
with mock.patch('django.db.backends.base.base.BaseDatabaseWrapper.cursor', side_effect=DatabaseError):
self._assert_health(503, Status.UNAVAILABLE, Status.UNAVAILABLE)
def _assert_health(self, status_code, overall_status, database_status):
"""Verify that the response matches expectations."""
response = self.client.get(reverse('health'))
self.assertEqual(response.status_code, status_code)
self.assertEqual(response['content-type'], 'application/json')
expected_data = {
'overall_status': overall_status,
'detailed_status': {
'database_status': database_status
}
}
self.assertJSONEqual(response.content, expected_data)
class AutoAuthTests(TestCase):
""" Auto Auth view tests. """
AUTO_AUTH_PATH = reverse('auto_auth')
@override_settings(ENABLE_AUTO_AUTH=False)
def test_setting_disabled(self):
"""When the ENABLE_AUTO_AUTH setting is False, the view should raise a 404."""
response = self.client.get(self.AUTO_AUTH_PATH)
self.assertEqual(response.status_code, 404)
@override_settings(ENABLE_AUTO_AUTH=True)
def test_setting_enabled(self):
"""
When ENABLE_AUTO_AUTH is set to True, the view should create and authenticate
a new User with superuser permissions.
"""
original_user_count = User.objects.count()
response = self.client.get(self.AUTO_AUTH_PATH)
# Verify that a redirect has occured and that a new user has been created
self.assertEqual(response.status_code, 302)
self.assertEqual(User.objects.count(), original_user_count + 1)
# Get the latest user
user = User.objects.latest()
# Verify that the user is logged in and that their username has the expected prefix
self.assertEqual(int(self.client.session['_auth_user_id']), user.pk)
self.assertTrue(user.username.startswith(settings.AUTO_AUTH_USERNAME_PREFIX))
# Verify that the user has superuser permissions
self.assertTrue(user.is_superuser)
""" Core views. """
import logging
import uuid
from django.db import transaction, connection, DatabaseError
from django.http import JsonResponse
from django.conf import settings
from django.contrib.auth import get_user_model, login, authenticate
from django.http import Http404
from django.shortcuts import redirect
from django.views.generic import View
from edx_course_discovery.apps.core.constants import Status
logger = logging.getLogger(__name__)
User = get_user_model()
@transaction.non_atomic_requests
def health(_):
"""Allows a load balancer to verify this service is up.
Checks the status of the database connection on which this service relies.
Returns:
HttpResponse: 200 if the service is available, with JSON data indicating the health of each required service
HttpResponse: 503 if the service is unavailable, with JSON data indicating the health of each required service
Example:
>>> response = requests.get('https://edx_course_discovery.edx.org/health')
>>> response.status_code
200
>>> response.content
'{"overall_status": "OK", "detailed_status": {"database_status": "OK", "lms_status": "OK"}}'
"""
try:
cursor = connection.cursor()
cursor.execute("SELECT 1")
cursor.fetchone()
cursor.close()
database_status = Status.OK
except DatabaseError:
database_status = Status.UNAVAILABLE
overall_status = Status.OK if (database_status == Status.OK) else Status.UNAVAILABLE
data = {
'overall_status': overall_status,
'detailed_status': {
'database_status': database_status,
},
}
if overall_status == Status.OK:
return JsonResponse(data)
else:
return JsonResponse(data, status=503)
class AutoAuth(View):
"""Creates and authenticates a new User with superuser permissions.
If the ENABLE_AUTO_AUTH setting is not True, returns a 404.
"""
def get(self, request):
"""
Create a new User.
Raises Http404 if auto auth is not enabled.
"""
if not getattr(settings, 'ENABLE_AUTO_AUTH', None):
raise Http404
username_prefix = getattr(settings, 'AUTO_AUTH_USERNAME_PREFIX', 'auto_auth_')
# Create a new user with staff permissions
username = password = username_prefix + uuid.uuid4().hex[0:20]
User.objects.create_superuser(username, email=None, password=password)
# Log in the new user
user = authenticate(username=username, password=password)
login(request, user)
return redirect('/')
# Configuration for i18n workflow.
locales:
- en # English - Source Language
- am # Amharic
- ar # Arabic
- az # Azerbaijani
- bg_BG # Bulgarian (Bulgaria)
- bn_BD # Bengali (Bangladesh)
- bn_IN # Bengali (India)
- bs # Bosnian
- ca # Catalan
- ca@valencia # Catalan (Valencia)
- cs # Czech
- cy # Welsh
- da # Danish
- de_DE # German (Germany)
- el # Greek
- en_GB # English (United Kingdom)
# Don't pull these until we figure out why pages randomly display in these locales,
# when the user's browser is in English and the user is not logged in.
# - en@lolcat # LOLCAT English
# - en@pirate # Pirate English
- es_419 # Spanish (Latin America)
- es_AR # Spanish (Argentina)
- es_EC # Spanish (Ecuador)
- es_ES # Spanish (Spain)
- es_MX # Spanish (Mexico)
- es_PE # Spanish (Peru)
- et_EE # Estonian (Estonia)
- eu_ES # Basque (Spain)
- fa # Persian
- fa_IR # Persian (Iran)
- fi_FI # Finnish (Finland)
- fil # Filipino
- fr # French
- gl # Galician
- gu # Gujarati
- he # Hebrew
- hi # Hindi
- hr # Croatian
- hu # Hungarian
- hy_AM # Armenian (Armenia)
- id # Indonesian
- it_IT # Italian (Italy)
- ja_JP # Japanese (Japan)
- kk_KZ # Kazakh (Kazakhstan)
- km_KH # Khmer (Cambodia)
- kn # Kannada
- ko_KR # Korean (Korea)
- lt_LT # Lithuanian (Lithuania)
- ml # Malayalam
- mn # Mongolian
- mr # Marathi
- ms # Malay
- nb # Norwegian Bokmål
- ne # Nepali
- nl_NL # Dutch (Netherlands)
- or # Oriya
- pl # Polish
- pt_BR # Portuguese (Brazil)
- pt_PT # Portuguese (Portugal)
- ro # Romanian
- ru # Russian
- si # Sinhala
- sk # Slovak
- sl # Slovenian
- sq # Albanian
- sr # Serbian
- ta # Tamil
- te # Telugu
- th # Thai
- tr_TR # Turkish (Turkey)
- uk # Ukranian
- ur # Urdu
- uz # Uzbek
- vi # Vietnamese
- zh_CN # Chinese (China)
- zh_HK # Chinese (Hong Kong)
- zh_TW # Chinese (Taiwan)
# The locales used for fake-accented English, for testing.
dummy_locales:
- eo
import os
from os.path import join, abspath, dirname
# PATH vars
here = lambda *x: join(abspath(dirname(__file__)), *x)
PROJECT_ROOT = here("..")
root = lambda *x: join(abspath(PROJECT_ROOT), *x)
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get('EDX_COURSE_DISCOVERY_SECRET_KEY', 'insecure-secret-key')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles'
)
THIRD_PARTY_APPS = (
'rest_framework',
'social.apps.django_app.default',
'waffle',
)
PROJECT_APPS = (
'edx_course_discovery.apps.core',
'edx_course_discovery.apps.api',
)
INSTALLED_APPS += THIRD_PARTY_APPS
INSTALLED_APPS += PROJECT_APPS
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'social.apps.django_app.middleware.SocialAuthExceptionMiddleware',
'waffle.middleware.WaffleMiddleware',
)
ROOT_URLCONF = 'edx_course_discovery.urls'
# Python dotted path to the WSGI application used by Django's runserver.
WSGI_APPLICATION = 'edx_course_discovery.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases
# Set this value in the environment-specific files (e.g. local.py, production.py, test.py)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.',
'NAME': '',
'USER': '',
'PASSWORD': '',
'HOST': '', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP.
'PORT': '', # Set to empty string for default.
}
}
# Internationalization
# https://docs.djangoproject.com/en/dev/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
LOCALE_PATHS = (
root('conf', 'locale'),
)
# MEDIA CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root
MEDIA_ROOT = root('media')
# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-url
MEDIA_URL = '/media/'
# END MEDIA CONFIGURATION
# STATIC FILE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root
STATIC_ROOT = root('assets')
# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url
STATIC_URL = '/static/'
# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS
STATICFILES_DIRS = (
root('static'),
)
# TEMPLATE CONFIGURATION
# See: https://docs.djangoproject.com/en/1.8/ref/settings/#templates
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': True,
'OPTIONS': {
'context_processors': (
'django.contrib.auth.context_processors.auth',
'django.template.context_processors.debug',
'django.template.context_processors.i18n',
'django.template.context_processors.media',
'django.template.context_processors.static',
'django.template.context_processors.tz',
'django.contrib.messages.context_processors.messages',
'edx_course_discovery.apps.core.context_processors.core',
),
'debug': True, # Django will only display debug pages if the global DEBUG setting is set to True.
}
},
]
# END TEMPLATE CONFIGURATION
# COOKIE CONFIGURATION
# The purpose of customizing the cookie names is to avoid conflicts when
# multiple Django services are running behind the same hostname.
# Detailed information at: https://docs.djangoproject.com/en/dev/ref/settings/
SESSION_COOKIE_NAME = 'edx_course_discovery_sessionid'
CSRF_COOKIE_NAME = 'edx_course_discovery_csrftoken'
LANGUAGE_COOKIE_NAME = 'edx_course_discovery_language'
# END COOKIE CONFIGURATION
# AUTHENTICATION CONFIGURATION
LOGIN_URL = '/login/'
LOGOUT_URL = '/logout/'
AUTH_USER_MODEL = 'core.User'
AUTHENTICATION_BACKENDS = (
'auth_backends.backends.EdXOpenIdConnect',
'django.contrib.auth.backends.ModelBackend',
)
ENABLE_AUTO_AUTH = False
AUTO_AUTH_USERNAME_PREFIX = 'auto_auth_'
# Set these to the correct values for your OAuth2/OpenID Connect provider (e.g., devstack)
SOCIAL_AUTH_EDX_OIDC_KEY = 'replace-me'
SOCIAL_AUTH_EDX_OIDC_SECRET = 'replace-me'
SOCIAL_AUTH_EDX_OIDC_URL_ROOT = 'replace-me'
SOCIAL_AUTH_EDX_OIDC_ID_TOKEN_DECRYPTION_KEY = SOCIAL_AUTH_EDX_OIDC_SECRET
# Request the user's permissions in the ID token
EXTRA_SCOPE = ['permissions']
# TODO Set this to another (non-staff, ideally) path.
LOGIN_REDIRECT_URL = '/admin/'
# END AUTHENTICATION CONFIGURATION
# OPENEDX-SPECIFIC CONFIGURATION
PLATFORM_NAME = 'Your Platform Name Here'
# END OPENEDX-SPECIFIC CONFIGURATION
from edx_course_discovery.settings.base import *
from edx_course_discovery.settings.utils import get_logger_config
DEBUG = True
# CACHE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#caches
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
}
}
# END CACHE CONFIGURATION
# DATABASE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': root('default.db'),
'USER': '',
'PASSWORD': '',
'HOST': '',
'PORT': '',
}
}
# END DATABASE CONFIGURATION
# EMAIL CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# END EMAIL CONFIGURATION
# TOOLBAR CONFIGURATION
# See: http://django-debug-toolbar.readthedocs.org/en/latest/installation.html
if os.environ.get('ENABLE_DJANGO_TOOLBAR', False):
INSTALLED_APPS += (
'debug_toolbar',
)
MIDDLEWARE_CLASSES += (
'debug_toolbar.middleware.DebugToolbarMiddleware',
)
DEBUG_TOOLBAR_PATCH_SETTINGS = False
INTERNAL_IPS = ('127.0.0.1',)
# END TOOLBAR CONFIGURATION
# AUTHENTICATION
# Set these to the correct values for your OAuth2/OpenID Connect provider (e.g., devstack)
SOCIAL_AUTH_EDX_OIDC_KEY = 'replace-me'
SOCIAL_AUTH_EDX_OIDC_SECRET = 'replace-me'
SOCIAL_AUTH_EDX_OIDC_URL_ROOT = 'replace-me'
SOCIAL_AUTH_EDX_OIDC_ID_TOKEN_DECRYPTION_KEY = SOCIAL_AUTH_EDX_OIDC_SECRET
ENABLE_AUTO_AUTH = True
LOGGING = get_logger_config(debug=DEBUG, dev_env=True, local_loglevel='DEBUG')
#####################################################################
# Lastly, see if the developer has any local overrides.
if os.path.isfile(join(dirname(abspath(__file__)), 'private.py')):
from .private import * # pylint: disable=import-error
SOCIAL_AUTH_EDX_OIDC_KEY = 'replace-me'
SOCIAL_AUTH_EDX_OIDC_SECRET = 'replace-me'
SOCIAL_AUTH_EDX_OIDC_URL_ROOT = 'http://127.0.0.1:8000/oauth'
SOCIAL_AUTH_EDX_OIDC_ID_TOKEN_DECRYPTION_KEY = SOCIAL_AUTH_EDX_OIDC_SECRET
from os import environ
import yaml
from edx_course_discovery.settings.base import *
from edx_course_discovery.settings.utils import get_env_setting, get_logger_config
DEBUG = False
TEMPLATE_DEBUG = DEBUG
ALLOWED_HOSTS = ['*']
LOGGING = get_logger_config()
CONFIG_FILE = get_env_setting('EDX_COURSE_DISCOVERY_CFG')
with open(CONFIG_FILE) as f:
config_from_yaml = yaml.load(f)
vars().update(config_from_yaml)
DB_OVERRIDES = dict(
PASSWORD=environ.get('DB_MIGRATION_PASS', DATABASES['default']['PASSWORD']),
ENGINE=environ.get('DB_MIGRATION_ENGINE', DATABASES['default']['ENGINE']),
USER=environ.get('DB_MIGRATION_USER', DATABASES['default']['USER']),
NAME=environ.get('DB_MIGRATION_NAME', DATABASES['default']['NAME']),
HOST=environ.get('DB_MIGRATION_HOST', DATABASES['default']['HOST']),
PORT=environ.get('DB_MIGRATION_PORT', DATABASES['default']['PORT']),
)
for override, value in DB_OVERRIDES.iteritems():
DATABASES['default'][override] = value
import os
from edx_course_discovery.settings.base import *
from edx_course_discovery.settings.utils import get_logger_config
# TEST SETTINGS
INSTALLED_APPS += (
'django_nose',
)
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
NOSE_ARGS = [
'--with-ignore-docstrings',
'--logging-level=DEBUG',
]
LOGGING = get_logger_config(debug=False, dev_env=True, local_loglevel='DEBUG')
# END TEST SETTINGS
# IN-MEMORY TEST DATABASE
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
'USER': '',
'PASSWORD': '',
'HOST': '',
'PORT': '',
},
}
# END IN-MEMORY TEST DATABASE
from os import environ, path
import platform
import sys
from logging.handlers import SysLogHandler
from django.core.exceptions import ImproperlyConfigured
def get_env_setting(setting):
""" Get the environment setting or raise exception """
try:
return environ[setting]
except KeyError:
error_msg = "Set the [%s] env variable!" % setting
raise ImproperlyConfigured(error_msg)
def get_logger_config(log_dir='/var/tmp',
logging_env="no_env",
edx_filename="edx.log",
dev_env=False,
debug=False,
local_loglevel='INFO',
service_variant='edx_course_discovery'):
"""
Return the appropriate logging config dictionary. You should assign the
result of this to the LOGGING var in your settings.
If dev_env is set to true logging will not be done via local rsyslogd,
instead, application logs will be dropped in log_dir.
"edx_filename" is ignored unless dev_env is set to true since otherwise logging is handled by rsyslogd.
"""
# Revert to INFO if an invalid string is passed in
if local_loglevel not in ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']:
local_loglevel = 'INFO'
hostname = platform.node().split(".")[0]
syslog_format = (
"[service_variant={service_variant}]"
"[%(name)s][env:{logging_env}] %(levelname)s "
"[{hostname} %(process)d] [%(filename)s:%(lineno)d] "
"- %(message)s"
).format(
service_variant=service_variant,
logging_env=logging_env, hostname=hostname
)
if debug:
handlers = ['console']
else:
handlers = ['local']
logger_config = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': '%(asctime)s %(levelname)s %(process)d '
'[%(name)s] %(filename)s:%(lineno)d - %(message)s',
},
'syslog_format': {'format': syslog_format},
'raw': {'format': '%(message)s'},
},
'handlers': {
'console': {
'level': 'DEBUG' if debug else 'INFO',
'class': 'logging.StreamHandler',
'formatter': 'standard',
'stream': sys.stdout,
},
},
'loggers': {
'django': {
'handlers': handlers,
'propagate': True,
'level': 'INFO'
},
'requests': {
'handlers': handlers,
'propagate': True,
'level': 'WARNING'
},
'factory': {
'handlers': handlers,
'propagate': True,
'level': 'WARNING'
},
'django.request': {
'handlers': handlers,
'propagate': True,
'level': 'WARNING'
},
'': {
'handlers': handlers,
'level': 'DEBUG',
'propagate': False
},
}
}
if dev_env:
edx_file_loc = path.join(log_dir, edx_filename)
logger_config['handlers'].update({
'local': {
'class': 'logging.handlers.RotatingFileHandler',
'level': local_loglevel,
'formatter': 'standard',
'filename': edx_file_loc,
'maxBytes': 1024 * 1024 * 2,
'backupCount': 5,
},
})
else:
logger_config['handlers'].update({
'local': {
'level': local_loglevel,
'class': 'logging.handlers.SysLogHandler',
# Use a different address for Mac OS X
'address': '/var/run/syslog' if sys.platform == "darwin" else '/dev/log',
'formatter': 'syslog_format',
'facility': SysLogHandler.LOG_LOCAL0,
},
})
return logger_config
"""edx_course_discovery URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/1.8/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
Including another URLconf
1. Add an import: from blog import urls as blog_urls
2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls))
"""
import os
from django.conf import settings
from django.conf.urls import include, url
from django.contrib import admin
from django.contrib.auth.views import logout
from django.core.urlresolvers import reverse_lazy
from django.views.generic import RedirectView
from edx_course_discovery.apps.core import views as core_views
admin.autodiscover()
# pylint: disable=invalid-name
# Always login via edX OpenID Connect
login = RedirectView.as_view(url=reverse_lazy('social:begin', args=['edx-oidc']), permanent=False, query_string=True)
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^api/', include('edx_course_discovery.apps.api.urls', namespace='api')),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
url(r'^auto_auth/$', core_views.AutoAuth.as_view(), name='auto_auth'),
url(r'^health/$', core_views.health, name='health'),
url(r'^login/$', login, name='login'),
url(r'^logout/$', logout, name='logout'),
url('', include('social.apps.django_app.urls', namespace='social')),
]
if settings.DEBUG and os.environ.get('ENABLE_DJANGO_TOOLBAR', False): # pragma: no cover
import debug_toolbar # pylint: disable=import-error
urlpatterns.append(url(r'^__debug__/', include(debug_toolbar.urls)))
"""
WSGI config for edx_course_discovery.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/
"""
import os
from os.path import abspath, dirname
from sys import path
SITE_ROOT = dirname(dirname(abspath(__file__)))
path.append(SITE_ROOT)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "edx_course_discovery.settings.local")
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application() # pylint: disable=invalid-name
#!/usr/bin/env python
"""
Django administration utility.
"""
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "edx_course_discovery.settings.local")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
# ***************************
# ** DO NOT EDIT THIS FILE **
# ***************************
#
# It is generated by:
# $ edx_lint write pylintrc
#
#
#
#
#
#
#
#
# STAY AWAY!
#
#
#
#
#
# SERIOUSLY.
#
# ------------------------------
[MASTER]
profile = no
ignore = ,migrations, settings, wsgi.py
persistent = yes
load-plugins = edx_lint.pylint,pylint_django
[MESSAGES CONTROL]
disable =
locally-disabled,
locally-enabled,
too-few-public-methods,
bad-builtin,
star-args,
abstract-class-not-used,
abstract-class-little-used,
no-init,
fixme,
too-many-lines,
no-self-use,
too-many-ancestors,
too-many-instance-attributes,
too-few-public-methods,
too-many-public-methods,
too-many-return-statements,
too-many-branches,
too-many-arguments,
too-many-locals,
unused-wildcard-import,
duplicate-code,invalid-name
[REPORTS]
output-format = text
files-output = no
reports = no
evaluation = 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
comment = no
[BASIC]
required-attributes =
bad-functions = map,filter,apply,input
module-rgx = (([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
const-rgx = (([A-Z_][A-Z0-9_]*)|(__.*__)|log|urlpatterns|logger|User)$
class-rgx = [A-Z_][a-zA-Z0-9]+$
function-rgx = ([a-z_][a-z0-9_]{2,30}|test_[a-z0-9_]+)$
method-rgx = ([a-z_][a-z0-9_]{2,40}|setUp|set[Uu]pClass|tearDown|tear[Dd]ownClass|assert[A-Z]\w*|maxDiff|test_[a-z0-9_]+)$
attr-rgx = [a-z_][a-z0-9_]{2,30}$
argument-rgx = [a-z_][a-z0-9_]{2,30}$
variable-rgx = [a-z_][a-z0-9_]{2,30}$
class-attribute-rgx = ([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
inlinevar-rgx = [A-Za-z_][A-Za-z0-9_]*$
good-names = f,i,j,k,db,ex,Run,_,__
bad-names = foo,bar,baz,toto,tutu,tata
no-docstring-rgx = __.*__$|test_.+|setUp$|setUpClass$|tearDown$|tearDownClass$|Meta$
docstring-min-length = -1
[FORMAT]
max-line-length = 120
ignore-long-lines = ^\s*(# )?<?https?://\S+>?$
single-line-if-stmt = no
no-space-check = trailing-comma,dict-separator
max-module-lines = 1000
indent-string = ' '
[MISCELLANEOUS]
notes = FIXME,XXX,TODO
[SIMILARITIES]
min-similarity-lines = 4
ignore-comments = yes
ignore-docstrings = yes
ignore-imports = no
[TYPECHECK]
ignore-mixin-members = yes
ignored-classes = SQLObject
zope = no
unsafe-load-any-extension = yes
generated-members =
REQUEST,
acl_users,
aq_parent,
objects,
DoesNotExist,
can_read,
can_write,
get_url,
size,
content,
status_code,
create,
build,
fields,
tag,
org,
course,
category,
name,
revision,
_meta,
[VARIABLES]
init-import = no
dummy-variables-rgx = _|dummy|unused|.*_unused
additional-builtins =
[CLASSES]
ignore-iface-methods = isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
defining-attr-methods = __init__,__new__,setUp
valid-classmethod-first-arg = cls
valid-metaclass-classmethod-first-arg = mcs
[DESIGN]
max-args = 5
ignored-argument-names = _.*
max-locals = 15
max-returns = 6
max-branches = 12
max-statements = 50
max-parents = 7
max-attributes = 7
min-public-methods = 2
max-public-methods = 20
[IMPORTS]
deprecated-modules = regsub,TERMIOS,Bastion,rexec
import-graph =
ext-import-graph =
int-import-graph =
[EXCEPTIONS]
overgeneral-exceptions = Exception
# 304129eed6c04643faf6fa5d5070d2b0f6b44ff0
[MASTER]
ignore+= ,migrations, settings, wsgi.py
[BASIC]
const-rgx = (([A-Z_][A-Z0-9_]*)|(__.*__)|log|urlpatterns|logger|User)$
[MESSAGES CONTROL]
DISABLE+= ,invalid-name
\ No newline at end of file
# This file is here because many Platforms as a Service look for
# requirements.txt in the root directory of a project.
-r requirements/production.txt
django>=1.8.6,<1.9
django-extensions==1.5.9
django-waffle
djangorestframework
edx-auth-backends
pytz
Sphinx
sphinx_rtd_theme
# Packages required for local development
-r test.txt
-r docs.txt
django-debug-toolbar
# i18n
transifex-client
git+https://github.com/edx/i18n-tools.git@v0.1.4#egg=i18n_tools==0.1.4
# Packages required in a production environment
-r base.txt
gevent==1.0.2
gunicorn==19.3.0
mysqlclient==1.3.7
PyYAML==3.11
# Packages required for testing
-r base.txt
coverage
django-dynamic-fixture
django-nose
mock
nose-ignore-docstring
pep8
pylint
edx-lint
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment