├── .dockerignore ├── .gitignore ├── .pyup.yml ├── .travis.yml ├── CHANGES.rst ├── Dockerfile ├── LICENSE ├── README.rst ├── VERSION ├── app.json ├── branding ├── Lato.zip ├── Wazi logo reference.pdf ├── Wazi logo vector.ai ├── wazimap-sticker-final.pdf ├── wazimap-sticker-final.png └── wazimap-sticker-final.svg ├── census ├── __init__.py ├── admin.py ├── context_processors.py ├── fixtures │ └── summary_levels.json ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ ├── cache_to_s3.py │ │ └── taxonify_table_metadata.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_python3_port_20190315_0944.py │ └── __init__.py ├── models.py ├── profile.py ├── static │ ├── css │ │ ├── _empty.scss │ │ └── vendor │ │ │ ├── Grid.css │ │ │ ├── images │ │ │ ├── layers-2x.png │ │ │ ├── layers.png │ │ │ ├── marker-icon-2x.png │ │ │ ├── marker-icon.png │ │ │ └── marker-shadow.png │ │ │ ├── leaflet-0.6.4.css │ │ │ ├── leaflet-0.6.4.ie.css │ │ │ ├── leaflet.label.css │ │ │ ├── normalize.css │ │ │ ├── normalize.min.css │ │ │ └── select2 │ │ │ ├── select2.css │ │ │ ├── select2.png │ │ │ ├── select2.textinput.css │ │ │ ├── select2x2.png │ │ │ └── spinner.gif │ ├── embed.html │ ├── iframe.html │ ├── img │ │ ├── code4sa-logo-small.png │ │ ├── examples │ │ │ ├── education_cleveland.png │ │ │ ├── hover_over_graph.png │ │ │ ├── language_los_angeles.png │ │ │ ├── map_search.png │ │ │ ├── poverty_spokane.png │ │ │ ├── section_overview.png │ │ │ ├── text_search.png │ │ │ ├── us_geographic_mobility_distribution.png │ │ │ ├── us_median_income_map.png │ │ │ └── us_race_table.png │ │ ├── icons │ │ │ ├── favicon.ico │ │ │ └── touch-icon-144x144.png │ │ ├── logo-48x24.png │ │ ├── logo-embed.png │ │ ├── logo.png │ │ ├── logo12.png │ │ ├── mma.png │ │ ├── openup-logo-small.png │ │ ├── questions │ │ │ ├── age-sex.png │ │ │ ├── ancestry.png │ │ │ ├── citizenship.png │ │ │ ├── commuting.png │ │ │ ├── disability.png │ │ │ ├── education.png │ │ │ ├── employment.png │ │ │ ├── fertility.png │ │ │ ├── health-insurance.png │ │ │ ├── housing-fuel.png │ │ │ ├── income.png │ │ │ ├── industry.png │ │ │ ├── language.png │ │ │ ├── marriage.png │ │ │ ├── migration.png │ │ │ ├── mortgage.png │ │ │ ├── occupation.png │ │ │ ├── place-of-birth.png │ │ │ ├── property-value.png │ │ │ ├── public-assistance.png │ │ │ ├── race.png │ │ │ ├── relationship.png │ │ │ ├── same-sex.png │ │ │ ├── seniors.png │ │ │ ├── tenure.png │ │ │ ├── va_rating.png │ │ │ ├── veteran.png │ │ │ └── veteran_period.png │ │ └── wazi-logo.png │ ├── js │ │ ├── address.search.js │ │ ├── app.js │ │ ├── charts.js │ │ ├── comparisons.js │ │ ├── cr-leaflet.js │ │ ├── data.query.builder.js │ │ ├── embed.chart.frame.js │ │ ├── embed.chart.make.js │ │ ├── glossary.js │ │ ├── head2head.js │ │ ├── profile.topic.picker.js │ │ ├── table.detail.js │ │ ├── tilelayer.js │ │ ├── vendor │ │ │ ├── Grid.js │ │ │ ├── chroma.js │ │ │ ├── chroma.min.js │ │ │ ├── classList.js │ │ │ ├── classList.min.js │ │ │ ├── flippant.js │ │ │ ├── flippant.min.js │ │ │ ├── leaflet-0.6.4.js │ │ │ ├── leaflet.label.js │ │ │ ├── r2d3.js │ │ │ ├── select2.js │ │ │ ├── select2.min.js │ │ │ └── topojson.v1.min.js │ │ └── widget.geo.select.js │ └── tilelayer.html ├── templates │ ├── 404.html │ ├── 500.html │ ├── _base.html │ ├── about.html │ ├── data │ │ ├── _base_data.html │ │ ├── _blocks │ │ │ ├── _data_query_builder.html │ │ │ ├── _toggle_menu.html │ │ │ └── _topic_select_input.html │ │ ├── data_builder.html │ │ ├── data_distribution.html │ │ ├── data_map.html │ │ └── data_table.html │ ├── examples │ │ ├── _base_examples.html │ │ └── embed_charts.html │ ├── glossary.html │ ├── healthcheck.html │ ├── homepage.html │ ├── locate │ │ └── locate.html │ ├── profile │ │ ├── _base_profile.html │ │ ├── _blocks │ │ │ ├── _comparative_list_item.html │ │ │ ├── _header_extra_links.html │ │ │ └── _stat_list.html │ │ ├── profile_detail.html │ │ └── profile_search.html │ ├── search │ │ ├── elasticsearch.html │ │ ├── geo_search.html │ │ └── table_search.html │ ├── table │ │ ├── _base_table.html │ │ ├── specific │ │ │ ├── B02003.html │ │ │ └── _base_specific.html │ │ ├── table_detail.html │ │ └── table_search.html │ └── topics │ │ ├── _base_topics.html │ │ ├── _question_aside.html │ │ ├── about_census.html │ │ ├── age_sex.html │ │ ├── children.html │ │ ├── commute.html │ │ ├── employment.html │ │ ├── families.html │ │ ├── geography.html │ │ ├── getting_started.html │ │ ├── health-insurance.html │ │ ├── income.html │ │ ├── migration.html │ │ ├── poverty.html │ │ ├── public-assistance.html │ │ ├── race_hispanic.html │ │ ├── same-sex.html │ │ ├── seniors.html │ │ ├── table-codes.html │ │ ├── topics_list.html │ │ └── veterans.html ├── templatetags │ ├── __init__.py │ ├── comparatives.py │ ├── lookup.py │ ├── madlibs.py │ ├── partition.py │ ├── sumlevs.py │ └── tabletags.py ├── tests.py ├── topics.py ├── urls.py ├── utils.py └── views.py ├── compose.yaml ├── deploy ├── .buildpacks ├── Procfile └── runtime.txt ├── docs ├── Makefile ├── conf.py ├── config.rst ├── customising.rst ├── data.rst ├── deploying.rst ├── geos.rst ├── index.rst ├── make.bat ├── profiles.rst ├── started.rst ├── upgrading.rst └── version_history.rst ├── manage.py ├── requirements.txt ├── setup.cfg ├── setup.py └── wazimap ├── __init__.py ├── admin.py ├── apps.py ├── context_processors.py ├── data ├── __init__.py ├── base.py ├── download.py ├── tables.py └── utils.py ├── geo.py ├── management ├── __init__.py └── commands │ ├── __init__.py │ └── upgradetables.py ├── middleware.py ├── migrations ├── 0001_initial.py ├── 0002_geography_long_name.py ├── 0003_remove_geography_osm_area_id.py ├── 0004_auto_20160302_1645.py ├── 0005_unique-geo-add-year.py ├── 0006_geo-year-to-geo-version.py ├── 0007_fieldtable_geoversion.py ├── 0008_auto_20170424_1209.py ├── 0009_datatables.py ├── 0010_null-total-column.py ├── 0011_release_citation.py ├── 0012_auto_20190111_1342.py ├── 0013_port_to_python3.py ├── 0014_auto_20191021_1216.py └── __init__.py ├── models ├── __init__.py ├── data.py └── geo.py ├── profiles.py ├── settings.py ├── static ├── css │ ├── _app.scss │ ├── _censusreporter.scss │ ├── _charts.scss │ ├── _custom.scss │ ├── _variables.scss │ ├── embed.scss │ ├── vendor │ │ └── _normalize.min.scss │ └── wazimap.scss └── js │ ├── comparisons.js │ ├── data.query.builder.js │ ├── embed.chart.frame.js │ ├── maps_static.js │ ├── profile_map.js │ ├── table.detail.js │ └── widget.geo.select.js ├── templates ├── _base.html ├── _footer.html ├── data │ ├── _base_data.html │ ├── _blocks │ │ └── _data_query_builder.html │ └── data_map.html ├── embed │ └── iframe.html ├── examples │ └── embed_charts.html ├── help.html ├── homepage.html ├── locate │ └── locate.html ├── profile │ ├── _blocks │ │ ├── _comparative_list_item.html │ │ └── _stat_list.html │ ├── head2head.html │ └── profile_detail.html ├── settings_js.html └── table │ └── table_detail.html ├── templatetags ├── __init__.py ├── jsonify.py └── stats.py ├── tests ├── __init__.py ├── data │ ├── __init__.py │ └── test_utils.py ├── support.py └── test_geo.py ├── urls.py ├── views.py └── wsgi.py /.dockerignore: -------------------------------------------------------------------------------- 1 | # Include any files or directories that you don't want to be copied to your 2 | # container here (e.g., local build artifacts, temporary files, etc.). 3 | # 4 | # For more help, visit the .dockerignore file reference guide at 5 | # https://docs.docker.com/engine/reference/builder/#dockerignore-file 6 | 7 | **/.DS_Store 8 | **/__pycache__ 9 | **/.venv 10 | **/.classpath 11 | **/.dockerignore 12 | **/.env 13 | **/.git 14 | **/.gitignore 15 | **/.project 16 | **/.settings 17 | **/.toolstarget 18 | **/.vs 19 | **/.vscode 20 | **/*.*proj.user 21 | **/*.dbmdl 22 | **/*.jfm 23 | **/bin 24 | **/charts 25 | **/docker-compose* 26 | **/compose* 27 | **/Dockerfile* 28 | **/node_modules 29 | **/npm-debug.log 30 | **/obj 31 | **/secrets.dev.yaml 32 | **/values.dev.yaml 33 | LICENSE 34 | README.md 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | env/ 3 | /dist/ 4 | _build/ 5 | *.egg-info/ 6 | *.pyc 7 | *.csv 8 | node_modules/ 9 | censusreporter/config/dev/local.py 10 | .idea/ 11 | censusreporter/embed_data/profiles/* 12 | debug 13 | .vagrant 14 | .eggs/ 15 | -------------------------------------------------------------------------------- /.pyup.yml: -------------------------------------------------------------------------------- 1 | # See https://pyup.io/docs/bot/config/ 2 | update: insecure 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | language: python 3 | python: 4 | - "3.7" 5 | services: 6 | - postgresql 7 | env: 8 | - DATABASE_URL=postgres://postgres:@localhost:5432/wazimap 9 | before_install: 10 | - sudo rm -f /etc/boto.cfg # workaround for https://github.com/travis-ci/travis-ci/issues/7940 11 | install: 12 | - python setup.py -q install 13 | script: python manage.py test 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7.17-slim-bullseye 2 | 3 | ENV PYTHONUNBUFFERED=1 \ 4 | PIP_NO_CACHE_DIR=1 \ 5 | PIP_DISABLE_PIP_VERSION_CHECK=1 6 | 7 | RUN set -ex \ 8 | && apt-get update \ 9 | && apt-get upgrade -y \ 10 | && apt-get install -y build-essential \ 11 | && apt-get install -y libpq-dev \ 12 | && apt-get install -y git \ 13 | && apt-get install -y libgdal-dev \ 14 | && apt-get autoremove -y \ 15 | && apt-get clean -y \ 16 | && rm -rf /var/lib/apt/lists/* 17 | 18 | WORKDIR /app 19 | 20 | COPY . . 21 | 22 | RUN pip install --upgrade pip \ 23 | pip install . 24 | 25 | EXPOSE 5000 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Census Reporter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | The Wazimap name and branding is Copyright 2013-2017 Media Monitoring Africa (MMA) 24 | and may not be used without permission. 25 | 26 | If you use this software, please provide attribution to Census Reporter, 27 | Wazimap, Media Monitoring Africa and OpenUp. 28 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Wazimap 2 | ======= 3 | 4 | .. image:: https://badge.fury.io/py/wazimap.svg 5 | :target: http://badge.fury.io/py/wazimap 6 | 7 | .. image:: https://travis-ci.org/Code4SA/wazimap.svg 8 | :target: http://travis-ci.org/Code4SA/wazimap 9 | 10 | This is the latest version of wazimap. Version 1 is located in the releases-v1 branch. 11 | 12 | 13 | Wazimap is a Django application for exploring census and other similar data. It makes it easy to understand a place 14 | through the eyes of the data, and to explore data across a range of places. It is most suited for census data 15 | but can easily be used with other data that is similarly focused on places in a country. 16 | 17 | Check out `Wazimap South Africa `_ and `HURUmap `_ to 18 | get an idea of what Wazimap is about. 19 | 20 | Wazimap is a fork of the excellent `Censusreporter `_ project which was funded by a 21 | `Knight News Challenge grant `_. 22 | You can also find `Censusreporter on GitHub `_. 23 | 24 | Wazimap builds on Censusreporter and makes it easier to re-use. Wazimap was originally built by 25 | `OpenUp `_ with the support of `Media Monitoring Africa `_. 26 | It is maintained by OpenUp. 27 | 28 | * Wazimap is on Twitter as `@Wazimap `_. 29 | * Read the `full Wazimap documentation `_. 30 | 31 | Using Wazimap 32 | ============= 33 | 34 | Read the `full Wazimap documentation `_ to get started. 35 | 36 | Development 37 | =========== 38 | 39 | Releasing a New Version 40 | ----------------------- 41 | 42 | 1. Run the tests:: 43 | 44 | python manage.py test 45 | 46 | 2. Update VERSION appropriately 47 | 3. Update the CHANGES.rst 48 | 4. Commit and push to github 49 | 5. Release to PyPI:: 50 | 51 | python setup.py sdist bdist_wheel upload 52 | 53 | License and Copyright 54 | ===================== 55 | 56 | Copyright (c) 2014 Census Reporter. 57 | 58 | Wazimap is licensed under the MIT License. 59 | 60 | The Wazimap name and branding is Copyright 2013-2017 Media Monitoring Africa (MMA) and may not be used without permission. 61 | 62 | If you use this software, please provide attribution to Census Reporter, Wazimap, Media Monitoring Africa and OpenUp. 63 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 2.1.3 2 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "dokku": { 4 | "predeploy": "python manage.py compilescss && python manage.py collectstatic --noinput && rm -rf /var/tmp/wazimap_cache" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /branding/Lato.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/branding/Lato.zip -------------------------------------------------------------------------------- /branding/Wazi logo reference.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/branding/Wazi logo reference.pdf -------------------------------------------------------------------------------- /branding/Wazi logo vector.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/branding/Wazi logo vector.ai -------------------------------------------------------------------------------- /branding/wazimap-sticker-final.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/branding/wazimap-sticker-final.pdf -------------------------------------------------------------------------------- /branding/wazimap-sticker-final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/branding/wazimap-sticker-final.png -------------------------------------------------------------------------------- /census/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/__init__.py -------------------------------------------------------------------------------- /census/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import SubjectConcept, SummaryLevel, Geography 4 | 5 | class SubjectConceptAdmin(admin.ModelAdmin): 6 | save_on_top = True 7 | prepopulated_fields = {'slug': ('name',)} 8 | list_display = ('name', 'census_category') 9 | list_editable = ('census_category',) 10 | 11 | class SummaryLevelAdmin(admin.ModelAdmin): 12 | save_on_top = True 13 | prepopulated_fields = {'slug': ('name',)} 14 | list_display = ('summary_level', 'name', 'short_name','plural_name') 15 | list_editable = ('short_name', 'plural_name') 16 | filter_horizontal = ('ancestors',) 17 | list_display_links = ('name',) 18 | 19 | admin.site.register(SubjectConcept, SubjectConceptAdmin) 20 | admin.site.register(SummaryLevel, SummaryLevelAdmin) 21 | admin.site.register(Geography) 22 | -------------------------------------------------------------------------------- /census/context_processors.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | def api_url(request): 4 | return {'API_URL': settings.API_URL} 5 | -------------------------------------------------------------------------------- /census/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/management/__init__.py -------------------------------------------------------------------------------- /census/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/management/commands/__init__.py -------------------------------------------------------------------------------- /census/management/commands/cache_to_s3.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from django.core.management.base import BaseCommand 3 | from multiprocessing import Pool 4 | from traceback import format_exc 5 | from boto.s3.connection import S3Connection 6 | from boto.s3.key import Key 7 | 8 | import json 9 | import io 10 | import gzip 11 | 12 | from ...profile import geo_profile, enhance_api_data 13 | 14 | import logging 15 | logging.basicConfig(level=logging.WARN) 16 | logger = logging.getLogger(__name__) 17 | 18 | s3 = S3Connection() 19 | 20 | def s3_keyname(geoid): 21 | return '/1.0/data/profiles/%s.json' % geoid 22 | 23 | def key(geoid): 24 | bucket = s3.get_bucket('embed.censusreporter.org') 25 | keyname = s3_keyname(geoid) 26 | key = Key(bucket, keyname) 27 | 28 | return key 29 | 30 | def write_profile_json(s3_key, data): 31 | s3_key.metadata['Content-Type'] = 'application/json' 32 | s3_key.metadata['Content-Encoding'] = 'gzip' 33 | s3_key.storage_class = 'REDUCED_REDUNDANCY' 34 | 35 | # create gzipped version of json in memory 36 | memfile = io.StringIO() 37 | #memfile.write(data) 38 | with gzip.GzipFile(filename=s3_key.key, mode='wb', fileobj=memfile) as gzip_data: 39 | gzip_data.write(data) 40 | memfile.seek(0) 41 | 42 | # store static version on S3 43 | s3_key.set_contents_from_file(memfile) 44 | 45 | def seed(geoid): 46 | logger.info("Working on {}".format(geoid)) 47 | try: 48 | api_data = geo_profile(geoid) 49 | api_data = enhance_api_data(api_data) 50 | 51 | s3key = key(geoid) 52 | write_profile_json(s3key, json.dumps(api_data)) 53 | logger.info("Wrote to key {}".format(s3key)) 54 | except Exception as e: 55 | logger.error("Problem caching {}".format(geoid)) 56 | logger.exception(e) 57 | logger.info("Done working on {}".format(geoid)) 58 | 59 | 60 | class Command(BaseCommand): 61 | help = 'Pre-generates some Census Reporter content and places it on S3.' 62 | 63 | def handle(self, *args, **options): 64 | if not args: 65 | print("Please include the name of a file containing the seed geo_ids.") 66 | return False 67 | 68 | parallelism = 4 69 | if 'parallelism' in options: 70 | parallelism = int(options.get('parallelism')) 71 | 72 | pool = Pool(parallelism) 73 | 74 | seed_file = open(args[0], 'r') 75 | 76 | for geoid in seed_file: 77 | pool.apply_async(seed, (geoid.strip(),)) 78 | 79 | pool.close() 80 | pool.join() 81 | -------------------------------------------------------------------------------- /census/management/commands/taxonify_table_metadata.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from django.core.management.base import BaseCommand 3 | from census.models import Table 4 | 5 | SUBJECT_AREA_TO_TOPICS = { 6 | 'Age-Sex': 'age, gender', 7 | 'Hispanic Origin': 'race', 8 | 'Race': 'race', 9 | 10 | 'Earnings': 'income', 11 | 'Employment Status': 'employment', 12 | 'Health Insurance': 'health insurance', 13 | 'Income': 'income', 14 | 'Industry-Occupation-Class of Worker': 'employment', 15 | 'Journey to Work': 'employment, commute', 16 | 'Poverty': 'poverty', 17 | 'Transfer Programs': 'public assistance', 18 | 19 | 'Ancestry': 'ancestry', 20 | 'Children - Relationship': 'children', 21 | 'Disability': 'disability', 22 | 'Educational Attainment': 'education', 23 | 'Fertility': 'fertility', 24 | 'Foreign Birth': 'place of birth', 25 | 'Grand(Persons) - Age of HH Members': 'children, grandparents', 26 | 'Households - Families': 'families', 27 | 'Language': 'language', 28 | 'Marital Status': 'marital status', 29 | 'Place of Birth - Native': 'place of birth', 30 | 'Residence Last Year - Migration': 'migration', 31 | 'School Enrollment': 'education', 32 | 'Veteran Status': 'veterans', 33 | 34 | 'Housing': 'housing', 35 | } 36 | 37 | TABLE_NAME_TEXT_TO_TOPICS = { 38 | 'children': 'children', 39 | 'disability': 'disability', 40 | 'bachelor\'s degree': 'education', 41 | 'education': 'education', 42 | 'school': 'education', 43 | 'employ': 'employment', 44 | 'occupation': 'employment', 45 | 'work': 'employment', 46 | 'families': 'families', 47 | 'family': 'families', 48 | 'nonfamily': 'roommates', 49 | 'grandparent': 'grandparents', 50 | 'health insurance': 'health insurance', 51 | 'living arrange': 'housing', 52 | #'household': 'households', 53 | 'earnings': 'income', 54 | 'income': 'income', 55 | 'geographical mobility': 'migration', 56 | 'poverty': 'poverty', 57 | 'food stamps': 'public assistance', 58 | 'public assistance': 'public assistance', 59 | '65 years and over': 'seniors', 60 | 'transportation': 'commute', 61 | 'va health care': 'veterans', 62 | 'veteran': 'veterans', 63 | } 64 | 65 | TABLE_NAME_TEXT_TO_FACETS = { 66 | 'by age': 'age', 67 | 'age by': 'age', 68 | 'citizenship': 'citizenship', 69 | 'naturalization': 'citizenship', 70 | 'by famil': 'families', 71 | 'by sex': 'gender', 72 | 'sex by': 'gender', 73 | #'by household': 'household type', 74 | #'household type by': 'household type', 75 | 'language': 'language', 76 | 'marriage': 'marital status', 77 | 'marital': 'marital status', 78 | 'nativity': 'place of birth', 79 | 'place of birth': 'place of birth', 80 | #'by relationship': 'relationship type', 81 | '(white': 'race', 82 | '(black': 'race', 83 | '(american': 'race', 84 | '(asian': 'race', 85 | '(native': 'race', 86 | '(some other race': 'race', 87 | '(two or more races': 'race', 88 | 'hispanic': 'race', 89 | } 90 | 91 | class Command(BaseCommand): 92 | help = 'Uses dicts above to apply taxonomy to Census table names.' 93 | def handle(self, *args, **options): 94 | table_list = Table.objects.all() 95 | 96 | for table in table_list: 97 | table_name = table.table_name 98 | subject_area = table.subject_area 99 | topics = [] 100 | print(table_name) 101 | 102 | if subject_area in SUBJECT_AREA_TO_TOPICS: 103 | # only keep the rows in the subject areas we want 104 | topics.extend( 105 | [topic.strip() for topic in SUBJECT_AREA_TO_TOPICS[subject_area].split(',')] 106 | ) 107 | 108 | for key in TABLE_NAME_TEXT_TO_TOPICS: 109 | if key in table_name.lower(): 110 | topics.extend( 111 | [topic.strip() for topic in TABLE_NAME_TEXT_TO_TOPICS[key].split(',')] 112 | ) 113 | 114 | # for now, we're storing these in the same place 115 | for key in TABLE_NAME_TEXT_TO_FACETS: 116 | if key in table_name.lower(): 117 | topics.extend( 118 | [facet.strip() for facet in TABLE_NAME_TEXT_TO_FACETS[key].split(',')] 119 | ) 120 | 121 | topics = ', '.join(sorted(set(topics))) 122 | table.topics = topics 123 | table.save() 124 | -------------------------------------------------------------------------------- /census/migrations/0002_python3_port_20190315_0944.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.20 on 2019-03-15 07:44 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('census', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='subjectconcept', 17 | name='census_category', 18 | field=models.CharField(blank=True, default='Population', max_length=128), 19 | ), 20 | migrations.AlterField( 21 | model_name='subjectconcept', 22 | name='source', 23 | field=models.CharField(blank=True, default='American Community Survey Subject Definitions', max_length=64), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /census/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/migrations/__init__.py -------------------------------------------------------------------------------- /census/static/css/_empty.scss: -------------------------------------------------------------------------------- 1 | // exists to force scss to use this as an include directory 2 | -------------------------------------------------------------------------------- /census/static/css/vendor/images/layers-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/css/vendor/images/layers-2x.png -------------------------------------------------------------------------------- /census/static/css/vendor/images/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/css/vendor/images/layers.png -------------------------------------------------------------------------------- /census/static/css/vendor/images/marker-icon-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/css/vendor/images/marker-icon-2x.png -------------------------------------------------------------------------------- /census/static/css/vendor/images/marker-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/css/vendor/images/marker-icon.png -------------------------------------------------------------------------------- /census/static/css/vendor/images/marker-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/css/vendor/images/marker-shadow.png -------------------------------------------------------------------------------- /census/static/css/vendor/leaflet-0.6.4.ie.css: -------------------------------------------------------------------------------- 1 | .leaflet-vml-shape { 2 | width: 1px; 3 | height: 1px; 4 | } 5 | .lvml { 6 | behavior: url(#default#VML); 7 | display: inline-block; 8 | position: absolute; 9 | } 10 | 11 | .leaflet-control { 12 | display: inline; 13 | } 14 | 15 | .leaflet-popup-tip { 16 | width: 21px; 17 | _width: 27px; 18 | margin: 0 auto; 19 | _margin-top: -3px; 20 | 21 | filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); 22 | -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; 23 | } 24 | .leaflet-popup-tip-container { 25 | margin-top: -1px; 26 | } 27 | .leaflet-popup-content-wrapper, .leaflet-popup-tip { 28 | border: 1px solid #999; 29 | } 30 | .leaflet-popup-content-wrapper { 31 | zoom: 1; 32 | } 33 | 34 | .leaflet-control-zoom, 35 | .leaflet-control-layers { 36 | border: 3px solid #999; 37 | } 38 | .leaflet-control-layers-toggle { 39 | } 40 | .leaflet-control-attribution, 41 | .leaflet-control-layers, 42 | .leaflet-control-scale-line { 43 | background: white; 44 | } 45 | .leaflet-zoom-box { 46 | filter: alpha(opacity=50); 47 | } 48 | .leaflet-control-attribution { 49 | border-top: 1px solid #bbb; 50 | border-left: 1px solid #bbb; 51 | } 52 | -------------------------------------------------------------------------------- /census/static/css/vendor/leaflet.label.css: -------------------------------------------------------------------------------- 1 | .leaflet-label { 2 | background: #fff; 3 | background-clip: padding-box; 4 | border: solid 4px #111; 5 | -webkit-border-radius: 4px; 6 | -moz-border-radius: 4px; 7 | border-radius: 4px; 8 | color: #111; 9 | display: block; 10 | position: absolute; 11 | -webkit-user-select: none; 12 | -moz-user-select: none; 13 | -ms-user-select: none; 14 | user-select: none; 15 | pointer-events: none; 16 | white-space: nowrap; 17 | z-index: 6; 18 | font-size: 12px; 19 | line-height: 1.4em; 20 | font-weight: bold; 21 | padding: 1px 6px; 22 | } 23 | 24 | .leaflet-label.leaflet-clickable { 25 | cursor: pointer; 26 | } 27 | 28 | .leaflet-label:before, 29 | .leaflet-label:after { 30 | border-top: 6px solid transparent; 31 | border-bottom: 6px solid transparent; 32 | content: none; 33 | position: absolute; 34 | top: 5px; 35 | } 36 | 37 | .leaflet-label:before { 38 | border-right: 6px solid black; 39 | border-right-color: inherit; 40 | left: -10px; 41 | } 42 | 43 | .leaflet-label:after { 44 | border-left: 6px solid black; 45 | border-left-color: inherit; 46 | right: -10px; 47 | } 48 | 49 | .leaflet-label-right:before, 50 | .leaflet-label-left:after { 51 | content: ""; 52 | } -------------------------------------------------------------------------------- /census/static/css/vendor/normalize.min.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v1.1.0 | MIT License | git.io/normalize */ 2 | article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}html,button,input,select,textarea{font-family:sans-serif}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}h2{font-size:1.5em;margin:.83em 0}h3{font-size:1.17em;margin:1em 0}h4{font-size:1em;margin:1.33em 0}h5{font-size:.83em;margin:1.67em 0}h6{font-size:.67em;margin:2.33em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}blockquote{margin:1em 40px}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}p,pre{margin:1em 0}code,kbd,pre,samp{font-family:monospace,serif;_font-family:'courier new',monospace;font-size:1em}pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}q{quotes:none}q:before,q:after{content:'';content:none}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}dl,menu,ol,ul{margin:1em 0}dd{margin:0 0 0 40px}menu,ol,ul{padding:0 0 0 40px}nav ul,nav ol{list-style:none;list-style-image:none}img{border:0;-ms-interpolation-mode:bicubic}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0;white-space:normal;*margin-left:-7px}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;*overflow:visible}button[disabled],html input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;*height:13px;*width:13px}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0} -------------------------------------------------------------------------------- /census/static/css/vendor/select2/select2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/css/vendor/select2/select2.png -------------------------------------------------------------------------------- /census/static/css/vendor/select2/select2x2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/css/vendor/select2/select2x2.png -------------------------------------------------------------------------------- /census/static/css/vendor/select2/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/css/vendor/select2/spinner.gif -------------------------------------------------------------------------------- /census/static/embed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Embed test 5 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /census/static/iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 18 | 47 | 48 | 54 | 55 | 56 |
57 | 58 | 59 | 60 | 61 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /census/static/img/code4sa-logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/code4sa-logo-small.png -------------------------------------------------------------------------------- /census/static/img/examples/education_cleveland.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/examples/education_cleveland.png -------------------------------------------------------------------------------- /census/static/img/examples/hover_over_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/examples/hover_over_graph.png -------------------------------------------------------------------------------- /census/static/img/examples/language_los_angeles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/examples/language_los_angeles.png -------------------------------------------------------------------------------- /census/static/img/examples/map_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/examples/map_search.png -------------------------------------------------------------------------------- /census/static/img/examples/poverty_spokane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/examples/poverty_spokane.png -------------------------------------------------------------------------------- /census/static/img/examples/section_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/examples/section_overview.png -------------------------------------------------------------------------------- /census/static/img/examples/text_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/examples/text_search.png -------------------------------------------------------------------------------- /census/static/img/examples/us_geographic_mobility_distribution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/examples/us_geographic_mobility_distribution.png -------------------------------------------------------------------------------- /census/static/img/examples/us_median_income_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/examples/us_median_income_map.png -------------------------------------------------------------------------------- /census/static/img/examples/us_race_table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/examples/us_race_table.png -------------------------------------------------------------------------------- /census/static/img/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/icons/favicon.ico -------------------------------------------------------------------------------- /census/static/img/icons/touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/icons/touch-icon-144x144.png -------------------------------------------------------------------------------- /census/static/img/logo-48x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/logo-48x24.png -------------------------------------------------------------------------------- /census/static/img/logo-embed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/logo-embed.png -------------------------------------------------------------------------------- /census/static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/logo.png -------------------------------------------------------------------------------- /census/static/img/logo12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/logo12.png -------------------------------------------------------------------------------- /census/static/img/mma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/mma.png -------------------------------------------------------------------------------- /census/static/img/openup-logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/openup-logo-small.png -------------------------------------------------------------------------------- /census/static/img/questions/age-sex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/age-sex.png -------------------------------------------------------------------------------- /census/static/img/questions/ancestry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/ancestry.png -------------------------------------------------------------------------------- /census/static/img/questions/citizenship.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/citizenship.png -------------------------------------------------------------------------------- /census/static/img/questions/commuting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/commuting.png -------------------------------------------------------------------------------- /census/static/img/questions/disability.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/disability.png -------------------------------------------------------------------------------- /census/static/img/questions/education.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/education.png -------------------------------------------------------------------------------- /census/static/img/questions/employment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/employment.png -------------------------------------------------------------------------------- /census/static/img/questions/fertility.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/fertility.png -------------------------------------------------------------------------------- /census/static/img/questions/health-insurance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/health-insurance.png -------------------------------------------------------------------------------- /census/static/img/questions/housing-fuel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/housing-fuel.png -------------------------------------------------------------------------------- /census/static/img/questions/income.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/income.png -------------------------------------------------------------------------------- /census/static/img/questions/industry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/industry.png -------------------------------------------------------------------------------- /census/static/img/questions/language.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/language.png -------------------------------------------------------------------------------- /census/static/img/questions/marriage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/marriage.png -------------------------------------------------------------------------------- /census/static/img/questions/migration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/migration.png -------------------------------------------------------------------------------- /census/static/img/questions/mortgage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/mortgage.png -------------------------------------------------------------------------------- /census/static/img/questions/occupation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/occupation.png -------------------------------------------------------------------------------- /census/static/img/questions/place-of-birth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/place-of-birth.png -------------------------------------------------------------------------------- /census/static/img/questions/property-value.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/property-value.png -------------------------------------------------------------------------------- /census/static/img/questions/public-assistance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/public-assistance.png -------------------------------------------------------------------------------- /census/static/img/questions/race.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/race.png -------------------------------------------------------------------------------- /census/static/img/questions/relationship.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/relationship.png -------------------------------------------------------------------------------- /census/static/img/questions/same-sex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/same-sex.png -------------------------------------------------------------------------------- /census/static/img/questions/seniors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/seniors.png -------------------------------------------------------------------------------- /census/static/img/questions/tenure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/tenure.png -------------------------------------------------------------------------------- /census/static/img/questions/va_rating.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/va_rating.png -------------------------------------------------------------------------------- /census/static/img/questions/veteran.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/veteran.png -------------------------------------------------------------------------------- /census/static/img/questions/veteran_period.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/questions/veteran_period.png -------------------------------------------------------------------------------- /census/static/img/wazi-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/static/img/wazi-logo.png -------------------------------------------------------------------------------- /census/static/js/embed.chart.make.js: -------------------------------------------------------------------------------- 1 | function makeCensusEmbeds() { 2 | var embed = { 3 | embeds: {} 4 | }; 5 | 6 | embed.init = function() { 7 | embed.containers = document.querySelectorAll('.census-reporter-embed'); 8 | embed.numContainers = embed.containers.length; 9 | for (var i = 0; i < embed.numContainers; i++) { 10 | embed.embeds[embed.containers[i].id] = { 11 | naturalWidth: embed.containers[i].width, 12 | naturalHeight: embed.containers[i].height, 13 | frameHeight: embed.containers[i].height 14 | } 15 | } 16 | embed.addListeners(); 17 | embed.sendDataToFrames({ resize: 'resize' }); 18 | } 19 | 20 | embed.addListeners = function() { 21 | var eventMethod = window.addEventListener ? 'addEventListener' : 'attachEvent', 22 | eventListener = window[eventMethod], 23 | messageEvent = (eventMethod == 'attachEvent') ? 'onmessage' : 'message', 24 | resizeEvent = (eventMethod == 'attachEvent') ? 'onresize' : 'resize'; 25 | 26 | eventListener(messageEvent, embed.handleMessage, false); 27 | eventListener(resizeEvent, embed.resize); 28 | } 29 | 30 | embed.handleMessage = function(event) { 31 | var messageData = JSON.parse(event.data); 32 | if (messageData.chartHeight && messageData.containerID) { 33 | embed.embeds[messageData.containerID].frameHeight = messageData.chartHeight; 34 | embed.setFrameSizes(); 35 | } 36 | } 37 | 38 | embed.debounce = function(func, wait, immediate) { 39 | var timeout; 40 | return function() { 41 | var context = this, args = arguments; 42 | var later = function() { 43 | timeout = null; 44 | if (!immediate) func.apply(context, args); 45 | }; 46 | var callNow = immediate && !timeout; 47 | clearTimeout(timeout); 48 | timeout = setTimeout(later, wait); 49 | if (callNow) func.apply(context, args); 50 | }; 51 | }; 52 | 53 | embed.resize = embed.debounce(function() { 54 | embed.setFrameSizes(); 55 | embed.sendDataToFrames({ resize: 'resize' }); 56 | }, 100); 57 | 58 | embed.setFrameSizes = function() { 59 | for (var i = 0; i < embed.numContainers; i++) { 60 | var thisContainer = embed.containers[i], 61 | thisEmbed = embed.embeds[embed.containers[i].id], 62 | parentWidth = thisContainer.offsetWidth; 63 | thisContainer.width = (parentWidth <= thisEmbed.naturalWidth) ? parentWidth : thisEmbed.naturalWidth; 64 | thisContainer.height = ((thisEmbed.frameHeight + 80) >= thisEmbed.naturalHeight) ? +thisEmbed.frameHeight+80 : thisEmbed.naturalHeight; 65 | } 66 | } 67 | 68 | embed.sendDataToFrames = function(data) { 69 | // IE9 can only send strings 70 | for (var i = 0; i < embed.numContainers; i++) { 71 | embed.containers[i].contentWindow.postMessage(JSON.stringify(data), '*'); 72 | } 73 | } 74 | 75 | embed.init(); 76 | 77 | return embed; 78 | } 79 | 80 | window.onload = function() { 81 | window.CensusReporterEmbeds = makeCensusEmbeds(); 82 | }; 83 | -------------------------------------------------------------------------------- /census/static/js/head2head.js: -------------------------------------------------------------------------------- 1 | /* Helper routines for managing the head-to-head comparison view. 2 | * 3 | * They ensure that the two iframes are sized to match the height 4 | * of their content, to prevent scrolling. 5 | */ 6 | function Head2Head() { 7 | var self = this; 8 | 9 | self.initParent = function() { 10 | // this is the parent frame in the head-to-head view 11 | self.isParent = true; 12 | self.isChild = false; 13 | }; 14 | 15 | self.initChild = function() { 16 | // this is a child frame in the head-to-head view 17 | self.isParent = false; 18 | self.isChild = true; 19 | 20 | $('body').addClass('profile-head2head-frame'); 21 | $('body').on('click', 'a[href]', self.navigateTo); 22 | 23 | // set the frame height 24 | setTimeout(self.resizeChild, 500); 25 | $(window).on('resize', _.debounce(self.resizeChild, 500)); 26 | }; 27 | 28 | self.resizeChild = function(e) { 29 | // set the iframe to fit the size of the child 30 | var height = document.body.offsetHeight + 50, 31 | frame = $(window.frameElement); 32 | 33 | if (frame.height() != height) { 34 | // height changed, update the iframe and check again in a few msecs 35 | frame.height(height); 36 | setTimeout(self.resizeChild, 500); 37 | } 38 | }; 39 | 40 | self.navigateTo = function(e) { 41 | // open links in the parent window 42 | if (e.target.href) { 43 | e.preventDefault(); 44 | window.parent.location = e.target.href; 45 | } 46 | }; 47 | } 48 | 49 | var h2h = new Head2Head(); 50 | 51 | if (window.parent != window && window.parent.location.pathname.indexOf('/compare/') > -1) { 52 | h2h.initChild(); 53 | } else if (window.location.pathname.indexOf('/compare/') > -1) { 54 | h2h.initParent(); 55 | } 56 | -------------------------------------------------------------------------------- /census/static/js/profile.topic.picker.js: -------------------------------------------------------------------------------- 1 | // powers the topic picker on profile pages 2 | 3 | // the template including this should set the following vars: 4 | // var thisGeoID = '{{ geography.this.full_geoid }}', 5 | // placeGeoID = '{{ geography.parents.place.full_geoid }}', 6 | // CBSAGeoID = '{{ geography.parents.CBSA.full_geoid }}', 7 | // countyGeoID = '{{ geography.county.full_geoid }}', 8 | // stateGeoID = '{{ geography.state.full_geoid }}', 9 | // nationGeoID = '{{ geography.nation.full_geoid }}'; 10 | 11 | var theseGeoIDs = [thisGeoID, placeGeoID, CBSAGeoID, countyGeoID, stateGeoID, nationGeoID].filter(function(n){return n}), 12 | chosenTableID = chosenTableID || null, 13 | topicSelect = $('#topic-select'); 14 | 15 | var tableSearchAPI = CR_API_URL + '/1.0/table/search', 16 | rootGeoAPI = CR_API_URL + '/1.0/geo/tiger2013/', 17 | dataAPI = CR_API_URL + '/1.0/data/show/latest'; 18 | 19 | var topicSelectEngine = new Bloodhound({ 20 | datumTokenizer: function(d) { return Bloodhound.tokenizers.whitespace(d.full_name); }, 21 | queryTokenizer: Bloodhound.tokenizers.whitespace, 22 | limit: 1500, 23 | remote: { 24 | url: tableSearchAPI, 25 | replace: function (url, query) { 26 | return url += '?q=' + query; 27 | }, 28 | filter: function(response) { 29 | var resultNumber = response.length; 30 | if (resultNumber === 0) { 31 | response.push({ 32 | table_name: 'Sorry, no matches found. Try changing your search.' 33 | }); 34 | } 35 | _.map(response, function(item) { 36 | if (!!item['topics']) { 37 | item['topic_string'] = item['topics'].join(', '); 38 | } 39 | }); 40 | return response; 41 | } 42 | } 43 | }); 44 | topicSelectEngine.initialize(); 45 | 46 | function makeTopicSelectWidget(element) { 47 | element.typeahead('destroy'); 48 | element.typeahead({ 49 | autoselect: true, 50 | highlight: false, 51 | hint: false, 52 | minLength: 2 53 | }, { 54 | name: 'topics', 55 | displayKey: 'simple_table_name', 56 | source: topicSelectEngine.ttAdapter(), 57 | templates: { 58 | suggestion: Handlebars.compile( 59 | [ 60 | '{{#if table_id}}
{{#if column_name}}Column in {{/if}}Table {{table_id}}
{{/if}}', 61 | '

{{simple_table_name}}

', 62 | '{{#if column_name}}

Column name: {{column_name}}

{{/if}}', 63 | '{{#if topic_string}}

Table topics: {{topic_string}}

{{/if}}' 64 | ].join('') 65 | ) 66 | } 67 | }); 68 | 69 | element.on('typeahead:selected', function(obj, datum) { 70 | chosenTableID = datum['table_id']; 71 | if (!!chosenTableID) { 72 | window.location = '/data/table/?table=' + chosenTableID + "&geo_ids=" + thisGeoID + "&primary_geo_id=" + thisGeoID; 73 | } 74 | }); 75 | } 76 | 77 | jQuery(document).ready(function(){ 78 | // initial setup for select widget 79 | makeTopicSelectWidget(topicSelect); 80 | }); 81 | -------------------------------------------------------------------------------- /census/static/js/vendor/classList.js: -------------------------------------------------------------------------------- 1 | /* 2 | * classList.js: Cross-browser full element.classList implementation. 3 | * 2014-01-31 4 | * 5 | * By Eli Grey, http://eligrey.com 6 | * Public Domain. 7 | * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | */ 9 | 10 | /*global self, document, DOMException */ 11 | 12 | /*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/ 13 | 14 | if ("document" in self && !("classList" in document.createElement("_"))) { 15 | 16 | (function (view) { 17 | 18 | "use strict"; 19 | 20 | if (!('Element' in view)) return; 21 | 22 | var 23 | classListProp = "classList" 24 | , protoProp = "prototype" 25 | , elemCtrProto = view.Element[protoProp] 26 | , objCtr = Object 27 | , strTrim = String[protoProp].trim || function () { 28 | return this.replace(/^\s+|\s+$/g, ""); 29 | } 30 | , arrIndexOf = Array[protoProp].indexOf || function (item) { 31 | var 32 | i = 0 33 | , len = this.length 34 | ; 35 | for (; i < len; i++) { 36 | if (i in this && this[i] === item) { 37 | return i; 38 | } 39 | } 40 | return -1; 41 | } 42 | // Vendors: please allow content code to instantiate DOMExceptions 43 | , DOMEx = function (type, message) { 44 | this.name = type; 45 | this.code = DOMException[type]; 46 | this.message = message; 47 | } 48 | , checkTokenAndGetIndex = function (classList, token) { 49 | if (token === "") { 50 | throw new DOMEx( 51 | "SYNTAX_ERR" 52 | , "An invalid or illegal string was specified" 53 | ); 54 | } 55 | if (/\s/.test(token)) { 56 | throw new DOMEx( 57 | "INVALID_CHARACTER_ERR" 58 | , "String contains an invalid character" 59 | ); 60 | } 61 | return arrIndexOf.call(classList, token); 62 | } 63 | , ClassList = function (elem) { 64 | var 65 | trimmedClasses = strTrim.call(elem.getAttribute("class") || "") 66 | , classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [] 67 | , i = 0 68 | , len = classes.length 69 | ; 70 | for (; i < len; i++) { 71 | this.push(classes[i]); 72 | } 73 | this._updateClassName = function () { 74 | elem.setAttribute("class", this.toString()); 75 | }; 76 | } 77 | , classListProto = ClassList[protoProp] = [] 78 | , classListGetter = function () { 79 | return new ClassList(this); 80 | } 81 | ; 82 | // Most DOMException implementations don't allow calling DOMException's toString() 83 | // on non-DOMExceptions. Error's toString() is sufficient here. 84 | DOMEx[protoProp] = Error[protoProp]; 85 | classListProto.item = function (i) { 86 | return this[i] || null; 87 | }; 88 | classListProto.contains = function (token) { 89 | token += ""; 90 | return checkTokenAndGetIndex(this, token) !== -1; 91 | }; 92 | classListProto.add = function () { 93 | var 94 | tokens = arguments 95 | , i = 0 96 | , l = tokens.length 97 | , token 98 | , updated = false 99 | ; 100 | do { 101 | token = tokens[i] + ""; 102 | if (checkTokenAndGetIndex(this, token) === -1) { 103 | this.push(token); 104 | updated = true; 105 | } 106 | } 107 | while (++i < l); 108 | 109 | if (updated) { 110 | this._updateClassName(); 111 | } 112 | }; 113 | classListProto.remove = function () { 114 | var 115 | tokens = arguments 116 | , i = 0 117 | , l = tokens.length 118 | , token 119 | , updated = false 120 | ; 121 | do { 122 | token = tokens[i] + ""; 123 | var index = checkTokenAndGetIndex(this, token); 124 | if (index !== -1) { 125 | this.splice(index, 1); 126 | updated = true; 127 | } 128 | } 129 | while (++i < l); 130 | 131 | if (updated) { 132 | this._updateClassName(); 133 | } 134 | }; 135 | classListProto.toggle = function (token, force) { 136 | token += ""; 137 | 138 | var 139 | result = this.contains(token) 140 | , method = result ? 141 | force !== true && "remove" 142 | : 143 | force !== false && "add" 144 | ; 145 | 146 | if (method) { 147 | this[method](token); 148 | } 149 | 150 | return !result; 151 | }; 152 | classListProto.toString = function () { 153 | return this.join(" "); 154 | }; 155 | 156 | if (objCtr.defineProperty) { 157 | var classListPropDesc = { 158 | get: classListGetter 159 | , enumerable: true 160 | , configurable: true 161 | }; 162 | try { 163 | objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); 164 | } catch (ex) { // IE 8 doesn't support enumerable:true 165 | if (ex.number === -0x7FF5EC54) { 166 | classListPropDesc.enumerable = false; 167 | objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); 168 | } 169 | } 170 | } else if (objCtr[protoProp].__defineGetter__) { 171 | elemCtrProto.__defineGetter__(classListProp, classListGetter); 172 | } 173 | 174 | }(self)); 175 | 176 | } 177 | -------------------------------------------------------------------------------- /census/static/js/vendor/classList.min.js: -------------------------------------------------------------------------------- 1 | /*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/ 2 | ;if("document" in self&&!("classList" in document.createElement("_"))){(function(j){"use strict";if(!("Element" in j)){return}var a="classList",f="prototype",m=j.Element[f],b=Object,k=String[f].trim||function(){return this.replace(/^\s+|\s+$/g,"")},c=Array[f].indexOf||function(q){var p=0,o=this.length;for(;p{{geo_level}}{{full_name}}

'), 3 | geoSelect = $('#geography-select, #geography-select-home'); 4 | 5 | var textMatchEngine = new Bloodhound({ 6 | datumTokenizer: function(d) { return Bloodhound.tokenizers.whitespace(d.full_name); }, 7 | queryTokenizer: Bloodhound.tokenizers.whitespace, 8 | limit: 20, 9 | remote: { 10 | url: textmatchAPI, 11 | rateLimitWait: 600, 12 | replace: function (url, query) { 13 | return url += '?q=' + query; 14 | }, 15 | filter: function(response) { 16 | return response.results; 17 | } 18 | } 19 | }); 20 | textMatchEngine.initialize(); 21 | 22 | function makeGeoSelectWidget(element, selected) { 23 | element.typeahead({ 24 | autoselect: true, 25 | highlight: false, 26 | hint: false, 27 | minLength: 2 28 | }, { 29 | // get textual matches from host 30 | name: 'textmatch', 31 | displayKey: 'full_name', 32 | source: textMatchEngine.ttAdapter(), 33 | limit: 20, 34 | templates: { 35 | suggestion: resultTemplate, 36 | }, 37 | }); 38 | 39 | element.on('typeahead:selected', selected || function(event, datum) { 40 | event.stopPropagation(); 41 | window.location = '/profiles/' + datum.full_geoid + '/'; 42 | }); 43 | } 44 | 45 | makeGeoSelectWidget(geoSelect); 46 | makeGeoSelectWidget($('#compare-place-select'), function(event, datum) { 47 | var geoId = [profileData.geography.this.geo_level, profileData.geography.this.geo_code].join('-'); 48 | event.stopPropagation(); 49 | window.location = '/compare/' + geoId + '/vs/' + datum.full_geoid + '/'; 50 | }); 51 | -------------------------------------------------------------------------------- /census/static/tilelayer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example of using Census Reporter GeoJSON tiles on your own maps. 5 | 6 | 7 | 10 |
11 | 12 | 13 | 14 | 107 | 108 | -------------------------------------------------------------------------------- /census/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends '_base.html' %}{% load humanize %} 2 | 3 | {% block head_title %}{{ block.super }}{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |

Not Found

9 | {% for message in messages %} 10 |

{{ message|safe }}

11 | {% endfor %} 12 |
13 |
14 |

In the spirit of open and iterative development, we’re making this site available to the public as early as possible. Much will change, and some things may not be working at any moment, but we want people to see what we’re working on and we welcome input about what might be most useful. (See below for all manner of contact information.)

15 |
16 | {% endblock %} -------------------------------------------------------------------------------- /census/templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends '_base.html' %}{% load humanize %} 2 | 3 | {% block head_title %}{{ block.super }}{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |

Server Error

9 |
10 |
11 |

Uh-oh, something’s gone wrong. We’ve logged the error and will check it out as soon as possible. In the meantime, do you want to return to the homepage?

12 |
13 | {% endblock %} -------------------------------------------------------------------------------- /census/templates/data/_base_data.html: -------------------------------------------------------------------------------- 1 | {% extends '_base.html' %} 2 | 3 | {% comment %} 4 | This page provides a base for all data view templates to inherit from, 5 | for applying things across that entire family of pages. 6 | {% endcomment %} 7 | 8 | {% block head_title %}Table {% if table %}{{ table }}{% endif %} {% if release %}({{ release.name }}){% endif %} - {{ block.super }}{% endblock %} 9 | 10 | {% block body_class %}data-view{% endblock %} 11 | 12 | {% block header_content %} 13 |
14 | {% include 'data/_blocks/_toggle_menu.html' %} 15 |
16 | {% endblock %} 17 | 18 | {% block content %} 19 | {% include 'data/_blocks/_topic_select_input.html' %} 20 | 21 |
22 |
23 |

Table explorer
24 | Change table 25 |

26 |
27 |
28 |
29 |

Table {{ table }}

30 |
31 |

32 | 33 |
34 |
35 | 36 | 37 |
38 |

39 | 40 |

41 |
42 |
43 |
44 | {% endblock %} 45 | 46 | {% block body_javascript_extra %} 47 | 48 | {% block page_specific_javascript %}{% endblock %} 49 | 75 | {% endblock %} 76 | -------------------------------------------------------------------------------- /census/templates/data/_blocks/_data_query_builder.html: -------------------------------------------------------------------------------- 1 |
2 |

Explore

3 |

Use keywords to search the American Community Survey by table and column name, then choose a place or places to view your data.

4 | 5 |
6 | 7 |
8 |
9 |
10 |

in

11 |
12 | 13 |

Find a place to explore data, or choose a geographic summary level to build a comparison.

14 |
15 |
16 |
17 |

in

18 |
19 | 20 |

21 |
22 |
23 | -------------------------------------------------------------------------------- /census/templates/data/_blocks/_toggle_menu.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 8 |
9 |
10 | 11 | Download data 12 | 13 | 22 |
23 |
24 | -------------------------------------------------------------------------------- /census/templates/data/_blocks/_topic_select_input.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /census/templates/data/data_builder.html: -------------------------------------------------------------------------------- 1 | {% extends 'data/_base_data.html' %}{% load humanize %} 2 | 3 | {% block body_class %}data-builder{% endblock %} 4 | 5 | {% block header_content %}{% endblock %} 6 | 7 | {% block content %} 8 |
9 | {% include "data/_blocks/_data_query_builder.html" %} 10 |
11 | {% endblock %} 12 | 13 | {% block body_javascript_extra %} 14 | 15 | {% endblock %} -------------------------------------------------------------------------------- /census/templates/data/data_distribution.html: -------------------------------------------------------------------------------- 1 | {% extends 'data/_base_data.html' %}{% load humanize %} 2 | 3 | {% block head_title %}Distribution View: {{ block.super }}{% endblock %} 4 | 5 | {% block head_css_extra %} 6 | 7 | {% endblock %} 8 | 9 | {% block aside_notes %} 10 | 11 | {% endblock %} 12 | 13 | {% block page_specific_javascript %} 14 | 15 | 16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /census/templates/data/data_map.html: -------------------------------------------------------------------------------- 1 | {% extends 'data/_base_data.html' %}{% load humanize %} 2 | {% load staticfiles %} 3 | 4 | {% block head_title %}Map View: {{ block.super }}{% endblock %} 5 | 6 | {% block head_css_extra %} 7 | 8 | 11 | 12 | {% endblock %} 13 | 14 | {% block body_class %}{{ block.super }} full-screen{% endblock %} 15 | 16 | {% block body %} 17 |
18 |
19 | 20 | Wazimap 21 | 22 |
23 |
24 |
25 | {% include 'data/_blocks/_toggle_menu.html' %} 26 | {% include 'data/_blocks/_topic_select_input.html' %} 27 |
28 | 29 |
30 |
31 |
32 | 33 |
34 |
35 | 36 |
37 | {% endblock %} 38 | 39 | {% block page_specific_javascript %} 40 | 41 | 42 | {% endblock %} 43 | -------------------------------------------------------------------------------- /census/templates/data/data_table.html: -------------------------------------------------------------------------------- 1 | {% extends 'data/_base_data.html' %}{% load humanize %} 2 | 3 | {% block head_title %}Grid View: {{ block.super }}{% endblock %} 4 | 5 | {% block head_css_extra %} 6 | 7 | {% endblock %} 8 | 9 | {% block aside_notes %} 10 | 11 | {% endblock %} 12 | 13 | {% block page_specific_javascript %} 14 | 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /census/templates/examples/_base_examples.html: -------------------------------------------------------------------------------- 1 | {% extends '_base.html' %} 2 | -------------------------------------------------------------------------------- /census/templates/healthcheck.html: -------------------------------------------------------------------------------- 1 | OK -------------------------------------------------------------------------------- /census/templates/locate/locate.html: -------------------------------------------------------------------------------- 1 | {% extends '_base.html' %} 2 | 3 | 4 | {% block content %} 5 |
6 |
7 | {% if not location %} 8 |

Finding your current location…

9 | {% else %} 10 | {% if places %} 11 |

Use your current location

12 |

Your browser says you’re in…

13 | {% else %} 14 |

We can't find you in South Africa.

15 |

Are you located somewhere else? Try the search box instead.

16 | {% endif %} 17 | {% endif %} 18 |
19 | {% if places %} 20 |
21 | 31 |
32 | {% endif %} 33 |
34 | {% endblock %} 35 | 36 | {% block body_javascript_extra %} 37 | 38 | 62 | {% endblock %} 63 | -------------------------------------------------------------------------------- /census/templates/profile/_base_profile.html: -------------------------------------------------------------------------------- 1 | {% extends '_base.html' %} 2 | 3 | {% block head_title %}Profile data - {{ block.super }}{% endblock %} 4 | 5 | {% comment %} 6 | At some point, we may end up needing to branch the profile page template 7 | in order to support data available for different releases. And we may end up 8 | with a special template for subtopic pages within a profile. This page provides 9 | a profile-specific base suitable for applying things across that entire 10 | family of pages. 11 | {% endcomment %} 12 | 13 | -------------------------------------------------------------------------------- /census/templates/profile/_blocks/_comparative_list_item.html: -------------------------------------------------------------------------------- 1 | {% load humanize madlibs %} 2 | {# to be called via `comparatives` inclusion tag #} 3 |
  • 4 | {% if index and stat_type != 'count' %} 5 | {{ index|comparison_index_phrase }} the {{ stat_type|stat_type_to_number_noun }} in {{ place_name }}: {% if stat_type == 'dollar' %}R{% endif %}{{ value|floatformat|intcomma|cut:".0" }}{% if stat_type == 'percentage' %}%{% endif %} 6 | {% if error_ratio >= 10 %}{% endif %} 7 | {% else %} 8 | {{ place_name }}: {% if stat_type == 'dollar' %}R{% endif %}{{ value|floatformat|intcomma|cut:".0" }}{% if stat_type == 'percentage' %}%{% endif %} 9 | {% endif %} 10 | {% if numerator %} 11 | {% if stat_type == 'dollar' %}R{% endif %}{{ numerator|floatformat|intcomma|cut:".0" }} 12 | {% if error %} 13 | (±{% if stat_type == 'dollar' %}R{% endif %}{{ error|floatformat|intcomma|cut:".0" }}{% if stat_type == 'percentage' %}%{% endif %}{% if numerator_error %} / ±{% if stat_type == 'dollar' %}R{% endif %}{{ numerator_error|floatformat|intcomma|cut:".0" }}{% endif %}) 14 | {% endif %} 15 | {% elif error %} 16 | ±{% if stat_type == 'dollar' %}R{% endif %}{{ error|floatformat|intcomma|cut:".0" }}{% if stat_type == 'percentage' %}%{% endif %} 17 | {% endif %} 18 |
  • 19 | -------------------------------------------------------------------------------- /census/templates/profile/_blocks/_header_extra_links.html: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /census/templates/profile/_blocks/_stat_list.html: -------------------------------------------------------------------------------- 1 | {% load humanize madlibs comparatives %} 2 | {% if not stat_wrapper == 'false' %}{% endif %} 3 | 4 | 5 | {% if not stat.values.this %} 6 | {{ WAZIMAP.na_label }} 7 | {% else %} 8 | {% if stat_type == 'name' %} 9 | {{ stat.name }} 10 | {% else %} 11 | {% if stat_type == 'dollar' %}R{% endif %}{{ stat.values.this|floatformat|intcomma|cut:".0" }}{% if stat_type == 'percentage' %}%{% endif %}{% if stat_suffix %} {{ stat_suffix }}{% endif %} 12 | {% endif %} 13 | {% if stat.error.this_ratio >= 10 %}{% endif %} 14 | {% endif %} 15 | 16 | {% if not stat_class == 'secondary' %} 17 | {% if stat.error.this %} 18 | ±{% if stat_type == 'dollar' %}R{% endif %}{{ stat.error.this|floatformat|intcomma|cut:".0" }}{% if stat_type == 'percentage' %}%{% endif %} 19 | {% endif %} 20 | 21 | {% if stat.numerators.this %} 22 | ({% if stat_type == 'dollar' %}R{% endif %}{{ stat.numerators.this|floatformat|intcomma|cut:".0" }}  ±{% if stat_type == 'dollar' %}R{% endif %}{{ stat.numerator_errors.this|floatformat|intcomma|cut:".0" }}) 23 | {% endif %} 24 | {% endif %} 25 | 26 | {% if stat_type == 'name' %}{{ stat_name }}{% else %}{{ stat.name }}{% endif %} 27 | 28 | {% if stat_class == 'secondary' %} 29 | 30 | {% if stat.error.this %} 31 | ±{% if stat_type == 'dollar' %}R{% endif %}{{ stat.error.this|floatformat|intcomma|cut:".0" }}{% if stat_type == 'percentage' %}%{% endif %} 32 | {% endif %} 33 | 34 | {% if stat.numerators.this %} 35 | ({% if stat_type == 'dollar' %}R{% endif %}{{ stat.numerators.this|floatformat|intcomma|cut:".0" }}  ±{% if stat_type == 'dollar' %}R{% endif %}{{ stat.numerator_errors.this|floatformat|intcomma|cut:".0" }}) 36 | {% endif %} 37 | 38 | {% endif %} 39 | 40 | 41 | {% if geography.comparatives and stat.values.this and not stat_class == 'secondary' %} 42 |
      43 | {% for sumlev in geography.comparatives %} 44 | {% build_comparative_item sumlev stat stat_type geography %} 45 | {% endfor %} 46 |
    47 | {% endif %} 48 | {% if not stat_wrapper == 'false' %}
    {% endif %} 49 | -------------------------------------------------------------------------------- /census/templates/search/geo_search.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 | 4 | 5 | Census Reporter Geo Search Example 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 |
    17 | 18 | 19 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /census/templates/search/table_search.html: -------------------------------------------------------------------------------- 1 | {% extends '_base.html' %} 2 | 3 | {% block head_css_extra %} 4 | 20 | {% endblock %} 21 | 22 | {% block content %} 23 |
    24 |

    Search data tables and columns

    25 |
    26 | 27 | 28 | 29 | 35 |
    36 |
    37 | 38 | {% if tables %} 39 |
    40 |

    Tables

    41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | {% for table in tables %} 51 | 52 | 53 | 54 | 55 | 56 | {% endfor %} 57 | 58 |
    Table IDTable NameUniverse
    {{ table.release }} {{ table.table_id }}{{ table.table_name }}{{ table.table_universe }}
    59 |
    60 | {% endif %} 61 | 62 | {% if columns %} 63 |
    64 |

    Columns

    65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | {% for column in columns %} 76 | 77 | 78 | 79 | 80 | 81 | 82 | {% endfor %} 83 | 84 |
    Table IDTable NameColumn IDColumn Name
    {{ column.table.release }} {{ column.parent_table_id }}{{ column.table.table_name }}{{ column.column_id }}{{ column.column_name }}
    85 |
    86 | {% endif %} 87 | 88 | {% endblock %} 89 | -------------------------------------------------------------------------------- /census/templates/table/_base_table.html: -------------------------------------------------------------------------------- 1 | {% extends '_base.html' %} 2 | -------------------------------------------------------------------------------- /census/templates/table/specific/B02003.html: -------------------------------------------------------------------------------- 1 | {% extends "table/specific/_base_specific.html" %} 2 | {% block table_specific %} 3 |

    4 | Because the columns in B02003 are so detailed, this table is only available for the entire US, and is only included in the 1-year and 3-year ACS releases. If you want similar information for any other geographies or for the 5-year ACS, use table C02003 5 |

    6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /census/templates/table/specific/_base_specific.html: -------------------------------------------------------------------------------- 1 |
    2 |

    About this table

    3 | {% block table_specific %} 4 |

    5 | Extend this template to maintain consistency. 6 | Fill in this block with details specific to a table. 7 | Don't forget to use the 'explain' class on your <p> tags. 8 |

    9 | {% endblock %} 10 |
    11 | -------------------------------------------------------------------------------- /census/templates/topics/_base_topics.html: -------------------------------------------------------------------------------- 1 | {% extends '_base.html' %} 2 | 3 | {% block head_title %}{% if topic.title %}{{ topic.title }}{% else %}Topics{% endif %} - {{ block.super }}{% endblock %} 4 | 5 | {% block content_container %} 6 |
    7 |

    In addition to serving data, Census Reporter wants to help you understand how to use it. Let us know if there are topics you’d like to see us explain.

    8 | 9 |
    10 |
    11 | {% if topic.slug %}

    ← Topics

    {% endif %} 12 |

    {{ topic.title }}

    13 |

    {{ topic.description }}

    14 |
    15 | 16 | {% block content %}{% endblock %} 17 |
    18 |
    19 | {% endblock %} 20 | 21 | {% block body_javascript_extra %}{{ block.super }} 22 | 42 | {% endblock %} -------------------------------------------------------------------------------- /census/templates/topics/_question_aside.html: -------------------------------------------------------------------------------- 1 | {% if topic.question_images or topic.question_pdfs %} 2 | 19 | {% endif %} 20 | -------------------------------------------------------------------------------- /census/templates/topics/about_census.html: -------------------------------------------------------------------------------- 1 | {% extends 'topics/_base_topics.html' %} 2 | 3 | {% block content %} 4 |
    5 | {% include "topics/_question_aside.html" %} 6 |

    The High Level

    7 |

    Originally, the intention of the US Census was to count the population every ten years so that representation could be apportioned. Over time, more questions were added to the decennial census, and other completely new data collection projects were developed.

    8 |

    Today, the Census Bureau has four major programs: the decennial census, the Current Population Survey (CPS), the Population Estimates Program, and the American Community Survey (ACS). Census Reporter (this site) only offers data from the ACS.

    9 |

    These programs differ in their data collection methodology, the frequency with which data is released, and the level of geographic specificity they can provide. There are a few other Census products

    10 |
    11 |
    12 |

    The Decennial Census

    13 |

    As its name indicates, the decennial census is run every ten years. It attempts to actually count every resident in the United States as of a specific day (April 1st of the census year, in recent decades.) Because it is a count and not a survey, the Census can publish precise counts for much smaller geographies than the other products.

    14 |
    15 | {% endblock %} 16 | 17 | 18 | -------------------------------------------------------------------------------- /census/templates/topics/age_sex.html: -------------------------------------------------------------------------------- 1 | {% extends 'topics/_base_topics.html' %} 2 | 3 | {% block content %} 4 |
    5 | {% include "topics/_question_aside.html" %} 6 |

    Overview

    7 |

    Throughout the American Community Survey, data is broken down by age and sex. However, there are a few tables that are focused directly on these basic statistics.

    8 | 9 |

    Note: do not substitute the word “gender” for “sex.” Census Bureau specifically refers to the biological sex of survey respondents, not their gender presentation.

    10 | 11 |
      12 |
    • B01001: Sex by Age: provides the estimated count of men and women in age buckets, mostly of five years (e.g. 25 to 29 years, 30 to 34 years). Around the later teens and in the 60’s the brackets are of differing sizes, including a column for just 20-year-olds and another for just 21-year-olds.)
    • 13 |
    • B01002: Median Age: provides the estimated median age for all people in the given geography, and then also for males and females separately.
    • 14 |
    • B01003: Total population: a single column with the estimated total population for the geography. This value is the same as the first column of B01001 for a given geography.
    • 15 |
    16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 44 | 45 | 46 |
    CodeTitle
    B01001Sex by Age
    B01002Median Age by Sex
    B01003Total Population
    41 | Also available in racial iterations.
    42 | Table also available in “collapsed” version: change “B” to “C” for table code. Note: There is no table C01001. 43 |
    47 | 48 |
    49 | 50 |
    51 |

    Age and Sex elsewhere in the American Community Survey

    52 | 53 |

    Hundreds of ACS tables are tabulated by age and sex. So it’s not worth listing them all here. If you are looking for a table and it has what you want, but is also split by age and sex, you may have some of your own work to do. These tables always have the total in the first column, so you may just want that and disregard the tabulations. There are also usually columns for all male and all female, but if you want to count by age and not by sex, you will have to sum those values.

    54 |
    55 | {% endblock %} 56 | 57 | 58 | -------------------------------------------------------------------------------- /census/templates/topics/getting_started.html: -------------------------------------------------------------------------------- 1 | {% extends 'topics/_base_topics.html' %} 2 | 3 | {% block content %} 4 |
    5 |

    What's in the Census?

    6 |

    The Census isn't one thing. The U.S. Census Bureau has a number of data collection programs and even more data products based on them. Census Reporter focuses on just one of them, the American Community Survey, or ACS. Our About the Census page gets into the basics you should know about the other Census products.

    7 |

    OK, then, what's in the ACS?

    8 |

    The ACS covers a good variety of general-interest topics about the US: not only basic demographics like age, sex, and race, but also a number of economic and social questions, and details about the actual homes where people live, like how many rooms are in a housing unit and how many units are in the building. Our guide to the structure of table codes includes a list of the main subject areas.

    9 |

    When is the ACS updated?

    10 |

    11 | Every fall the Census Bureau releases new ACS estimates in three forms. The different releases are based on data collected over different periods of time. Collecting data over longer periods of time helps to reduce the margin of error for places with a smaller population. 12 |

    13 |

    14 | The releases are staggered, with the 1-year release delivered first, usually in September, followed by the 3-year release, and finally, the 5-year release is usually delivered in early December. 15 |

    16 |

    17 | Census Reporter always shows you the data for the release appropriate to the places you're looking at, so you generally don't have to worry about it too much. However, technically you shouldn't directly compare data from different releases, so be careful if you are gathering estimates from different profile pages. 18 |

    19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
    Collection PeriodGeographies coveredMonth released
    1 yearPopulation > 65,000September
    3 yearsPopulation > 20,000October
    5 yearsAll major geographies down to block groupDecember
    45 |
    46 | {% endblock %} 47 | 48 | 49 | -------------------------------------------------------------------------------- /census/templates/topics/public-assistance.html: -------------------------------------------------------------------------------- 1 | {% extends 'topics/_base_topics.html' %} 2 | 3 | {% block content %} 4 |
    5 | {% include "topics/_question_aside.html" %} 6 |

    Overview

    7 |

    These tables address the topic of public assistance.

    8 | 9 |

    The ACS tables beginning with 22 are about public assistance. In the listing below, table names have been simplified in some cases. References to children and grandchildren almost always mean under 18 years old. Income is for the last 12 months, adjusted for inflation to the value of dollars in the last year of the survey period. References to poverty status/level, receipt of food stamps/SNAP, and work status are all for the last 12 months.

    10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 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 | 56 | 57 | 58 |
    CodeTitle
    B22001Receipt of Food Stamps/SNAP by Presence of People 60 Years and Over for Households
    B22002Receipt of Food Stamps/SNAP by Presence of Children by Household Type for Households
    B22003Receipt of Food Stamps/SNAP by Poverty Status for Households
    B22004Receipt of Food Stamps by Disability Status for Households
    B22005Receipt of Food Stamps/SNAP by Race of Householder
    B22007Receipt of Food Stamps/SNAP by Family Type by Number of Workers in Family
    B22008Median Household Income by Receipt of Food Stamps/SNAP
    B22010Receipt of Food Stamps/SNAP by Disability Status for Households
    53 | Table also available in "collapsed" version: change "B" to "C" for table code.
    54 | Also available in racial iterations.
    55 |
    59 | 60 |

    61 | Other ACS tables that relate to public assistance

    62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 99 | 100 | 101 |
    CodeTitle
    B09010Receipt of Supplemental Security Income (SSI), Cash Public Assistance Income, or Food Stamps/SNAP by Household Type for Children in Households
    B13015Women 15 to 50 Years Who Had a Birth in the Past 12 Months by Marital Status and Receipt of Public Assistance Income
    B17015Poverty Status of Families by Family Type by Social Security Income by Supplemental Security Income (SSI) and Cash Public Assistance Income
    B19057Public Assistance Income for Households
    B19058Public Assistance Income or Food Stamps/SNAP for Households
    B19067Aggregate Public Assistance Income for Households
    97 | Table also available in "collapsed" version: change "B" to "C" for table code.
    98 |
    102 |
    103 | {% endblock %} 104 | 105 | 106 | -------------------------------------------------------------------------------- /census/templates/topics/same-sex.html: -------------------------------------------------------------------------------- 1 | {% extends 'topics/_base_topics.html' %} 2 | 3 | {% block content %} 4 |
    5 | {% include "topics/_question_aside.html" %} 6 |

    Overview

    7 |

    The Census Bureau does not ask questions about sexual orientation. However, responses to some questions provide data about same-sex couples. This data should not be used to draw general conclusions about gay/lesbian/bisexual Americans, but when handled with appropriate care, may still be useful for reporting.

    8 |

    9 | Table B11009, Unmarried-partner Households by Sex of Partner, is the only table which provides information about people who have told the Census Bureau that they live with a partner of the same sex. As explained in the Census Bureau's Frequently Asked Questions About Same-Sex Couple Households document, responses reporting a spouse of the same sex as the house holder are changed to indicate an unmarried partner, regardless of state laws permitting same-sex marriage. 10 |

    11 |

    12 | Consult the Census Bureau's documentation for more details. Specifically, American Community Survey Data on Same Sex Couples provides national-level data on the household characteristics (including income, education, etc) of same-sex couples compared to married and unmarried opposite-sex couples and married couples dating back to 2005, as well as more data and methodological details. 13 |

    14 | 25 |
    26 | {% endblock %} 27 | 28 | 29 | -------------------------------------------------------------------------------- /census/templates/topics/topics_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'topics/_base_topics.html' %} 2 | 3 | {% block content %} 4 |
    5 |
    6 | {% for topic in topics_list %} 7 |
    {{ topic.title }}
    8 |
    {{ topic.description|safe|linebreaks }}
    9 | {% endfor %} 10 |
    11 |
    12 | {% endblock %} 13 | 14 | 15 | -------------------------------------------------------------------------------- /census/templates/topics/veterans.html: -------------------------------------------------------------------------------- 1 | {% extends 'topics/_base_topics.html' %} 2 | 3 | {% block content %} 4 |
    5 | {% include "topics/_question_aside.html" %} 6 |

    Overview

    7 |

    8 | The Census Bureau asks three questions specific to service in the armed forces. The tables summarizing responses to those questions are listed below. In addition to the Veteran-specific tables, there are a few other pertinent tables to know about. 9 |

    10 |

    Employment

    11 |

    12 | In general, the Census tables about employment include "Armed Forces" as a component of the labor force. So, you can use tables like B23001 to know how many people are in active service for a given geography. (See the B23002 series for the same data broken down in racial iterations.) Table B23025 presents employment status by industry, including active service armed forces. 13 |

    14 |

    Social and Family Characteristics

    15 |

    16 | These tables have "armed forces" broken out as a separate column: 17 |

      18 |
    • B12006: Marital Status by Sex by Labor Force Participation
    • 19 |
    • B23003: Presence of Own Children by Age of Children
    • 20 |
    • B23006: Educational Attainment by Employment Status
    • 21 |
    • B23007: Presence of Own Children by Family Type by Employment
    • 22 |
    • B23024: Poverty Status by Disability Status by Employment Status
    • 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 | 70 | 71 | 72 | 75 | 76 | 77 | 78 |
    Codetitle
    B21001Sex by Age by Veteran Status for the Civilian Population
    B21002Period of Military Service for Civilian Veterans
    B21003Veteran Status by Educational Attainment for the Civilian Population
    B21004Median Income by Veteran Status by Sex for the Civilian Population
    B21005Age by Veteran Status by Employment Status
    B21007Age by Veteran Status by Poverty Status in the Past 12 Months by Disability Status for the Civilian Population
    B21100Service-connected Disability-rating Status and Ratings for Civilian Veterans
    B99211Imputation of Veteran Status for the Population
    B99212Imputation of Period of Military Service for Civilian Veterans
    68 | Also available in racial iterations. 69 |
    73 | Table also available in "collapsed" version: change "B" to "C" for table code. 74 |
    79 | 80 | 81 | {% endblock %} -------------------------------------------------------------------------------- /census/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/census/templatetags/__init__.py -------------------------------------------------------------------------------- /census/templatetags/comparatives.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | register = template.Library() 4 | 5 | 6 | @register.inclusion_tag('profile/_blocks/_comparative_list_item.html') 7 | def build_comparative_item(sumlev, stat, stat_type, geography, decimals=None): 8 | if sumlev == 'CBSA': 9 | place_name = 'the %s' % geography['parents'][sumlev]['full_name'] 10 | else: 11 | place_name = geography['parents'][sumlev]['short_name'] 12 | 13 | item_context = { 14 | 'place_name': place_name, 15 | 'stat_type': stat_type, 16 | 'decimals': decimals, 17 | 'value': stat['values'][sumlev], 18 | 'index': stat['index'][sumlev], 19 | 'error': stat['error'][sumlev], 20 | 'error_ratio': stat['error_ratio'][sumlev], 21 | 'numerator': stat['numerators'][sumlev], 22 | 'numerator_error': stat['numerator_errors'][sumlev], 23 | } 24 | return item_context 25 | -------------------------------------------------------------------------------- /census/templatetags/lookup.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | register = template.Library() 4 | 5 | @register.filter 6 | def get(d, key): 7 | return d.get(key, '') 8 | -------------------------------------------------------------------------------- /census/templatetags/madlibs.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.utils.safestring import mark_safe 3 | 4 | register = template.Library() 5 | 6 | COMPARISON_PHRASE_MAP = { 7 | 206: ["more than double", ""], 8 | 195: ["about double", ""], 9 | 180: ["nearly double", ""], 10 | 161: ["more than 1.5 times", ""], 11 | 145: ["about 1.5 times", ""], 12 | 135: ["about 1.4 times", ""], 13 | 128: ["about 1.3 times", ""], 14 | 122: ["about 25 percent higher", "than"], 15 | 115: ["about 20 percent higher", "than"], 16 | 107: ["about 10 percent higher", "than"], 17 | 103: ["a little higher", "than"], 18 | 98: ["about the same as", ""], 19 | 94: ["a little less", "than"], 20 | 86: ["about 90 percent", "of"], 21 | 78: ["about 80 percent", "of"], 22 | 72: ["about three-quarters", "of"], 23 | 64: ["about two-thirds", "of"], 24 | 56: ["about three-fifths", "of"], 25 | 45: ["about half", ""], 26 | 37: ["about two-fifths", "of"], 27 | 30: ["about one-third", "of"], 28 | 23: ["about one-quarter", "of"], 29 | 17: ["about one-fifth", "of"], 30 | 13: ["less than a fifth", "of"], 31 | 8: ["about 10 percent", "of"], 32 | 0: ["less than 10 percent", "of"], 33 | } 34 | 35 | @register.filter 36 | def comparison_index_phrase(value): 37 | ''' 38 | Each stat on the profile page can have country- and province-level 39 | values, indexed to 100 for comparisons (that is, expressed as a percentage 40 | of that statistic's value for the profile geography). That index value can 41 | be passed into this template filter to generate a comparative phrase. 42 | 43 | The COMPARISON_PHRASE_MAP defines the comparative phrases; the dict keys 44 | are the lower boundary of the range of values that result in that phrase. 45 | 46 | For example, the effective range of index values that return the phrase 47 | "about half" would be 45 to 55. 48 | ''' 49 | # make sure we have an int for comparison 50 | index = round(float(value)) 51 | 52 | # get lower boundaries for each phrase in the map 53 | thresholds = sorted([k for k,v in list(COMPARISON_PHRASE_MAP.items())]) 54 | 55 | # get highest boundary that's less than the index value we've been passed 56 | phrase_key = max(k for k in thresholds if k <= index) 57 | 58 | phrase_bits = COMPARISON_PHRASE_MAP[phrase_key] 59 | phrase = "%s %s" % (phrase_bits[0], phrase_bits[1]) 60 | return mark_safe(phrase) 61 | 62 | @register.filter 63 | def stat_type_to_number_noun(stat_type): 64 | if stat_type == 'dollar': 65 | return 'amount' 66 | elif stat_type == 'percentage': 67 | return 'rate' 68 | return 'figure' 69 | -------------------------------------------------------------------------------- /census/templatetags/sumlevs.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from census.utils import SUMMARY_LEVEL_DICT 3 | register = template.Library() 4 | 5 | @register.filter 6 | def sumlev_name(sumlev): 7 | if SUMMARY_LEVEL_DICT[sumlev]: 8 | return SUMMARY_LEVEL_DICT[sumlev]['name'] 9 | return '' 10 | 11 | @register.filter 12 | def sumlev_name_plural(sumlev): 13 | if SUMMARY_LEVEL_DICT[sumlev]: 14 | return SUMMARY_LEVEL_DICT[sumlev]['plural'] 15 | return '' 16 | 17 | @register.filter 18 | def list_cut(itemlist, term): 19 | return [ i for i in itemlist if not i == term ] 20 | -------------------------------------------------------------------------------- /census/templatetags/tabletags.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | register = template.Library() 4 | 5 | @register.simple_tag(takes_context=True) 6 | def table_specific(context, table_id): 7 | """Safely include a fragment specific to the given table, but handle no special info gracefully.""" 8 | try: 9 | fragment_path = "table/specific/%s.html" % table_id 10 | t = template.loader.get_template(fragment_path) 11 | return t.render(context) 12 | except template.TemplateDoesNotExist: 13 | return "" 14 | -------------------------------------------------------------------------------- /census/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from .views import GeographyDetailView 3 | 4 | class ParseTestCase(TestCase): 5 | def setUp(self): 6 | self.view = GeographyDetailView() 7 | 8 | def test_parse_typical(self): 9 | """Animals that can speak are correctly identified""" 10 | (geoid,slug) = self.view.parse_fragment('16000US1714000-chicago-il') 11 | self.assertEqual(geoid,'16000US1714000') 12 | self.assertEqual(slug,'chicago-il') 13 | 14 | def test_parse_geoid(self): 15 | """Animals that can speak are correctly identified""" 16 | (geoid,slug) = self.view.parse_fragment('16000US1714000') 17 | self.assertEqual(geoid,'16000US1714000') 18 | self.assertEqual(slug,None) 19 | 20 | def test_parse_vermont(self): 21 | """Animals that can speak are correctly identified""" 22 | (geoid,slug) = self.view.parse_fragment('61000US50ADD-addison-state-senate-district-vt') 23 | self.assertEqual(geoid,'61000US50ADD') 24 | self.assertEqual(slug,'addison-state-senate-district-vt') 25 | 26 | def test_parse_problem_geoid(self): 27 | (geoid,slug) = self.view.parse_fragment('61000US50E-O') 28 | self.assertEqual(geoid,'61000US50E-O') 29 | self.assertEqual(slug,None) 30 | 31 | def test_parse_problem_geoid_and_slug(self): 32 | (geoid,slug) = self.view.parse_fragment('61000US50E-O-essex-orleans-state-senate-district-vt') 33 | self.assertEqual(geoid,'61000US50E-O') 34 | self.assertEqual(slug,'essex-orleans-state-senate-district-vt') 35 | 36 | -------------------------------------------------------------------------------- /compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | server: 3 | build: 4 | context: . 5 | volumes: 6 | - .:/app 7 | ports: 8 | - 8000:5000 9 | environment: 10 | - DATABASE_URL=postgresql://wazimap:wazimap@db/wazimap 11 | depends_on: 12 | db: 13 | condition: service_healthy 14 | command: python manage.py runserver 0.0.0.0:5000 15 | db: 16 | image: postgres:11.6 17 | environment: 18 | - POSTGRES_USER=wazimap 19 | - POSTGRES_PASSWORD=wazimap 20 | - POSTGRES_DB=wazimap 21 | - PGUSER=wazimap 22 | ports: 23 | - "5433:5432" 24 | volumes: 25 | - db-data:/var/lib/postgresql/data 26 | command: ["postgres", "-c", "log_statement=all"] 27 | healthcheck: 28 | test: ["CMD-SHELL", "pg_isready", "-U postgres"] 29 | interval: 10s 30 | timeout: 5s 31 | retries: 5 32 | 33 | volumes: 34 | db-data: 35 | -------------------------------------------------------------------------------- /deploy/.buildpacks: -------------------------------------------------------------------------------- 1 | https://github.com/cyberdelia/heroku-geo-buildpack.git 2 | https://github.com/heroku/heroku-buildpack-python.git 3 | -------------------------------------------------------------------------------- /deploy/Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn --worker-class gevent config.prod.wsgi:application -t 120 --log-file - 2 | -------------------------------------------------------------------------------- /deploy/runtime.txt: -------------------------------------------------------------------------------- 1 | python-2.7.8 2 | -------------------------------------------------------------------------------- /docs/customising.rst: -------------------------------------------------------------------------------- 1 | .. _customising: 2 | 3 | Customising Wazimap 4 | =================== 5 | 6 | Overriding Templates 7 | -------------------- 8 | 9 | The easiest way to change page layouts and content is to add your own 10 | templates, as you would for any Django application. Most pages have ``blocks`` 11 | which make it simple to override just specific parts of a page. 12 | 13 | 1. Find the existing template that you want to change by looking in the ``wazimap/templates`` directory of the wazimap package. You can also `browse the repo `_. 14 | 2. Create an empty file with the same name inside your project's ``templates`` directory. 15 | 3. Use ``{% extends %}`` to tell Django that your template extends the original Wazimap template 16 | 4. Override the page's blocks to make your changes. 17 | 18 | In the next section we provide an example. 19 | 20 | Changing the homepage 21 | --------------------- 22 | 23 | Let's say you'd like to change the homepage. You're happy with the header and the footer, but would like to change some of the text in between. 24 | 25 | The file you want to override is ``templates/homepage.html``, you can `see what it looks like in the repo `_. You want to change the text in the ``homepage_detail`` block. 26 | 27 | Create a new file in your project called ``templates/homepage.html`` that extends the existing template and provides 28 | your new content for the ``homepage_detail`` block:: 29 | 30 | {% extends 'homepage.html' %} 31 | 32 | {% block homepage_detail %} 33 | your new content! 34 | {% endblock %} 35 | 36 | If you reload your site you'll see the homepage has your new content. Django uses this template instead of Wazimap's version, 37 | relying on Wazimap for the blocks you don't override. 38 | 39 | .. seealso:: There's more information on changing profile page template in :ref:`profiles`. 40 | 41 | Changing CSS theme 42 | ------------------ 43 | 44 | In addition to providing your own CSS files by overriding the templates, you can also override the 45 | SCSS variables that control the Wazimap colour scheme (theme). This is the fastest and easiest 46 | way to match Wazimap's colours to your brand. 47 | 48 | .. note:: If you customise the CSS using this method, you MUST call ``python manage.py compilescss`` before running ``collectstatic`` when you deploy your application. 49 | 50 | Create a new file in your project called ``static/css/_custom.scss``. In it, override the `default Wazimap colour variables `. 51 | 52 | These are the most important ones: 53 | 54 | * ``$brand-primary-color``: a bright primary colour 55 | * ``$brand-secondary-color``: a darker, secondary colour. Variations on this colour are used throughout the site. 56 | * ``$brand-secondary-{lightest, lighter, darkest, darker}-color``: lighter and darker shades of the secondary colour. We recommend simply lightening or darkening your ``$brand-secondary-color`` by 10% and 20%. 57 | * ``$general-bg-color`` and ``$general-bg-{lightest, lighter, darkest, darker}-color``: general background colours and lighter and darker shades. We recommend simply lightening or darkening your ``$general-bg-color`` by 10% and 20%. 58 | -------------------------------------------------------------------------------- /docs/deploying.rst: -------------------------------------------------------------------------------- 1 | .. _deploying: 2 | 3 | Deploying 4 | ========= 5 | 6 | Wazimap can be deployed like any Django application. It's particularly easy to deploy on `Heroku `_ or a 7 | Heroku-like environment such as `Dokku `_. You 8 | can use the Wazimap WSGI ``application`` variable from ``wazimap.wsgi``. 9 | 10 | .. important:: 11 | 12 | Always ensure that you set the ``DJANGO_SETTINGS_MODULE`` environment variable to the module path of your ``settings.py`` file. 13 | 14 | Deploying on Heroku or Dokku 15 | ---------------------------- 16 | 17 | .. seealso:: 18 | 19 | You can find example of the files necessary to deploy on Dokku or Heroku 20 | in the `Wazimap deploy `_ directory. 21 | 22 | To deploy on Heroku on Dokku, create your application in the normal way. Then set these config variables: 23 | 24 | * ``DATABASE_URL=`` 25 | * ``DJANGO_SECRET_KEY=`` 26 | * ``DJANGO_DEBUG=False`` 27 | * ``DJANGO_SETTINGS_MODULE=`` 28 | * ``DISABLE_COLLECTSTATIC=1=`` 29 | 30 | You'll need a Procfile, too: :: 31 | 32 | web: gunicorn --worker-class gevent wazimap.wsgi:application -t 120 --log-file - 33 | 34 | GDAL 35 | .... 36 | 37 | Wazimap requires `GDAL `_ to support data downloads in formats like KML. 38 | The easiet way to get these installed on Heroku or Dokku is to use multiple 39 | buildpacks. Create a file ``.buildpacks`` in your project's root directory: :: 40 | 41 | https://github.com/cyberdelia/heroku-geo-buildpack.git 42 | https://github.com/heroku/heroku-buildpack-python.git 43 | 44 | That tells Heroku and Dokku to install GDAL and then continue with the usual Python install. Alternatively, 45 | install GDAL for your platform manually. 46 | 47 | By default, Wazimap doesn't install the libraries to use GDAL because it can be difficult to install. 48 | Tell Wazimap to install everything it needs for GDAL by installing it with ``wazimap[gdal]`` or specify 49 | ``GDAL>=1.11.0,<2.0`` in your requirements.txt. 50 | 51 | Be sure that the platform GDAL and Python GDAL versions match. 52 | 53 | Dependencies 54 | ............ 55 | 56 | Wazimap requires Django 1.9. 57 | 58 | Add Wazimap and Django as dependencies for your project in your ``requirements.txt`` file: :: 59 | 60 | wazimap[gdal] 61 | Django==1.9.2 62 | 63 | Copy `Wazimap's ``app.json``` into your project: :: 64 | 65 | { 66 | "scripts": { 67 | "dokku": { 68 | "predeploy": "python manage.py compilescss && python manage.py collectstatic --noinput && rm -rf /var/tmp/wazimap_cache" 69 | } 70 | } 71 | } 72 | 73 | Finally, just use ``git push heroku`` or ``git push dokku master`` to deploy as you usually would. 74 | 75 | Should you need to do a database migration, you can run ``dokku run python manage.py migrate`` on your server. 76 | 77 | HTTPS and SSL 78 | ------------- 79 | 80 | .. note:: We recommend running your website over HTTPS. 81 | 82 | We recommend that you run your site over HTTPS (SSL). If you don't use HTTPS, then any website 83 | that does use HTTPS **will not** be able to embed a chart from your Wazimap. This is because 84 | websites using HTTPS cannot load content from non-HTTPS sites. 85 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Wazimap 2 | ======= 3 | 4 | Wazimap is a Django application for exploring census and other similar data. It makes it easy to understand a place 5 | through the eyes of the data, and to explore data across a range of places. It is most suited for census data 6 | but can easily be used with other data that is similarly focused on places in a country. 7 | 8 | Check out `Wazimap South Africa `_ and `Wazimap Kenya `_ to 9 | get an idea of what Wazimap is about. 10 | 11 | Wazimap is a fork of the excellent `Censusreporter `_ project which was funded by a 12 | `Knight News Challenge grant `_. 13 | You can also find `Censusreporter on GitHub `_. 14 | 15 | Wazimap builds on Censusreporter and makes it easier to re-use. Wazimap was originally built by 16 | `OpenUp `_ with the support of `Media Monitoring Africa `_. 17 | It is maintained by OpenUp. 18 | 19 | * Wazimap is on GitHub at `github.com/OpenUpSA/wazimap `_. 20 | * Wazimap is on Twitter as `@Wazimap `_. 21 | 22 | Sites using Wazimap 23 | =================== 24 | 25 | * `Wazimap South Africa `_ 26 | * `Nepal Map `_ 27 | * `Counting India `_ 28 | 29 | License and Copyright 30 | ===================== 31 | 32 | Copyright (c) 2014 Census Reporter 33 | 34 | Wazimap is licensed under the MIT License. 35 | 36 | The Wazimap name and branding is Copyright 2013-2017 Media Monitoring Africa (MMA) and may not be used without permission. 37 | 38 | If you use this software, please provide attribution to Census Reporter, Wazimap, Media Monitoring Africa and OpenUp. 39 | We recommend using this text: 40 | 41 | X is built on `Wazimap `_, an open source platform by `OpenUp `_ and `Media Monitoring Africa `_ for making census data more understandable. Wazimap is based on `Census Reporter `_ which was funded by a Knight News Challenge grant. 42 | 43 | 44 | Contents 45 | ======== 46 | 47 | .. toctree:: 48 | :maxdepth: 2 49 | 50 | started 51 | config 52 | geos 53 | data 54 | profiles 55 | customising 56 | deploying 57 | upgrading 58 | version_history 59 | 60 | 61 | Indices and tables 62 | ================== 63 | 64 | * :ref:`search` 65 | -------------------------------------------------------------------------------- /docs/started.rst: -------------------------------------------------------------------------------- 1 | Getting Started 2 | =============== 3 | 4 | Wazimap is a Django application that you'll need to configure correctly before you can use it. 5 | You're going to need to import place metadata, census data and write some code to tell Wazimap 6 | what census data you want to show. 7 | 8 | Wazimap is a bit different to most Django applications. It comes with all the URLs and most 9 | of the settings that you're going to need, so you need to tell Django to load the details 10 | from the Wazimap application, rather than your Django application like normal. 11 | 12 | .. note:: 13 | 14 | You'll need to choose a short name for your application. In these examples, 15 | we'll be using ``wazimap_ex`` for Wazimap Example. We suggest using 16 | the `two-letter country code `_ of your 17 | country in place of the ``ex``. 18 | 19 | 1. Setup a python virtual environment, we recommend using `virtualenv `_. 20 | 21 | 2. Install Django (version 1.8.0 or newer) and start a new Django project. :: 22 | 23 | pip install 'django<1.10' 24 | django-admin startproject wazimap_ex 25 | cd wazimap_ex 26 | 27 | 3. This will have created some files that are unnecessary because Wazimap already provides them. 28 | You can safely delete them. :: 29 | 30 | rm wazimap_ex/urls.py wazimap_ex/wsgi.py 31 | 32 | 4. Wazimap needs GDAL installed to allow users to download data in KML, GeoJSON, Excel, etc. 33 | GDAL can be complicated to install. For development, we recommend you follow the 34 | `Django instructions `_. 35 | 36 | 5. Install Wazimap and its dependencies :: 37 | 38 | pip install wazimap 39 | 40 | 6. At this point you have the absolute basics in place. We're now going to configure 41 | your Django application to load its settings from Wazimap. 42 | 43 | Change your ``settings.py`` file to import most settings from Wazimap, and then 44 | override the settings that you want to change. Replace the contents of 45 | your file with the following: :: 46 | 47 | 48 | 49 | # pull in the default wazimap settings 50 | from wazimap.settings import * # noqa 51 | 52 | # install this app before Wazimap 53 | INSTALLED_APPS = ['wazimap_ex'] + INSTALLED_APPS 54 | 55 | # Localise this instance of Wazimap 56 | WAZIMAP['name'] = 'Wazimap Example' 57 | # NB: this must be https if your site supports HTTPS. 58 | WAZIMAP['url'] = 'http://wazimap.example.com' 59 | WAZIMAP['country_code'] = 'EX' 60 | 61 | .. seealso:: 62 | 63 | See the :ref:`config` section for more details on configuration options. 64 | 65 | 7. You'll need a running PostgreSQL server. The default database settings are: 66 | 67 | * Username: wazimap 68 | * Password: wazimap 69 | * Database: wazimap 70 | * Hostname: localhost 71 | 72 | So create the user and database locally: :: 73 | 74 | createuser -P wazimap 75 | createdb -O wazimap wazimap 76 | 77 | Or alternatively from inside psql: :: 78 | 79 | CREATE USER wazimap WITH PASSWORD 'wazimap'; 80 | CREATE DATABASE wazimap OWNER wazimap; 81 | 82 | .. note:: 83 | 84 | If you want to use a different database, configure the default database 85 | as you normally would for a Django application. 86 | 87 | 8. Run migrations to setup the database: :: 88 | 89 | python manage.py migrate 90 | 91 | 9. Create a superuser to manage your site: :: 92 | 93 | python manage.py createsuperuser 94 | 95 | 10. You now have a Wazimap application and matching database ready to load 96 | geographies and data into. Those steps are covered in the rest of this 97 | guide. 98 | -------------------------------------------------------------------------------- /docs/upgrading.rst: -------------------------------------------------------------------------------- 1 | Upgrading 2 | ========= 3 | 4 | Upgrading from 1.x to 2.x 5 | ------------------------- 6 | 7 | As of version 2.0.0, Wazimap supports multiple releases of a dataset. This means that you can, for example, have two releases of a national census in Wazimap at the same time. You will need to make some changes to how you declare your tables and build profile pages. 8 | 9 | * Data table management is done through the Django admin interface. 10 | * Datasets are a concrete concept, they bring together a collection of related data tables. A dataset can have multiple releases (ie. updates) over the years. A good example of a dataset is a national census, which could be released every few years. 11 | * Data tables must have at least one release, which is a set of data for that table, released at a particular point in time. 12 | 13 | Run migrations 14 | .............. 15 | 16 | Before anything else, run migrations:: 17 | 18 | python manage.py migrate 19 | 20 | Upgrade existing tables 21 | ....................... 22 | 23 | The old way of declaring tables in ``tables.py`` is no longer used. Before you remove those declarations, use them to upgrade your table descriptions into the new format. Run:: 24 | 25 | python manage.py upgradetables 26 | 27 | This will upgrade the tables into the database. 28 | 29 | .. note:: 30 | 31 | Do not delete the data tables from the database, they are still needed and will be used by the upgraded tables. 32 | 33 | Visit the admin area 34 | .................... 35 | 36 | Create a superuser so you can login to the admin area:: 37 | 38 | python manage.py createsuperuser 39 | 40 | Visit http://localhost:8000/admin to log in and check that your tables are correct. 41 | 42 | Remove the old table descriptions 43 | ................................. 44 | 45 | If you're happy that your tables have been imported correctly, you can remove them from your ``tables.py`` file. 46 | -------------------------------------------------------------------------------- /docs/version_history.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CHANGES.rst 2 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "wazimap.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django~=2.2.6 2 | SQLAlchemy<1.4 3 | boto~=2.49.0 4 | dj-database-url~=2.1.0 5 | django-cors-headers~=3.1.0 6 | django-compressor~=2.2 7 | django-pipeline~=1.6.14 8 | django-sass-processor~=0.7.3 9 | ecdsa~=0.11 10 | libsass~=0.17.0 11 | paramiko~=1.18.5 12 | psycopg2~=2.8.5 13 | requests~=2.21 14 | six~=1.12.0 15 | unicodecsv~=0.14.1 16 | whitenoise~=4.1.2 17 | 18 | gdal==3.2.2 19 | Shapely~=1.5.13 -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = E501 3 | exclude = tests/* 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from codecs import open 3 | from os import path 4 | 5 | here = path.abspath(path.dirname(__file__)) 6 | 7 | # Get the long description from the relevant file 8 | with open(path.join(here, "README.rst"), encoding="utf-8") as f: 9 | long_description = f.read() 10 | 11 | with open(path.join(here, "VERSION")) as f: 12 | version = f.read().strip() 13 | setup( 14 | name="wazimap", 15 | # Versions should comply with PEP440. For a discussion on single-sourcing 16 | # the version across setup.py and the project code, see 17 | # https://packaging.python.org/en/latest/single_source_version.html 18 | version=version, 19 | description="A Django application for exploring census and other place-specific data", 20 | long_description=long_description, 21 | # The project's main homepage. 22 | url="https://github.com/OpenUpSA/wazimap", 23 | # Author details 24 | author="OpenUp", 25 | author_email="webapps@openup.org.za", 26 | # Choose your license 27 | license="MIT", 28 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 29 | classifiers=[ 30 | # How mature is this project? Common values are 31 | # 3 - Alpha 32 | # 4 - Beta 33 | # 5 - Production/Stable 34 | "Development Status :: 4 - Beta", 35 | # Indicate who your project is intended for 36 | "Intended Audience :: Developers", 37 | # Pick your license as you wish (should match "license" above) 38 | "License :: OSI Approved :: MIT License", 39 | # Specify the Python versions you support here. In particular, ensure 40 | # that you indicate whether you support Python 2, Python 3 or both. 41 | "Programming Language :: Python", 42 | "Programming Language :: Python :: 3", 43 | "Programming Language :: Python :: 3.6", 44 | ], 45 | # You can just specify the packages manually here if your project is 46 | # simple. Or you can use find_packages(). 47 | packages=find_packages(), 48 | include_package_data=True, 49 | # List run-time dependencies here. These will be installed by pip when 50 | # your project is installed. For an analysis of "install_requires" vs pip's 51 | # requirements files see: 52 | # https://packaging.python.org/en/latest/requirements.html 53 | install_requires=[ 54 | 'Django~=2.2.6', 55 | 'SQLAlchemy~=1.2.17', 56 | 'boto~=2.49.0', 57 | 'dj-database-url~=0.5.0', 58 | 'django-cors-headers~=3.0.2', 59 | 'django-compressor~=2.2', 60 | 'django-pipeline~=1.6.14', 61 | 'django-sass-processor~=0.7.3', 62 | 'ecdsa~=0.11', 63 | 'libsass~=0.17.0', 64 | 'paramiko~=1.18.5', 65 | 'psycopg2~=2.8.5', 66 | 'requests~=2.21', 67 | 'unicodecsv~=0.14.1', 68 | 'whitenoise~=4.1.2', 69 | ], 70 | 71 | setup_requires=[ 72 | "setuptools_git ~= 1.0", 73 | ], 74 | 75 | # List additional groups of dependencies here (e.g. development 76 | # dependencies). You can install these using the following syntax, 77 | # for example: 78 | # $ pip install -e .[dev,test] 79 | extras_require={ 80 | 'dev': ['nose', 'flake8'], 81 | 'test': ['nose', 'flake8'], 82 | 'gdal': ['GDAL', 'Shapely~=1.5.13'], 83 | }, 84 | ) 85 | -------------------------------------------------------------------------------- /wazimap/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/wazimap/__init__.py -------------------------------------------------------------------------------- /wazimap/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Dataset, Release, SimpleTable, FieldTable, DBTable, FieldTableRelease, SimpleTableRelease 4 | 5 | 6 | admin.site.register(DBTable) 7 | 8 | 9 | class FieldTableReleaseInline(admin.StackedInline): 10 | model = FieldTableRelease 11 | fields = ('release', 'db_table') 12 | extra = 0 13 | 14 | 15 | def field_list(model): 16 | return ', '.join(model.fields) 17 | field_list.short_description = 'Fields' 18 | 19 | 20 | @admin.register(FieldTable) 21 | class FieldTableAdmin(admin.ModelAdmin): 22 | list_display = ('name', field_list, 'dataset', 'universe') 23 | list_filter = ('dataset', 'universe') 24 | search_fields = ['name'] 25 | inlines = (FieldTableReleaseInline, ) 26 | fieldsets = ( 27 | (None, { 28 | 'fields': ('dataset', 'universe', 'fields', 'description') 29 | }), 30 | ('Advanced', { 31 | 'fields': ('name', 'stat_type', 'value_type', 'denominator_key', 'has_total'), 32 | 'classes': ('collapse', ), 33 | }) 34 | ) 35 | 36 | 37 | class SimpleTableReleaseInline(admin.StackedInline): 38 | model = SimpleTableRelease 39 | fields = ('release', 'db_table') 40 | extra = 0 41 | 42 | 43 | @admin.register(SimpleTable) 44 | class SimpleTableAdmin(admin.ModelAdmin): 45 | list_display = ('name', 'dataset', 'universe') 46 | list_filter = ('dataset', 'universe') 47 | inlines = (SimpleTableReleaseInline, ) 48 | fieldsets = ( 49 | (None, { 50 | 'fields': ('name', 'dataset', 'universe', 'description'), 51 | }), 52 | ('Advanced', { 53 | 'fields': ('stat_type', 'total_column'), 54 | 'classes': ('collapse', ), 55 | }) 56 | ) 57 | 58 | 59 | class ReleaseInline(admin.StackedInline): 60 | model = Release 61 | fields = ('name', 'year') 62 | extra = 0 63 | min_num = 1 64 | 65 | 66 | @admin.register(Dataset) 67 | class DatasetAdmin(admin.ModelAdmin): 68 | inlines = (ReleaseInline, ) 69 | 70 | 71 | @admin.register(Release) 72 | class ReleaseAdmin(admin.ModelAdmin): 73 | list_display = ('name', 'year') 74 | list_filter = ('year',) 75 | -------------------------------------------------------------------------------- /wazimap/apps.py: -------------------------------------------------------------------------------- 1 | from importlib import import_module 2 | import logging 3 | import os.path 4 | 5 | from django.apps import AppConfig, apps as django_apps 6 | 7 | 8 | class WazimapConfig(AppConfig): 9 | name = 'wazimap' 10 | verbose_name = "Wazimap" 11 | log = logging.getLogger(__name__) 12 | 13 | def ready(self): 14 | self.check_gdal() 15 | self.load_tables() 16 | 17 | def check_gdal(self): 18 | # GDAL is difficult to install, so we make it an optional dependency. 19 | # Here, we check if it's installed and warn if it isn't. 20 | from wazimap.geo import HAS_GDAL, gdal_missing 21 | if HAS_GDAL: 22 | import osgeo.gdal 23 | self.log.info("Wazimap found GDAL version %s" % (osgeo.gdal.VersionInfo("RELEASE_NAME"))) 24 | else: 25 | gdal_missing() 26 | 27 | def load_tables(self): 28 | """ Search installed apps for tables.py and import it. 29 | This gives child Wazimap applications a chance to declare 30 | their tables. 31 | """ 32 | for app in django_apps.get_app_configs(): 33 | if (os.path.exists(os.path.join(app.path, 'tables.py')) or 34 | os.path.exists(os.path.join(app.path, 'tables/__init__.py'))): 35 | import_module(app.name + '.tables') 36 | -------------------------------------------------------------------------------- /wazimap/context_processors.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | 4 | def wazimap_settings(request): 5 | from wazimap.geo import geo_data 6 | 7 | return { 8 | 'WAZIMAP': settings.WAZIMAP, 9 | 'geo_data': geo_data 10 | } 11 | -------------------------------------------------------------------------------- /wazimap/data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/wazimap/data/__init__.py -------------------------------------------------------------------------------- /wazimap/data/base.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.ext.declarative import declarative_base, declared_attr 2 | from wazimap.data.utils import _metadata 3 | 4 | 5 | class Base(object): 6 | @declared_attr 7 | def __tablename__(cls): 8 | return cls.__name__.lower() 9 | 10 | def __repr__(self): 11 | return '%s(%s)' % (self.__class__.__name__, 12 | ', '.join(['%s="%s"' % (c.name, getattr(self, c.name)) 13 | for c in self.__table__.columns])) 14 | 15 | 16 | Base = declarative_base(cls=Base, metadata=_metadata) -------------------------------------------------------------------------------- /wazimap/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/wazimap/management/__init__.py -------------------------------------------------------------------------------- /wazimap/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/wazimap/management/commands/__init__.py -------------------------------------------------------------------------------- /wazimap/management/commands/upgradetables.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | 3 | from wazimap.models import FieldTable, SimpleTable, Dataset, Release, FieldTableRelease, SimpleTableRelease, DBTable 4 | 5 | 6 | class Command(BaseCommand): 7 | help = "Upgrades old-style Wazimap FieldTables to the version 2.0" 8 | 9 | def handle(self, *args, **options): 10 | from wazimap.data.tables import FIELD_TABLES, DATA_TABLES 11 | 12 | for table in list(FIELD_TABLES.values()): 13 | self.upgrade_field_table(table) 14 | 15 | for table in list(DATA_TABLES.values()): 16 | if not hasattr(table, 'fields'): 17 | self.upgrade_simple_table(table) 18 | 19 | def upgrade_field_table(self, table): 20 | # does it need upgrading? 21 | existing = FieldTable.objects.filter( 22 | fields=table.fields, 23 | universe=table.universe, 24 | dataset__name=table.dataset_name, 25 | ).first() 26 | 27 | if existing: 28 | self.stdout.write(self.style.WARNING("Ignoring already upgraded table '%s' that matches '%s'" % ( 29 | table.id, existing))) 30 | else: 31 | # upgrade it 32 | self.stdout.write(self.style.SUCCESS("Upgrading table '%s'..." % table.id)) 33 | 34 | dataset, created = Dataset.objects.get_or_create(name=table.dataset_name) 35 | if created: 36 | self.stdout.write(self.style.SUCCESS("Created dataset %s" % dataset.name)) 37 | 38 | new_table = FieldTable( 39 | name=table.id, 40 | fields=table.fields, 41 | universe=table.universe, 42 | denominator_key=table.denominator_key, 43 | has_total=table.has_total, 44 | value_type=table.value_type.__name__, 45 | stat_type=table.stat_type, 46 | description=table.description, 47 | dataset=dataset, 48 | ) 49 | new_table.clean() 50 | new_table.save() 51 | 52 | release, created = Release.objects.get_or_create( 53 | dataset=dataset, 54 | year=table.year, 55 | name=dataset.name, 56 | ) 57 | if created: 58 | self.stdout.write(self.style.SUCCESS("Created release %s for dataset %s" % (release.year, dataset.name))) 59 | 60 | db_table, created = DBTable.objects.get_or_create(name=table.db_table) 61 | if created: 62 | self.stdout.write(self.style.SUCCESS("Created db_table entry for %s" % db_table.name)) 63 | 64 | ft_release = FieldTableRelease.objects.create(data_table=new_table, db_table=db_table, release=release) 65 | self.stdout.write(self.style.SUCCESS("Created field table release %s" % ft_release)) 66 | 67 | self.stdout.write(self.style.SUCCESS("Finished upgrading table '%s'" % table.id)) 68 | 69 | def upgrade_simple_table(self, table): 70 | # does it need upgrading? 71 | existing = SimpleTable.objects.filter(name__iexact=table.id).first() 72 | 73 | if existing: 74 | self.stdout.write(self.style.WARNING("Ignoring already upgraded table '%s' that matches '%s'" % ( 75 | table.id, existing))) 76 | else: 77 | # upgrade it 78 | self.stdout.write(self.style.SUCCESS("Upgrading table '%s'..." % table.id)) 79 | 80 | dataset, created = Dataset.objects.get_or_create(name=table.dataset_name) 81 | if created: 82 | self.stdout.write(self.style.SUCCESS("Created dataset %s" % dataset.name)) 83 | 84 | new_table = SimpleTable( 85 | name=table.id.lower(), 86 | universe=table.universe, 87 | stat_type=table.stat_type, 88 | description=table.description, 89 | dataset=dataset, 90 | ) 91 | new_table.clean() 92 | new_table.save() 93 | 94 | release, created = Release.objects.get_or_create( 95 | dataset=dataset, 96 | year=table.year, 97 | name=dataset.name, 98 | ) 99 | if created: 100 | self.stdout.write(self.style.SUCCESS("Created release %s for dataset %s" % (release.year, dataset.name))) 101 | 102 | db_table, created = DBTable.objects.get_or_create(name=table.db_table) 103 | if created: 104 | self.stdout.write(self.style.SUCCESS("Created db_table entry for %s" % db_table.name)) 105 | 106 | ft_release = SimpleTableRelease.objects.create(data_table=new_table, db_table=db_table, release=release) 107 | self.stdout.write(self.style.SUCCESS("Created simple table release %s" % ft_release)) 108 | 109 | self.stdout.write(self.style.SUCCESS("Finished upgrading table '%s'" % table.id)) 110 | -------------------------------------------------------------------------------- /wazimap/middleware.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.http.response import HttpResponsePermanentRedirect 3 | 4 | 5 | class RedirectMiddleware(object): 6 | def __init__(self, get_response): 7 | self.get_response = get_response 8 | # One-time configuration and initialization. 9 | 10 | def __call__(self, request): 11 | # Code to be executed for each request before 12 | # the view (and later middleware) are called. 13 | 14 | host = request.get_host() 15 | 16 | # redirect www.foo.com to foo.com? 17 | if settings.STRIP_WWW and host.startswith("www."): 18 | redirect_url = '%s://%s%s' % (request.scheme, host[4:], request.get_full_path()) 19 | return HttpResponsePermanentRedirect(redirect_url) 20 | 21 | response = self.get_response(request) 22 | 23 | # Code to be executed for each request/response after 24 | # the view is called. 25 | 26 | return response 27 | -------------------------------------------------------------------------------- /wazimap/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | from django.db import models, migrations 5 | import wazimap.models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Geography', 16 | fields=[ 17 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 18 | ('geo_level', models.CharField(max_length=15)), 19 | ('geo_code', models.CharField(max_length=10)), 20 | ('name', models.CharField(max_length=20, db_index=True)), 21 | ('year', models.IntegerField(null=True, db_index=True)), 22 | ('osm_area_id', models.IntegerField(null=True, db_index=True)), 23 | ('square_kms', models.FloatField(null=True)), 24 | ('parent_level', models.CharField(max_length=15, null=True)), 25 | ('parent_code', models.CharField(max_length=10, null=True)), 26 | ], 27 | bases=(models.Model, wazimap.models.GeoMixin), 28 | ), 29 | migrations.AlterUniqueTogether( 30 | name='geography', 31 | unique_together=set([('geo_level', 'geo_code')]), 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /wazimap/migrations/0002_geography_long_name.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.2 on 2016-03-02 11:35 3 | 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('wazimap', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='geography', 17 | name='long_name', 18 | field=models.CharField(db_index=True, max_length=100, null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /wazimap/migrations/0003_remove_geography_osm_area_id.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.2 on 2016-03-02 11:37 3 | 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('wazimap', '0002_geography_long_name'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='geography', 17 | name='osm_area_id', 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /wazimap/migrations/0004_auto_20160302_1645.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.2 on 2016-03-02 14:45 3 | 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('wazimap', '0003_remove_geography_osm_area_id'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='geography', 17 | name='name', 18 | field=models.CharField(db_index=True, max_length=100), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /wazimap/migrations/0005_unique-geo-add-year.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.13 on 2017-04-20 11:42 3 | 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('wazimap', '0004_auto_20160302_1645'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterUniqueTogether( 16 | name='geography', 17 | unique_together=set([('geo_level', 'geo_code', 'year')]), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /wazimap/migrations/0006_geo-year-to-geo-version.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.13 on 2017-04-21 06:36 3 | 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('wazimap', '0005_unique-geo-add-year'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='geography', 17 | name='version', 18 | field=models.CharField(db_index=True, max_length=100, null=True), 19 | ), 20 | migrations.RunSQL(""" 21 | update wazimap_geography set version = year::text 22 | """), 23 | migrations.AlterUniqueTogether( 24 | name='geography', 25 | unique_together=set([('geo_level', 'geo_code', 'version')]), 26 | ), 27 | migrations.RemoveField( 28 | model_name='geography', 29 | name='year', 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /wazimap/migrations/0007_fieldtable_geoversion.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.2 on 2017-04-21 14:44 3 | 4 | 5 | from django.db import migrations 6 | 7 | 8 | from sqlalchemy import inspect 9 | from sqlalchemy.schema import AddConstraint 10 | from wazimap.data.utils import get_session 11 | from wazimap.data.tables import DATA_TABLES 12 | 13 | 14 | def forwards(apps, schema_editor): 15 | """ 16 | Ensure all data tables have the new geo_version column, with a default of '' 17 | """ 18 | session = get_session() 19 | inspector = inspect(session.bind) 20 | 21 | try: 22 | for data_table in list(DATA_TABLES.values()): 23 | db_model = data_table.model 24 | table = db_model.__table__ 25 | 26 | cols = [c['name'] for c in inspector.get_columns(table.name)] 27 | if 'geo_version' in cols: 28 | continue 29 | 30 | # remove the old primary key constraint, if any 31 | pk = inspector.get_pk_constraint(table.name)['name'] 32 | if pk: 33 | session.execute("ALTER TABLE %s DROP CONSTRAINT %s" % (table.name, pk)) 34 | 35 | # add the new column 36 | session.execute("ALTER TABLE %s ADD COLUMN geo_version VARCHAR(100) DEFAULT ''" % table.name) 37 | 38 | # add the correct new constraint 39 | session.execute(AddConstraint(table.primary_key)) 40 | 41 | session.commit() 42 | finally: 43 | session.close() 44 | 45 | 46 | def reverse(apps, schema_editor): 47 | """ 48 | Drop the new geo_version column from all data tables 49 | """ 50 | session = get_session() 51 | inspector = inspect(session.bind) 52 | 53 | try: 54 | for data_table in list(DATA_TABLES.values()): 55 | db_model = data_table.model 56 | table = db_model.__table__ 57 | 58 | # remove the primary key constraint, if any 59 | pk = inspector.get_pk_constraint(table.name)['name'] 60 | if pk: 61 | session.execute("ALTER TABLE %s DROP CONSTRAINT %s" % (table.name, pk)) 62 | 63 | # drop the new column 64 | session.execute("ALTER TABLE %s DROP COLUMN geo_version" % table.name) 65 | 66 | # add the old pk constraint 67 | pk = table.primary_key 68 | pk.columns.remove(table.c.geo_version) 69 | session.execute(AddConstraint(pk)) 70 | 71 | session.commit() 72 | finally: 73 | session.close() 74 | 75 | 76 | class Migration(migrations.Migration): 77 | 78 | dependencies = [ 79 | ('wazimap', '0006_geo-year-to-geo-version'), 80 | ] 81 | 82 | operations = [ 83 | # We no longer need this migration in 2.x 84 | # Be sure to upgrade to 1.x before 2.x if you run into issues. 85 | ] 86 | -------------------------------------------------------------------------------- /wazimap/migrations/0008_auto_20170424_1209.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.2 on 2017-04-24 10:09 3 | 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('wazimap', '0007_fieldtable_geoversion'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='geography', 17 | name='version', 18 | field=models.CharField(db_index=True, default=b'', max_length=100), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /wazimap/migrations/0010_null-total-column.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.2 on 2017-10-23 14:06 3 | 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('wazimap', '0009_datatables'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name='fieldtable', 17 | options={'ordering': ('name',)}, 18 | ), 19 | migrations.AlterModelOptions( 20 | name='simpletable', 21 | options={'ordering': ('name',)}, 22 | ), 23 | migrations.AlterField( 24 | model_name='simpletable', 25 | name='total_column', 26 | field=models.CharField(blank=True, help_text=b"Name of the column that contains the total value of all the columns in the row. Wazimap usse this to express column values as a percentage. If this is not set, the table doesn't have the concept of a total and only absolute values (not percentages) will be displayed.", max_length=50, null=True), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /wazimap/migrations/0011_release_citation.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.2 on 2017-12-06 09:08 3 | 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('wazimap', '0010_null-total-column'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='release', 17 | name='citation', 18 | field=models.TextField(blank=True, help_text=b'What the user should use when citing this data.', max_length=250, null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /wazimap/migrations/0012_auto_20190111_1342.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.13 on 2019-01-11 11:42 3 | 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('wazimap', '0011_release_citation'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='release', 17 | name='citation', 18 | field=models.TextField(blank=True, help_text=b'What the user should use when citing this data.', null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /wazimap/migrations/0014_auto_20191021_1216.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.6 on 2019-10-21 12:16 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('wazimap', '0013_port_to_python3'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='geography', 15 | name='geo_level', 16 | field=models.CharField(max_length=25), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /wazimap/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/wazimap/migrations/__init__.py -------------------------------------------------------------------------------- /wazimap/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .geo import GeographyBase, GeoMixin, Geography # noqa 2 | from .data import FieldTable, SimpleTable, DBTable, Dataset, Release, FieldTableRelease, SimpleTableRelease # noqa 3 | -------------------------------------------------------------------------------- /wazimap/profiles.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | from itertools import repeat 3 | 4 | from census.profile import find_dicts_with_key 5 | from census.utils import get_ratio 6 | 7 | from wazimap.geo import geo_data 8 | 9 | 10 | def enhance_api_data(api_data): 11 | dict_list = find_dicts_with_key(api_data, 'values') 12 | 13 | for d in dict_list: 14 | raw = {} 15 | enhanced = {} 16 | geo_value = d['values']['this'] 17 | num_comparatives = 2 18 | 19 | # create our containers for transformation 20 | for obj in ['values', 'error', 'numerators', 'numerator_errors']: 21 | if obj not in d: 22 | raw[obj] = dict(list(zip(geo_data.comparative_levels, repeat(0)))) 23 | else: 24 | raw[obj] = d[obj] 25 | enhanced[obj] = OrderedDict() 26 | enhanced['index'] = OrderedDict() 27 | enhanced['error_ratio'] = OrderedDict() 28 | comparative_sumlevs = [] 29 | 30 | # enhance 31 | for sumlevel in geo_data.comparative_levels: 32 | # add the index value for comparatives 33 | if sumlevel in raw['values']: 34 | enhanced['values'][sumlevel] = raw['values'][sumlevel] 35 | enhanced['index'][sumlevel] = get_ratio(geo_value, raw['values'][sumlevel]) 36 | 37 | # add to our list of comparatives for the template to use 38 | if sumlevel != 'this': 39 | comparative_sumlevs.append(sumlevel) 40 | 41 | # add the moe ratios 42 | if (sumlevel in raw['values']) and (sumlevel in raw['error']): 43 | enhanced['error'][sumlevel] = raw['error'][sumlevel] 44 | enhanced['error_ratio'][sumlevel] = get_ratio(raw['error'][sumlevel], raw['values'][sumlevel], 3) 45 | 46 | # add the numerators and numerator_errors 47 | if sumlevel in raw['numerators']: 48 | enhanced['numerators'][sumlevel] = raw['numerators'][sumlevel] 49 | 50 | if (sumlevel in raw['numerators']) and (sumlevel in raw['numerator_errors']): 51 | enhanced['numerator_errors'][sumlevel] = raw['numerator_errors'][sumlevel] 52 | 53 | if len(enhanced['values']) >= (num_comparatives + 1): 54 | break 55 | 56 | # replace data with enhanced version 57 | for obj in ['values', 'index', 'error', 'error_ratio', 'numerators', 'numerator_errors']: 58 | d[obj] = enhanced[obj] 59 | 60 | api_data['geography']['comparatives'] = comparative_sumlevs 61 | 62 | return api_data 63 | -------------------------------------------------------------------------------- /wazimap/static/css/_custom.scss: -------------------------------------------------------------------------------- 1 | // To customise the CSS, include a copy of this file in your Django application's 2 | // static folder and name it 'static/css/_custom.scss', then declare the variables 3 | // you'd like to override. 4 | -------------------------------------------------------------------------------- /wazimap/static/css/_variables.scss: -------------------------------------------------------------------------------- 1 | // --- 2 | // colours 3 | // primary brand color 4 | $orange: #f55b2c !default; 5 | 6 | // secondary color and shades 7 | $teal-lightest: #a1d0c6 !default; 8 | $teal-lighter: #02aeab !default; 9 | $teal: #1a9d95 !default; 10 | $teal-darker: #027b7a !default; 11 | $teal-darkest: #014948 !default; 12 | 13 | $tan-lighter: #f7f8f3 !default; 14 | $tan: #eff1e9 !default; 15 | $tan-darker: #e0e4d4 !default; 16 | $tan-darkest: #cad0b5 !default; 17 | 18 | $grey: #aaa !default; 19 | $grey-darker: #777 !default; 20 | $grey-darkest: #222 !default; 21 | 22 | $white: #fff !default; 23 | 24 | 25 | // these two colours should complement each other: 26 | // - primary should be bright 27 | // - secondary should be darker and contrast with primary 28 | $brand-primary-color: $orange !default; 29 | $brand-primary-fg-color: $white !default; 30 | $brand-primary-bg-color: $brand-primary-color !default; 31 | 32 | $brand-secondary-lightest-color: $teal-lightest !default; 33 | $brand-secondary-lighter-color: $teal-lighter !default; 34 | $brand-secondary-color: $teal !default; 35 | $brand-secondary-darker-color: $teal-darker !default; 36 | $brand-secondary-darkest-color: $teal-darkest !default; 37 | 38 | $general-bg-lighter-color: $tan-lighter !default; 39 | $general-bg-color: $tan !default; 40 | $general-bg-darker-color: $tan-darker !default; 41 | $general-bg-darkest-color: $tan-darkest !default; 42 | 43 | $background-bg-color: $general-bg-lighter-color !default; 44 | 45 | $header-bg-color: $brand-secondary-darkest-color !default; 46 | $header-fg-color: $white !default; 47 | $footer-bg-color: $general-bg-darker-color !default; 48 | 49 | $border-color: $general-bg-darkest-color !default; 50 | $border-lighter-color: $general-bg-darker-color !default; // often used at the top of elements with $border-color at the bottom to create a shadow effect 51 | 52 | $link-color: $brand-secondary-darkest-color !default; 53 | $link-active-color: $brand-secondary-lighter-color !default; 54 | 55 | $btn-active-bg-color: $brand-primary-bg-color !default; 56 | $btn-active-fg-color: $brand-primary-fg-color !default; 57 | $btn-muted-bg-color: lighten($btn-active-bg-color, 20%) !default; 58 | $btn-muted-fg-color: $btn-active-fg-color !default; 59 | 60 | $text-highlight-color: $brand-secondary-lighter-color !default; 61 | $text-muted-color: $grey !default; 62 | // --- 63 | 64 | // --- typography 65 | $font-family: "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif !default; 66 | $font-family-secondary: "Merriweather", Georgia, serif !default; 67 | $font-family-monospace: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal !default; 68 | $font-size-base: 14px !default; 69 | -------------------------------------------------------------------------------- /wazimap/static/css/embed.scss: -------------------------------------------------------------------------------- 1 | /* import font before calling any other CSS rules. */ 2 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700); 3 | 4 | // settings 5 | @import "css/custom"; 6 | @import "css/variables"; 7 | 8 | // charts 9 | @import "css/charts"; 10 | -------------------------------------------------------------------------------- /wazimap/static/css/vendor/_normalize.min.scss: -------------------------------------------------------------------------------- 1 | /*! normalize.css v1.1.0 | MIT License | git.io/normalize */ 2 | article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}html,button,input,select,textarea{font-family:sans-serif}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}h2{font-size:1.5em;margin:.83em 0}h3{font-size:1.17em;margin:1em 0}h4{font-size:1em;margin:1.33em 0}h5{font-size:.83em;margin:1.67em 0}h6{font-size:.67em;margin:2.33em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}blockquote{margin:1em 40px}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}p,pre{margin:1em 0}code,kbd,pre,samp{font-family:monospace,serif;_font-family:'courier new',monospace;font-size:1em}pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}q{quotes:none}q:before,q:after{content:'';content:none}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}dl,menu,ol,ul{margin:1em 0}dd{margin:0 0 0 40px}menu,ol,ul{padding:0 0 0 40px}nav ul,nav ol{list-style:none;list-style-image:none}img{border:0;-ms-interpolation-mode:bicubic}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0;white-space:normal;*margin-left:-7px}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;*overflow:visible}button[disabled],html input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;*height:13px;*width:13px}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0} -------------------------------------------------------------------------------- /wazimap/static/css/wazimap.scss: -------------------------------------------------------------------------------- 1 | // settings 2 | @import "css/custom"; 3 | @import "css/variables"; 4 | 5 | // app 6 | @import "css/charts"; 7 | @import "css/censusreporter"; 8 | @import "css/app"; 9 | -------------------------------------------------------------------------------- /wazimap/static/js/maps_static.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * A class that loads geography boundary information from 3 | * topojson files. 4 | * 5 | * The +geometry_urls+ parameter is a map from geo levels to 6 | * URLs of a topojson or geojson file with geometry information. 7 | * A key of the empty string specifies a default URL to use for levels 8 | * not in the map. 9 | * 10 | * The easiest is to have all boundary information in one file. 11 | */ 12 | function StaticGeometryLoader(geometry_urls) { 13 | var self = this; 14 | 15 | this.geometry_urls = geometry_urls; 16 | this.geometry = {}; 17 | 18 | /** 19 | * Fetches geometry data for a comparison view and calls the +success+ 20 | * callback with an object mapping each geo-id to a GeoJSON object. 21 | */ 22 | this.loadGeometryForComparison = function(comparison, success) { 23 | this.loadGeometryForGeoIds(comparison.dataGeoIDs, comparison.geoVersion, success); 24 | }; 25 | 26 | /** 27 | * Fetches geometry data for a list of geo ids (level-code) 28 | * and calls the +success+ callback with an object mapping 29 | * each geo-id to a GeoJSON object. 30 | */ 31 | this.loadGeometryForGeoIds = function(geo_ids, geo_version, success) { 32 | var by_level = {}; 33 | 34 | _.each(geo_ids, function(geo_id) { 35 | var parts = geo_id.split('-'), 36 | level = parts[0]; 37 | 38 | if (!(level in by_level)) { 39 | by_level[level] = []; 40 | } 41 | by_level[level].push(geo_id); 42 | }); 43 | 44 | // load the levels we need 45 | self.loadLevels(_.keys(by_level), geo_version, function() { 46 | var features = {}; 47 | 48 | _.each(by_level, function(geo_ids, level) { 49 | _.each(geo_ids, function(geo_id) { 50 | features[geo_id] = self.geometry[geo_version][level][geo_id]; 51 | }); 52 | }); 53 | 54 | success(features); 55 | }); 56 | }; 57 | 58 | this.loadGeometryForLevel = function(level, geo_version, success) { 59 | self.loadLevels([level], geo_version, function() { 60 | success({features: _.values(self.geometry[geo_version][level])}); 61 | }); 62 | }; 63 | 64 | /** 65 | * Load the geometry data for +levels+ and then call +success+. 66 | */ 67 | this.loadLevels = function(levels, geo_version, success) { 68 | var counter = levels.length; 69 | 70 | function loaded(level) { 71 | counter--; 72 | if (counter <= 0) { 73 | success(); 74 | } 75 | } 76 | 77 | _.each(levels, function(level) { 78 | if (self.geometry[geo_version] && self.geometry[geo_version][level]) { 79 | // already have it 80 | loaded(level); 81 | } else { 82 | // load it remotely 83 | d3.json(self.geometry_urls[geo_version][level], function(error, json) { 84 | var features; 85 | 86 | if (error) return console.warn(error); 87 | if (json) { 88 | if (!self.geometry[geo_version]) self.geometry[geo_version] = {}; 89 | self.geometry[geo_version][level] = {}; 90 | 91 | if (json.type == 'Topology') { 92 | // topojson -> geojson 93 | features = topojson.feature(json, json.objects[level]); 94 | } 95 | 96 | if ('features' in features) { 97 | features = features.features; 98 | } else { 99 | features = [features]; 100 | } 101 | 102 | // stash them 103 | _.each(features, function(feature) { 104 | self.geometry[geo_version][level][feature.properties.geoid] = feature; 105 | }); 106 | } 107 | 108 | loaded(level); 109 | }); 110 | } 111 | }); 112 | }; 113 | } 114 | 115 | var GeometryLoader = new StaticGeometryLoader(GEOMETRY_URLS); 116 | -------------------------------------------------------------------------------- /wazimap/static/js/widget.geo.select.js: -------------------------------------------------------------------------------- 1 | var textmatchAPI = '/place-search/json/', 2 | geocodingAPI = 'https://maps.googleapis.com/maps/api/geocode/json?address=%QUERY&components=country:' + WAZIMAP_COUNTRY_CODE + '®ion=' + WAZIMAP_COUNTRY_CODE + '&key=' + GOOGLE_GEOCODE_API_KEY, 3 | resultTemplate = Handlebars.compile('

    {{geo_level}}{{full_name}}

    '), 4 | geoSelect = $('#geography-select, #geography-select-home'); 5 | 6 | var textMatchEngine = new Bloodhound({ 7 | datumTokenizer: function(d) { return Bloodhound.tokenizers.whitespace(d.full_name); }, 8 | queryTokenizer: Bloodhound.tokenizers.whitespace, 9 | limit: 20, 10 | remote: { 11 | url: textmatchAPI, 12 | rateLimitWait: 600, 13 | replace: function (url, query) { 14 | return url += '?q=' + query; 15 | }, 16 | filter: function(response) { 17 | return response.results; 18 | } 19 | } 20 | }); 21 | textMatchEngine.initialize(); 22 | 23 | var geocodeAddressEngine = new Bloodhound({ 24 | datumTokenizer: function(d) { return Bloodhound.tokenizers.whitespace(d.full_name); }, 25 | queryTokenizer: Bloodhound.tokenizers.whitespace, 26 | limit: 5, 27 | remote: { 28 | url: geocodingAPI, 29 | rateLimitWait: 600, 30 | filter: function(response) { 31 | if (response.status != 'OK') return []; 32 | 33 | // collect coords 34 | var coords = []; 35 | for (var i = 0; i < response.results.length; i++) { 36 | var result = response.results[i]; 37 | 38 | coords.push({'lat': result.geometry.location.lat, 'lng': result.geometry.location.lng}); 39 | } 40 | 41 | return coords; 42 | } 43 | }, 44 | }); 45 | geocodeAddressEngine.initialize(); 46 | 47 | var geocodeEngine = function(query, cb) { 48 | // first use google to geocode the address, handling caching, 49 | // the translate coordinates into places using our api 50 | geocodeAddressEngine.get(query, function(datums) { 51 | // now lookup places for these coords 52 | var coords = _.map(datums, function(d) { return d.lat + ',' + d.lng; }); 53 | var url = textmatchAPI + '?coords=' + coords.join('&coords='); 54 | $.getJSON(url, function(response) { 55 | cb(response.results); 56 | }); 57 | }); 58 | }; 59 | 60 | function makeGeoSelectWidget(element, selected) { 61 | element.typeahead({ 62 | autoselect: true, 63 | highlight: false, 64 | hint: false, 65 | minLength: 2 66 | }, { 67 | // get textual matches from host 68 | name: 'textmatch', 69 | displayKey: 'full_name', 70 | source: textMatchEngine.ttAdapter(), 71 | limit: 20, 72 | templates: { 73 | suggestion: resultTemplate, 74 | }, 75 | }, { 76 | // get geocoded matches 77 | name: 'geocoded', 78 | displayKey: 'full_name', 79 | source: geocodeEngine, 80 | limit: 20, 81 | templates: { 82 | suggestion: resultTemplate, 83 | }, 84 | }); 85 | 86 | element.on('typeahead:selected', selected || function(event, datum) { 87 | event.stopPropagation(); 88 | window.location = '/profiles/' + datum.full_geoid + '/'; 89 | }); 90 | } 91 | 92 | makeGeoSelectWidget(geoSelect); 93 | makeGeoSelectWidget($('#compare-place-select'), function(event, datum) { 94 | var geoId = [profileData.geography.this.geo_level, profileData.geography.this.geo_code].join('-'); 95 | event.stopPropagation(); 96 | window.location = '/compare/' + geoId + '/vs/' + datum.full_geoid + '/' + '?release=' + RELEASE; 97 | }); 98 | -------------------------------------------------------------------------------- /wazimap/templates/_footer.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 |
    4 | 14 |
    15 |
    16 | 24 |
    25 |
    26 |
      27 | {% block footer_branding %} 28 |
    • 29 | 30 |
    • 31 |
    • 32 | 33 |
    • 34 | {% endblock %} 35 |
    36 |
    37 | -------------------------------------------------------------------------------- /wazimap/templates/data/_base_data.html: -------------------------------------------------------------------------------- 1 | {% extends 'data/_base_data.html' %} 2 | {% load staticfiles %} 3 | 4 | {% block body_javascript_extra %} 5 | 6 | {{ block.super }} 7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /wazimap/templates/data/_blocks/_data_query_builder.html: -------------------------------------------------------------------------------- 1 |
    2 |

    Find data

    3 |

    Use keywords to search {{ WAZIMAP.name }} for data, then choose a place or places to view your data.

    4 | 5 |
    6 | 7 |
    8 |
    9 |
    10 |

    in

    11 |
    12 | 13 |

    Find a place to explore data, or choose a geographic summary level to build a comparison.

    14 |
    15 |
    16 |
    17 |

    in

    18 |
    19 | 20 |

    21 |
    22 |
    23 | -------------------------------------------------------------------------------- /wazimap/templates/data/data_map.html: -------------------------------------------------------------------------------- 1 | {% extends 'data/data_map.html' %}{% load humanize %} 2 | {% load staticfiles %} 3 | 4 | {% block head_css_extra %} 5 | 6 | 9 | 10 | {% endblock %} 11 | 12 | {% block body %} 13 |
    14 |
    15 | {% block data_map_header %} 16 | 17 | {{ WAZIMAP.name }} 18 | 19 | {% endblock %} 20 |
    21 |
    22 |
    23 | {% include 'data/_blocks/_toggle_menu.html' %} 24 | {% include 'data/_blocks/_topic_select_input.html' %} 25 |
    26 | 27 |
    28 |
    29 |
    30 | 31 |
    32 |
    33 | 34 |
    35 | {% endblock %} 36 | 37 | {% block page_specific_javascript %} 38 | {{ block.super }} 39 | 40 | {% endblock %} 41 | -------------------------------------------------------------------------------- /wazimap/templates/embed/iframe.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | {% load sass_tags %} 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {% if WAZIMAP.ga_tracking_id %} 11 | 12 | 13 | 20 | {% endif %} 21 | 22 | 51 | 52 | 58 | 59 | 60 |
    61 | 62 | 63 | 64 | 65 | 68 | {% include 'settings_js.html' %} 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /wazimap/templates/help.html: -------------------------------------------------------------------------------- 1 | {% extends '_base.html' %}{% load partition staticfiles %} 2 | 3 | {% block head_title %}Help - {{ block.super }}{% endblock %} 4 | 5 | {% block content %} 6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /wazimap/templates/locate/locate.html: -------------------------------------------------------------------------------- 1 | {% extends '_base.html' %} 2 | 3 | 4 | {% block content %} 5 |
    6 |
    7 | {% if not location %} 8 |

    Finding your current location…

    9 | {% else %} 10 | {% if places %} 11 |

    Use your current location

    12 |

    Your browser says you’re in…

    13 | {% else %} 14 |

    We can't find you in {{ geo_data.root_geography.name }}.

    15 |

    Are you located somewhere else? Try the search box instead.

    16 | {% endif %} 17 | {% endif %} 18 |
    19 | {% if places %} 20 |
    21 | 31 |
    32 | {% endif %} 33 |
    34 | {% endblock %} 35 | 36 | {% block body_javascript_extra %} 37 | 38 | 62 | {% endblock %} 63 | -------------------------------------------------------------------------------- /wazimap/templates/profile/_blocks/_comparative_list_item.html: -------------------------------------------------------------------------------- 1 | {% load humanize madlibs stats %} 2 | {# to be called via `comparatives` inclusion tag #} 3 |
  • 4 | {% if index and stat_type != 'count' %} 5 | {{ index|comparison_index_phrase }} the {{ stat_type|stat_type_to_number_noun }} in {{ place_name }}: {% statvalue value %} 6 | {% if error_ratio >= 10 %}{% endif %} 7 | {% else %} 8 | {{ place_name }}: {% statvalue value %} 9 | {% endif %} 10 | {% if numerator %} 11 | {% statvalue numerator isnumerator=True %} 12 | {% if error %} 13 | (± {% statvalue error %}{% if numerator_error %} / ±{% statvalue numerator_error isnumerator=True %}{% endif %}) 14 | {% endif %} 15 | {% elif error %} 16 | ±{% statvalue error %} 17 | {% endif %} 18 |
  • 19 | -------------------------------------------------------------------------------- /wazimap/templates/profile/_blocks/_stat_list.html: -------------------------------------------------------------------------------- 1 | {% load humanize madlibs comparatives stats %} 2 | 3 | {% if not stat_wrapper == 'false' %}{% endif %} 4 | 5 | 6 | {% if stat.values.this == -2 %} 7 | {% statvalue 0 %} 8 | {% elif not stat.values.this %} 9 | {{ WAZIMAP.na_label }} 10 | {% else %} 11 | {% if stat_type == 'name' %} 12 | {{ stat.name }} 13 | {% else %} 14 | {% statvalue stat.values.this %} 15 | {% if stat_suffix %} {{ stat_suffix }}{% endif %} 16 | {% endif %} 17 | {% if stat.error.this_ratio >= 10 %}{% endif %} 18 | {% endif %} 19 | 20 | {% if not stat_class == 'secondary' %} 21 | {% if stat.error.this %} 22 | ±{% statvalue stat.error.this %} 23 | {% endif %} 24 | 25 | {% if stat.numerators.this %} 26 | ({% statvalue stat.numerators.this isnumerator=True %}{% if stat.numerator_error.this %}  ±{% statvalue stat.numerator_errors.this isnumerator=True %}{% endif %}) 27 | {% endif %} 28 | {% endif %} 29 | 30 | {% if stat_type == 'name' %}{{ stat_name }}{% else %}{{ stat.name }}{% endif %} 31 | 32 | {% if stat_class == 'secondary' %} 33 | 34 | {% if stat.error.this %} 35 | ±{% statvalue stat.error.this %} 36 | {% endif %} 37 | 38 | {% if stat.numerators.this %} 39 | ({% statvalue stat.numerators.this isnumerator=True %}{% if stat.numerator_error.this %}  ±{% statvalue stat.numerator_errors.this isnumerator=True %}{% endif %}) 40 | {% endif %} 41 | 42 | {% endif %} 43 | 44 | 45 | {% if geography.comparatives and stat.values.this and not stat_class == 'secondary' %} 46 |
      47 | {% for sumlev in geography.comparatives %} 48 | {% build_comparative_item sumlev stat stat_type geography decimals %} 49 | {% endfor %} 50 |
    51 | {% endif %} 52 | {% if not stat_wrapper == 'false' %}
    {% endif %} 53 | -------------------------------------------------------------------------------- /wazimap/templates/profile/head2head.html: -------------------------------------------------------------------------------- 1 | {% extends '_base.html' %} 2 | {% load static %} 3 | 4 | {% block head_title %}{{ geo1.name }} vs {{ geo2.name }} - {{ block.super }}{% endblock %} 5 | {% block head_facebook_tags %} 6 | 7 | 8 | 9 | 10 | 11 | {% endblock %} 12 | 13 | {% block body_class %}profile-head2head-view{% endblock %} 14 | 15 | {% block content_container %} 16 | 17 |
    18 | 19 | 20 | 21 |
    22 |
    23 | {% endblock %} 24 | 25 | {% block body_javascript_extra %} 26 | 27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /wazimap/templates/settings_js.html: -------------------------------------------------------------------------------- 1 | {% load jsonify staticfiles %} 2 | 3 | 30 | -------------------------------------------------------------------------------- /wazimap/templates/table/table_detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'table/_base_table.html' %} 2 | {% load tabletags static %} 3 | {% block head_title %}Table {{ table.name }}: {{ table.description }} - {{ block.super }}{% endblock %} 4 | 5 | {% block head_facebook_tags %} 6 | 7 | 8 | 9 | 10 | 11 | {#}{#} 12 | {% endblock %} 13 | 14 | {% block body_id %}table{% endblock %} 15 | {% block body_class %}data-view{% endblock %} 16 | 17 | {% block header_content %} 18 |
    19 |
    20 |

    Table {{ table.name }}: {{ table.description }}

    21 |

    Show data from this table: Choose a place or area

    22 |
    23 | 24 | {#}

    Search for a place to explore data, or choose a geographic summary level to build a comparison.

    {#} 25 |
    26 |
    27 |
    28 |

    in

    29 |
    30 | 31 |

    32 |
    33 |
    34 |
    35 | {% endblock %} 36 | 37 | {% block content %} 38 | {% include 'data/_blocks/_topic_select_input.html' %} 39 | 40 |
    41 |
    42 |

    Table explorer

    43 |
    44 |
    45 |
    46 |

    Table {{ table.name }}

    47 |

    Table universe: {{ table.universe }}

    48 | 56 | 57 |
    58 |

    Columns in this table

    59 |
      {% for column_id, column_data in table_columns.items %} 60 |
    • {{ column_data.name }}
    • 61 | {% endfor %}
    62 |
    63 |
    64 |
    65 |
    66 | {% endblock %} 67 | 68 | {% block body_javascript_extra %}{{ block.super }} 69 | 70 | 80 | {% endblock %} 81 | -------------------------------------------------------------------------------- /wazimap/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/wazimap/templatetags/__init__.py -------------------------------------------------------------------------------- /wazimap/templatetags/jsonify.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from django import template 4 | 5 | register = template.Library() 6 | 7 | 8 | @register.filter 9 | def jsonify(value): 10 | return json.dumps(value) 11 | -------------------------------------------------------------------------------- /wazimap/templatetags/stats.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.utils import formats 3 | from django.template.defaultfilters import floatformat 4 | from django.contrib.humanize.templatetags.humanize import intcomma 5 | from django.conf import settings 6 | 7 | register = template.Library() 8 | 9 | CURRENCY_SYMBOL = formats.get_format('CURRENCY_SYMBOL') 10 | if CURRENCY_SYMBOL == 'CURRENCY_SYMBOL': 11 | CURRENCY_SYMBOL = '$' 12 | 13 | DECIMAL_SEPARATOR = formats.get_format('DECIMAL_SEPARATOR') 14 | DOT_ZERO = DECIMAL_SEPARATOR + "0" 15 | 16 | 17 | @register.simple_tag(takes_context=True) 18 | def statvalue(context, value, decimals=None, stat_type=None, isnumerator=False): 19 | if value is None: 20 | return settings.WAZIMAP.get('na_label', '') 21 | 22 | if decimals is None: 23 | decimals = context.get('decimals', -1) 24 | if stat_type is None: 25 | stat_type = context.get('stat_type', "number") 26 | 27 | value = intcomma(floatformat(value, decimals)) 28 | if value.endswith(DOT_ZERO): 29 | value = value[:-2] 30 | 31 | if stat_type in ["dollar", "currency"]: 32 | value = CURRENCY_SYMBOL + value 33 | elif stat_type == "percentage" and not isnumerator: 34 | value = value + "%" 35 | 36 | return value 37 | -------------------------------------------------------------------------------- /wazimap/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/wazimap/tests/__init__.py -------------------------------------------------------------------------------- /wazimap/tests/data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUpSA/wazimap/05075a99a73ca7e831844c567b537406bd14670d/wazimap/tests/data/__init__.py -------------------------------------------------------------------------------- /wazimap/tests/data/test_utils.py: -------------------------------------------------------------------------------- 1 | from wazimap.tests.support import WazimapTestCase 2 | from wazimap.data.utils import get_stat_data 3 | from wazimap.data.tables import FieldTable 4 | from wazimap.geo import geo_data 5 | 6 | 7 | class UtilsTestCase(WazimapTestCase): 8 | def setUp(self): 9 | super(UtilsTestCase, self).setUp() 10 | self.geo = geo_data.geo_model(geo_level='lev', geo_code='code', version='') 11 | 12 | def test_get_stat_data_percentage(self): 13 | self.field_table(['gender'], """ 14 | lev,code,Male,10 15 | lev,code,Female,20 16 | """) 17 | data, total = get_stat_data(['gender'], self.geo, self.s) 18 | self.assertEqual(total, 30) 19 | self.assertEqual(data['Male']['numerators']['this'], 10) 20 | self.assertEqual(data['Male']['values']['this'], 33.33) 21 | self.assertEqual(data['Female']['numerators']['this'], 20) 22 | self.assertEqual(data['Female']['values']['this'], 66.67) 23 | 24 | data, total = get_stat_data(['gender'], self.geo, self.s, percent=False) 25 | self.assertEqual(total, 30) 26 | self.assertIsNone(data['Male']['numerators']['this']) 27 | self.assertEqual(data['Male']['values']['this'], 10) 28 | self.assertIsNone(data['Female']['numerators']['this']) 29 | self.assertEqual(data['Female']['values']['this'], 20) 30 | 31 | def test_get_stat_data_nulls(self): 32 | self.field_table(['gender'], """ 33 | lev,code,Male,10 34 | lev,code,Female, 35 | """) 36 | data, total = get_stat_data(['gender'], self.geo, self.s) 37 | self.assertEqual(total, 10) 38 | self.assertEqual(data['Male']['numerators']['this'], 10) 39 | self.assertEqual(data['Male']['values']['this'], 100) 40 | self.assertIsNone(data['Female']['numerators']['this']) 41 | self.assertIsNone(data['Female']['values']['this']) 42 | 43 | data, total = get_stat_data(['gender'], self.geo, self.s, percent=False) 44 | self.assertEqual(total, 10) 45 | self.assertIsNone(data['Male']['numerators']['this']) 46 | self.assertEqual(data['Male']['values']['this'], 10) 47 | self.assertIsNone(data['Female']['numerators']['this']) 48 | self.assertIsNone(data['Female']['values']['this']) 49 | 50 | def test_get_stat_data_nulls_with_denominator_key(self): 51 | table = self.field_table(['household goods'], None, universe='Households', denominator_key='total households') 52 | self.load_data(table, """ 53 | lev,code,fridge,10 54 | lev,code,computer,5 55 | lev,code,total households, 56 | """) 57 | data, total = get_stat_data(['household goods'], self.geo, self.s) 58 | self.assertEqual(total, None) 59 | self.assertEqual(data['Fridge']['numerators']['this'], 10) 60 | self.assertIsNone(data['Fridge']['values']['this']) 61 | self.assertEqual(data['Computer']['numerators']['this'], 5) 62 | self.assertIsNone(data['Computer']['values']['this']) 63 | -------------------------------------------------------------------------------- /wazimap/tests/support.py: -------------------------------------------------------------------------------- 1 | from django.test import TransactionTestCase 2 | from wazimap.data.utils import dataset_context 3 | from django.db import transaction 4 | 5 | from wazimap.data.utils import get_session, _engine 6 | from wazimap.models import FieldTable, Dataset, Release, DBTable 7 | 8 | 9 | class WazimapTestCase(TransactionTestCase): 10 | def setUp(self): 11 | self.s = get_session() 12 | self.ctxt = dataset_context(year='latest') 13 | self.ctxt.__enter__() 14 | 15 | def tearDown(self): 16 | self.s.close() 17 | _engine.dispose() 18 | self.ctxt.__exit__(None, None, None) 19 | 20 | def field_table(self, fields, data_str, **kwargs): 21 | with transaction.atomic(): 22 | dataset, _ = Dataset.objects.get_or_create(name="Test Dataset") 23 | release, _ = Release.objects.get_or_create(name="Test release", year="2000", dataset=dataset) 24 | table = FieldTable(fields=fields, dataset=dataset, **kwargs) 25 | table.clean() 26 | table.save() 27 | 28 | db_table, _ = DBTable.objects.get_or_create(name=table.name.lower()) 29 | table.release_class.objects.create(data_table=table, db_table=db_table, release=release) 30 | 31 | table.ensure_db_tables_exist() 32 | 33 | if data_str: 34 | self.load_data(table, data_str) 35 | 36 | return table 37 | 38 | def load_data(self, table, data_str): 39 | db_table = table.get_db_table(year='latest') 40 | model = db_table.model 41 | 42 | for row in data_str.strip().split("\n"): 43 | parts = row.strip().split(",") 44 | entry = model(geo_level=parts[0], geo_code=parts[1], geo_version='') 45 | 46 | # set fields 47 | for i, field in enumerate(table.fields): 48 | setattr(entry, field, parts[2 + i]) 49 | 50 | v = parts[-1] 51 | v = None if v == '' else int(v) 52 | entry.total = v 53 | 54 | self.s.add(entry) 55 | 56 | self.s.flush() 57 | -------------------------------------------------------------------------------- /wazimap/tests/test_geo.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.conf import settings 3 | 4 | from wazimap.geo import geo_data, GeoData 5 | 6 | 7 | class GeoTestCase(TestCase): 8 | def test_versioned_geos(self): 9 | # create two geos at different versions 10 | cpt11 = geo_data.geo_model.objects.create(geo_level='municipality', geo_code='cpt', long_name='City of Cape Town', version='2011') 11 | cpt16 = geo_data.geo_model.objects.create(geo_level='municipality', geo_code='cpt', long_name='City of Cape Town', version='2016') 12 | 13 | self.assertEqual(cpt16, geo_data.get_geography('cpt', 'municipality')) 14 | self.assertEqual(cpt11, geo_data.get_geography('cpt', 'municipality', '2011')) 15 | self.assertEqual(cpt16, geo_data.get_geography('cpt', 'municipality', '2016')) 16 | 17 | def test_geometry(self): 18 | # if the geometry_data is missing the version, we should raise an error 19 | settings.WAZIMAP['geometry_data'] = {'country': 'geo/country.geojson'} 20 | 21 | with self.assertRaises(ValueError): 22 | GeoData() 23 | 24 | # if the geometry_data is missing the version, we should raise an error 25 | # raises an attribute error from line 188 geo.py 26 | settings.WAZIMAP['geometry_data'] = {'': 'geo/country.geojson'} 27 | 28 | with self.assertRaises(AttributeError): 29 | GeoData() 30 | -------------------------------------------------------------------------------- /wazimap/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for wazimap project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ 8 | 9 | """ 10 | 11 | import os 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "wazimap.settings") 15 | application = get_wsgi_application() 16 | --------------------------------------------------------------------------------