├── __init__.py ├── blog ├── __init__.py ├── admin.py └── models.py ├── help ├── __init__.py ├── views.py ├── models.py └── tests.py ├── wiki ├── __init__.py ├── forms │ ├── __init__.py │ ├── messages.py │ └── courses.py ├── utils │ ├── __init__.py │ ├── users.py │ ├── constants.py │ ├── tools.py │ ├── currents.py │ ├── history.py │ ├── decorators.py │ └── pages.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── remarkdown.py ├── templatetags │ ├── __init__.py │ ├── extras.py │ └── wikinotes_markup.py ├── admin │ ├── __init__.py │ ├── faculties.py │ ├── departments.py │ ├── pages.py │ ├── series.py │ └── courses.py ├── context_processors.py ├── models │ ├── __init__.py │ ├── faculties.py │ ├── departments.py │ ├── history.py │ ├── series.py │ └── users.py └── tests │ ├── __init__.py │ ├── series.py │ ├── users.py │ ├── pages.py │ └── pagetypes.py ├── views ├── __init__.py ├── news.py └── messages.py ├── assets ├── favicon.ico ├── img │ ├── 404.png │ ├── pin.png │ ├── email.png │ ├── github.png │ ├── gplus.png │ ├── grid.png │ ├── logo.png │ ├── cc-by-nc.png │ ├── facebook.png │ ├── twitter.png │ ├── grid-18px.png │ ├── social │ │ ├── fb.png │ │ ├── g+.png │ │ └── yahoo.png │ ├── wikinotes.png │ ├── digital-ocean.png │ ├── faculty │ │ ├── arts.png │ │ ├── science.png │ │ └── engineering.png │ ├── pages │ │ ├── summary.png │ │ ├── past-exam.png │ │ ├── vocab-list.png │ │ ├── course-quiz.png │ │ └── lecture-notes.png │ ├── department │ │ ├── ANAT.png │ │ ├── ANTH.png │ │ ├── ARCH.png │ │ ├── BIEN.png │ │ ├── BIOC.png │ │ ├── BIOL.png │ │ ├── BMDE.png │ │ ├── CHEE.png │ │ ├── CHEM.png │ │ ├── CIVE.png │ │ ├── COMP.png │ │ ├── EAST.png │ │ ├── ECON.png │ │ ├── ECSE.png │ │ ├── ENGL.png │ │ ├── EPSC.png │ │ ├── FACC.png │ │ ├── GEOG.png │ │ ├── INTD.png │ │ ├── MATH.png │ │ ├── MECH.png │ │ ├── MIME.png │ │ ├── MIMM.png │ │ ├── MPMC.png │ │ ├── NSCI.png │ │ ├── PHAR.png │ │ ├── PHGY.png │ │ ├── PHIL.png │ │ ├── PHYS.png │ │ ├── POLI.png │ │ ├── PSYC.png │ │ ├── URBD.png │ │ ├── URBP.png │ │ ├── ANAT_large.png │ │ ├── ANTH_large.png │ │ ├── ARCH_large.png │ │ ├── BIEN_large.png │ │ ├── BIOC_large.png │ │ ├── BIOL_large.png │ │ ├── BMDE_large.png │ │ ├── CHEE_large.png │ │ ├── CHEM_large.png │ │ ├── CIVE_large.png │ │ ├── COMP_large.png │ │ ├── EAST_large.png │ │ ├── ECON_large.png │ │ ├── ECSE_large.png │ │ ├── ENGL_large.png │ │ ├── EPSC_large.png │ │ ├── FACC_large.png │ │ ├── GEOG_large.png │ │ ├── INTD_large.png │ │ ├── MATH_large.png │ │ ├── MECH_large.png │ │ ├── MIME_large.png │ │ ├── MIMM_large.png │ │ ├── MPMC_large.png │ │ ├── NSCI_large.png │ │ ├── PHAR_large.png │ │ ├── PHGY_large.png │ │ ├── PHIL_large.png │ │ ├── PHYS_large.png │ │ ├── POLI_large.png │ │ ├── PSYC_large.png │ │ ├── URBD_large.png │ │ └── URBP_large.png │ └── edit_buttons │ │ ├── url.png │ │ ├── image.png │ │ ├── list.png │ │ ├── math.png │ │ ├── quote.png │ │ └── table.png ├── apple-touch-icon-precomposed.png ├── apple-touch-icon-72x72-precomposed.png ├── apple-touch-icon-114x114-precomposed.png ├── js │ └── sectionedit.js └── css │ ├── prettify.css │ ├── bootstrap.less │ ├── variables.less │ ├── scaffolding.less │ ├── type.less │ ├── pygments.less │ ├── tables.less │ ├── reset.less │ └── chosen.less ├── templates ├── pages │ ├── summary │ │ ├── cell.html │ │ ├── list_header.html │ │ └── list_body.html │ ├── course-quiz │ │ ├── cell.html │ │ ├── list_header.html │ │ └── list_body.html │ ├── vocab-list │ │ ├── cell.html │ │ ├── list_header.html │ │ └── list_body.html │ ├── past-exam │ │ ├── cell.html │ │ ├── list_header.html │ │ └── list_body.html │ ├── lecture-notes │ │ ├── list_header.html │ │ ├── cell.html │ │ └── list_body.html │ ├── subject_field.html │ ├── subject_data.html │ ├── professor_id_data.html │ ├── link_field.html │ ├── field_templates.html │ ├── link_data.html │ ├── content.html │ ├── exam_field.html │ ├── professor_id_field.html │ ├── semester_field.html │ ├── create_popup.html │ ├── create.html │ ├── edit.html │ ├── series_listing.html │ ├── printview.html │ ├── date_field.html │ ├── history.html │ ├── form.html │ ├── edit_buttons.html │ ├── show.html │ └── commit.html ├── main │ ├── markdown.html │ ├── course_search.html │ ├── ucp.html │ ├── login_error.html │ ├── recent.html │ ├── contributions.html │ └── registration.html ├── about │ ├── history.md │ └── licensing.md ├── help │ ├── overview.md │ └── lexers.md ├── courses │ ├── get_all.html │ ├── popular.html │ ├── faculty_browse.html │ ├── professor_browse.html │ ├── department_overview.html │ ├── department_browse.html │ ├── create.html │ ├── semester_overview.html │ ├── all_browse.html │ ├── category_overview.html │ ├── professor_overview.html │ ├── recent.html │ └── faculty_overview.html ├── 500.html ├── ucp │ ├── overview.html │ ├── preferences.html │ ├── account.html │ └── profile.html ├── news │ ├── view.html │ └── main.html ├── messages │ ├── base.html │ ├── inbox.html │ ├── outbox.html │ ├── view.html │ └── compose.html ├── 403.html ├── search │ └── results.html ├── 404.html └── static.html ├── .gitignore ├── requirements.txt ├── .travis.yml ├── manage.py ├── fabfile.py ├── mdx ├── mdx_mathjax.py ├── mdx_mentions.py ├── mdx_downheader.py ├── mdx_plotly.py ├── mdx_subscript.py ├── mdx_superscript.py ├── mdx_urlize.py ├── mdx_wiki_def_list.py └── mdx_wiki_tables.py ├── readme.md └── urls.py /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blog/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /help/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wiki/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wiki/forms/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wiki/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wiki/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /views/__init__.py: -------------------------------------------------------------------------------- 1 | import main 2 | -------------------------------------------------------------------------------- /wiki/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wiki/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /help/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | -------------------------------------------------------------------------------- /help/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/favicon.ico -------------------------------------------------------------------------------- /assets/img/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/404.png -------------------------------------------------------------------------------- /assets/img/pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/pin.png -------------------------------------------------------------------------------- /assets/img/email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/email.png -------------------------------------------------------------------------------- /assets/img/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/github.png -------------------------------------------------------------------------------- /assets/img/gplus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/gplus.png -------------------------------------------------------------------------------- /assets/img/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/grid.png -------------------------------------------------------------------------------- /assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/logo.png -------------------------------------------------------------------------------- /templates/pages/summary/cell.html: -------------------------------------------------------------------------------- 1 | {{ page.subject }} 2 | -------------------------------------------------------------------------------- /assets/img/cc-by-nc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/cc-by-nc.png -------------------------------------------------------------------------------- /assets/img/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/facebook.png -------------------------------------------------------------------------------- /assets/img/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/twitter.png -------------------------------------------------------------------------------- /templates/main/markdown.html: -------------------------------------------------------------------------------- 1 | {% load wikinotes_markup %} 2 | {{ content|wikinotes_markdown|safe }} 3 | -------------------------------------------------------------------------------- /templates/pages/course-quiz/cell.html: -------------------------------------------------------------------------------- 1 | {{ page.subject }} 2 | -------------------------------------------------------------------------------- /templates/pages/vocab-list/cell.html: -------------------------------------------------------------------------------- 1 | {{ page.subject }} 2 | -------------------------------------------------------------------------------- /assets/img/grid-18px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/grid-18px.png -------------------------------------------------------------------------------- /assets/img/social/fb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/social/fb.png -------------------------------------------------------------------------------- /assets/img/social/g+.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/social/g+.png -------------------------------------------------------------------------------- /assets/img/wikinotes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/wikinotes.png -------------------------------------------------------------------------------- /templates/pages/past-exam/cell.html: -------------------------------------------------------------------------------- 1 | {{ page.title.title }} 2 | -------------------------------------------------------------------------------- /assets/img/digital-ocean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/digital-ocean.png -------------------------------------------------------------------------------- /assets/img/faculty/arts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/faculty/arts.png -------------------------------------------------------------------------------- /assets/img/pages/summary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/pages/summary.png -------------------------------------------------------------------------------- /assets/img/social/yahoo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/social/yahoo.png -------------------------------------------------------------------------------- /assets/img/department/ANAT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/ANAT.png -------------------------------------------------------------------------------- /assets/img/department/ANTH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/ANTH.png -------------------------------------------------------------------------------- /assets/img/department/ARCH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/ARCH.png -------------------------------------------------------------------------------- /assets/img/department/BIEN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/BIEN.png -------------------------------------------------------------------------------- /assets/img/department/BIOC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/BIOC.png -------------------------------------------------------------------------------- /assets/img/department/BIOL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/BIOL.png -------------------------------------------------------------------------------- /assets/img/department/BMDE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/BMDE.png -------------------------------------------------------------------------------- /assets/img/department/CHEE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/CHEE.png -------------------------------------------------------------------------------- /assets/img/department/CHEM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/CHEM.png -------------------------------------------------------------------------------- /assets/img/department/CIVE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/CIVE.png -------------------------------------------------------------------------------- /assets/img/department/COMP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/COMP.png -------------------------------------------------------------------------------- /assets/img/department/EAST.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/EAST.png -------------------------------------------------------------------------------- /assets/img/department/ECON.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/ECON.png -------------------------------------------------------------------------------- /assets/img/department/ECSE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/ECSE.png -------------------------------------------------------------------------------- /assets/img/department/ENGL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/ENGL.png -------------------------------------------------------------------------------- /assets/img/department/EPSC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/EPSC.png -------------------------------------------------------------------------------- /assets/img/department/FACC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/FACC.png -------------------------------------------------------------------------------- /assets/img/department/GEOG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/GEOG.png -------------------------------------------------------------------------------- /assets/img/department/INTD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/INTD.png -------------------------------------------------------------------------------- /assets/img/department/MATH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/MATH.png -------------------------------------------------------------------------------- /assets/img/department/MECH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/MECH.png -------------------------------------------------------------------------------- /assets/img/department/MIME.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/MIME.png -------------------------------------------------------------------------------- /assets/img/department/MIMM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/MIMM.png -------------------------------------------------------------------------------- /assets/img/department/MPMC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/MPMC.png -------------------------------------------------------------------------------- /assets/img/department/NSCI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/NSCI.png -------------------------------------------------------------------------------- /assets/img/department/PHAR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/PHAR.png -------------------------------------------------------------------------------- /assets/img/department/PHGY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/PHGY.png -------------------------------------------------------------------------------- /assets/img/department/PHIL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/PHIL.png -------------------------------------------------------------------------------- /assets/img/department/PHYS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/PHYS.png -------------------------------------------------------------------------------- /assets/img/department/POLI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/POLI.png -------------------------------------------------------------------------------- /assets/img/department/PSYC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/PSYC.png -------------------------------------------------------------------------------- /assets/img/department/URBD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/URBD.png -------------------------------------------------------------------------------- /assets/img/department/URBP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/URBP.png -------------------------------------------------------------------------------- /assets/img/edit_buttons/url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/edit_buttons/url.png -------------------------------------------------------------------------------- /assets/img/faculty/science.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/faculty/science.png -------------------------------------------------------------------------------- /assets/img/pages/past-exam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/pages/past-exam.png -------------------------------------------------------------------------------- /assets/img/pages/vocab-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/pages/vocab-list.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | db.sqlite 3 | data 4 | content 5 | wiki/migrations 6 | assets/css/bootstrap.css 7 | *.swp 8 | *~ 9 | -------------------------------------------------------------------------------- /assets/img/edit_buttons/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/edit_buttons/image.png -------------------------------------------------------------------------------- /assets/img/edit_buttons/list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/edit_buttons/list.png -------------------------------------------------------------------------------- /assets/img/edit_buttons/math.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/edit_buttons/math.png -------------------------------------------------------------------------------- /assets/img/edit_buttons/quote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/edit_buttons/quote.png -------------------------------------------------------------------------------- /assets/img/edit_buttons/table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/edit_buttons/table.png -------------------------------------------------------------------------------- /assets/img/pages/course-quiz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/pages/course-quiz.png -------------------------------------------------------------------------------- /wiki/admin/__init__.py: -------------------------------------------------------------------------------- 1 | import courses 2 | import faculties 3 | import departments 4 | import pages 5 | import series 6 | -------------------------------------------------------------------------------- /assets/img/department/ANAT_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/ANAT_large.png -------------------------------------------------------------------------------- /assets/img/department/ANTH_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/ANTH_large.png -------------------------------------------------------------------------------- /assets/img/department/ARCH_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/ARCH_large.png -------------------------------------------------------------------------------- /assets/img/department/BIEN_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/BIEN_large.png -------------------------------------------------------------------------------- /assets/img/department/BIOC_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/BIOC_large.png -------------------------------------------------------------------------------- /assets/img/department/BIOL_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/BIOL_large.png -------------------------------------------------------------------------------- /assets/img/department/BMDE_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/BMDE_large.png -------------------------------------------------------------------------------- /assets/img/department/CHEE_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/CHEE_large.png -------------------------------------------------------------------------------- /assets/img/department/CHEM_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/CHEM_large.png -------------------------------------------------------------------------------- /assets/img/department/CIVE_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/CIVE_large.png -------------------------------------------------------------------------------- /assets/img/department/COMP_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/COMP_large.png -------------------------------------------------------------------------------- /assets/img/department/EAST_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/EAST_large.png -------------------------------------------------------------------------------- /assets/img/department/ECON_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/ECON_large.png -------------------------------------------------------------------------------- /assets/img/department/ECSE_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/ECSE_large.png -------------------------------------------------------------------------------- /assets/img/department/ENGL_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/ENGL_large.png -------------------------------------------------------------------------------- /assets/img/department/EPSC_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/EPSC_large.png -------------------------------------------------------------------------------- /assets/img/department/FACC_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/FACC_large.png -------------------------------------------------------------------------------- /assets/img/department/GEOG_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/GEOG_large.png -------------------------------------------------------------------------------- /assets/img/department/INTD_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/INTD_large.png -------------------------------------------------------------------------------- /assets/img/department/MATH_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/MATH_large.png -------------------------------------------------------------------------------- /assets/img/department/MECH_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/MECH_large.png -------------------------------------------------------------------------------- /assets/img/department/MIME_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/MIME_large.png -------------------------------------------------------------------------------- /assets/img/department/MIMM_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/MIMM_large.png -------------------------------------------------------------------------------- /assets/img/department/MPMC_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/MPMC_large.png -------------------------------------------------------------------------------- /assets/img/department/NSCI_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/NSCI_large.png -------------------------------------------------------------------------------- /assets/img/department/PHAR_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/PHAR_large.png -------------------------------------------------------------------------------- /assets/img/department/PHGY_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/PHGY_large.png -------------------------------------------------------------------------------- /assets/img/department/PHIL_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/PHIL_large.png -------------------------------------------------------------------------------- /assets/img/department/PHYS_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/PHYS_large.png -------------------------------------------------------------------------------- /assets/img/department/POLI_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/POLI_large.png -------------------------------------------------------------------------------- /assets/img/department/PSYC_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/PSYC_large.png -------------------------------------------------------------------------------- /assets/img/department/URBD_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/URBD_large.png -------------------------------------------------------------------------------- /assets/img/department/URBP_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/department/URBP_large.png -------------------------------------------------------------------------------- /assets/img/faculty/engineering.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/faculty/engineering.png -------------------------------------------------------------------------------- /assets/img/pages/lecture-notes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/img/pages/lecture-notes.png -------------------------------------------------------------------------------- /templates/about/history.md: -------------------------------------------------------------------------------- 1 | Page under construction. Some info on the old site (http://wikinotes.ca/wiki/wikinotes:About). 2 | -------------------------------------------------------------------------------- /assets/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /templates/pages/course-quiz/list_header.html: -------------------------------------------------------------------------------- 1 | 2 | Subject 3 | Semester 4 | 5 | -------------------------------------------------------------------------------- /templates/pages/summary/list_header.html: -------------------------------------------------------------------------------- 1 | 2 | Subject 3 | Semester 4 | 5 | -------------------------------------------------------------------------------- /templates/pages/vocab-list/list_header.html: -------------------------------------------------------------------------------- 1 | 2 | Subject 3 | Semester 4 | 5 | -------------------------------------------------------------------------------- /assets/apple-touch-icon-72x72-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/apple-touch-icon-72x72-precomposed.png -------------------------------------------------------------------------------- /templates/pages/lecture-notes/list_header.html: -------------------------------------------------------------------------------- 1 | Date 2 | Professor 3 | Semester 4 | -------------------------------------------------------------------------------- /templates/pages/past-exam/list_header.html: -------------------------------------------------------------------------------- 1 | Exam type 2 | Semester 3 | Professor 4 | -------------------------------------------------------------------------------- /assets/apple-touch-icon-114x114-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dellsystem/wikinotes/HEAD/assets/apple-touch-icon-114x114-precomposed.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.5.2 2 | Fabric==1.9.1 3 | GitPython==0.3.2.RC1 4 | Markdown==2.2.1 5 | Pygments==1.6 6 | django-gravatar==0.1.0 7 | mock==1.0.1 8 | -------------------------------------------------------------------------------- /wiki/admin/faculties.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from wiki.models.faculties import Faculty 4 | 5 | 6 | admin.site.register(Faculty) 7 | -------------------------------------------------------------------------------- /wiki/admin/departments.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from wiki.models.departments import Department 4 | 5 | 6 | admin.site.register(Department) 7 | -------------------------------------------------------------------------------- /templates/pages/lecture-notes/cell.html: -------------------------------------------------------------------------------- 1 |

{{ page.title }}

{% if page.subject %}

{{ page.subject }}

{% endif %} 2 | -------------------------------------------------------------------------------- /wiki/utils/users.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def validate_username(username): 5 | if re.match('^\w+$', username): 6 | return True 7 | else: 8 | return False 9 | -------------------------------------------------------------------------------- /wiki/utils/constants.py: -------------------------------------------------------------------------------- 1 | from wiki.utils.currents import current_year 2 | 3 | terms = ['winter', 'summer', 'fall'] 4 | years = range(current_year, 2002, -1) 5 | exam_types = ['midterm', 'final'] 6 | -------------------------------------------------------------------------------- /templates/pages/summary/list_body.html: -------------------------------------------------------------------------------- 1 | {{ page.subject }} 2 | {{ page.course_sem.get_semester }} 3 | -------------------------------------------------------------------------------- /templates/pages/course-quiz/list_body.html: -------------------------------------------------------------------------------- 1 | {{ page.subject }} 2 | {{ page.course_sem.get_semester }} 3 | -------------------------------------------------------------------------------- /templates/pages/subject_field.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | -------------------------------------------------------------------------------- /templates/pages/vocab-list/list_body.html: -------------------------------------------------------------------------------- 1 | {{ page.subject }} 2 | {{ page.course_sem.get_semester }} 3 | -------------------------------------------------------------------------------- /templates/main/course_search.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 | -------------------------------------------------------------------------------- /wiki/context_processors.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | 4 | def config(request): 5 | return { 6 | 'compile_less': settings.COMPILE_LESS, 7 | 'site_url': settings.SITE_URL, 8 | } 9 | -------------------------------------------------------------------------------- /blog/admin.py: -------------------------------------------------------------------------------- 1 | from blog.models import BlogPost 2 | from django.contrib import admin 3 | 4 | class BlogAdmin(admin.ModelAdmin): 5 | prepopulated_fields = {"slug": ("title",)} 6 | 7 | admin.site.register(BlogPost, BlogAdmin) 8 | -------------------------------------------------------------------------------- /templates/help/overview.md: -------------------------------------------------------------------------------- 1 | Although this section of the website is still under construction, you can check out our mostly up-to-date and fairly comprehensive [formatting docs](/help/formatting) to learn how to use the markup on this site. 2 | -------------------------------------------------------------------------------- /templates/pages/subject_data.html: -------------------------------------------------------------------------------- 1 |
2 |
Subject
3 |
4 | {% if subject %}{{ subject }}{% else %}N/A{% endif %} 5 |
6 |
7 | -------------------------------------------------------------------------------- /templates/pages/professor_id_data.html: -------------------------------------------------------------------------------- 1 |
2 |
Professor
3 |
4 | {% if professor %}{{ professor }}{% else %}N/A{% endif %} 5 |
6 |
7 | -------------------------------------------------------------------------------- /templates/pages/link_field.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | -------------------------------------------------------------------------------- /wiki/models/__init__.py: -------------------------------------------------------------------------------- 1 | from courses import * 2 | from departments import * 3 | from faculties import * 4 | from page_types import * # not real django models BUT must be imported before pages 5 | from pages import * 6 | from history import * 7 | from users import * 8 | from series import * 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | install: 5 | - pip install -r requirements.txt 6 | before: 7 | - python manage.py syncdb 8 | - python manage.py loaddata faculties departments courses professors coursesemesters 9 | script: python -Wall manage.py test wiki 10 | -------------------------------------------------------------------------------- /templates/pages/past-exam/list_body.html: -------------------------------------------------------------------------------- 1 | {{ page.title.title }} 2 | {{ page.course_sem.get_semester }} 3 | {{ page.professor }} 4 | -------------------------------------------------------------------------------- /wiki/utils/tools.py: -------------------------------------------------------------------------------- 1 | class Struct: 2 | """Used for testing, so that arbitrary attributes for an object created 3 | on-the-fly can be accessed using dot notation. See wiki/tests/views.py for 4 | more details.""" 5 | def __init__(self, entries): 6 | self.__dict__.update(entries) 7 | -------------------------------------------------------------------------------- /wiki/admin/pages.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from wiki.models.pages import Page, ExternalPage 4 | 5 | 6 | class PageAdmin(admin.ModelAdmin): 7 | list_display = ('get_title', 'page_type', 'course_sem') 8 | 9 | 10 | admin.site.register(Page, PageAdmin) 11 | admin.site.register(ExternalPage) 12 | -------------------------------------------------------------------------------- /templates/courses/get_all.html: -------------------------------------------------------------------------------- 1 | {% for course in courses %}{% endfor %} 2 | {% comment %} 3 | Cannot use parentheses to show the course name because then chosen won't allow you to search for it properly 4 | {% endcomment %} 5 | -------------------------------------------------------------------------------- /wiki/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from wiki.tests.markdown import * 2 | from wiki.tests.pages import * 3 | from wiki.tests.series import * 4 | from wiki.tests.users import * 5 | from wiki.tests.urls import * 6 | from wiki.tests.views import * 7 | from wiki.tests.pagetypes import * 8 | 9 | """ 10 | MORE TESTS TO COME, EVENTUALLY 11 | """ 12 | -------------------------------------------------------------------------------- /templates/pages/field_templates.html: -------------------------------------------------------------------------------- 1 |
2 | {% for field_template in field_templates %} 3 |
4 | {% include field_template %} 5 |
6 | {% endfor %} 7 |
8 | -------------------------------------------------------------------------------- /wiki/admin/series.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from wiki.models.series import Series, SeriesPage, SeriesBanner 4 | 5 | 6 | class SeriesAdmin(admin.ModelAdmin): 7 | prepopulated_fields = {"slug": ("name",)} 8 | 9 | 10 | admin.site.register(Series, SeriesAdmin) 11 | admin.site.register(SeriesPage) 12 | admin.site.register(SeriesBanner) 13 | -------------------------------------------------------------------------------- /templates/pages/link_data.html: -------------------------------------------------------------------------------- 1 |
2 |
Link
3 |
4 |

{% if link %}{{ link }}{% else %}N/A{% endif %}

5 |
6 | Something not correct? Contact admin@wikinotes.ca 7 |
8 |
9 |
10 | -------------------------------------------------------------------------------- /templates/pages/lecture-notes/list_body.html: -------------------------------------------------------------------------------- 1 |

{{ page.title }}

{% if page.subject %}

{{ page.subject }}

{% endif %} 2 | {{ page.professor }} 3 | {{ page.course_sem.get_semester }} 4 | -------------------------------------------------------------------------------- /wiki/utils/currents.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | now = datetime.now() 4 | 5 | # Figure out the term from the month ... let's hope McGill doesn't change this anytime soon 6 | month = now.month 7 | if month < 5: 8 | current_term = 'winter' 9 | elif month < 9: 10 | current_term = 'summer' 11 | else: 12 | current_term = 'fall' 13 | 14 | current_year = now.year 15 | -------------------------------------------------------------------------------- /templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 |
6 |

Error

7 |

Something is broken! Please contact develop@wikinotes.ca, and let us know what you were doing before you received this error message, if possible. We apologise for the inconvenience.

8 |
9 |
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /templates/pages/content.html: -------------------------------------------------------------------------------- 1 |
2 | {{ page.content|safe }} 3 |
4 | Reset quiz 5 | Grade answers » 6 | out of correct ( percent) 7 |
8 |
9 | -------------------------------------------------------------------------------- /help/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates writing tests using the unittest module. These will pass 3 | when you run "manage.py test". 4 | 5 | Replace this with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | 11 | class SimpleTest(TestCase): 12 | def test_basic_addition(self): 13 | """ 14 | Tests that 1 + 1 always equals 2. 15 | """ 16 | self.assertEqual(1 + 1, 2) 17 | -------------------------------------------------------------------------------- /templates/pages/exam_field.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | {% for exam_type in exam_types %} 5 | 6 | {% endfor %} 7 |
8 |
9 | -------------------------------------------------------------------------------- /wiki/admin/courses.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from wiki.models.courses import Course, CourseSemester, Professor 4 | 5 | 6 | class CourseAdmin(admin.ModelAdmin): 7 | exclude = ('num_watchers', 'latest_activity', 'watchers') 8 | 9 | 10 | class ProfessorAdmin(admin.ModelAdmin): 11 | prepopulated_fields = {'slug': ('name',)} 12 | 13 | 14 | admin.site.register(Course, CourseAdmin) 15 | admin.site.register(CourseSemester) 16 | admin.site.register(Professor, ProfessorAdmin) 17 | -------------------------------------------------------------------------------- /templates/ucp/overview.html: -------------------------------------------------------------------------------- 1 |

2 | Welcome to your user control panel, {{ user }}. You can edit your login 3 | details in the Account section, 4 | your profile details in the 5 | Profile section, and your site preferences in the Preferences section. 7 |

8 | 9 |

You have been a member since {{ user.date_joined|date:"F j, Y" }}.

10 | -------------------------------------------------------------------------------- /views/news.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, get_object_or_404 2 | 3 | from blog.models import BlogPost 4 | from wiki.utils.decorators import show_object_detail 5 | 6 | 7 | def main(request): 8 | data = { 9 | 'title': 'News', 10 | 'blog_posts': BlogPost.objects.all(), 11 | } 12 | 13 | return render(request, 'news/main.html', data) 14 | 15 | 16 | @show_object_detail(BlogPost) 17 | def view(request, post): 18 | return { 19 | 'title': str(post), 20 | 'post': post, 21 | } 22 | -------------------------------------------------------------------------------- /templates/news/view.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | {% load wikinotes_markup %} 5 |
6 |
7 |
8 |

{{ post.title }}

9 |
10 |

{{ post.timestamp }}

11 |

{{ post.get_num_comments }} comment{{ post.get_num_comments|pluralize }}

12 | {{ post.body|wikinotes_markdown|safe }} 13 |
14 |
15 |
16 | 17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from django.core.management import execute_manager 3 | import imp 4 | try: 5 | imp.find_module('settings') # Assumed to be in the same directory. 6 | except ImportError: 7 | import sys 8 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__) 9 | sys.exit(1) 10 | 11 | import settings 12 | 13 | if __name__ == "__main__": 14 | execute_manager(settings) 15 | -------------------------------------------------------------------------------- /wiki/templatetags/extras.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | 3 | from django import template 4 | 5 | register = template.Library() 6 | 7 | 8 | @register.filter 9 | def chunks(value, chunk_length): 10 | """ 11 | Breaks a list up into a list of lists of size . 12 | From StackOverflow (http://stackoverflow.com/a/7374167) 13 | """ 14 | clen = int(chunk_length) 15 | i = iter(value) 16 | while True: 17 | chunk = list(itertools.islice(i, clen)) 18 | if chunk: 19 | yield chunk 20 | else: 21 | break 22 | -------------------------------------------------------------------------------- /assets/js/sectionedit.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | var baseURL = window.location.pathname; 3 | var headers = $('.header'); 4 | var i, header, numHeaders, anchor, editLink; 5 | for (i = 0, numHeaders = headers.length; i < numHeaders; i++) { 6 | header = headers[i]; 7 | // Get the name of the anchor for this header 8 | anchor = header.children[1].name; 9 | // Append an element 10 | editLink = 'edit'; 11 | $(header).append(editLink); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /wiki/forms/messages.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | from wiki.models.users import PrivateMessage 4 | 5 | 6 | class PrivateMessageForm(forms.ModelForm): 7 | class Meta: 8 | model = PrivateMessage 9 | fields = ('recipient', 'subject', 'message') 10 | 11 | def __init__(self, *args, **kwargs): 12 | super(PrivateMessageForm, self).__init__(*args, **kwargs) 13 | self.fields['subject'].widget.attrs['class'] = 'xxlarge' 14 | self.fields['message'].widget.attrs['class'] = 'xxlarge' 15 | self.fields['recipient'].widget.attrs['class'] = 'chosen' 16 | -------------------------------------------------------------------------------- /wiki/management/commands/remarkdown.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand, CommandError 2 | 3 | from wiki.models import Page 4 | from wiki.templatetags.wikinotes_markup import wikinotes_markdown 5 | 6 | 7 | class Command(BaseCommand): 8 | help = "Refreshes the content field of pages by re-processing all the markdown" 9 | 10 | def handle(self, *args, **options): 11 | pages = Page.objects.all() 12 | for page in pages: 13 | content = page.load_content() 14 | page.content = wikinotes_markdown(content) 15 | page.save() 16 | self.stdout.write("Refreshed %s\n" % page) 17 | -------------------------------------------------------------------------------- /templates/messages/base.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 |

Private messages :: {{ this_mode|capfirst }}

7 |
    8 | {% for mode, mode_url in modes %} 9 | 10 | {{ mode|capfirst }} 11 | 12 | {% endfor %} 13 |
14 | 15 | {% block inner_content %}{% endblock %} 16 |
17 |
18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /templates/main/ucp.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 |

User control panel

7 | 16 | {% include template %} 17 |
18 |
19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /assets/css/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} -------------------------------------------------------------------------------- /templates/pages/professor_id_field.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 11 | Can't find your professor in the list? Send us an email at admin@wikinotes.ca. 12 |
13 | -------------------------------------------------------------------------------- /fabfile.py: -------------------------------------------------------------------------------- 1 | from fabric.api import * 2 | 3 | 4 | def less(): 5 | local("lessc -x assets/css/bootstrap.less > assets/css/bootstrap.css") 6 | 7 | 8 | def broadcast(): 9 | local("python manage.py runserver 0.0.0.0:8000") 10 | 11 | 12 | def up(): 13 | local("python manage.py runserver --insecure") 14 | 15 | 16 | def test(): 17 | local("python manage.py test wiki") 18 | 19 | 20 | def refresh(): 21 | local("python manage.py remarkdown") 22 | 23 | 24 | def sh(): 25 | local("python manage.py shell") 26 | 27 | 28 | def backup(): 29 | local("python manage.py dumpdata > backup.json") 30 | 31 | 32 | def restart(): 33 | local("kill -HUP `cat /tmp/gunicorn.pid`") 34 | -------------------------------------------------------------------------------- /templates/main/login_error.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 |

Login error

7 |
8 |

The username and password combination you entered is invalid.

9 |

10 | If you've forgotten your password, send an email to 11 | admin@wikinotes.ca and we'll reset it for you. 12 |

13 |

14 | Haven't registered yet? 15 |

16 |
17 |
18 |
19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /templates/news/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | {% load wikinotes_markup %} 5 |
6 |
7 |
8 | {% for post in blog_posts %} 9 |

{{ post.title }}

10 |
11 |

{{ post.timestamp }}

12 |

{{ post.get_num_comments }} comment{{ post.get_num_comments|pluralize }}

13 | {{ post.body|wikinotes_markdown|safe }} 14 |
15 | {% endfor %} 16 |
17 |
18 | 19 |
20 | 21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /templates/pages/semester_field.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | {% for term in terms %} 5 | 6 | {% endfor %} 7 |
8 |
9 | {% for year in years %} 10 | 11 | {% endfor %} 12 |
13 |
14 | -------------------------------------------------------------------------------- /assets/css/bootstrap.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap @VERSION 3 | * 4 | * Copyright 2011 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | * Date: @DATE 10 | */ 11 | 12 | // CSS Reset 13 | @import "reset.less"; 14 | 15 | // Core variables and mixins 16 | @import "variables.less"; 17 | @import "mixins.less"; 18 | 19 | // Grid system and page structure 20 | @import "scaffolding.less"; 21 | 22 | // Styled patterns and elements 23 | @import "type.less"; 24 | @import "forms.less"; 25 | @import "tables.less"; 26 | @import "patterns.less"; 27 | 28 | @import "markdown.less"; 29 | 30 | @import "wikinotes.less"; 31 | @import "pygments.less"; 32 | 33 | @import "chosen.less"; 34 | -------------------------------------------------------------------------------- /mdx/mdx_mathjax.py: -------------------------------------------------------------------------------- 1 | """ 2 | From https://github.com/mayoff/python-markdown-mathjax 3 | """ 4 | 5 | import markdown 6 | 7 | class MathJaxPattern(markdown.inlinepatterns.Pattern): 8 | 9 | def __init__(self): 10 | markdown.inlinepatterns.Pattern.__init__(self, r'(? 5 |
6 |

Unauthorised access

7 |

Unfortunately, it seems that you do not have permission to access this page. 8 | {% if user.is_authenticated %} 9 | If you have permission to access this page under a different account, please log out and sign in under that account. 10 | {% else %} 11 | If you have an account which does have permission to access this page, please sign in. 12 | {% endif %} 13 |

14 |

If you have any questions about why we're suddenly restricting access or what pages we've restricted, please contact us (admin at the wikinotes domain). More information on this will be available soon.

15 |
16 | 17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /templates/messages/inbox.html: -------------------------------------------------------------------------------- 1 | {% extends "messages/base.html" %} 2 | 3 | {% block inner_content %} 4 | {% if messages.count %} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {% for message in messages %} 15 | 16 | 17 | 18 | 19 | 20 | {% endfor %} 21 | 22 |
FromSubjectTimestamp
{{ message.sender }}{{ message.subject|default:"(no subject)" }}{{ message.timestamp }}
23 | {% else %} 24 |

You have no messages.

25 | {% endif %} 26 | {% endblock %} 27 | -------------------------------------------------------------------------------- /templates/messages/outbox.html: -------------------------------------------------------------------------------- 1 | {% extends "messages/base.html" %} 2 | 3 | {% block inner_content %} 4 | {% if messages.count %} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {% for message in messages %} 15 | 16 | 17 | 18 | 19 | 20 | {% endfor %} 21 | 22 |
ToSubjectTimestamp
{{ message.recipient }}{{ message.subject|default:"(no subject)" }}{% if not message.is_read %} (unread){% endif %}{{ message.timestamp }}
23 | {% else %} 24 |

You have no sent messages.

25 | {% endif %} 26 | {% endblock %} 27 | -------------------------------------------------------------------------------- /templates/pages/create_popup.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 15 | 16 |
17 | -------------------------------------------------------------------------------- /mdx/mdx_mentions.py: -------------------------------------------------------------------------------- 1 | """ 2 | To mention a user: @theirusername 3 | 4 | Will add doctests later 5 | """ 6 | 7 | import markdown 8 | 9 | # Global Vars 10 | MENTION_RE = r'(?\w+)' 11 | # '[@' + username + '](/user/' + username + ')' 12 | 13 | class MentionPattern(markdown.inlinepatterns.Pattern): 14 | def handleMatch(self, m): 15 | username = m.group('username') 16 | 17 | el = markdown.util.etree.Element('a') 18 | el.text = '@' + username 19 | el.set('href', '/users/' + username) 20 | return el 21 | 22 | class MentionExtension(markdown.Extension): 23 | 24 | def extendMarkdown(self, md, md_globals): 25 | md.inlinePatterns['mention'] = MentionPattern(MENTION_RE, md) 26 | 27 | def makeExtension(configs=None): 28 | return MentionExtension(configs=configs) 29 | 30 | if __name__ == "__main__": 31 | import doctest 32 | doctest.testmod() 33 | -------------------------------------------------------------------------------- /templates/pages/create.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 |
7 |

Creating a page :: {{ page_type.long_name }}

8 | 13 | {% if does_not_exist %} 14 |

The page you requested does not exist! Create it now?

15 | {% endif %} 16 | {% include "pages/form.html" %} 17 |
18 |
19 |
20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /wiki/tests/series.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | from wiki.models.courses import Course 4 | from wiki.models.pages import Page 5 | from wiki.models.series import Series 6 | 7 | 8 | class TestGetNextPosition(TestCase): 9 | fixtures = ['test'] 10 | 11 | def setUp(self): 12 | self.series = Series.objects.create(course=Course.objects.get(pk=1), 13 | name='new', position=2, slug='new') 14 | 15 | def test_empty_series(self): 16 | self.assertEqual(self.series.get_next_position(), 1) 17 | 18 | def test_nonempty_series(self): 19 | page_1 = Page.objects.get(pk=1) 20 | page_2 = Page.objects.get(pk=2) 21 | self.series.seriespage_set.create(page=page_1, position=1) 22 | self.assertEqual(self.series.get_next_position(), 2) 23 | self.series.seriespage_set.create(page=page_2, position=2) 24 | self.assertEqual(self.series.get_next_position(), 3) 25 | -------------------------------------------------------------------------------- /wiki/models/departments.py: -------------------------------------------------------------------------------- 1 | from django.core.urlresolvers import reverse 2 | from django.db import models 3 | 4 | 5 | class Department(models.Model): 6 | short_name = models.CharField(max_length=4, primary_key=True) 7 | long_name = models.CharField(max_length=255) 8 | faculty = models.ForeignKey('Faculty') 9 | url_fields = { 10 | 'department': 'short_name__iexact', 11 | } 12 | 13 | class Meta: 14 | app_label = 'wiki' 15 | ordering = ['short_name'] 16 | 17 | def __unicode__(self): 18 | return "Department of %s (%s)" % (self.long_name, self.short_name) 19 | 20 | def get_absolute_url(self): 21 | return reverse('courses_department_overview', args=[self.pk]) 22 | 23 | def get_image(self): 24 | return "/static/img/department/%s.png" % self.short_name 25 | 26 | def get_large_image(self): 27 | return "/static/img/department/%s_large.png" % self.short_name 28 | -------------------------------------------------------------------------------- /templates/pages/edit.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 |
7 |

Editing {{ page.get_title }}

8 | 15 | {% include "pages/form.html" %} 16 |
17 |
18 |
19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /templates/search/results.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 |

Search results for {{ query }}

7 |

Courses

8 | {% if course_results %} 9 | 14 | {% else %} 15 |

No courses found containing {{ query }} in their name, number or description.

16 |
17 | {% endif %} 18 |
19 |
20 |

21 | The search feature is still under construction. Got a suggestion for 22 | how search should behave? 23 | Let us know! 24 |

25 |
26 |
27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /templates/pages/series_listing.html: -------------------------------------------------------------------------------- 1 | {% with series_pages=page.seriespage_set.all %} 2 | {% if series_pages %} 3 |
4 | 11 | {% endif %} 12 | {% endwith %} 13 | -------------------------------------------------------------------------------- /mdx/mdx_downheader.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import markdown 3 | import re 4 | 5 | def makeExtension(configs=None) : 6 | return DownHeaderExtension(configs=configs) 7 | 8 | class DownHeaderExtension(markdown.Extension): 9 | def extendMarkdown(self, md, md_globals): 10 | if 'downheader' in md.treeprocessors.keys(): 11 | md.treeprocessors['downheader'].offset += 1 12 | else: 13 | md.treeprocessors.add('downheader', DownHeaderProcessor(), '_end') 14 | 15 | class DownHeaderProcessor(markdown.treeprocessors.Treeprocessor): 16 | def __init__(self, offset=1): 17 | markdown.treeprocessors.Treeprocessor.__init__(self) 18 | self.offset = offset 19 | def run(self, node): 20 | expr = re.compile('h(\d)') 21 | for child in node.getiterator(): 22 | match = expr.match(child.tag) 23 | if match: 24 | child.tag = 'h' + str(min(6, int(match.group(1))+self.offset)) 25 | return node 26 | -------------------------------------------------------------------------------- /templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 |
6 |
7 | 404 - Page not found 8 |
9 |

10 | We can't seem to find the page you're looking for. Sorry! Please 11 | keep in mind that this site is still under development, and so not 12 | all the pages and features are complete. Feel free to contact us by 13 | email, Facebook or 14 | Twitter if you have questions, comments, or suggestions relating 15 | to the site. 16 |

17 |

18 | If you're trying to access a course that we don't have in our 19 | database, you can always create it yourself &endash; 20 | Create a course 21 |

22 |
23 |
24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /blog/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from django.core.urlresolvers import reverse 3 | from django.db import models 4 | 5 | 6 | class BlogPost(models.Model): 7 | author = models.ForeignKey(User) 8 | title = models.CharField(max_length=50) 9 | timestamp = models.DateTimeField() 10 | body = models.TextField() 11 | summary = models.CharField(max_length=100) 12 | slug = models.SlugField() 13 | url_fields = { 14 | 'slug': 'slug', 15 | } 16 | 17 | class Meta: 18 | ordering = ['-timestamp'] 19 | 20 | def __unicode__(self): 21 | return "%s (%s)" % (self.title, self.timestamp.strftime("%B %d, %Y")) 22 | 23 | def get_absolute_url(self): 24 | return reverse('news_view', args=[self.slug]) 25 | 26 | def get_num_comments(self): 27 | return self.blogcomment_set.count() 28 | 29 | 30 | class BlogComment(models.Model): 31 | author = models.ForeignKey(User) 32 | post = models.ForeignKey(BlogPost) 33 | body = models.TextField() 34 | -------------------------------------------------------------------------------- /wiki/forms/courses.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.utils.safestring import mark_safe 3 | 4 | from wiki.models.courses import Course 5 | 6 | 7 | class CourseForm(forms.ModelForm): 8 | class Meta: 9 | model = Course 10 | exclude = ('watchers', 'num_watchers', 'latest_activity') 11 | 12 | def clean(self): 13 | # Make sure we don't already have this course 14 | cleaned_data = super(CourseForm, self).clean() 15 | department = cleaned_data.get('department') 16 | number = cleaned_data.get('number') 17 | matching = Course.objects.filter(department=department, number=number) 18 | 19 | if matching.exists(): 20 | course = matching[0] 21 | course_url = course.get_absolute_url() 22 | course_link = '%s' % (course_url, course) 23 | raise forms.ValidationError(mark_safe("The course you're trying to" 24 | " create already exists: %s." % course_link)) 25 | 26 | return cleaned_data 27 | -------------------------------------------------------------------------------- /templates/courses/popular.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 | 9 |
10 |
11 |

Shows the courses, listed by popularity (defined by number of watchers maybe)

12 | {% for dept in departments %} 13 |

{{ dept.long }} » View department page

14 | {% if dept.courses %} 15 |
    16 | {% for course in dept.courses %} 17 |
  • {{ course }}
  • 18 | {% endfor %} 19 |
20 | {% else %} 21 |

No courses in this department

22 | {% endif %} 23 | {% endfor %} 24 |
25 |
26 |
27 |
28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /templates/courses/faculty_browse.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 | 9 |
10 |
11 |

Shows all the courses, listed alphabetically under the relevant faculty.

12 | {% for faculty, courses in faculties %} 13 |

{{ faculty.name }} » View faculty page

14 | {% if courses %} 15 |
    16 | {% for course in courses %} 17 |
  • {{ course }}
  • 18 | {% endfor %} 19 |
20 | {% else %} 21 |

No courses in this faculty

22 | {% endif %} 23 | {% endfor %} 24 |
25 |
26 |
27 |
28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /templates/messages/view.html: -------------------------------------------------------------------------------- 1 | {% extends "messages/base.html" %} 2 | 3 | {% load wikinotes_markup %} 4 | {% load url from future %} 5 | 6 | {% block inner_content %} 7 | {% if show_reply %} 8 |
9 |

Reply

10 |
11 | {% endif %} 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
From:{{ message.sender }}
To:{{ message.recipient }}
Subject:{{ message.subject }}
Sent on:{{ message.timestamp }}
Message:{{ message.message|wikinotes_markdown|safe }}
35 | {% endblock %} 36 | -------------------------------------------------------------------------------- /templates/pages/printview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ page }} - WikiNotes 6 | 7 | 8 | 16 | 17 | 18 |
19 |

{{ page.get_title }} 20 | {% if page.subject %}{{ page.subject }}{% endif %} 21 |

22 | 27 | 28 | {{ page.content|safe }} 29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /mdx/mdx_plotly.py: -------------------------------------------------------------------------------- 1 | """Plot.ly extension for Markdown. 2 | 3 | Expected format: {plot dellsystem 0} 4 | """ 5 | 6 | import markdown 7 | 8 | PLOTLY_RE = r'\{plot ([^ ]+) (\d+)\}' 9 | WIDTH = 940 10 | HEIGHT = 500 11 | 12 | class PlotLyPattern(markdown.inlinepatterns.Pattern): 13 | def handleMatch(self, m): 14 | username = m.group(2) 15 | plot_id = m.group(3) 16 | 17 | el = markdown.util.etree.Element('iframe') 18 | el.attrib['width'] = str(WIDTH) 19 | el.attrib['height'] = str(HEIGHT) 20 | el.attrib['scrolling'] = 'no' 21 | el.attrib['seamless'] = 'seamless' 22 | el.attrib['src'] = 'https://plot.ly/~%s/%s/%d/%d' % (username, 23 | plot_id, 24 | WIDTH, 25 | HEIGHT) 26 | return el 27 | 28 | class PlotLyExtension(markdown.Extension): 29 | """Plot.ly extension for Python-Markdown. """ 30 | 31 | def extendMarkdown(self, md, md_globals): 32 | md.inlinePatterns['plotly'] = PlotLyPattern(PLOTLY_RE, md) 33 | 34 | def makeExtension(configs=None): 35 | return PlotLyExtension(configs=configs) 36 | -------------------------------------------------------------------------------- /wiki/utils/history.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | 4 | # Put here because, why not 5 | def get_date_x_days_ago(num_days): 6 | return datetime.datetime.now() - datetime.timedelta(days=num_days) 7 | 8 | 9 | # Used by HistoryItem to output a human-readable timestamp 10 | # More vague than the built-in timesince filter, which is good 11 | # Written by Joey Bratton http://www.joeyb.org/blog/2009/10/08/custom-django-template-filter-for-humanized-timesince 12 | def humanise_timesince(start_time): 13 | delta = datetime.datetime.now() - start_time 14 | 15 | plural = lambda x: 's' if x != 1 else '' 16 | 17 | num_years = delta.days / 365 18 | if (num_years > 0): 19 | return "%d year%s" % (num_years, plural(num_years)) 20 | 21 | num_weeks = delta.days / 7 22 | if (num_weeks > 0): 23 | return "%d week%s" % (num_weeks, plural(num_weeks)) 24 | 25 | if (delta.days > 0): 26 | return "%d day%s" % (delta.days, plural(delta.days)) 27 | 28 | num_hours = delta.seconds / 3600 29 | if (num_hours > 0): 30 | return "%d hour%s" % (num_hours, plural(num_hours)) 31 | 32 | num_minutes = delta.seconds / 60 33 | if (num_minutes > 0): 34 | return "%d minute%s" % (num_minutes, plural(num_minutes)) 35 | 36 | return "a few seconds" 37 | -------------------------------------------------------------------------------- /wiki/templatetags/wikinotes_markup.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | import markdown 4 | from django import template 5 | from django.utils.html import escape 6 | 7 | register = template.Library() 8 | 9 | """ 10 | Extensions are either part of the standard markdown library or located in the root directory, as mdx_{extension name}.py 11 | 12 | Standard extensions used, unmodified: 13 | * footnotes 14 | * nl2br 15 | 16 | All extensions beginning with wiki_ are modified version of standard extensions. 17 | 18 | mentions converts all @mentions to [@mention](/user/mention) (though in straight HTML), doesn't check if the user exists or not because it's not that smart but otherwise it shouldn't be too problematic 19 | """ 20 | 21 | md = markdown.Markdown(extensions=['mentions', 'wiki_footnotes', 'wiki_toc', 'downheader', 'wiki_tables', 'wiki_codehilite', 'wiki_fenced_code', 'wiki_def_list', 'nl2br', 'subscript', 'superscript', 'mathjax', 'urlize', 'wikilinks', 'plotly'], safe_mode='escape', output_format='xhtml1') 22 | 23 | 24 | # NEEDS TESTS 25 | @register.filter() 26 | def wikinotes_markdown(value): 27 | # Must reset it to clear the footnotes and maybe other stuff too 28 | md.reset() 29 | # Replace \$ with \\$ so that markdown doesn't do anything else to (in conjunction with mathjax's processEscapes) 30 | return md.convert(value) 31 | -------------------------------------------------------------------------------- /templates/courses/professor_browse.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 | 9 |
10 |
11 |

Shows all the courses, listed alphabetically under the relevant professor.

12 | {% for professor in professors %} 13 |

{{ professor }} 14 | 15 | » View professor details 16 | 17 |

18 | {% with courses=professor.get_courses %} 19 | {% if courses.count %} 20 |
    21 | {% for course in courses %} 22 |
  • {{ course }}
  • 23 | {% endfor %} 24 |
25 | {% else %} 26 |

This professor has no associated courses.

27 | {% endif %} 28 | {% endwith %} 29 | {% endfor %} 30 |
31 |
32 |
33 |
34 | {% endblock %} 35 | -------------------------------------------------------------------------------- /templates/courses/department_overview.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 | 9 |
10 |
11 |
12 | 13 |
14 |
15 |

{{ dept.faculty }}

16 |

{{ num_pages }} pages in {{ courses.count }} courses

17 |
18 |
19 |

Courses

20 | {% if courses %} 21 |
    22 | {% for course in courses %} 23 |
  • {{ course }}
  • 24 | {% endfor %} 25 |
26 | {% endif %} 27 |

Active contributors

28 |

List of people who have made a lot of edits to courses (maybe order in terms of deltas) in this department

29 |

Recent activity

30 |

List of recent edits/etc to courses in this department

31 |
32 |
33 | 34 | {% endblock %} 35 | -------------------------------------------------------------------------------- /templates/courses/department_browse.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 | 9 |
10 |
11 |

Shows all the courses, listed alphabetically under the relevant department.

12 | {% for dept in departments %} 13 |

{{ dept.long_name }} 14 | 15 | » View department page 16 | 17 |

18 | {% with courses=dept.course_set.all %} 19 | {% if courses.count %} 20 | 27 | {% else %} 28 |

No courses in this department

29 | {% endif %} 30 | {% endwith %} 31 | {% endfor %} 32 |
33 |
34 |
35 |
36 | {% endblock %} 37 | -------------------------------------------------------------------------------- /templates/messages/compose.html: -------------------------------------------------------------------------------- 1 | {% extends "messages/base.html" %} 2 | 3 | {% block inner_content %} 4 | 5 |
6 | {% csrf_token %} 7 | 8 | {% if form.errors %} 9 |
10 |

Please correct the following errors:

11 |
    12 | {% for field in form %} 13 | {% if field.errors %} 14 |
  • {{ field.label_tag|striptags }}:{{ field.errors|striptags }}
  • 15 | {% endif %} 16 | {% endfor %} 17 |
18 | {{ form.non_field_errors }} 19 |
20 | {% endif %} 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 42 | 43 | 44 |
From:{{ user }}
To:{{ form.recipient }}
Subject:{{ form.subject }}
36 | Message: 37 |

38 | The message contents will be processed with Markdown. 40 |

41 |
{{ form.message }}
45 | 46 |
47 | 48 | 49 |
50 |
51 | {% endblock %} 52 | -------------------------------------------------------------------------------- /templates/static.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | {% load wikinotes_markup %} 5 |
6 |
7 |
8 |
9 |
10 |

{{ mode.title }} {{ page.title }}

11 |
12 | {% if markdown_file %} 13 | {% filter wikinotes_markdown %}{% include markdown_file %}{% endfilter %} 14 | {% else %} 15 | {% if html_file %} 16 | {% include html_file %} 17 | {% else %} 18 |

Coming soon

19 | {% endif %} 20 | {% endif %} 21 |
22 |
23 |
24 |
25 |

{{ mode.title }}

26 | 37 |
38 |
39 |
40 |
41 |
42 | {% endblock %} 43 | -------------------------------------------------------------------------------- /templates/ucp/preferences.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | {% csrf_token %} 6 | Edit site preferences 7 | {% if success %} 8 |
9 |
10 |
11 |

Successfully updated site preferences.

12 |
13 |
14 |
15 | {% endif %} 16 |
17 | 18 |
19 | No Yes 20 |
21 |
22 |
23 |   24 |
25 |
26 |
27 |
28 |
29 |

Configure if you want logged-in users to be able to see your email I guess. That's all I have for now.

30 |
31 |
32 | -------------------------------------------------------------------------------- /mdx/mdx_subscript.py: -------------------------------------------------------------------------------- 1 | """Subscript extension for Markdown. 2 | 3 | To subscript something, place a tilde symbol, '~', before and after the 4 | text that you would like in subscript: C~6~H~12~O~6~ 5 | The numbers in this example will be subscripted. See below for more: 6 | 7 | Examples: 8 | 9 | >>> import markdown 10 | >>> md = markdown.Markdown(extensions=['subscript']) 11 | >>> md.convert('This is sugar: C~6~H~12~O~6~') 12 | u'

This is sugar: C6H12O6

' 13 | 14 | Paragraph breaks will nullify subscripts across paragraphs. Line breaks 15 | within paragraphs will not. 16 | """ 17 | 18 | import markdown 19 | 20 | # Global Vars 21 | SUBSCRIPT_RE = r'(\~)([^\~]*)\2' # the number is subscript~2~ 22 | 23 | class SubscriptPattern(markdown.inlinepatterns.Pattern): 24 | """ Return a subscript Element: `C~6~H~12~O~6~' """ 25 | def handleMatch(self, m): 26 | subsc = m.group(3) 27 | 28 | text = subsc 29 | 30 | el = markdown.util.etree.Element("sub") 31 | el.text = markdown.util.AtomicString(text) 32 | return el 33 | 34 | class SubscriptExtension(markdown.Extension): 35 | """ Subscript Extension for Python-Markdown. """ 36 | 37 | def extendMarkdown(self, md, md_globals): 38 | """ Replace subscript with SubscriptPattern """ 39 | md.inlinePatterns['subscript'] = SubscriptPattern(SUBSCRIPT_RE, md) 40 | 41 | def makeExtension(configs=None): 42 | return SubscriptExtension(configs=configs) 43 | 44 | if __name__ == "__main__": 45 | import doctest 46 | doctest.testmod() 47 | -------------------------------------------------------------------------------- /templates/courses/create.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 | 9 | 10 |
11 |

The department you want isn't in our database? Email the admins (admin at the wikinotes domain).

12 |
13 | 14 | {% if form.errors %} 15 |
16 |

Please correct the following errors:

17 |
    18 | {% for field in form %} 19 | {% if field.errors %} 20 |
  • {{ field.label_tag|striptags }}:{{ field.errors|striptags }}
  • 21 | {% endif %} 22 | {% endfor %} 23 |
24 | {{ form.non_field_errors }} 25 |
26 | {% endif %} 27 | 28 |
29 | {% csrf_token %} 30 |
31 | {% for field in form %} 32 |
33 | {{ field.label_tag }} 34 |
{{ field }}
35 |
36 | {% endfor %} 37 |
38 | 39 |
40 | 41 | 42 |
43 |
44 |
45 |
46 | {% endblock %} 47 | -------------------------------------------------------------------------------- /templates/courses/semester_overview.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block og_image %} 4 | 5 | {% endblock %} 6 | {% block content %} 7 | 8 |
9 |
10 | 14 |

{{ course_sem.get_semester }}

15 |
16 | {% if pages %} 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | {% for page in pages %} 28 | 29 | 30 | 31 | 32 | 33 | {% endfor %} 34 | 35 |
PageCategoryProfessor
{% include page.get_type.get_cell_template %}{{ page.get_type.long_name }}{% if page.professor %}{{ page.professor }}{% else %}N/A{% endif %}
36 | {% endif %} 37 |
38 |
39 | {% endblock %} 40 | -------------------------------------------------------------------------------- /wiki/tests/users.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from django.test import TestCase 3 | 4 | from wiki.models.users import UserProfile 5 | from wiki.models.pages import Page 6 | from wiki.models.history import HistoryItem 7 | from wiki.models.courses import Course 8 | 9 | 10 | class TestUserProfile(TestCase): 11 | fixtures = ['test'] 12 | 13 | def setUp(self): 14 | self.user = User.objects.get(pk=1) 15 | self.profile = self.user.get_profile() 16 | 17 | def test_get_recent_pages(self): 18 | pages = Page.objects.all() 19 | course = Course.objects.get(pk=1) 20 | 21 | # First, edit page 1 twice 22 | HistoryItem.objects.create(user=self.user, action='edited', page=pages[0], course=course) 23 | HistoryItem.objects.create(user=self.user, action='edited', page=pages[0], course=course) 24 | # Then edit page 2 once 25 | HistoryItem.objects.create(user=self.user, action='edited', page=pages[1], course=course) 26 | # Then edit page 4 once 27 | HistoryItem.objects.create(user=self.user, action='edited', page=pages[3], course=course) 28 | # Then edit page 2 once 29 | HistoryItem.objects.create(user=self.user, action='edited', page=pages[1], course=course) 30 | # Then edit page 3 once 31 | HistoryItem.objects.create(user=self.user, action='edited', page=pages[2], course=course) 32 | 33 | self.assertEqual(self.profile.get_recent_pages(5), [pages[2], pages[1], pages[3], pages[0]]) 34 | self.assertEqual(self.profile.get_recent_pages(3), [pages[2], pages[1], pages[3]]) 35 | 36 | def tearDown(self): 37 | self.user.delete() 38 | -------------------------------------------------------------------------------- /templates/courses/all_browse.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 | 10 | {% if courses %} 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% for course in courses %} 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | {% endfor %} 33 | 34 |
CourseDepartmentFacultyCurrent professor(s)WatchingLatest activity
{{ course }}{{ course.department.long_name }}{{ course.department.faculty }}None{{ course.num_watchers }}{{ course.latest_activity.timestamp|date:"F j Y f" }}
35 | {% endif %} 36 |
37 |
38 | {% endblock %} 39 | -------------------------------------------------------------------------------- /templates/ucp/account.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | {% csrf_token %} 5 |
6 | Change your password 7 | {% if success %} 8 |
9 |
10 |
11 |

Successfully changed password.

12 |
13 |
14 |
15 | {% endif %} 16 | {% if form.errors %} 17 |
18 |
19 |
20 | {{ form.errors }} 21 |
22 |
23 |
24 | {% endif %} 25 | {% for field in form %} 26 |
27 | {{ field.label_tag }} 28 |
29 | {{ field }} 30 |
31 |
32 | {% endfor %} 33 |
34 |   35 |
36 |
37 |
38 |
39 |
40 |

You can change your password from this page.

41 |
42 |
43 | -------------------------------------------------------------------------------- /wiki/tests/pages.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | from wiki.utils.pages import get_section_start_end 4 | 5 | 6 | class TestSectionEdit(TestCase): 7 | lines = [ 8 | '# lol', # 0 9 | '## lol', # 1 10 | '# lol', # 2 11 | '# text', # 3 12 | 'blah blah blah', # 4 13 | '## Some subsection', # 5 14 | 'blah blah', # 6 15 | '### Another subsection', # 7 16 | '# Going back up', # 8 17 | 'text', # 9 18 | '```', # 10 19 | '# text', # 11 20 | '```', # 12 21 | '# Text', # 13 22 | ] 23 | tests = { 24 | # The first section 25 | 'lol': (0, 2), 26 | # The first subsection' 27 | 'lol_1': (1, 2), 28 | # A non-existent section 29 | 'header': (0, 14), 30 | # Another section 31 | 'lol_2': (2, 3), 32 | # Another non-existent section 33 | 'lol_3': (0, 14), 34 | # A longer section 35 | 'text': (3, 8), 36 | # Another subsection 37 | 'some-subsection': (5, 8), 38 | # Deeper subsection 39 | 'another-subsection': (7, 8), 40 | # Another section 41 | 'going-back-up': (8, 13), 42 | # Ignoring code blocks 43 | 'text_1': (13, 14), 44 | } 45 | 46 | def runTest(self): 47 | for anchor, expected in self.tests.iteritems(): 48 | actual = get_section_start_end(self.lines, anchor) 49 | self.assertEqual(actual, expected) 50 | -------------------------------------------------------------------------------- /wiki/utils/decorators.py: -------------------------------------------------------------------------------- 1 | from django.http import Http404 2 | from django.shortcuts import render 3 | 4 | 5 | def show_object_detail(model, show_custom_404=False, 6 | always_pass_groups=False): 7 | def wrapper(view): 8 | def new_view(request, **groups): 9 | filters = {} 10 | for group_name, field_name in model.url_fields.iteritems(): 11 | # groups as in regex groups 12 | filters[field_name] = groups[group_name] 13 | 14 | try: 15 | instance = model.objects.get(**filters) 16 | kwargs = groups if always_pass_groups else {} 17 | except model.DoesNotExist: 18 | # If show_custom_404 is set, pass it None as the instance, 19 | # and pass the groups dictionary as well 20 | if show_custom_404: 21 | instance = None 22 | kwargs = groups 23 | else: 24 | raise Http404 25 | 26 | # Call the function to obtain the context dictionary. If it returns 27 | # something else (say, a redirect object), just return that. 28 | context = view(request, instance, **kwargs) 29 | if type(context) is not dict: 30 | return context 31 | 32 | # Figure out the template name based on the function name 33 | parent_module = view.__module__.split('.')[-1] 34 | function_name = view.func_name 35 | template_filename = '%s/%s.html' % (parent_module, 36 | function_name) 37 | 38 | return render(request, template_filename, context) 39 | 40 | return new_view 41 | return wrapper 42 | -------------------------------------------------------------------------------- /templates/pages/date_field.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | -------------------------------------------------------------------------------- /templates/help/lexers.md: -------------------------------------------------------------------------------- 1 | Here you can find a list of the more common languages we support syntax highlighting for, as well as some languages which we don't support directly but which can be highlighted using another language's lexer. For some of the languages, you will also find a short code sample for each language, highlighted with the relevant lexer. 2 | 3 | To see the entire list of languages, take a look at the [Pygments documentation](http://pygments.org/docs/lexers/). 4 | 5 | # Common languages 6 | 7 | **Format:** 8 | 9 | Name of language 10 | : short name (i.e. what to enter in the code block to make it highlight; if there are multiple short names, they are separated by a comma, and using any is acceptable) 11 | 12 | For example, if the short name were `somelang`, you can select that lexer by doing 13 | 14 | ```somelang 15 | [...] 16 | ``` 17 | 18 | Bash 19 | : bash, sh, ksh 20 | Bash session 21 | : console 22 | C 23 | : c 24 | C++ 25 | : c++, cpp 26 | CSS 27 | : css 28 | HTML 29 | : html 30 | Java 31 | : java 32 | Javascript 33 | : js, javascript 34 | Makefile 35 | : make, makefile, mf, bsdmake[^bsdmake] 36 | MATLAB 37 | : matlab 38 | MATLAB session 39 | : matlabsession 40 | MIPS 41 | : gas[^gas] 42 | PHP 43 | : php, php3, php4, php5 44 | Python console 45 | : pycon 46 | Python 47 | : python, py 48 | Python traceback 49 | : pytb 50 | Ruby console 51 | : rbcon, irb 52 | Ruby 53 | : rb, ruby, duby 54 | SML 55 | : ocaml[^ocaml] 56 | SQL 57 | : sql 58 | TeX 59 | : tex, latex 60 | 61 | # Code samples 62 | 63 | Later 64 | 65 | [^gas]: There is no actual lexer for MIPS, but the [gas lexer](http://en.wikipedia.org/wiki/GNU_Assembler) comes close. 66 | [^bsdmake]: For BSD makefiles. 67 | [^ocaml]: Sadly, there is no lexer for Standard ML. The OCaml lexer comes close, though. 68 | -------------------------------------------------------------------------------- /templates/courses/category_overview.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block og_image %} 4 | 5 | {% endblock %} 6 | 7 | {% block content %} 8 |
9 |
10 | 14 |
15 | Create page 16 |
17 |

Viewing category :: {{ category.long_name }}

18 |
19 |
20 | {% if pages %} 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {% for page in pages %} 31 | 32 | 33 | 34 | 35 | 36 | {% endfor %} 37 | 38 |
PageSemesterProfessor
{% include page.get_type.get_cell_template %}{{ page.course_sem.get_semester }}{% if page.professor %}{{ page.professor }}{% else %}N/A{% endif %}
39 | {% else %} 40 |

No pages in this category.

41 | {% endif %} 42 |
43 |
44 | {% endblock %} 45 | -------------------------------------------------------------------------------- /templates/courses/professor_overview.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 | 9 | 10 | {% if professor.link %} 11 |

Personal webpage: {{ professor.link }} 12 | {% endif %} 13 | 14 |

Associated pages

15 | {% if pages %} 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | {% for page in pages %} 27 | {% with course=page.course_sem.course %} 28 | 29 | 30 | 35 | 36 | 37 | 38 | {% endwith %} 39 | {% endfor %} 40 | 41 |
DepartmentCourseSemesterPage
{{ course.department.short_name }} 31 | 32 | {{ course }} ({{ course.name }}) 33 | 34 | {{ page.course_sem.get_semester }}{{ page }}
42 | {% else %} 43 |

There are no pages associated with this professor.

44 | {% endif %} 45 |
46 |
47 | {% endblock %} 48 | -------------------------------------------------------------------------------- /wiki/models/history.py: -------------------------------------------------------------------------------- 1 | from django.core.urlresolvers import reverse 2 | from django.contrib.auth.models import User 3 | from django.db import models 4 | 5 | from wiki.utils.history import get_date_x_days_ago, humanise_timesince 6 | 7 | 8 | class HistoryManager(models.Manager): 9 | # Returns all the HistoryItems with a timestamp between now and x days ago 10 | def get_since_x_days(self, num_days, show_all): 11 | # If show_all is False, ignore watch actions (that's all it means) 12 | cutoff_date = get_date_x_days_ago(int(num_days)) 13 | query_set = self.filter(timestamp__gt=cutoff_date).order_by('-timestamp') 14 | 15 | if not show_all: 16 | query_set = query_set.exclude(page__isnull=True) 17 | return query_set 18 | 19 | 20 | class HistoryItem(models.Model): 21 | objects = HistoryManager() 22 | user = models.ForeignKey(User) 23 | action = models.CharField(max_length=30) 24 | timestamp = models.DateTimeField(auto_now=True) 25 | page = models.ForeignKey('Page', null=True) 26 | message = models.CharField(max_length=255, null=True) 27 | course = models.ForeignKey('Course') 28 | hexsha = models.CharField(max_length=40, null=True) # only used for page editing 29 | 30 | class Meta: 31 | app_label = 'wiki' 32 | 33 | def __unicode__(self): 34 | return 'timestamp: %s, course: %s' % (self.timestamp, self.course) 35 | 36 | def get_absolute_url(self): 37 | if self.page: 38 | if self.hexsha: 39 | url_args = self.page.get_url_args() + (self.hexsha,) 40 | return reverse('pages_commit', args=url_args) 41 | else: 42 | return self.page.get_history_url() 43 | else: 44 | return self.course.get_recent_url() 45 | 46 | def get_timesince(self): 47 | return humanise_timesince(self.timestamp) 48 | 49 | def get_short_hexsha(self): 50 | if self.hexsha: 51 | return self.hexsha[:7] 52 | else: 53 | return '' 54 | -------------------------------------------------------------------------------- /templates/courses/recent.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | {% load gravatar %} 5 |
6 |
7 | 11 |

Recent activity

12 |
13 | {% if history %} 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | {% for item in history %} 25 | 26 | 27 | 28 | 37 | 38 | 39 | {% endfor %} 40 | 41 |
User(s)ActivityCommitTime
{% gravatar_img_for_user item.user 20 %} {{ item.user.username }} {% if not item.page and item.group_count %} and {{ item.group_count }} other{{ item.group_count|pluralize }}{% endif %}{{ item.action }} {% if item.page %}{{ item.page }} {% if item.group_count %}{% if item.group_count == 1 %}twice{% else %}{{ item.group_count|add:"1" }} times{% endif %}{% endif %}{% endif %} 29 | {% if item.hexsha %} 30 | 31 | {{ item.get_short_hexsha }} 32 | 33 | {% else %} 34 | -- 35 | {% endif %} 36 | {{ item.get_timesince}} ago
42 | {% endif %} 43 |
44 |
45 | {% endblock %} 46 | -------------------------------------------------------------------------------- /mdx/mdx_superscript.py: -------------------------------------------------------------------------------- 1 | """Superscipt extension for Markdown. From https://github.com/sgraber/markdown.superscript 2 | 3 | To superscript something, place a carat symbol, '^', before and after the 4 | text that you would like in superscript: 6.02 x 10^23^ 5 | The '23' in this example will be superscripted. See below. 6 | 7 | Examples: 8 | 9 | >>> import markdown 10 | >>> md = markdown.Markdown(extensions=['superscript']) 11 | >>> md.convert('This is a reference to a footnote^1^.') 12 | u'

This is a reference to a footnote1.

' 13 | 14 | >>> md.convert('This is scientific notation: 6.02 x 10^23^') 15 | u'

This is scientific notation: 6.02 x 1023

' 16 | 17 | >>> md.convert('This is scientific notation: 6.02 x 10^23. Note lack of second carat.') 18 | u'

This is scientific notation: 6.02 x 10^23. Note lack of second carat.

' 19 | 20 | >>> md.convert('Scientific notation: 6.02 x 10^23. Add carat at end of sentence.^') 21 | u'

Scientific notation: 6.02 x 1023. Add a carat at the end of sentence..

' 22 | 23 | Paragraph breaks will nullify superscripts across paragraphs. Line breaks 24 | within paragraphs will not. 25 | 26 | """ 27 | 28 | import markdown 29 | 30 | # Global Vars 31 | SUPERSCRIPT_RE = r'(\^)([^\^]*)\2' # the number is a superscript^2^ 32 | 33 | class SuperscriptPattern(markdown.inlinepatterns.Pattern): 34 | """ Return a superscript Element (`word^2^`). """ 35 | def handleMatch(self, m): 36 | supr = m.group(3) 37 | 38 | text = supr 39 | 40 | el = markdown.util.etree.Element("sup") 41 | el.text = markdown.util.AtomicString(text) 42 | return el 43 | 44 | class SuperscriptExtension(markdown.Extension): 45 | """ Superscript Extension for Python-Markdown. """ 46 | 47 | def extendMarkdown(self, md, md_globals): 48 | """ Replace superscript with SuperscriptPattern """ 49 | md.inlinePatterns['superscript'] = SuperscriptPattern(SUPERSCRIPT_RE, md) 50 | 51 | def makeExtension(configs=None): 52 | return SuperscriptExtension(configs=configs) 53 | 54 | if __name__ == "__main__": 55 | import doctest 56 | doctest.testmod() 57 | -------------------------------------------------------------------------------- /assets/css/variables.less: -------------------------------------------------------------------------------- 1 | /* Variables.less 2 | * Variables to customize the look and feel of Bootstrap 3 | * ----------------------------------------------------- */ 4 | 5 | 6 | // Links 7 | @linkColor: #2F8DA8; 8 | @linkColorHover: darken(@linkColor, 15); 9 | 10 | // Grays 11 | @black: #000; 12 | @grayDark: lighten(@black, 25%); 13 | @gray: lighten(@black, 50%); 14 | @grayLight: lighten(@black, 75%); 15 | @grayLighter: lighten(@black, 90%); 16 | @white: #fff; 17 | 18 | // Accent Colors 19 | @blue: #5bc0de; 20 | @blueDark: #339bb9; 21 | @green: #46a546; 22 | @red: #9d261d; 23 | @yellow: #ffc40d; 24 | @orange: #f89406; 25 | @pink: #c3325f; 26 | @purple: #7a43b6; 27 | 28 | // Baseline grid 29 | @basefont: 13px; 30 | @baseline: 18px; 31 | 32 | // Griditude 33 | // Modify the grid styles in mixins.less 34 | @gridColumns: 16; 35 | @gridColumnWidth: 40px; 36 | @gridGutterWidth: 20px; 37 | @extraSpace: (@gridGutterWidth * 2); // For our grid calculations 38 | @siteWidth: (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1)); 39 | 40 | // Color Scheme 41 | // Use this to roll your own color schemes if you like (unused by Bootstrap by default) 42 | @baseColor: @blue; // Set a base color 43 | @complement: spin(@baseColor, 180); // Determine a complementary color 44 | @split1: spin(@baseColor, 158); // Split complements 45 | @split2: spin(@baseColor, -158); 46 | @triad1: spin(@baseColor, 135); // Triads colors 47 | @triad2: spin(@baseColor, -135); 48 | @tetra1: spin(@baseColor, 90); // Tetra colors 49 | @tetra2: spin(@baseColor, -90); 50 | @analog1: spin(@baseColor, 22); // Analogs colors 51 | @analog2: spin(@baseColor, -22); 52 | 53 | 54 | 55 | // More variables coming soon: 56 | // - @basefont to @baseFontSize 57 | // - @baseline to @baseLineHeight 58 | // - @baseFontFamily 59 | // - @primaryButtonColor 60 | // - anything else? File an issue on GitHub 61 | -------------------------------------------------------------------------------- /templates/courses/faculty_overview.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 | 9 |
10 |
11 | 12 |
13 |
14 |

{{ faculty }}

15 |
16 |
17 |

Departments

18 |
19 | {% for dept in departments %} 20 |
21 | 22 |

{{ dept.short_name }}

23 |
24 | {% if forloop.counter|divisibleby:8 %} 25 |
26 |
27 | {% endif %} 28 | {% empty %} 29 |

This faculty has no departments

30 | {% endfor %} 31 |
32 |

Courses

33 |
34 | {% for course in courses %} 35 |
36 | 37 |

{{ course }}

38 |
39 | {% if forloop.counter|divisibleby:8 %} 40 |
41 |
42 | {% endif %} 43 | {% empty %} 44 |

This faculty also has no courses

45 | {% endfor %} 46 |
47 |

Active contributors

48 |

List of people who have made a lot of edits to courses (maybe order in terms of deltas) in this faculty

49 |

Recent activity

50 |

List of recent edits/etc to courses in this faculty

51 |
52 |
53 | 54 | {% endblock %} 55 | -------------------------------------------------------------------------------- /mdx/mdx_urlize.py: -------------------------------------------------------------------------------- 1 | """A more liberal autolinker 2 | 3 | Inspired by Django's urlize function. 4 | 5 | Positive examples: 6 | 7 | >>> import markdown 8 | >>> md = markdown.Markdown(extensions=['urlize']) 9 | 10 | >>> md.convert('http://example.com/') 11 | u'

http://example.com/

' 12 | 13 | >>> md.convert('go to http://example.com') 14 | u'

go to http://example.com

' 15 | 16 | >>> md.convert('example.com') 17 | u'

example.com

' 18 | 19 | >>> md.convert('example.net') 20 | u'

example.net

' 21 | 22 | >>> md.convert('www.example.us') 23 | u'

www.example.us

' 24 | 25 | >>> md.convert('(www.example.us/path/?name=val)') 26 | u'

(www.example.us/path/?name=val)

' 27 | 28 | >>> md.convert('go to now!') 29 | u'

go to http://example.com now!

' 30 | 31 | Negative examples: 32 | 33 | >>> md.convert('del.icio.us') 34 | u'

del.icio.us

' 35 | 36 | """ 37 | 38 | import markdown 39 | 40 | # Global Vars 41 | URLIZE_RE = '(%s)' % '|'.join([ 42 | r'<(?:f|ht)tps?://[^>]*>', 43 | r'\b(?:f|ht)tps?://[^)<>\s]+[^.,)<>\s]', 44 | r'\bwww\.[^)<>\s]+[^.,)<>\s]', 45 | r'[^(<\s]+\.(?:com|net|org|ca)\b', 46 | ]) 47 | 48 | class UrlizePattern(markdown.inlinepatterns.Pattern): 49 | """ Return a link Element given an autolink (`http://example/com`). """ 50 | def handleMatch(self, m): 51 | url = m.group(2) 52 | 53 | if url.startswith('<'): 54 | url = url[1:-1] 55 | 56 | text = url 57 | 58 | if not url.split('://')[0] in ('http','https','ftp'): 59 | if '@' in url and not '/' in url: 60 | url = 'mailto:' + url 61 | else: 62 | url = 'http://' + url 63 | 64 | el = markdown.util.etree.Element("a") 65 | el.set('href', url) 66 | el.text = markdown.util.AtomicString(text) 67 | return el 68 | 69 | class UrlizeExtension(markdown.Extension): 70 | """ Urlize Extension for Python-Markdown. """ 71 | 72 | def extendMarkdown(self, md, md_globals): 73 | """ Replace autolink with UrlizePattern """ 74 | md.inlinePatterns['autolink'] = UrlizePattern(URLIZE_RE, md) 75 | 76 | def makeExtension(configs=None): 77 | return UrlizeExtension(configs=configs) 78 | 79 | if __name__ == "__main__": 80 | import doctest 81 | doctest.testmod() 82 | -------------------------------------------------------------------------------- /templates/main/recent.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | {% load gravatar %} 5 |
6 |
7 |

Recent activity 8 | Last {{ num_days }} day{{ num_days|pluralize }} 9 |

10 |

11 | 12 | {% if show_all %} 13 | Show only page-related activity 14 | {% else %} 15 | Show course- and page-related activity 16 | {% endif %} 17 | 18 |

19 |

20 | Show activity from: 21 | Last 24 hours | 22 | Last 3 days | 23 | Last week | 24 | Last 30 days 25 |

26 | {% if history %} 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | {% for event in history %} 39 | 40 | 41 | 50 | 51 | 52 | 53 | 54 | {% endfor %} 55 | 56 |
UserActionPageCourseTimestamp
{% gravatar_img_for_email event.user.email 20 %} {{ event.user }} 42 | {% if event.hexsha %} 43 | 44 | {{ event.action }} 45 | 46 | {% else %} 47 | {{ event.action }} 48 | {% endif %} 49 | {% if event.page %}{{ event.page }}{% else %}N/A{% endif %}{{ event.course }}{{ event.get_timesince }} ago
57 | {% else %} 58 |

No activity in the last {{ num_days }} day{{ num_days|pluralize }}! 59 | Why not try a larger timeframe, or make something happen?

60 | {% endif %} 61 |
62 |
63 | {% endblock %} 64 | -------------------------------------------------------------------------------- /templates/pages/history.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load url from future %} 3 | 4 | {% block content %} 5 | {% load gravatar %} 6 |
7 |
8 | 15 |

Viewing history :: {{ page }}

16 |
17 | {% if commit_history %} 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | {% for commit in commit_history %} 30 | 31 | 36 | 37 | 38 | 49 | 54 | 55 | {% endfor %} 56 | 57 |
AuthorEdit messageDateStatsOptions
{% gravatar_img_for_user commit.author_name 20 %} 32 | 33 | {{ commit.author_name }} 34 | 35 | {{ commit.message|truncatechars:70|escape }}{{ commit.get_date|date:"D, j M, Y H:i" }} 39 |
41 |
43 |
44 |
45 | {{ commit.num_lines }} lines 46 | (+{{ commit.num_insertions }}, 47 | -{{ commit.num_deletions }}) 48 |
50 | 51 | View details 52 | 53 |
58 | {% endif %} 59 |
60 |
61 | {% endblock %} 62 | -------------------------------------------------------------------------------- /wiki/utils/pages.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import re 3 | 4 | from markdown.extensions.headerid import slugify 5 | 6 | from wiki.models import page_types as types 7 | 8 | 9 | # A dictionary for reverse lookup of page type by the short name 10 | # So, given "course-quiz", find the CourseQuiz object etc 11 | page_types = {} 12 | for name, obj in inspect.getmembers(types): 13 | if inspect.isclass(obj): 14 | # Because it returns a tuple. There is probably a better way of doing it but issubclass() doesn't work so don't try it 15 | if obj.__bases__ == (types.PageType,): 16 | page_types[obj.short_name] = obj() 17 | 18 | page_type_choices = tuple([(name, obj.long_name) for name, obj in page_types.iteritems()]) 19 | 20 | 21 | def get_section_start_end(lines, anchor_name): 22 | """ 23 | THIS NEEDS TESTS 24 | """ 25 | # If there's an underscore + a number at the end of the slug 26 | section_number_match = re.match('[^_]*_(\d+)', anchor_name) 27 | if section_number_match: 28 | section_number = int(section_number_match.group(1)) 29 | # Strip the underscore + number 30 | section_name = re.match('[^_]*', anchor_name).group() 31 | else: 32 | section_number = 0 33 | section_name = anchor_name 34 | 35 | # Start going through the lines, one by one, looking for perfect headers 36 | in_code_block = False 37 | num_found = 0 38 | last_depth = 5 39 | start = 0 40 | end = len(lines) 41 | looking_for_end = False 42 | 43 | # Set up some regular expressions 44 | code_block_re = re.compile('[~`]{3,}') 45 | header_re = re.compile('(#{1,5}) ?(.+)') 46 | 47 | for line_number, line in enumerate(lines): 48 | # Ignore headers inside code blocks 49 | code_block_match = code_block_re.match(line) 50 | if code_block_match: 51 | in_code_block = not in_code_block 52 | 53 | if in_code_block: 54 | continue 55 | 56 | header_match = header_re.match(line) 57 | if header_match: 58 | # Figure out the depth (needed to determine the end of the section) 59 | header_depth = len(header_match.group(1)) 60 | if header_depth <= last_depth and looking_for_end: 61 | end = line_number 62 | looking_for_end = False 63 | 64 | # Save the name of the section here 65 | header = unicode(header_match.group(2)) 66 | if slugify(header, '-') == section_name: 67 | # This is a potential header! 68 | if num_found == section_number: 69 | # This is the right one. Start looking for the end 70 | looking_for_end = True 71 | start = line_number 72 | last_depth = header_depth 73 | 74 | num_found += 1 75 | 76 | return (start, end) 77 | -------------------------------------------------------------------------------- /templates/pages/form.html: -------------------------------------------------------------------------------- 1 |
2 | {% if errors %} 3 |
4 |

Something went wrong! Please correct the errors and try again.

5 |
    6 | {% for error in errors %} 7 |
  • {{ error|safe }}
  • 8 | {% endfor %} 9 |
10 |
11 | {% endif %} 12 | 13 | {% if conflict %} 14 |
15 |

16 | The document was changed by someone else while you were editing. 17 | Please review the merged version for any conflicts (look for the 18 | <<<<<<<). 19 |

20 |
21 | {% endif %} 22 | 23 | {% if no_changes %} 24 |
25 |

You didn't make any changes! Please try again.

26 |
27 | {% endif %} 28 | 29 | {% csrf_token %} 30 |
31 | {% for field_template in field_templates %} 32 |
33 | {% include field_template %} 34 |
35 | {% endfor %} 36 | {% if course_series.count %} 37 |
38 | 39 |
40 | 50 |
51 |
52 | {% endif %} 53 |
54 |
55 |
56 | {% include "pages/edit_buttons.html" %} 57 | 58 |
59 |

60 |

61 | Go fullscreen :: 62 | Help! How 63 | do I format this? 64 |

65 |
66 |
67 |
68 | 69 |
70 | 71 | 72 |
73 |
74 |
75 |
76 |
77 | -------------------------------------------------------------------------------- /templates/pages/edit_buttons.html: -------------------------------------------------------------------------------- 1 |
2 | 6 |
7 |
8 | B 9 | i 10 | H1 11 | H2 12 | H3 13 | 14 | 15 | 16 | 17 | 18 | <> 19 | 20 | :{% comment %} needs some sort of icon lol {% endcomment %} 21 | 22 |

23 |
24 | -------------------------------------------------------------------------------- /templates/about/licensing.md: -------------------------------------------------------------------------------- 1 | All of the course-related content on WikiNotes is available under a [Creative Commons non-commercial license](http://creativecommons.org/licenses/by-nc/3.0/). By course-related content, we mean the editable pages on this site associated with a specific course. This [BIOL 111 organism chart](/BIOL_111/summary/fall-2011/organism-chart), this [guide on solving propositional logic problems for MATH 318](/MATH_318/summary/fall-2011/htsefp-propositional-logic) and this [guide on solving combinational logic problems for COMP 273](/COMP_273/summary/winter-2012/htsefp-digital-logic-combinational-logic) are all examples of student-created, course-related pages whose content is made available under [CC-BY-NC-3.0](http://creativecommons.org/licenses/by-nc/3.0/). 2 | 3 | # Why this license? 4 | 5 | The notions of copyright and ownership can become a bit blurry in the context of a collaborative environment like WikiNotes. In this vein, we've adopted the same sort of policy as [Wikipedia](http://en.wikipedia.org/wiki/Wikipedia:Copyrights) (although we're using a different CC license and we're not co-licensing our content under the GFDL, purely to keep things simple). Our main goal is to ensure that all the information on this site remains freely accessible to anyone who wants to access it. We don't plan on ever restricting access by forcing users to pay or otherwise jump through hoops, and our choice of license is one way of showing our commitment to free-as-in-libre content. (We also don't intend on commercialising this site through advertisements or any other means.) 6 | 7 | # What does this mean for me? 8 | 9 | ## As a contributor 10 | 11 | If you contribute course content, you are making them available under the same [CC-BY-NC](http://creativecommons.org/licenses/by-nc/3.0/) license that all of our course content uses. Of course, you still retain copyright to your content - you are simply granting WikiNotes a license to display your content publicly. If you are not comfortable with this but still wish to contribute, [contact us](/about#contact) and we'll work something out. 12 | 13 | ## As a reader 14 | 15 | You are free to modify and redistribute any of the CC-licensed content on our website. Printing it out for personal use, sharing it with a friend, posting it on another website, or even mirroring the content on your own website (see paragraph below) are not only acceptable by the terms of the license, but encouraged. As long as you abide by the terms of the license - which really just means attributing the content to WikiNotes and refraining from using it for commercial purposes - then it's totally fine with us. 16 | 17 | On the other hand, please do _not_ use our content for commercial purposes or without attribution. This includes selling the notes, posting it on your website and passing it off as your own, or posting it on a website from which you derive ad revenue. 18 | 19 | If you modify the content substantially without intending to post the changes on WikiNotes, that's okay too, although we do hope that your improvements find their way back to our site eventually. 20 | -------------------------------------------------------------------------------- /templates/pages/show.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load url from future %} 3 | 4 | {% block og_image %} 5 | 6 | {% endblock %} 7 | 8 | {% block meta_description %}A student-created resource for the course {{ course }} - {{ course.name }} at McGill University. Available for free on WikiNotes.{% endblock %} 9 | 10 | {% block content %} 11 |
12 |
13 | 19 |
20 | Print 21 | History 22 | Edit 23 | {% if request.user.is_superuser %} 24 | Manage 25 | {% endif %} 26 |
27 |

{{ page.get_title }} CC-BY-NC

28 |
29 |
30 | {% with metadata=page.get_metadata %} 31 | {% if metadata %} 32 |
    33 | {% for field, content in metadata.iteritems %} 34 |
  • {{ field.title }}: {% if content %}{% if content.get_absolute_url %}{{ content }}{% else %}{{ content|urlize }}{% endif %}{% endif %}
  • 35 | {% endfor %} 36 |
37 | {% else %} 38 | {% if page.seriespage_set.count > 0 %} {% endif %} 39 | {% endif %} 40 | {% endwith %} 41 |
42 |
43 | {% include "pages/series_listing.html" %} 44 |
45 |
46 |
47 | {% load wikinotes_markup %} 48 | {% for series in page.seriespage_set.all %} 49 | {% if series.get_banner_markdown %} 50 |
51 | {{ series.get_banner_markdown|wikinotes_markdown|safe }} 52 |
53 | {% endif %} 54 | {% endfor %} 55 | {% include "pages/content.html" %} 56 |
57 | {% include "pages/series_listing.html" %} 58 |
59 |
60 | 61 | 62 | 63 | {% endblock %} 64 | -------------------------------------------------------------------------------- /templates/pages/commit.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | {% load gravatar %} 5 |
6 |
7 | 15 |

Commit {{ commit.hexsha }}

16 |
17 |
18 |
19 |
20 | 21 | 22 | 23 |
24 | {{ commit.author }} 25 |
26 |
27 |
    28 |
  • Timestamp: {{ commit.get_date|date:"F j Y, H:i" }}
  • 29 |
  • Statistics: 30 | {{ commit.num_lines}} 31 | line{{ commit.num_lines|pluralize }} changed 32 | ({{ commit.num_deletions }} 33 | deletion{{ commit.num_deletions|pluralize }}, 34 | {{ commit.num_insertions }} 35 | insertion{{ commit.num_insertions|pluralize }}) 36 |
  • 37 |
  • Edit message: {{ commit.message }}
  • 38 |
39 |
40 |
41 |
42 |

Diff

43 |
44 |
45 | {% for section in commit.get_diff %} 46 |

Starting from line {{ section.first_line }}. {{ section.lines_before }} line{{ section.lines_before|pluralize }} in the previous commit, {{ section.lines_after }} line{{ section.lines_after|pluralize }} after.

47 |
{% for diff_line in section.lines %}{{ diff_line|slice:"1:" }}{% if diff_line.0 == '-' %} {% endif %}{% endfor %}
48 | 
49 | {% empty %} 50 |

This was the first commit. See raw below for the changes made.

51 | {% endfor %} 52 |
53 |

Raw

54 |
55 |
56 | {% with content=commit.get_content %} 57 |
{{ commit.get_content }}
58 |

59 |

Preview

60 |
61 |
62 | {% include "main/markdown.html" %} 63 |
64 | {% endwith %} 65 |
66 |
67 | {% endblock %} 68 | -------------------------------------------------------------------------------- /wiki/models/series.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class SeriesManager(models.Manager): 5 | """ 6 | This custom manager is defined solely for the purpose of allowing hidden series 7 | """ 8 | def visible(self, user, **kwargs): 9 | if user.is_staff: 10 | return self.filter(**kwargs) 11 | else: 12 | return self.filter(is_hidden=False, **kwargs) 13 | 14 | 15 | class Series(models.Model): 16 | objects = SeriesManager() 17 | course = models.ForeignKey('Course') 18 | name = models.CharField(max_length=255) 19 | position = models.IntegerField() 20 | slug = models.SlugField() 21 | banner = models.ForeignKey('SeriesBanner', null=True, blank=True) 22 | is_hidden = models.BooleanField(default=False) 23 | 24 | class Meta: 25 | app_label = 'wiki' 26 | unique_together = (('course', 'position'), ('course', 'slug')) 27 | verbose_name_plural = 'Series' 28 | 29 | def __unicode__(self): 30 | return "%s (%s)" % (self.name, self.course) 31 | 32 | def get_absolute_url(self): 33 | """This can't use `reverse` because it includes an anchor link.""" 34 | return self.course.get_absolute_url() + '#series-' + self.slug 35 | 36 | def can_view(self, user): 37 | return not self.is_hidden or user.is_staff 38 | 39 | def get_num_total(self): 40 | return self.seriespage_set.count() 41 | 42 | def get_next_position(self): 43 | query = self.seriespage_set.all().aggregate(models.Max('position')) 44 | max_position = query['position__max'] 45 | if max_position: 46 | return max_position + 1 47 | else: 48 | return 1 49 | 50 | 51 | class SeriesPage(models.Model): 52 | page = models.ForeignKey('Page') 53 | series = models.ForeignKey('Series') 54 | position = models.IntegerField('position') 55 | 56 | class Meta: 57 | app_label = 'wiki' 58 | # Each page can only be in a series once, each series can have only one page per position 59 | unique_together = (('position', 'series'), ('page', 'series')) 60 | ordering = ['series', 'position'] 61 | 62 | def __unicode__(self): 63 | return "%s - %s (%d)" % (self.page, self.series, self.position) 64 | 65 | def get_previous_page(self): 66 | if self.position > 1: 67 | return SeriesPage.objects.get(series=self.series, position=self.position - 1).page 68 | 69 | def get_next_page(self): 70 | if self.position < self.series.get_num_total(): 71 | return SeriesPage.objects.get(series=self.series, position=self.position + 1).page 72 | 73 | def get_banner_markdown(self): 74 | if self.series.banner: 75 | page = self.page 76 | course_sem = page.course_sem 77 | raw_text = self.series.banner.text 78 | text = raw_text % { 79 | 'series_number': self.position, 80 | 'maintainer': '@%s' % page.maintainer, 81 | 'edit_link': page.get_absolute_url() + '/edit', 82 | 'course': course_sem.course, 83 | 'semester': '%s %d' % (course_sem.term.title(), course_sem.year) 84 | } 85 | 86 | return text 87 | else: 88 | return '' 89 | 90 | 91 | class SeriesBanner(models.Model): 92 | name = models.CharField(max_length=255) 93 | text = models.TextField() 94 | 95 | class Meta: 96 | app_label = 'wiki' 97 | 98 | def __unicode__(self): 99 | return self.name 100 | -------------------------------------------------------------------------------- /assets/css/scaffolding.less: -------------------------------------------------------------------------------- 1 | /* 2 | * Scaffolding 3 | * Basic and global styles for generating a grid system, structural layout, and page templates 4 | * ------------------------------------------------------------------------------------------- */ 5 | 6 | 7 | // STRUCTURAL LAYOUT 8 | // ----------------- 9 | 10 | html, body { 11 | background-color: @white; 12 | } 13 | body { 14 | margin: 0; 15 | #font > .sans-serif(normal,@basefont,@baseline); 16 | color: @grayDark; 17 | } 18 | 19 | // Container (centered, fixed-width layouts) 20 | .container { 21 | .fixed-container(); 22 | } 23 | 24 | // Fluid layouts (left aligned, with sidebar, min- & max-width content) 25 | .container-fluid { 26 | position: relative; 27 | min-width: 940px; 28 | padding-left: 20px; 29 | padding-right: 20px; 30 | .clearfix(); 31 | > .sidebar { 32 | float: left; 33 | width: 220px; 34 | } 35 | // TODO in v2: rename this and .popover .content to be more specific 36 | > .content { 37 | margin-left: 240px; 38 | } 39 | } 40 | 41 | 42 | // BASE STYLES 43 | // ----------- 44 | 45 | // Links 46 | a { 47 | color: @linkColor; 48 | text-decoration: none; 49 | line-height: inherit; 50 | font-weight: inherit; 51 | &:hover { 52 | color: @linkColorHover; 53 | text-decoration: underline; 54 | } 55 | } 56 | 57 | // Quick floats 58 | .pull-right { 59 | float: right; 60 | } 61 | .pull-left { 62 | float: left; 63 | } 64 | 65 | // Toggling content 66 | .hide { 67 | display: none; 68 | } 69 | .show { 70 | display: block; 71 | } 72 | 73 | 74 | // GRID SYSTEM 75 | // ----------- 76 | // To customize the grid system, bring up the variables.less file and change the column count, size, and gutter there 77 | 78 | .row { 79 | .clearfix(); 80 | margin-left: -1 * @gridGutterWidth; 81 | } 82 | 83 | // Find all .span# classes within .row and give them the necessary properties for grid columns (supported by all browsers back to IE7) 84 | // Credit to @dhg for the idea 85 | [class*="span"] { 86 | .gridColumn(); 87 | } 88 | 89 | // Default columns 90 | .span1 { .columns(1); } 91 | .span2 { .columns(2); } 92 | .span3 { .columns(3); } 93 | .span4 { .columns(4); } 94 | .span5 { .columns(5); } 95 | .span6 { .columns(6); } 96 | .span7 { .columns(7); } 97 | .span8 { .columns(8); } 98 | .span9 { .columns(9); } 99 | .span10 { .columns(10); } 100 | .span11 { .columns(11); } 101 | .span12 { .columns(12); } 102 | .span13 { .columns(13); } 103 | .span14 { .columns(14); } 104 | .span15 { .columns(15); } 105 | .span16 { .columns(16); } 106 | 107 | // For optional 24-column grid 108 | .span17 { .columns(17); } 109 | .span18 { .columns(18); } 110 | .span19 { .columns(19); } 111 | .span20 { .columns(20); } 112 | .span21 { .columns(21); } 113 | .span22 { .columns(22); } 114 | .span23 { .columns(23); } 115 | .span24 { .columns(24); } 116 | 117 | // Offset column options 118 | .offset1 { .offset(1); } 119 | .offset2 { .offset(2); } 120 | .offset3 { .offset(3); } 121 | .offset4 { .offset(4); } 122 | .offset5 { .offset(5); } 123 | .offset6 { .offset(6); } 124 | .offset7 { .offset(7); } 125 | .offset8 { .offset(8); } 126 | .offset9 { .offset(9); } 127 | .offset10 { .offset(10); } 128 | .offset11 { .offset(11); } 129 | .offset12 { .offset(12); } 130 | 131 | // Unique column sizes for 16-column grid 132 | .span-one-third { width: 300px; } 133 | .span-two-thirds { width: 620px; } 134 | .offset-one-third { margin-left: 340px; } 135 | .offset-two-thirds { margin-left: 660px; } 136 | -------------------------------------------------------------------------------- /assets/css/type.less: -------------------------------------------------------------------------------- 1 | /* Typography.less 2 | * Headings, body text, lists, code, and more for a versatile and durable typography system 3 | * ---------------------------------------------------------------------------------------- */ 4 | 5 | 6 | // BODY TEXT 7 | // --------- 8 | 9 | p { 10 | #font > .shorthand(normal,@basefont,@baseline); 11 | padding-bottom: @baseline / 2; // Was @baseline / 2 12 | small { 13 | font-size: @basefont - 2; 14 | color: @grayLight; 15 | } 16 | } 17 | 18 | 19 | // HEADINGS 20 | // -------- 21 | 22 | h1, h2, h3, h4, h5, h6 { 23 | font-weight: bold; 24 | color: @grayDark; 25 | small { 26 | color: @grayLight; 27 | } 28 | } 29 | h1 { 30 | padding-bottom: @baseline; 31 | font-size: 32px; 32 | line-height: @baseline * 2; 33 | small { 34 | font-size: 18px; 35 | } 36 | } 37 | h2 { 38 | font-size: 24px; 39 | line-height: 50px; 40 | small { 41 | font-size: 14px; 42 | } 43 | } 44 | h3, h4, h5, h6 { 45 | line-height: @baseline * 2; 46 | } 47 | h3 { 48 | font-size: 18px; 49 | small { 50 | font-size: 14px; 51 | } 52 | } 53 | h4 { 54 | font-size: 16px; 55 | small { 56 | font-size: 12px; 57 | } 58 | } 59 | h5 { 60 | font-size: 14px; 61 | } 62 | h6 { 63 | font-size: 13px; 64 | color: @grayLight; 65 | text-transform: uppercase; 66 | } 67 | 68 | 69 | // COLORS 70 | // ------ 71 | 72 | // Unordered and Ordered lists 73 | ul, ol { 74 | margin: 0 0 @baseline 25px; 75 | } 76 | ul ul, 77 | ul ol, 78 | ol ol, 79 | ol ul { 80 | padding-bottom: 0; 81 | } 82 | ul { 83 | list-style: disc; 84 | } 85 | ol { 86 | list-style: decimal; 87 | } 88 | li { 89 | line-height: @baseline; 90 | //color: @gray; 91 | } 92 | ul.unstyled { 93 | list-style: none; 94 | margin-left: 0; 95 | } 96 | 97 | // Description Lists 98 | dl { 99 | padding-bottom: @baseline; 100 | dt, dd { 101 | line-height: @baseline; 102 | } 103 | dt { 104 | font-weight: bold; 105 | } 106 | dd { 107 | margin-left: @baseline / 2; 108 | } 109 | } 110 | 111 | // MISC 112 | // ---- 113 | 114 | // Horizontal rules 115 | hr { 116 | margin: 0 0 0px; 117 | border: 0; 118 | border-bottom: 1px solid @grayLighter - #111; 119 | } 120 | 121 | // Emphasis 122 | strong { 123 | font-style: inherit; 124 | font-weight: bold; 125 | } 126 | em { 127 | font-style: italic; 128 | font-weight: inherit; 129 | line-height: inherit; 130 | } 131 | .muted { 132 | color: @grayLight; 133 | } 134 | 135 | // Blockquotes 136 | blockquote { 137 | border-left: 5px solid #eee; 138 | padding-left: 15px; 139 | p { 140 | padding-bottom: 0; 141 | } 142 | small { 143 | display: block; 144 | #font > .shorthand(300,12px,@baseline); 145 | color: @grayLight; 146 | &:before { 147 | content: '\2014 \00A0'; 148 | } 149 | } 150 | } 151 | 152 | // Addresses 153 | address { 154 | display: block; 155 | line-height: @baseline; 156 | padding-bottom: @baseline; 157 | } 158 | 159 | // Inline and block code styles 160 | code, pre { 161 | padding: 0 3px 2px; 162 | #font > .monospace; 163 | .border-radius(3px); 164 | } 165 | code { 166 | background-color: lighten(@orange, 40%); 167 | color: rgba(0,0,0,.75); 168 | padding: 1px 3px; 169 | } 170 | pre { 171 | background-color: #f5f5f5; 172 | display: block; 173 | padding: (@baseline - 1) / 2; 174 | margin: 0 0 @baseline; 175 | line-height: @baseline; 176 | font-size: 12px; 177 | border: 1px solid #ccc; 178 | border: 1px solid rgba(0,0,0,.15); 179 | .border-radius(3px); 180 | white-space: pre; 181 | white-space: pre-wrap; 182 | word-wrap: break-word; 183 | 184 | } 185 | -------------------------------------------------------------------------------- /views/messages.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import login_required 2 | from django.contrib.auth.models import User 3 | from django.core.exceptions import PermissionDenied 4 | from django.shortcuts import render, get_object_or_404, redirect 5 | 6 | from wiki.forms.messages import PrivateMessageForm 7 | from wiki.models.users import PrivateMessage 8 | 9 | 10 | class ViewMessage(Exception): 11 | def __init__(self, message_id): 12 | self.message_id = message_id 13 | 14 | 15 | def base_view(function): 16 | @login_required 17 | def inner_view(request, *args, **kwargs): 18 | name = function.__name__ 19 | template_file = 'messages/%s.html' % name 20 | try: 21 | data = function(request, *args, **kwargs) 22 | data['this_mode'] = name 23 | 24 | # If the title is not set in the context, use a generic one. 25 | if 'title' not in data: 26 | data['title'] = "Private messages (%s)" % name 27 | 28 | modes = ['inbox', 'outbox', 'compose'] 29 | data['modes'] = [(mode, 'messages_%s' % mode) for mode in modes] 30 | return render(request, template_file, data) 31 | except PermissionDenied: 32 | return redirect('messages_inbox') 33 | except ViewMessage, e: 34 | return redirect('messages_view', message_id=e.message_id) 35 | 36 | return inner_view 37 | 38 | 39 | @base_view 40 | def inbox(request): 41 | return { 42 | 'messages': request.user.received_messages.all(), 43 | 'num_new': request.user.received_messages.new().count(), 44 | } 45 | 46 | 47 | @base_view 48 | def view(request, message_id): 49 | message = get_object_or_404(PrivateMessage, pk=message_id) 50 | 51 | if request.user == message.sender or request.user == message.recipient: 52 | if not message.is_read and request.user == message.recipient: 53 | message.is_read = True 54 | message.save() 55 | 56 | if request.user == message.sender: 57 | from_or_to = "to" 58 | user = message.recipient 59 | else: 60 | from_or_to = "from" 61 | user = message.sender 62 | 63 | return { 64 | 'title': 'Viewing private message %s %s' % (from_or_to, user), 65 | 'message': message, 66 | 'show_reply': request.user == message.recipient, 67 | } 68 | else: 69 | # Trying to view someone else's message ... return to inbox 70 | raise PermissionDenied 71 | 72 | 73 | @base_view 74 | def outbox(request): 75 | return { 76 | 'messages': request.user.sent_messages.all() 77 | } 78 | 79 | 80 | @base_view 81 | def compose(request): 82 | if request.method == 'POST': 83 | message = PrivateMessage() 84 | form = PrivateMessageForm(request.POST, instance=message) 85 | if form.is_valid(): 86 | message.sender = request.user 87 | form.save() 88 | raise ViewMessage(message.id) 89 | else: 90 | message = PrivateMessage() 91 | 92 | # If we want to send a message to a specific person 93 | recipient_name = request.GET.get('to', '') 94 | try: 95 | message.recipient = User.objects.get(username__iexact=recipient_name) 96 | except User.DoesNotExist: 97 | pass 98 | 99 | # Pre-filled subject (reply to) 100 | reply_to = request.GET.get('reply_to', '') 101 | if reply_to: 102 | message.subject = 'Re: %s' % reply_to 103 | 104 | form = PrivateMessageForm(instance=message) 105 | 106 | return {'form': form} 107 | -------------------------------------------------------------------------------- /mdx/mdx_wiki_def_list.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Python 2 | """ 3 | Definition List Extension for Python-Markdown 4 | ============================================= 5 | 6 | Added parsing of Definition Lists to Python-Markdown. 7 | 8 | A simple example: 9 | 10 | Apple 11 | : Pomaceous fruit of plants of the genus Malus in 12 | the family Rosaceae. 13 | : An american computer company. 14 | Orange 15 | : The fruit of an evergreen tree of the genus Citrus. 16 | 17 | Copyright 2008 - [Waylan Limberg](http://achinghead.com) 18 | 19 | """ 20 | 21 | import re 22 | import markdown 23 | from markdown.util import etree 24 | 25 | 26 | class DefListProcessor(markdown.blockprocessors.BlockProcessor): 27 | """ Process Definition Lists. """ 28 | 29 | RE = re.compile(r'(^|\n)[ ]{0,3}:[ ]{1,3}(.*?)(\n|$)') 30 | 31 | def test(self, parent, block): 32 | return bool(self.RE.search(block)) 33 | 34 | def run(self, parent, blocks): 35 | block = blocks.pop(0) 36 | m = self.RE.search(block) 37 | terms = [l for l in block[:m.start()].split('\n') if l.strip()] 38 | block = block[m.end():] 39 | d, theRest = self.detab(block) 40 | # Got rid of the noindent thing (basically set it to false always) 41 | if d: 42 | d = '%s\n%s' % (m.group(2), d) 43 | else: 44 | d = m.group(2) 45 | sibling = self.lastChild(parent) 46 | if not terms and sibling.tag == 'p': 47 | # The previous paragraph contains the terms 48 | state = 'looselist' 49 | terms = sibling.text.split('\n') 50 | parent.remove(sibling) 51 | # Aquire new sibling 52 | sibling = self.lastChild(parent) 53 | else: 54 | state = 'list' 55 | 56 | if sibling and sibling.tag == 'dl': 57 | # This is another item on an existing list 58 | dl = sibling 59 | if len(dl) and dl[-1].tag == 'dd' and len(dl[-1]): 60 | state = 'looselist' 61 | else: 62 | # This is a new list 63 | dl = etree.SubElement(parent, 'dl') 64 | # Add terms 65 | for term in terms: 66 | dt = etree.SubElement(dl, 'dt') 67 | dt.text = term 68 | # Add definition 69 | self.parser.state.set(state) 70 | dd = etree.SubElement(dl, 'dd') 71 | self.parser.parseBlocks(dd, [d]) 72 | self.parser.state.reset() 73 | 74 | if theRest: 75 | blocks.insert(0, theRest) 76 | 77 | class DefListIndentProcessor(markdown.blockprocessors.ListIndentProcessor): 78 | """ Process indented children of definition list items. """ 79 | 80 | ITEM_TYPES = ['dd'] 81 | LIST_TYPES = ['dl'] 82 | 83 | def create_item(self, parent, block): 84 | """ Create a new dd and parse the block with it as the parent. """ 85 | dd = markdown.etree.SubElement(parent, 'dd') 86 | self.parser.parseBlocks(dd, [block]) 87 | 88 | 89 | class DefListExtension(markdown.Extension): 90 | """ Add definition lists to Markdown. """ 91 | 92 | def extendMarkdown(self, md, md_globals): 93 | """ Add an instance of DefListProcessor to BlockParser. """ 94 | md.parser.blockprocessors.add('defindent', 95 | DefListIndentProcessor(md.parser), 96 | '>indent') 97 | md.parser.blockprocessors.add('deflist', 98 | DefListProcessor(md.parser), 99 | '>ulist') 100 | 101 | 102 | def makeExtension(configs={}): 103 | return DefListExtension(configs=configs) 104 | 105 | -------------------------------------------------------------------------------- /wiki/tests/pagetypes.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | from wiki.utils.pages import page_types 4 | 5 | 6 | class _TestPageType(TestCase): 7 | """Parent class for testing various methods on the PageType subclasses.""" 8 | def setUp(self): 9 | self.page_type = page_types[self.short_name] 10 | 11 | def test_slug(self): 12 | generated_slug = self.page_type._generate_slug(self.data) 13 | self.assertEqual(generated_slug, self.expected_slug) 14 | 15 | def test_title(self): 16 | generated_title = self.page_type._generate_title(self.data) 17 | self.assertEqual(generated_title, self.expected_title) 18 | 19 | def test_validation(self): 20 | """Tests the find_errors() method on the base class, including the 21 | custom validators.""" 22 | initial_data = { 23 | 'term': 'winter', 24 | 'year': '2014', 25 | 'content': 'test', 26 | } 27 | 28 | for invalid_data in self.invalid_inputs: 29 | # Basically incorporate all the possible context values 30 | data = {} 31 | data.update(initial_data) 32 | data.update(self.data) 33 | data.update(invalid_data) 34 | 35 | errors = self.page_type.find_errors(data) 36 | self.assertEqual(len(errors), 1) 37 | 38 | # Make sure the regular self.data dict doesn't trigger errors 39 | default_data = {} 40 | default_data.update(initial_data) 41 | default_data.update(self.data) 42 | self.assertFalse(self.page_type.find_errors(default_data)) 43 | 44 | 45 | class TestLectureNotes(_TestPageType): 46 | short_name = 'lecture-notes' 47 | data = { 48 | 'date_weekday': 'tuesday', 49 | 'date_month': 'april', 50 | 'date_date': '15', 51 | 'year': '2014', 52 | } 53 | expected_slug = 'tuesday-april-15' 54 | expected_title = 'Tuesday, April 15, 2014' 55 | invalid_inputs = [ 56 | { 57 | 'date_weekday': 'saturday', 58 | }, 59 | { 60 | 'date_month': 'not a month', 61 | }, 62 | { 63 | 'date_date': 'lol', 64 | }, 65 | { 66 | 'date_date': '0', 67 | }, 68 | { 69 | 'date_weekday': 'monday', # it's actually a tuesday 70 | 'date_month': 'april', 71 | 'date_date': '15', 72 | 'date_year': '2014', 73 | }, 74 | ] 75 | 76 | 77 | class TestPastExam(_TestPageType): 78 | short_name = 'past-exam' 79 | data = { 80 | 'term': 'winter', 81 | 'year': '2014', 82 | 'exam_type': 'final', 83 | } 84 | expected_slug = 'final' 85 | expected_title = 'Winter 2014 Final' 86 | invalid_inputs = [{ 87 | 'exam_type': 'not a type', 88 | }] 89 | 90 | 91 | class TestCourseSummary(_TestPageType): 92 | short_name = 'summary' 93 | data = { 94 | 'subject': 'HTSEFP', 95 | } 96 | expected_slug = 'htsefp' 97 | expected_title = None 98 | invalid_inputs = [{ 99 | 'subject': '', 100 | }] 101 | 102 | 103 | class TestVocabList(_TestPageType): 104 | short_name = 'vocab-list' 105 | data = { 106 | 'subject': 'Vocabulary list', 107 | } 108 | expected_slug = 'vocabulary-list' 109 | expected_title = None 110 | invalid_inputs = [{ 111 | 'subject': '', 112 | }] 113 | 114 | 115 | class TestCourseQuiz(_TestPageType): 116 | short_name = 'course-quiz' 117 | data = { 118 | 'subject': 'Course quiz', 119 | } 120 | expected_slug = 'course-quiz' 121 | expected_title = None 122 | invalid_inputs = [{ 123 | 'subject': '', 124 | }] 125 | -------------------------------------------------------------------------------- /wiki/models/users.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from django.core.urlresolvers import reverse 3 | from django.db import models 4 | from django.db.models.signals import post_save 5 | 6 | 7 | class UserProfile(models.Model): 8 | user = models.OneToOneField(User) 9 | twitter = models.CharField(max_length=15, null=True) # max length of a twitter username 10 | courses = models.ManyToManyField('Course') 11 | pinned_courses = models.ManyToManyField('Course', related_name='pinners') 12 | website = models.CharField(max_length=255, null=True) 13 | bio = models.CharField(max_length=300, null=True) 14 | facebook = models.CharField(max_length=72, null=True) # apparently that's the limit 15 | github = models.CharField(max_length=30, null=True) # no idea 16 | gplus = models.CharField(max_length=21, null=True) # i think, all numbers but char just in case 17 | major = models.CharField(max_length=100, null=True) 18 | show_email = models.BooleanField(default=False) 19 | url_fields = { 20 | 'username': 'user__username', 21 | } 22 | 23 | class Meta: 24 | app_label = 'wiki' 25 | 26 | def start_watching(self, course): 27 | self.courses.add(course) 28 | course.increase_num_watchers_by(1) 29 | course.add_event(self.user, action='started watching') 30 | 31 | def stop_watching(self, course): 32 | self.courses.remove(course) 33 | course.increase_num_watchers_by(-1) 34 | 35 | def is_watching(self, course): 36 | return self.courses.filter(pk=course.pk).exists() 37 | 38 | def has_pinned(self, course): 39 | return self.pinned_courses.filter(pk=course.pk).exists() 40 | 41 | def get_recent_pages(self, n, created=False): 42 | """ 43 | Return the last n pages that the user has edited. 44 | """ 45 | pages = [] 46 | i = 0 47 | history_items = self.user.historyitem_set.order_by('-timestamp') 48 | 49 | # If we want to limit it to pages that the user has created (false by default) 50 | if created: 51 | history_items = history_items.filter(action='created') 52 | 53 | for history_item in history_items: 54 | page = history_item.page 55 | if page is not None and page not in pages: 56 | pages.append(page) 57 | i += 1 58 | 59 | if i == n: 60 | break 61 | 62 | return pages 63 | 64 | def get_absolute_url(self): 65 | return reverse('main_profile', args=[self.user.username]) 66 | 67 | 68 | """ 69 | A simple private messaging system. Useful because not everyone provides 70 | contact information. 71 | """ 72 | class PrivateMessageManager(models.Manager): 73 | def new(self, **kwargs): 74 | return self.filter(is_read=False, **kwargs) 75 | 76 | 77 | class PrivateMessage(models.Model): 78 | objects = PrivateMessageManager() 79 | sender = models.ForeignKey(User, related_name="sent_messages") 80 | recipient = models.ForeignKey(User, related_name="received_messages") 81 | subject = models.CharField(max_length=100, blank=True, null=True) 82 | message = models.TextField(blank=True, null=True) 83 | is_read = models.BooleanField(default=False) 84 | timestamp = models.DateTimeField(auto_now_add=True) 85 | 86 | class Meta: 87 | app_label = 'wiki' 88 | ordering = ['-pk'] 89 | 90 | def get_absolute_url(self): 91 | return reverse('messages_view', args=[self.id]) 92 | 93 | 94 | def create_user_profile(sender, instance, created, **kwargs): 95 | if created: 96 | UserProfile.objects.create(user=instance) 97 | 98 | 99 | # Register a handler for the post_save signal 100 | # Otherwise the user profile does not get created 101 | post_save.connect(create_user_profile, sender=User) 102 | -------------------------------------------------------------------------------- /assets/css/pygments.less: -------------------------------------------------------------------------------- 1 | .codehilite .c, .codehilite .cm { color: #BEBEBE; font-style: italic } /* Comment */ 2 | .codehilite .err { color: #a61717; background-color: #e3d2d2 } /* Error */ 3 | .codehilite .k { color: #FF6600; font-weight: bold } /* Keyword */ 4 | .codehilite .javascript .k { color: #CCCC66; font-weight: bold } /* Keyword */ 5 | .codehilite .o { color: #FFFFFF; font-weight: bold } /* Operator */ 6 | .codehilite .cp { color: #3A6EF2; font-weight: bold } /* Comment.Preproc */ 7 | .codehilite .c1 { color: #3A6EF2; font-style: italic } /* Comment.Single */ 8 | .codehilite .cs { color: #3A6EF2; font-weight: bold; font-style: italic } /* Comment.Special */ 9 | .codehilite .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ 10 | .codehilite .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */ 11 | .codehilite .ge { font-style: italic } /* Generic.Emph */ 12 | .codehilite .gr { color: #aa0000 } /* Generic.Error */ 13 | .codehilite .gh { color: #999999 } /* Generic.Heading */ 14 | .codehilite .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ 15 | .codehilite .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */ 16 | .codehilite .go { color: #888888 } /* Generic.Output */ 17 | .codehilite .gp { color: #555555 } /* Generic.Prompt */ 18 | .codehilite .gs { font-weight: bold } /* Generic.Strong */ 19 | .codehilite .gu { color: #aaaaaa } /* Generic.Subheading */ 20 | .codehilite .gt { color: #aa0000 } /* Generic.Traceback */ 21 | .codehilite .kc { font-weight: bold } /* Keyword.Constant */ 22 | .codehilite .kd { font-weight: bold } /* Keyword.Declaration */ 23 | .codehilite .kp { font-weight: bold } /* Keyword.Pseudo */ 24 | .codehilite .kr { font-weight: bold } /* Keyword.Reserved */ 25 | .codehilite .kt { color: #5E76BE; font-weight: bold } /* Keyword.Type */ 26 | .codehilite .m { color: #009999 } /* Literal.Number */ 27 | .codehilite .s { color: #66FF00 } /* Literal.String */ 28 | .codehilite .na { color: #99CC99 } /* Name.Attribute */ 29 | .codehilite .nb { } /* Name.Builtin */ 30 | .codehilite .nc { color: #FFFFFF; font-weight: bold } /* Name.Class */ 31 | .codehilite .no { color: #FFFFFF } /* Name.Constant */ 32 | .codehilite .ni { color: #339999 } /* Name.Entity */ 33 | .codehilite .ne { color: #FF0000; font-weight: bold } /* Name.Exception */ 34 | .codehilite .nf { color: #FFCC00; font-weight: bold } /* Name.Function */ 35 | .codehilite .nn { color: #AAAAAA } /* Name.Namespace */ 36 | .codehilite .nt { color: #FF6600 } /* Name.Tag */ 37 | .codehilite .nv { color: #008080 } /* Name.Variable */ 38 | .codehilite .ow { font-weight: bold } /* Operator.Word */ 39 | .codehilite .w { color: #bbbbbb } /* Text.Whitespace */ 40 | .codehilite .mf { color: #CCFF33 } /* Literal.Number.Float */ 41 | .codehilite .mh { color: #CCFF33 } /* Literal.Number.Hex */ 42 | .codehilite .mi { color: #CCFF33 } /* Literal.Number.Integer */ 43 | .codehilite .mo { color: #CCFF33 } /* Literal.Number.Oct */ 44 | .codehilite .sb { background: #CCCC33; color: #000000 } /* Literal.String.Backtick */ 45 | .codehilite .sc { color: #66FF00 } /* Literal.String.Char */ 46 | .codehilite .sd { color: #66FF00 } /* Literal.String.Doc */ 47 | .codehilite .s2 { color: #66FF00 } /* Literal.String.Double */ 48 | .codehilite .se { color: #66FF00 } /* Literal.String.Escape */ 49 | .codehilite .sh { color: #66FF00 } /* Literal.String.Heredoc */ 50 | .codehilite .si { color: #d555555 } /* Literal.String.Interpol */ 51 | .codehilite .sx { color: #66FF00 } /* Literal.String.Other */ 52 | .codehilite .sr { color: #009926 } /* Literal.String.Regex */ 53 | .codehilite .s1 { color: #66FF00 } /* Literal.String.Single */ 54 | .codehilite .ss { color: #339999 } /* Literal.String.Symbol */ 55 | .codehilite .bp { color: #999999 } /* Name.Builtin.Pseudo */ 56 | .codehilite .vc { color: #008080 } /* Name.Variable.Class */ 57 | .codehilite .vg { color: #008080 } /* Name.Variable.Global */ 58 | .codehilite .vi { color: #008080 } /* Name.Variable.Instance */ 59 | .codehilite .il { color: #009999 } /* Literal.Number.Integer.Long */ 60 | -------------------------------------------------------------------------------- /templates/main/contributions.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 | 10 | 13 | 14 | {% if mode == 'courses' %} 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% for course in table_data %} 24 | 25 | 26 | 27 | 28 | 29 | {% empty %} 30 | Nothing here 31 | {% endfor %} 32 | 33 | {% elif mode == 'edits' %} 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | {% for edit in table_data %} 44 | 45 | 46 | 47 | 48 | 53 | 54 | {% empty %} 55 | Nothing here 56 | {% endfor %} 57 | 58 | {% else %} 59 | {% comment %}Created, or modified{% endcomment %} 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | {% for page in table_data %} 69 | {% with course=page.course_sem.course %} 70 | 71 | 72 | 73 | 74 | 75 | {% endwith %} 76 | {% empty %} 77 | Nothing here 78 | {% endfor %} 79 | 80 | {% endif %} 81 |
FacultyDepartmentCourse

{{ course.department.faculty }}

{{ course.department.short_name }}
{{ course }}: {{ course.name }}
DepartmentCoursePageEdit information

{{ edit.course.department.short_name }}
{{ edit.course }}: {{ edit.course.name }}{{ edit.page }} 49 | Commit message: {{ edit.message|default:"Minor edit" }} 50 |
51 | {{ edit.get_timesince }} ago 52 |
DepartmentCoursePage

{{ course.department.short_name }}
{{ course }}: {{ course.name }}{{ page }}
82 |
83 |
84 | {% endblock %} 85 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![WikiNotes logo](http://www.wikinotes.ca/static/img/wikinotes.png) 2 | 3 | WikiNotes is a wiki-based note-sharing platform created to facilitate student 4 | collaboration. Although it is being created primarily to replace our old 5 | MediaWiki-backed website for McGill students, we are releasing the code under 6 | the [GPLv3][gpl] so that non-McGill students (or anyone, really) can benefit 7 | from it as well. Although this platform is still under development, we are now 8 | using it to power our main website at www.wikinotes.ca. 9 | 10 | Want to find out more about us? Visit our [about page][about], join our IRC 11 | channel ([#wikinotes on freenode][irc]) or drop us a line at 12 | . 13 | 14 | Contributing 15 | ------------ 16 | 17 | We'd love to have you contribute, whether it be through adding features, filing 18 | bug reports, writing tests or whatever takes your fancy. Contributing code is 19 | easy - just fork this repository, make your changes, and send us a pull request. 20 | To see what needs to be done, check out the [list of outstanding 21 | issues][issues]. If you notice something else that needs to be done, feel free 22 | to [open an issue][open] for it. 23 | 24 | See also our [development wiki][wiki] for things like what style conventions we 25 | use, development notes, and how the code is organised. 26 | 27 | We're using [Travis][travis] for continuous integration and Django's 28 | unit-testing framework for the tests. Current build status of the master branch: 29 | 30 | [![Build status][status]][ci] 31 | 32 | Dependencies 33 | ------------ 34 | 35 | To run it on your local machine, you'll need Python 2.7+, and Git. If you're on 36 | Windows, you might run into some issues which I won't be able to help debug, so 37 | I'd recommend using Linux if possible. You'll also need a bunch of Python 38 | modules, which can be installed with [pip]: 39 | 40 | ```console 41 | pip install -r requirements.txt 42 | ``` 43 | 44 | If you don't have pip, either install it using your system's package manager or 45 | make use of [virtualenv] \(which is a good idea in any case\). 46 | 47 | Configuration 48 | ------------- 49 | 50 | If you're running it for the first time, here's what you have to do: 51 | 52 | * Run `python manage.py syncdb`. Make sure to create the superuser when 53 | prompted. You will be able to use the credentials you choose for the 54 | superuser account to log on to the site, and to access the administration 55 | panel. 56 | * Run `python manage.py runserver` (or `fab up` if you have [Fabric][fabric] 57 | installed) to start the development server. By default, this makes the 58 | platform accessible at ; add `0.0.0.0:8000` as an 59 | argument if you want to make it publicly accessible through your IP address 60 | (at port 8000), or run `fab broadcast`. To actually deploy it properly on a 61 | server, you'll probably want to use something like [gunicorn]. 62 | 63 | Initially, there won't be any courses on the site. You can create them through 64 | the admin panel (sign in using the superuser account details through the login 65 | box in the header, then click the "admin" button), or write a script to import 66 | them if you have a list of all the courses/departments/faculties to create. 67 | 68 | If you have questions, send me an email (ilostwaldo, gmail). 69 | 70 | Testing 71 | ------- 72 | 73 | To run the unit tests, run `python manage.py test wiki` or `fab test`. You can 74 | view the history of the tests run for commits pushed to the master branch on 75 | [Travis][ci]. 76 | 77 | [gpl]: http://opensource.org/licenses/GPL-3.0 78 | [about]: http://www.wikinotes.ca/about 79 | [irc]: http://webchat.freenode.net/?channels=wikinotes 80 | [issues]: https://github.com/dellsystem/wikinotes/issues 81 | [open]: https://github.com/dellsystem/wikinotes/issues/new 82 | [wiki]: https://github.com/dellsystem/wikinotes/wiki 83 | [travis]: http://travis-ci.org 84 | [status]: https://secure.travis-ci.org/dellsystem/wikinotes.png?branch=master 85 | [ci]: http://travis-ci.org/dellsystem/wikinotes 86 | [pip]: http://www.pip-installer.org/en/latest/index.html 87 | [virtualenv]: http://www.virtualenv.org/en/latest/index.html 88 | [fabric]: http://fabfile.org 89 | [gunicorn]: https://docs.djangoproject.com/en/dev/howto/deployment/wsgi/gunicorn/ 90 | -------------------------------------------------------------------------------- /templates/ucp/profile.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | {% csrf_token %} 6 | Edit profile information 7 | {% if success %} 8 |
9 |
10 |
11 |

Successfully updated profile.

12 |
13 |
14 |
15 | {% endif %} 16 |
17 | 18 |
19 | 20 | 21 | A short description of yourself. Limit it to 300 characters or so. Markdown is not parsed. 22 | 23 |
24 |
25 |
26 | 27 |
28 | 29 |
30 |
31 |
32 | 33 |
34 | 35 |
36 |
37 |
38 | 39 |
40 | 41 |
42 |
43 |
44 | 45 |
46 | 47 |
48 |
49 |
50 | 51 |
52 | 53 |
54 |
55 |
56 | 57 |
58 | 59 |
60 |
61 |
62 |   63 |
64 |
65 |
66 |
67 |
68 |

All fields are optional.

69 | 70 |
    71 |
  • Edit your bio
  • 72 |
  • Edit your major
  • 73 |
  • Link to website
  • 74 |
  • Twitter
  • 75 |
  • Github
  • 76 |
  • Facebook
  • 77 |
  • Google plus
  • 78 |
79 | 80 |

You can view your profile by clicking on your gravatar in the header (or by clicking here).

81 |
82 |
83 | -------------------------------------------------------------------------------- /templates/main/registration.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 | 9 |
10 |
11 | {% if errors %} 12 |
13 | 14 |

Something went wrong! Please fix the errors and try again.

15 |
    16 | {% for error in errors %} 17 |
  • {{ error }}
  • 18 | {% endfor %} 19 |
20 |
21 | {% else %} 22 |
23 |

Already have an account? Sign in using the login bar above.

24 |
25 | {% endif %} 26 |
27 |
28 | {% csrf_token %} 29 |
30 | 31 |
32 | 33 |
34 |
35 |
36 | 37 |
38 | 39 |
40 |
41 |
42 | 43 |
44 | 45 |
46 |
47 |
48 | 49 |
50 | 51 |
52 |
53 |
54 | 55 | 56 |
57 | 58 |
59 |
60 |
61 | 62 | 63 |
64 |
65 |
66 |
67 |
68 |

Why register?

69 |

You absolutely don't need to register to be able to access the content on WikiNotes! We believe that the content on this site should be available freely to anyone who wants to see it, regardless of whether they have an account or not. Registration is mostly useful if you want to contribute, as we do require edits to be made by logged-in users, or if you want an an easy way of staying up-to-date with the content on this site for your courses.

70 |

What will you do with my information?

71 |

Rest assured that your account details are safe with us - we will not share, sell, or do other bad things to your email address. At the moment, entering your email is optional, although if you have a gravatar, your email will be used to display it, and in the future we will provide user-configurable email notifications and use your email to update you of important site-related news.

72 |
73 |
74 | 76 |
77 |
78 | {% endblock %} 79 | -------------------------------------------------------------------------------- /assets/css/tables.less: -------------------------------------------------------------------------------- 1 | /* 2 | * Tables.less 3 | * Tables for, you guessed it, tabular data 4 | * ---------------------------------------- */ 5 | 6 | 7 | // BASELINE STYLES 8 | // --------------- 9 | 10 | table { 11 | width: 100%; 12 | margin-bottom: @baseline; 13 | padding: 0; 14 | border-collapse: separate; // Done so we can round those corners! 15 | *border-collapse: collapse; /* IE7, collapse table to remove spacing */ 16 | font-size: @basefont; 17 | border: 1px solid #ddd; 18 | .border-radius(4px); 19 | th, td { 20 | padding: 10px 10px 9px; 21 | line-height: @baseline; 22 | text-align: left; 23 | } 24 | th { 25 | padding-top: 9px; 26 | font-weight: bold; 27 | vertical-align: middle; 28 | border-bottom: 1px solid #ddd; 29 | } 30 | td { 31 | vertical-align: top; 32 | } 33 | th + th, 34 | td + td { 35 | border-left: 1px solid #ddd; 36 | } 37 | tr + tr td { 38 | border-top: 1px solid #ddd; 39 | } 40 | tbody tr:first-child td:first-child { 41 | .border-radius(4px 0 0 0); 42 | } 43 | tbody tr:first-child td:last-child { 44 | .border-radius(0 4px 0 0); 45 | } 46 | tbody tr:last-child td:first-child { 47 | .border-radius(0 0 0 4px); 48 | } 49 | tbody tr:last-child td:last-child { 50 | .border-radius(0 0 4px 0); 51 | } 52 | } 53 | 54 | 55 | // ZEBRA-STRIPING 56 | // -------------- 57 | 58 | // Default zebra-stripe styles (alternating gray and transparent backgrounds) 59 | .zebra-striped { 60 | tbody { 61 | tr:nth-child(odd) td { 62 | background-color: #f9f9f9; 63 | } 64 | tr:hover td { 65 | background-color: #f5f5f5; 66 | } 67 | } 68 | } 69 | 70 | table { 71 | // Tablesorting styles w/ jQuery plugin 72 | .header { 73 | cursor: pointer; 74 | &:after { 75 | content: ""; 76 | float: right; 77 | margin-top: 7px; 78 | border-width: 0 4px 4px; 79 | border-style: solid; 80 | border-color: #000 transparent; 81 | visibility: hidden; 82 | } 83 | } 84 | // Style the sorted column headers (THs) 85 | .headerSortUp, 86 | .headerSortDown { 87 | background-color: rgba(141,192,219,.25); 88 | text-shadow: 0 1px 1px rgba(255,255,255,.75); 89 | } 90 | // Style the ascending (reverse alphabetical) column header 91 | .header:hover { 92 | &:after { 93 | visibility:visible; 94 | } 95 | } 96 | // Style the descending (alphabetical) column header 97 | .headerSortDown, 98 | .headerSortDown:hover { 99 | &:after { 100 | visibility:visible; 101 | .opacity(60); 102 | } 103 | } 104 | // Style the ascending (reverse alphabetical) column header 105 | .headerSortUp { 106 | &:after { 107 | border-bottom: none; 108 | border-left: 4px solid transparent; 109 | border-right: 4px solid transparent; 110 | border-top: 4px solid #000; 111 | visibility:visible; 112 | .box-shadow(none); //can't add boxshadow to downward facing arrow :( 113 | .opacity(60); 114 | } 115 | } 116 | // Blue Table Headings 117 | .blue { 118 | color: @blue; 119 | border-bottom-color: @blue; 120 | } 121 | .headerSortUp.blue, 122 | .headerSortDown.blue { 123 | background-color: lighten(@blue, 40%); 124 | } 125 | // Green Table Headings 126 | .green { 127 | color: @green; 128 | border-bottom-color: @green; 129 | } 130 | .headerSortUp.green, 131 | .headerSortDown.green { 132 | background-color: lighten(@green, 40%); 133 | } 134 | // Red Table Headings 135 | .red { 136 | color: @red; 137 | border-bottom-color: @red; 138 | } 139 | .headerSortUp.red, 140 | .headerSortDown.red { 141 | background-color: lighten(@red, 50%); 142 | } 143 | // Yellow Table Headings 144 | .yellow { 145 | color: @yellow; 146 | border-bottom-color: @yellow; 147 | } 148 | .headerSortUp.yellow, 149 | .headerSortDown.yellow { 150 | background-color: lighten(@yellow, 40%); 151 | } 152 | // Orange Table Headings 153 | .orange { 154 | color: @orange; 155 | border-bottom-color: @orange; 156 | } 157 | .headerSortUp.orange, 158 | .headerSortDown.orange { 159 | background-color: lighten(@orange, 40%); 160 | } 161 | // Purple Table Headings 162 | .purple { 163 | color: @purple; 164 | border-bottom-color: @purple; 165 | } 166 | .headerSortUp.purple, 167 | .headerSortDown.purple { 168 | background-color: lighten(@purple, 40%); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /assets/css/reset.less: -------------------------------------------------------------------------------- 1 | /* Reset.less 2 | * Props to Eric Meyer (meyerweb.com) for his CSS reset file. We're using an adapted version here that cuts out some of the reset HTML elements we will never need here (i.e., dfn, samp, etc). 3 | * ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */ 4 | 5 | 6 | // ERIC MEYER RESET 7 | // -------------------------------------------------- 8 | 9 | html, body { margin: 0; padding: 0; } 10 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, cite, code, del, dfn, em, img, q, s, samp, small, strike, strong, sub, sup, tt, var, dd, dl, dt, li, ol, ul, fieldset, form, label, legend, button, table, caption, tbody, tfoot, thead, tr, th, td { margin: 0; padding: 0; border: 0; font-weight: normal; font-style: normal; font-size: 100%; line-height: 1; font-family: inherit; } 11 | table { border-collapse: collapse; border-spacing: 0; } 12 | ol, ul { list-style: none; } 13 | q:before, q:after, blockquote:before, blockquote:after { content: ""; } 14 | 15 | 16 | // Normalize.css 17 | // Pulling in select resets form the normalize.css project 18 | // -------------------------------------------------- 19 | 20 | // Display in IE6-9 and FF3 21 | // ------------------------- 22 | // Source: http://github.com/necolas/normalize.css 23 | html { 24 | overflow-y: scroll; 25 | font-size: 100%; 26 | -webkit-text-size-adjust: 100%; 27 | -ms-text-size-adjust: 100%; 28 | } 29 | // Focus states 30 | a:focus { 31 | outline: thin dotted; 32 | } 33 | // Hover & Active 34 | a:hover, 35 | a:active { 36 | outline: 0; 37 | } 38 | 39 | // Display in IE6-9 and FF3 40 | // ------------------------- 41 | // Source: http://github.com/necolas/normalize.css 42 | article, 43 | aside, 44 | details, 45 | figcaption, 46 | figure, 47 | footer, 48 | header, 49 | hgroup, 50 | nav, 51 | section { 52 | display: block; 53 | } 54 | 55 | // Display block in IE6-9 and FF3 56 | // ------------------------- 57 | // Source: http://github.com/necolas/normalize.css 58 | audio, 59 | canvas, 60 | video { 61 | display: inline-block; 62 | *display: inline; 63 | *zoom: 1; 64 | } 65 | 66 | // Prevents modern browsers from displaying 'audio' without controls 67 | // ------------------------- 68 | // Source: http://github.com/necolas/normalize.css 69 | audio:not([controls]) { 70 | display: none; 71 | } 72 | 73 | // Prevents sub and sup affecting line-height in all browsers 74 | // ------------------------- 75 | // Source: http://github.com/necolas/normalize.css 76 | sub, 77 | sup { 78 | font-size: 75%; 79 | line-height: 0; 80 | position: relative; 81 | vertical-align: baseline; 82 | } 83 | sup { 84 | top: -0.5em; 85 | } 86 | sub { 87 | bottom: -0.25em; 88 | } 89 | 90 | // Img border in a's and image quality 91 | // ------------------------- 92 | // Source: http://github.com/necolas/normalize.css 93 | img { 94 | border: 0; 95 | -ms-interpolation-mode: bicubic; 96 | } 97 | 98 | // Forms 99 | // ------------------------- 100 | // Source: http://github.com/necolas/normalize.css 101 | 102 | // Font size in all browsers, margin changes, misc consistency 103 | button, 104 | input, 105 | select, 106 | textarea { 107 | font-size: 100%; 108 | margin: 0; 109 | vertical-align: baseline; 110 | *vertical-align: middle; 111 | } 112 | button, 113 | input { 114 | line-height: normal; // FF3/4 have !important on line-height in UA stylesheet 115 | *overflow: visible; // Inner spacing ie IE6/7 116 | } 117 | button::-moz-focus-inner, 118 | input::-moz-focus-inner { // Inner padding and border oddities in FF3/4 119 | border: 0; 120 | padding: 0; 121 | } 122 | button, 123 | input[type="button"], 124 | input[type="reset"], 125 | input[type="submit"] { 126 | cursor: pointer; // Cursors on all buttons applied consistently 127 | -webkit-appearance: button; // Style clicable inputs in iOS 128 | } 129 | input[type="search"] { // Appearance in Safari/Chrome 130 | -webkit-appearance: textfield; 131 | -webkit-box-sizing: content-box; 132 | -moz-box-sizing: content-box; 133 | box-sizing: content-box; 134 | } 135 | input[type="search"]::-webkit-search-decoration { 136 | -webkit-appearance: none; // Inner-padding issues in Chrome OSX, Safari 5 137 | } 138 | textarea { 139 | overflow: auto; // Remove vertical scrollbar in IE6-9 140 | vertical-align: top; // Readability and alignment cross-browser 141 | } 142 | -------------------------------------------------------------------------------- /urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import patterns, include, url 2 | from django.contrib import admin 3 | 4 | admin.autodiscover() 5 | 6 | """ 7 | Basic, reusable patterns 8 | """ 9 | faculty = r'(?P\w+)' 10 | department = r'(?P\w{4})' 11 | number = '(?P\d{3}[DNJ]?[123]?)' 12 | course = department + '_' + number 13 | page_type = '(?P[^/]+)' 14 | semester = '(?P\w{4,6})-(?P\d{4})' 15 | slug = '(?P[^/]+)' 16 | page = course + '/' + page_type + '/' + semester + '/' + slug 17 | sha = '(?P[a-z0-9]{40})' 18 | professor = '(?P[a-z-]*)' 19 | 20 | """ 21 | Begin mappings (URLs should be defined in order of descending priority (so highest priority first)) 22 | """ 23 | direct_to_view = ( 24 | ('main', ( 25 | ('login', 'login_logout'), 26 | ('recent', 'recent'), 27 | ('recent/(?P\d+)', 'recent'), 28 | ('recent/all', 'all_recent'), 29 | ('recent/all/(?P\d+)', 'all_recent'), 30 | ('ucp', 'ucp'), 31 | ('ucp/(?P\w*)', 'ucp'), 32 | ('users/(?P\w+)', 'profile'), 33 | ('users/(?P\w+)/contributions', 'contributions'), 34 | ('search', 'search'), 35 | ('markdown', 'markdown'), 36 | ('register', 'register') 37 | )), 38 | ('messages', ( 39 | ('messages', 'inbox'), 40 | ('messages/inbox', 'inbox'), 41 | ('messages/outbox', 'outbox'), 42 | ('messages/compose', 'compose'), 43 | ('messages/view/(?P\d+)', 'view'), 44 | )), 45 | ('news', ( 46 | ('news', 'main'), 47 | ('news/' + slug, 'view'), 48 | )), 49 | ('pages', ( 50 | ('pages/random', 'random'), 51 | (course + '/create/' + page_type, 'create'), 52 | (page, 'show'), 53 | (page + '/edit', 'edit'), 54 | (page + '/history', 'history'), 55 | (page + '/print', 'printview'), 56 | #(page + '/raw', 'raw'), 57 | (page + '/commit/' + sha, 'commit'), 58 | #(page + '/inline', 'inline'), 59 | )), 60 | ('courses', ( 61 | ('courses', 'index'), 62 | ('courses/create', 'create'), 63 | ('courses/all', 'all_browse'), 64 | ('courses/faculty', 'faculty_browse'), 65 | ('courses/department', 'department_browse'), 66 | ('courses/professor', 'professor_browse'), 67 | ('courses/popular', 'popular_browse'), 68 | ('courses/random', 'random'), 69 | ('courses/active', 'active_browse'), 70 | ('courses/get_all', 'get_all'), 71 | # Redirect department/number to department_number 72 | (department + '/' + number + '.*', 'remove_slash'), 73 | (course, 'overview'), 74 | (course + '/recent', 'recent'), 75 | (course + '/watch', 'watch'), 76 | (course + '/pin', 'pin'), 77 | (course + '/unpin', 'unpin'), 78 | (course + '/' + semester, 'semester_overview'), 79 | (course + '/' + page_type, 'category_overview'), 80 | (department, 'department_overview'), 81 | ('faculty/' + faculty, 'faculty_overview'), 82 | ('professor/' + professor, 'professor_overview'), 83 | )), 84 | ) 85 | 86 | # Maps straight from about/history to the static view in main.py 87 | static_urls = { 88 | 'about': ['history', 'licensing', 'platform'], # the index one is implicit 89 | 'contributing': ['moderating', 'development', 'content', 'guidelines'], 90 | 'help': ['copyright', 'formatting', 'lexers'], 91 | } 92 | 93 | urlpatterns = patterns('', 94 | url(r'^admin/', include(admin.site.urls)), 95 | ) 96 | 97 | """ 98 | Begin code for mapping the mappings 99 | """ 100 | 101 | # The index view has to be done separately 102 | urlpatterns += patterns('', 103 | url(r'^$', 'views.main.index', name='home'), 104 | ) 105 | 106 | for prefix, filenames in static_urls.iteritems(): 107 | index_url = url(r'^' + prefix + '(?:/overview)?/$', 'views.main.static', 108 | {'mode': prefix, 'page': 'overview'}, name=prefix) 109 | urls = [url(r'^' + prefix + '/' + filename + '/$', 'views.main.static', 110 | {'mode': prefix, 'page': filename}, 111 | name=prefix + '_' + filename) for filename in filenames] 112 | urlpatterns += patterns('', index_url, *urls) 113 | 114 | for prefix, mapping in direct_to_view: 115 | urls = [url('^' + regex + '/$', view, name='%s_%s' % (prefix, view)) for regex, view in mapping] 116 | urlpatterns += patterns('views.' + prefix, *urls) 117 | -------------------------------------------------------------------------------- /mdx/mdx_wiki_tables.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Python 2 | """ 3 | Tables Extension for Python-Markdown 4 | ==================================== 5 | 6 | Added parsing of tables to Python-Markdown. 7 | 8 | A simple example: 9 | 10 | First Header | Second Header 11 | ------------- | ------------- 12 | Content Cell | Content Cell 13 | Content Cell | Content Cell 14 | 15 | Copyright 2009 - [Waylan Limberg](http://achinghead.com) 16 | """ 17 | import markdown 18 | from markdown.util import etree 19 | 20 | 21 | class TableProcessor(markdown.blockprocessors.BlockProcessor): 22 | """ Process Tables. """ 23 | allowed_classes = ['sort', 'clear', 'autumn', 'lights', 'ribbon', 'fresh', 'iris', 'column'] # lol 24 | 25 | def test(self, parent, block): 26 | rows = block.split('\n') 27 | return (len(rows) > 2 and '|' in rows[0] and 28 | '|' in rows[1] and '-' in rows[1] and 29 | rows[1].strip()[0] in ['|', ':', '-']) 30 | 31 | def run(self, parent, blocks): 32 | """ Parse a table block and build table. """ 33 | block = blocks.pop(0).split('\n') 34 | header = block[0].strip() 35 | separator = block[1].strip() 36 | rows = block[2:] 37 | # Get format type (bordered by pipes or not) 38 | border = False 39 | if header.startswith('|'): 40 | border = True 41 | # Get alignment of columns 42 | align = [] 43 | for c in self._split_row(separator, border): 44 | if c.startswith(':') and c.endswith(':'): 45 | align.append('center') 46 | elif c.startswith(':'): 47 | align.append('left') 48 | elif c.endswith(':'): 49 | align.append('right') 50 | else: 51 | align.append(None) 52 | # Build table 53 | table = etree.SubElement(parent, 'table') 54 | thead = etree.SubElement(table, 'thead') 55 | self._build_row(header, thead, align, border) 56 | tbody = etree.SubElement(table, 'tbody') 57 | 58 | # Set any necessary classes on the table 59 | classes = self.__get_classes(separator) 60 | if classes: 61 | table.set('class', ' '.join(classes)) 62 | 63 | for row in rows: 64 | self._build_row(row.strip(), tbody, align, border) 65 | 66 | def _build_row(self, row, parent, align, border): 67 | """ Given a row of text, build table cells. """ 68 | tr = etree.SubElement(parent, 'tr') 69 | tag = 'td' 70 | if parent.tag == 'thead': 71 | tag = 'th' 72 | cells = self._split_row(row, border) 73 | # We use align here rather than cells to ensure every row 74 | # contains the same number of columns. 75 | span = 0 76 | for i, a in enumerate(align): 77 | try: 78 | if cells[i] == "": 79 | span += 1 80 | continue 81 | except: 82 | pass 83 | c = etree.SubElement(tr, tag) 84 | 85 | try: 86 | if span > 0: 87 | c.set('colspan',"%d" % (span+1)) 88 | span = 0 89 | c.text = cells[i].strip() 90 | except IndexError: 91 | c.text = "" 92 | if a: 93 | c.set('class', a + '-align') 94 | 95 | def _split_row(self, row, border): 96 | """ split a row of text into list of cells. """ 97 | if border: 98 | if row.startswith('|'): 99 | row = row[1:] 100 | if row.endswith('|'): 101 | row = row[:-1] 102 | return row.split('|') 103 | 104 | def __get_classes(self, separator): 105 | classes = [] 106 | for possible_class in separator.split(' '): 107 | if not possible_class.startswith('-') and not possible_class.startswith('|'): 108 | if possible_class in self.allowed_classes: 109 | classes.append(possible_class) 110 | return classes 111 | 112 | class TableExtension(markdown.Extension): 113 | """ Add tables to Markdown. """ 114 | 115 | def extendMarkdown(self, md, md_globals): 116 | """ Add an instance of TableProcessor to BlockParser. """ 117 | md.parser.blockprocessors.add('table', 118 | TableProcessor(md.parser), 119 | '