├── forms
├── __init__.py
├── migrations
│ ├── __init__.py
│ ├── 0026_remove_internetnewssheet_person_secondary.py
│ ├── 0010_televisionsheet_comments.py
│ ├── 0007_auto_20150309_1422.py
│ ├── 0005_internetnewsperson_age.py
│ ├── 0008_auto_20150309_1902.py
│ ├── 0039_auto_20200120_1536.py
│ ├── 0040_auto_20200120_1538.py
│ ├── 0041_auto_20200123_1224.py
│ ├── 0045_auto_20200306_1510.py
│ ├── 0029_auto_20191210_2042.py
│ ├── 0030_auto_20191210_2043.py
│ ├── 0033_auto_20191212_2003.py
│ ├── 0006_auto_20150309_1403.py
│ ├── 0053_add_monitoring_mode_20200909_1013.py
│ ├── 0013_auto_20150324_0700.py
│ ├── 0012_auto_20150312_1400.py
│ ├── 0011_auto_20150312_1358.py
│ ├── 0057_add_monitor_code.py
│ ├── 0054_monitoring_mode_messages_20200910_1441.py
│ ├── 0017_auto_20150331_1815.py
│ ├── 0060_add_moldova_to_country_region.py
│ ├── 0036_auto_20200114_1337.py
│ ├── 0022_assign_country_region_to_sheet_models.py
│ ├── 0044_auto_20200226_0758.py
│ ├── 0025_auto_20191210_1924.py
│ ├── 0002_auto_20150309_1227.py
│ ├── 0009_auto_20150312_1347.py
│ ├── 0050_add_created_and_updated_at.py
│ ├── 0049_auto_20200820_0739.py
│ ├── 0059_update_country_region.py
│ ├── 0043_update_country_region.py
│ ├── 0058_add_deleted_attribute.py
│ ├── 0003_auto_20150309_1340.py
│ ├── 0018_auto_20150416_0855.py
│ ├── 0019_auto_20150506_1052.py
│ ├── 0004_auto_20150309_1358.py
│ ├── 0051_increate_people_in_the_news_age.py
│ ├── 0046_auto_20200312_0949.py
│ ├── 0042_auto_20200220_1247.py
│ └── 0020_auto_20150506_1136.py
├── tests.py
├── views.py
└── static
│ └── forms
│ └── admin
│ ├── check_tab_errors
│ ├── radioperson.js
│ ├── newspaperperson.js
│ ├── televisionperson.js
│ ├── internetnewsperson.js
│ └── check.js
│ ├── move_fields.js
│ ├── move_radio_fields.js
│ ├── move_fields_newspaper.js
│ ├── move_twitter_fields.js
│ ├── move_television_fields.js
│ └── move_internet_fields.js
├── reports
├── __init__.py
├── management
│ ├── __init__.py
│ └── commands
│ │ ├── __init__.py
│ │ ├── import-weights.py
│ │ ├── generate_dataset.py
│ │ └── import-historical.py
├── migrations
│ ├── __init__.py
│ ├── 0006_unique_country_mediatype.py
│ ├── 0001_initial.py
│ ├── 0009_delete_tv_media_type.py
│ ├── 0003_indonesia-weights.py
│ ├── 0004_transnational_weights.py
│ ├── 0008_gsheetcountryweights.py
│ └── 0005_duplicate_weights.py
├── tests.py
├── admin.py
├── apps.py
├── historical
│ ├── __init__.py
│ ├── canon.py
│ ├── historical.py
│ └── _base_importer.py
├── urls.py
├── signals.py
├── models.py
├── forms.py
└── views.py
├── static
├── .placeholder
└── js
│ └── tabs.js
├── gmmp
├── management
│ ├── __init__.py
│ └── commands
│ │ ├── __init__.py
│ │ ├── write_weights_to_dict.py
│ │ ├── fix_countries.py
│ │ ├── fix_regions.py
│ │ ├── bulk_password_reset_emails.py
│ │ ├── sync_users.py
│ │ └── map_weights_to_codes.py
├── migrations
│ ├── __init__.py
│ ├── 0003_auto_20200114_1330.py
│ ├── 0002_auto_20191204_1908.py
│ ├── 0005_special_questions_gsheet_integration.py
│ ├── 0001_initial.py
│ ├── 0004_specialquestions.py
│ └── 0006_countryuser.py
├── __init__.py
├── static
│ ├── help.pdf
│ ├── favicon.ico
│ ├── img
│ │ ├── wmtn.png
│ │ ├── internet.png
│ │ ├── gmmp
│ │ │ ├── full.png
│ │ │ ├── logo.png
│ │ │ └── text.png
│ │ └── newspaper.png
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── mstile-150x150.png
│ ├── apple-touch-icon.png
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── wazimap
│ │ ├── favicon.5debd593.ico
│ │ ├── loading.a08ac827.gif
│ │ ├── slide-1.56cb412e.jpg
│ │ ├── slide-2.940fd1c6.jpg
│ │ ├── slide-2b.f364dcbf.jpg
│ │ ├── slide-3.874979e5.jpg
│ │ ├── slide-4.daa3644d.jpg
│ │ ├── slide-5.f9daed8f.jpg
│ │ ├── slide-7.9eecdd38.jpg
│ │ ├── webclip.7bba710e.png
│ │ ├── js.cba122a9.css
│ │ ├── js.cba122a9.css.map
│ │ └── normalize.83e55e48.css
│ ├── fonts
│ │ ├── glyphicons-halflings-regular.eot
│ │ ├── glyphicons-halflings-regular.ttf
│ │ ├── glyphicons-halflings-regular.woff
│ │ └── glyphicons-halflings-regular.woff2
│ ├── css
│ │ ├── login.css
│ │ ├── 1-col-portfolio.css
│ │ ├── gmmp.css
│ │ └── 3-col-portfolio.css
│ ├── browserconfig.xml
│ └── site.webmanifest
├── apps.py
├── wsgi.py
├── templates
│ ├── gmmp
│ │ └── dashboard_modules
│ │ │ ├── submissions.html
│ │ │ └── add_submission.html
│ ├── admin
│ │ ├── change_form.html
│ │ ├── _footer.html
│ │ ├── submit_line.html
│ │ ├── login.html
│ │ └── base_site.html
│ ├── data_export.html
│ ├── report_filter.html
│ ├── base.html
│ └── index.html
├── middlewares.py
├── views.py
├── dashboard.py
├── admin.py
├── templatetags
│ └── i18n_switcher.py
├── models.py
├── signals.py
├── urls.py
├── dashboard_modules.py
└── locale
│ └── en
│ └── LC_MESSAGES
│ └── django.po
├── runtime.txt
├── requirements.txt
├── requirements-dev.txt
├── .env
└── .env.template
├── contrib
└── docker
│ ├── entrypoint.sh
│ └── cmd.sh
├── manage.py
├── requirements-all.txt
├── .github
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
├── workflows
│ └── main.yml
└── PULL_REQUEST_TEMPLATE.md
├── README.md
├── docker-compose.yml
└── Dockerfile
/forms/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/reports/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/.placeholder:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/forms/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gmmp/management/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gmmp/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/runtime.txt:
--------------------------------------------------------------------------------
1 | python-3.8.0
2 |
--------------------------------------------------------------------------------
/reports/management/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/reports/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gmmp/management/commands/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/reports/management/commands/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gmmp/__init__.py:
--------------------------------------------------------------------------------
1 | default_app_config = 'gmmp.app.GmmpConfig'
2 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | -r requirements-all.txt
2 | psycopg2==2.8.6
3 |
--------------------------------------------------------------------------------
/forms/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/reports/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/forms/views.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import render
2 |
3 | # Create your views here.
4 |
--------------------------------------------------------------------------------
/reports/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | # Register your models here.
4 |
--------------------------------------------------------------------------------
/requirements-dev.txt:
--------------------------------------------------------------------------------
1 | -r requirements-all.txt
2 | black==20.8b1
3 | psycopg2-binary==2.8.6
4 |
--------------------------------------------------------------------------------
/gmmp/static/help.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/help.pdf
--------------------------------------------------------------------------------
/gmmp/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/favicon.ico
--------------------------------------------------------------------------------
/gmmp/static/img/wmtn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/img/wmtn.png
--------------------------------------------------------------------------------
/gmmp/static/img/internet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/img/internet.png
--------------------------------------------------------------------------------
/gmmp/static/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/favicon-16x16.png
--------------------------------------------------------------------------------
/gmmp/static/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/favicon-32x32.png
--------------------------------------------------------------------------------
/gmmp/static/img/gmmp/full.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/img/gmmp/full.png
--------------------------------------------------------------------------------
/gmmp/static/img/gmmp/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/img/gmmp/logo.png
--------------------------------------------------------------------------------
/gmmp/static/img/gmmp/text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/img/gmmp/text.png
--------------------------------------------------------------------------------
/gmmp/static/img/newspaper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/img/newspaper.png
--------------------------------------------------------------------------------
/gmmp/static/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/mstile-150x150.png
--------------------------------------------------------------------------------
/gmmp/static/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/apple-touch-icon.png
--------------------------------------------------------------------------------
/gmmp/static/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/android-chrome-192x192.png
--------------------------------------------------------------------------------
/gmmp/static/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/android-chrome-512x512.png
--------------------------------------------------------------------------------
/.env/.env.template:
--------------------------------------------------------------------------------
1 | SITE_URL=
2 | GMMP_EMAIL_HOST_PASSWORD=
3 | GMMP_EMAIL_FROM=
4 | GSHEETS_WEIGHTS_SPREADSHEET_ID=
5 | SENTRY_DNS=
6 |
--------------------------------------------------------------------------------
/gmmp/static/wazimap/favicon.5debd593.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/wazimap/favicon.5debd593.ico
--------------------------------------------------------------------------------
/gmmp/static/wazimap/loading.a08ac827.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/wazimap/loading.a08ac827.gif
--------------------------------------------------------------------------------
/gmmp/static/wazimap/slide-1.56cb412e.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/wazimap/slide-1.56cb412e.jpg
--------------------------------------------------------------------------------
/gmmp/static/wazimap/slide-2.940fd1c6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/wazimap/slide-2.940fd1c6.jpg
--------------------------------------------------------------------------------
/gmmp/static/wazimap/slide-2b.f364dcbf.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/wazimap/slide-2b.f364dcbf.jpg
--------------------------------------------------------------------------------
/gmmp/static/wazimap/slide-3.874979e5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/wazimap/slide-3.874979e5.jpg
--------------------------------------------------------------------------------
/gmmp/static/wazimap/slide-4.daa3644d.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/wazimap/slide-4.daa3644d.jpg
--------------------------------------------------------------------------------
/gmmp/static/wazimap/slide-5.f9daed8f.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/wazimap/slide-5.f9daed8f.jpg
--------------------------------------------------------------------------------
/gmmp/static/wazimap/slide-7.9eecdd38.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/wazimap/slide-7.9eecdd38.jpg
--------------------------------------------------------------------------------
/gmmp/static/wazimap/webclip.7bba710e.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/wazimap/webclip.7bba710e.png
--------------------------------------------------------------------------------
/gmmp/static/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/gmmp/static/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/gmmp/static/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/gmmp/static/fonts/glyphicons-halflings-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/gmmp/master/gmmp/static/fonts/glyphicons-halflings-regular.woff2
--------------------------------------------------------------------------------
/gmmp/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class GmmpConfig(AppConfig):
5 | name = "gmmp"
6 |
7 | def ready(self):
8 | import gmmp.signals
9 |
--------------------------------------------------------------------------------
/reports/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class ReportsConfig(AppConfig):
5 | name = "reports"
6 |
7 | def ready(self):
8 | import reports.signals
9 |
--------------------------------------------------------------------------------
/gmmp/wsgi.py:
--------------------------------------------------------------------------------
1 | from django.core.wsgi import get_wsgi_application
2 | import os
3 |
4 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'gmmp.settings')
5 | application = get_wsgi_application()
6 |
--------------------------------------------------------------------------------
/forms/static/forms/admin/check_tab_errors/radioperson.js:
--------------------------------------------------------------------------------
1 | (function($){
2 | $(document).ready(function() {
3 | check_tab_errors($, '#radioperson_set-group', 6);
4 | });
5 | }(jet.jQuery));
6 |
--------------------------------------------------------------------------------
/forms/static/forms/admin/check_tab_errors/newspaperperson.js:
--------------------------------------------------------------------------------
1 | (function($){
2 | $(document).ready(function() {
3 | check_tab_errors($, '#newspaperperson_set-group', 6);
4 | });
5 | }(jet.jQuery));
6 |
--------------------------------------------------------------------------------
/forms/static/forms/admin/check_tab_errors/televisionperson.js:
--------------------------------------------------------------------------------
1 | (function($){
2 | $(document).ready(function() {
3 | check_tab_errors($, '#televisionperson_set-group', 6);
4 | });
5 | }(jet.jQuery));
6 |
--------------------------------------------------------------------------------
/forms/static/forms/admin/check_tab_errors/internetnewsperson.js:
--------------------------------------------------------------------------------
1 | (function($){
2 | $(document).ready(function() {
3 | check_tab_errors($, '#internetnewsperson_set-group', 6);
4 | });
5 | }(jet.jQuery));
6 |
--------------------------------------------------------------------------------
/reports/historical/__init__.py:
--------------------------------------------------------------------------------
1 | __all__ = ["historical", "canon"]
2 |
3 | # Preserve the current `from reports.historical import Historical, canon` syntax
4 | from .historical import Historical
5 | from .canon import canon
6 |
--------------------------------------------------------------------------------
/forms/static/forms/admin/check_tab_errors/check.js:
--------------------------------------------------------------------------------
1 | var check_tab_errors = function($, tab_id, tab_number) {
2 | if ($(tab_id + ' *').hasClass('errorlist')) {
3 | $('.changeform-tabs li:nth-child(' + tab_number + ')').addClass('errors');
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/reports/historical/canon.py:
--------------------------------------------------------------------------------
1 | from ._recodes import recode
2 |
3 |
4 | def canon(key):
5 | if key:
6 | canon_key = key.replace("…", "")
7 | canon_key = recode(canon_key.strip())
8 | return canon_key.strip().lower()
9 | return ""
10 |
--------------------------------------------------------------------------------
/gmmp/static/css/login.css:
--------------------------------------------------------------------------------
1 | body.login {
2 | padding-top: 10px;
3 | }
4 | body.login .img {
5 | width: 100%;
6 | }
7 | body.login .login-form {
8 | margin-bottom: 2rem;
9 | }
10 | body.login .submit-row input[type="submit"] {
11 | background-color: #29abe2;
12 | }
13 |
--------------------------------------------------------------------------------
/contrib/docker/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | if [ "$DATABASE" = "postgres" ]
4 | then
5 | echo "Waiting for postgres..."
6 |
7 | while ! nc -z $SQL_HOST $SQL_PORT; do
8 | sleep 0.1
9 | done
10 |
11 | echo "PostgreSQL started"
12 | fi
13 |
14 | exec "$@"
15 |
--------------------------------------------------------------------------------
/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", "gmmp.settings")
7 |
8 | from django.core.management import execute_from_command_line
9 |
10 | execute_from_command_line(sys.argv)
11 |
--------------------------------------------------------------------------------
/forms/static/forms/admin/move_fields.js:
--------------------------------------------------------------------------------
1 | var move_journalist = function($, group_id) {
2 | var a = $(group_id)
3 | $('.journalists-fieldset').append(a)
4 | }
5 |
6 | var move_people = function($, group_id) {
7 | var a = $(group_id)
8 | $('.people-fieldset').append(a)
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/gmmp/static/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #29abe2
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/forms/static/forms/admin/move_radio_fields.js:
--------------------------------------------------------------------------------
1 | (function($){
2 | $(document).ready(function() {
3 | move_journalist($, "#radiojournalist_set-group");
4 | move_people($, "#radioperson_set-group");
5 | $(".changeform-tabs > li").last().remove()
6 | $(".changeform-tabs > li").last().remove()
7 | });
8 | }(jet.jQuery));
9 |
10 |
--------------------------------------------------------------------------------
/forms/static/forms/admin/move_fields_newspaper.js:
--------------------------------------------------------------------------------
1 | (function($){
2 | $(document).ready(function() {
3 | move_journalist($, "#newspaperjournalist_set-group");
4 | move_people($, "#newspaperperson_set-group");
5 | $(".changeform-tabs > li").last().remove()
6 | $(".changeform-tabs > li").last().remove()
7 | });
8 | }(jet.jQuery));
9 |
--------------------------------------------------------------------------------
/forms/static/forms/admin/move_twitter_fields.js:
--------------------------------------------------------------------------------
1 | (function($){
2 | $(document).ready(function() {
3 | move_journalist($, "#twitterjournalist_set-group");
4 | move_people($, "#twitterperson_set-group");
5 | $(".changeform-tabs > li").last().remove()
6 | $(".changeform-tabs > li").last().remove()
7 | });
8 | }(jet.jQuery));
9 |
10 |
--------------------------------------------------------------------------------
/gmmp/templates/gmmp/dashboard_modules/submissions.html:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/forms/static/forms/admin/move_television_fields.js:
--------------------------------------------------------------------------------
1 | (function($){
2 | $(document).ready(function() {
3 | move_journalist($, "#televisionjournalist_set-group");
4 | move_people($, "#televisionperson_set-group");
5 | $(".changeform-tabs > li").last().remove()
6 | $(".changeform-tabs > li").last().remove()
7 | });
8 | }(jet.jQuery));
9 |
10 |
--------------------------------------------------------------------------------
/forms/static/forms/admin/move_internet_fields.js:
--------------------------------------------------------------------------------
1 | (function($){
2 | $(document).ready(function() {
3 | move_journalist($, "#internetnewsjournalist_set-group");
4 | move_people($, "#internetnewsperson_set-group");
5 | $(".changeform-tabs > li").last().remove()
6 | $(".changeform-tabs > li").last().remove()
7 | });
8 | }(jet.jQuery));
9 |
10 |
--------------------------------------------------------------------------------
/requirements-all.txt:
--------------------------------------------------------------------------------
1 | Django==2.2.28
2 | XlsxWriter==1.3.7
3 | dj-database-url==0.5.0
4 | dj-static==0.0.6
5 | django-countries==7.1.0
6 | django-debug-toolbar== 3.2.1
7 | django-extensions==3.1.3
8 | django-jet==1.0.8
9 | django-gsheets==0.0.10
10 | django-guardian==2.3.0
11 | gunicorn[gevent]==20.1.0
12 | openpyxl==3.0.7
13 | python-dotenv==0.17.0
14 | sentry-sdk==1.14.0
15 | whitenoise==5.0.1
16 |
--------------------------------------------------------------------------------
/gmmp/templates/gmmp/dashboard_modules/add_submission.html:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/reports/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from django.views.generic.base import RedirectView
3 | from reports.views import ReportView, data_export
4 |
5 | urlpatterns = [
6 | path('', ReportView.as_view(), name='get_xlsx_reports'),
7 | path('data_export/', data_export, name='get_data_export'),
8 | path('wazimap', RedirectView.as_view(url='/genmap', permanent=True), name='wazimap'),
9 | ]
10 |
--------------------------------------------------------------------------------
/gmmp/middlewares.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import redirect
2 | from whitenoise.middleware import WhiteNoiseMiddleware
3 |
4 |
5 | class ProtectedStaticFileMiddleware(WhiteNoiseMiddleware):
6 | def process_request(self, request):
7 | # check user authentication
8 | if not request.path.startswith('/static/wazimap/') or request.user.is_authenticated:
9 | return WhiteNoiseMiddleware().process_request(request)
10 | return redirect('wazimap')
11 |
--------------------------------------------------------------------------------
/reports/management/commands/import-weights.py:
--------------------------------------------------------------------------------
1 | from django.core.management.base import BaseCommand
2 |
3 | from reports.models import GSheetCountryWeights
4 |
5 |
6 | class Command(BaseCommand):
7 | help = "Import weights from a gSheet and update DB"
8 |
9 | def handle(self, *args, **options):
10 | # NOTE(kilemensi): Don't call `syncgsheets` since it auto discovers
11 | # and sync **all** models
12 | GSheetCountryWeights.pull_sheet()
13 |
--------------------------------------------------------------------------------
/gmmp/management/commands/write_weights_to_dict.py:
--------------------------------------------------------------------------------
1 | import csv
2 | from pprint import pprint
3 |
4 | from django.core.management.base import BaseCommand
5 |
6 | class Command(BaseCommand):
7 | def handle(self, *args, **options):
8 | with open(args[0]) as csvfile:
9 | reader = csv.DictReader(csvfile)
10 | weights = []
11 | for row in reader:
12 | row['Twitter'] = 1
13 | weights.append(row)
14 | pprint(weights)
15 |
--------------------------------------------------------------------------------
/forms/migrations/0026_remove_internetnewssheet_person_secondary.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('forms', '0025_auto_20191210_1924'),
11 | ]
12 |
13 | operations = [
14 | migrations.RemoveField(
15 | model_name='internetnewssheet',
16 | name='person_secondary',
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/gmmp/static/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Global Media Monitoring Project",
3 | "short_name": "GMMP",
4 | "icons": [
5 | {
6 | "src": "/static/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/static/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#ffffff",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/gmmp/static/css/1-col-portfolio.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Start Bootstrap - 1 Col Portfolio HTML Template (http://startbootstrap.com)
3 | * Code licensed under the Apache License v2.0.
4 | * For details, see http://www.apache.org/licenses/LICENSE-2.0.
5 | */
6 |
7 | body {
8 | padding-top: 70px; /* Required padding for .navbar-fixed-top. Remove if using .navbar-static-top. Change if height of navigation changes. */
9 | }
10 |
11 | footer {
12 | margin: 50px 0;
13 | }
14 |
15 | .navbar-brand {
16 | margin-top: -11px;
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/reports/migrations/0006_unique_country_mediatype.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.18 on 2021-02-18 12:12
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('reports', '0005_duplicate_weights'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddConstraint(
14 | model_name='weights',
15 | constraint=models.UniqueConstraint(fields=('country', 'media_type'), name='country_media_type_key'),
16 | ),
17 | ]
18 |
--------------------------------------------------------------------------------
/gmmp/migrations/0003_auto_20200114_1330.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.9 on 2020-01-14 13:30
2 |
3 | from django.db import migrations
4 | import django_countries.fields
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('gmmp', '0002_auto_20191204_1908'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='monitor',
16 | name='country',
17 | field=django_countries.fields.CountryField(default='KE', max_length=2),
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/gmmp/migrations/0002_auto_20191204_1908.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 | import django_countries.fields
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('gmmp', '0001_initial'),
12 | ]
13 |
14 | operations = [
15 | migrations.AlterField(
16 | model_name='monitor',
17 | name='country',
18 | field=django_countries.fields.CountryField(default=b'KE', max_length=2),
19 | preserve_default=True,
20 | ),
21 | ]
22 |
--------------------------------------------------------------------------------
/gmmp/static/wazimap/js.cba122a9.css:
--------------------------------------------------------------------------------
1 | .container{border:1px solid #000;width:800px;height:100px;margin-top:50px;margin-left:50px}.d3-tip{line-height:1;padding:8px;background:rgba(0,0,0,.8);color:#fff;border-radius:4px;font-size:16px;pointer-events:none}.d3-tip.n:after{content:"\25BC";margin:-2px 0 0;top:100%}.d3-tip.n:after,.d3-tip.s:before{box-sizing:border-box;display:inline;font-size:10px;width:100%;line-height:1;color:rgba(0,0,0,.8);position:absolute;text-align:center;left:0}.d3-tip.s:before{content:"\25B2";margin:0 0 -2px;bottom:100%}.indicator__chart_container rect.bar{fill:#e4653d}
2 | /*# sourceMappingURL=js.cba122a9.css.map */
--------------------------------------------------------------------------------
/gmmp/views.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import views as auth_views
2 | from gmmp import settings
3 |
4 |
5 | class CustomPassowrdResetView(auth_views.PasswordResetView):
6 | def get_context_data(self, **kwargs):
7 | context = super().get_context_data(**kwargs)
8 | context['site_header'] = settings.ADMIN_SITE_SITE_HEADER
9 | return context
10 |
11 | class CustomPasswordResetDoneView(auth_views.PasswordResetDoneView):
12 | def get_context_data(self, **kwargs):
13 | context = super().get_context_data(**kwargs)
14 | context['site_header'] = settings.ADMIN_SITE_SITE_HEADER
15 | return context
16 |
--------------------------------------------------------------------------------
/forms/migrations/0010_televisionsheet_comments.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('forms', '0009_auto_20150312_1347'),
11 | ]
12 |
13 | operations = [
14 | migrations.AddField(
15 | model_name='televisionsheet',
16 | name='comments',
17 | field=models.TextField(verbose_name='Describe any photographs included in the story and the conclusions you draw from them.', blank=True),
18 | preserve_default=True,
19 | ),
20 | ]
21 |
--------------------------------------------------------------------------------
/forms/migrations/0007_auto_20150309_1422.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('forms', '0006_auto_20150309_1403'),
11 | ]
12 |
13 | operations = [
14 | migrations.RenameField(
15 | model_name='televisionsheet',
16 | old_name='television_station',
17 | new_name='television_channel',
18 | ),
19 | migrations.RemoveField(
20 | model_name='televisionsheet',
21 | name='person_secondary',
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/gmmp/templates/admin/change_form.html:
--------------------------------------------------------------------------------
1 | {% extends "admin/change_form.html" %}
2 | {% load i18n admin_urls %}
3 |
4 | {% block breadcrumbs %}
5 |
11 | {% endblock %}
12 |
13 |
--------------------------------------------------------------------------------
/gmmp/templates/data_export.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load static %}
3 |
4 | {% block page_content %}
5 |
6 |
7 |
8 |
9 |
Data export
10 |
11 |
12 |
13 |
20 | {% endblock page_content %}
21 |
--------------------------------------------------------------------------------
/forms/migrations/0005_internetnewsperson_age.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('forms', '0004_auto_20150309_1358'),
11 | ]
12 |
13 | operations = [
14 | migrations.AddField(
15 | model_name='internetnewsperson',
16 | name='age',
17 | field=models.PositiveIntegerField(default=None, verbose_name='Age (person appears)', choices=[(0, 'Do not know'), (1, '12 and under'), (2, '13-18'), (3, '19-34'), (4, '35-49'), (5, '50-64'), (6, '65 years or more')]),
18 | preserve_default=False,
19 | ),
20 | ]
21 |
--------------------------------------------------------------------------------
/forms/migrations/0008_auto_20150309_1902.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('forms', '0007_auto_20150309_1422'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='televisionsheet',
16 | name='television_channel',
17 | field=models.CharField(help_text="Be as specific as possible. E.g. if the television company is called RTV, and if the newscast is broadcast on its second channel, write in 'RTV-2' ", max_length=255, verbose_name='Channel'),
18 | preserve_default=True,
19 | ),
20 | ]
21 |
--------------------------------------------------------------------------------
/gmmp/management/commands/fix_countries.py:
--------------------------------------------------------------------------------
1 | from django.core.management.base import BaseCommand
2 | from django.db.models import F
3 | from forms.models import sheet_models
4 |
5 | class Command(BaseCommand):
6 | def handle(self, *args, **options):
7 | for name, model in sheet_models.items():
8 | country_errors_sheets = model.objects.exclude(monitor__country__in=F('country'))
9 | for sheet in country_errors_sheets:
10 | try:
11 | sheet.country = sheet.monitor.country
12 | sheet.save()
13 | self.stdout.write("%s,%s" % (name, sheet.id))
14 | except AttributeError:
15 | self.stdout.write("Sheet has no monitor: %s %s" % (name, sheet.id))
16 |
17 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Dokku CD
2 | on:
3 | push:
4 | branches: [ master ]
5 | env:
6 | DOKKU_APP_NAME: 'gmmp'
7 | DOKKU_HOST: 'dokku-1.hurumap.org'
8 | DOKKU_REMOTE_BRANCH: 'master'
9 | GIT_PUSH_FLAGS: '--force'
10 | jobs:
11 | deploy:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v2
15 | with:
16 | fetch-depth: 0
17 | - id: deploy
18 | name: Deploy to dokku
19 | uses: idoberko2/dokku-deploy-github-action@v1
20 | with:
21 | ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
22 | dokku-host: ${{ env.DOKKU_HOST }}
23 | app-name: ${{ env.DOKKU_APP_NAME }}
24 | remote-branch: ${{ env.DOKKU_REMOTE_BRANCH }}
25 | git-push-flags: ${{ env.GIT_PUSH_FLAGS }}
26 |
--------------------------------------------------------------------------------
/gmmp/migrations/0005_special_questions_gsheet_integration.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.16 on 2020-09-07 06:08
2 |
3 | from django.db import migrations, models
4 | import uuid
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('gmmp', '0004_specialquestions'),
11 | ]
12 |
13 | operations = [
14 | migrations.RemoveField(
15 | model_name='specialquestions',
16 | name='id',
17 | ),
18 | migrations.RemoveField(
19 | model_name='specialquestions',
20 | name='user',
21 | ),
22 | migrations.AddField(
23 | model_name='specialquestions',
24 | name='guid',
25 | field=models.CharField(default=uuid.uuid4, max_length=255, primary_key=True, serialize=False),
26 | ),
27 | ]
28 |
--------------------------------------------------------------------------------
/gmmp/static/css/gmmp.css:
--------------------------------------------------------------------------------
1 | /* JET DASHBOARD */
2 | .dashboard-item-content .submission {
3 | border: 0.07143rem solid #dce0e6;
4 | border-radius: 4px;
5 | }
6 | .dashboard-item-content .submission-action {
7 | border-bottom-left-radius: 4px;
8 | border-bottom-right-radius: 4px;
9 | display: block;
10 | padding: 1.5rem 2rem;
11 | }
12 | .dashboard-item-content .submission-action a {
13 | color: inherit;
14 | }
15 | .dashboard-item-content .submission-description {
16 | padding: 1.5rem 2rem;
17 | height: 13.5rem;
18 | overflow-y: auto;
19 | }
20 | .dashboard-item-content .submission .submission-item {
21 | font-size: 1rem;
22 | padding: 1.45rem 2rem;
23 | }
24 |
25 | .dashboard #footer {
26 | padding: 0 1.42857rem;
27 | }
28 |
29 | /* FORMS */
30 | .submit-row input.default {
31 | float: right;
32 | margin-right: 0;
33 | }
34 |
--------------------------------------------------------------------------------
/reports/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 | import django_countries.fields
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='Weights',
16 | fields=[
17 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
18 | ('country', django_countries.fields.CountryField(max_length=2)),
19 | ('media_type', models.CharField(max_length=32)),
20 | ('weight', models.DecimalField(max_digits=4, decimal_places=2)),
21 | ],
22 | options={
23 | },
24 | bases=(models.Model,),
25 | ),
26 | ]
27 |
--------------------------------------------------------------------------------
/gmmp/templates/admin/_footer.html:
--------------------------------------------------------------------------------
1 | {% load static i18n %}
2 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Description
2 |
3 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.
4 |
5 | Fixes # (issue)
6 |
7 | ## Type of change
8 |
9 | Please delete options that are not relevant.
10 |
11 | - [ ] Bug fix (non-breaking change which fixes an issue)
12 | - [ ] New feature (non-breaking change which adds functionality)
13 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
14 | - [ ] This change requires a documentation update
15 |
16 | ## Screenshots
17 |
18 | ## Checklist:
19 |
20 | - [ ] My code follows the style guidelines of this project
21 | - [ ] I have performed a self-review of my own code
22 | - [ ] I have commented my code, particularly in hard-to-understand areas
23 | - [ ] I have made corresponding changes to the documentation
24 |
--------------------------------------------------------------------------------
/gmmp/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 | from django.conf import settings
6 | import django_countries.fields
7 |
8 |
9 | class Migration(migrations.Migration):
10 |
11 | dependencies = [
12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
13 | ]
14 |
15 | operations = [
16 | migrations.CreateModel(
17 | name='Monitor',
18 | fields=[
19 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
20 | ('country', django_countries.fields.CountryField(max_length=2)),
21 | ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
22 | ],
23 | options={
24 | },
25 | bases=(models.Model,),
26 | ),
27 | ]
28 |
--------------------------------------------------------------------------------
/contrib/docker/cmd.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | python manage.py migrate guardian --noinput
3 | python manage.py migrate jet --noinput
4 | python manage.py migrate dashboard --noinput
5 | python manage.py migrate --noinput # Apply database migrations
6 | django-admin compilemessages # Compile *.po translation files
7 | python manage.py collectstatic --clear --noinput # Collect static files
8 |
9 | # Prepare log files and start outputting logs to stdout
10 | touch /app/logs/gunicorn.log
11 | touch /app/logs/access.log
12 | tail -n 0 -f /app/logs/*.log &
13 |
14 | # Start Gunicorn processes
15 | echo Starting Gunicorn.
16 | exec gunicorn \
17 | --timeout=${GMMP_GUNICORN_TIMEOUT:-60} \
18 | --bind=0.0.0.0:8000 \
19 | --workers=${GMMP_GUNICORN_WORKERS:-3} \
20 | --worker-class=gevent \
21 | --log-level=info \
22 | --log-file=/app/logs/gunicorn.log \
23 | --access-logfile=/app/logs/access.log \
24 | --name=gmmp \
25 | "$@"
26 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/reports/migrations/0009_delete_tv_media_type.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.18 on 2021-02-18 12:12
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | def delete_tv_media_type_weights(apps, schema_editor):
7 | Weights = apps.get_model("reports", "Weights")
8 | db_alias = schema_editor.connection.alias
9 |
10 | # Weights with wrong media type 'TV' were accidentally added by
11 | # `import-weights` command. The command has been updated to use
12 | # 'Television' as media type. It is best to delete existing rows
13 | # and import weights afresh.
14 | Weights.objects.using(db_alias).filter(media_type="TV").delete()
15 |
16 |
17 | def backwards(apps, schema_editor):
18 | pass
19 |
20 |
21 | class Migration(migrations.Migration):
22 |
23 | dependencies = [
24 | ("reports", "0008_gsheetcountryweights"),
25 | ]
26 |
27 | operations = [
28 | migrations.RunPython(
29 | delete_tv_media_type_weights,
30 | backwards,
31 | ),
32 | ]
33 |
--------------------------------------------------------------------------------
/gmmp/dashboard.py:
--------------------------------------------------------------------------------
1 | from django.utils.translation import gettext_lazy as _
2 | from jet.dashboard import modules
3 | from jet.dashboard.dashboard import Dashboard, AppIndexDashboard
4 |
5 | from gmmp.dashboard_modules import (
6 | AddInternetNewsSubmission,
7 | AddNewspaperSubmission,
8 | AddRadioSubmission,
9 | AddTelevisionSubmission,
10 | AddTwitterSubmission,
11 | Submissions,
12 | )
13 |
14 |
15 | class CustomIndexDashboard(Dashboard):
16 | columns = 3
17 |
18 | def init_with_context(self, context):
19 | self.available_children.append(modules.LinkList)
20 | self.children.append(AddNewspaperSubmission(column=0, order=0))
21 | self.children.append(AddRadioSubmission(column=1, order=0))
22 | self.children.append(AddTelevisionSubmission(column=2, order=0))
23 | self.children.append(AddInternetNewsSubmission(column=0, order=1))
24 | self.children.append(AddTwitterSubmission(column=1, order=1))
25 | self.children.append(Submissions(column=2, order=1))
26 |
--------------------------------------------------------------------------------
/gmmp/management/commands/fix_regions.py:
--------------------------------------------------------------------------------
1 | import os
2 | import csv
3 | from collections import defaultdict
4 | from django.core.management.base import BaseCommand
5 | from forms.models import sheet_models
6 | from forms.modelutils import CountryRegion
7 |
8 | class Command(BaseCommand):
9 |
10 | def handle(self, *args, **options):
11 | file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)))
12 | with open(file_path + "/bad_sheets.csv") as csvfile:
13 | reader = csv.DictReader(csvfile)
14 | sheets_to_fix = defaultdict(list)
15 | for row in reader:
16 | sheets_to_fix[row['sheet_type']].append(row['id'])
17 | for sheet_type, sheet_ids in sheets_to_fix.items():
18 | model = sheet_models[sheet_type]
19 | for obj_id in sheet_ids:
20 | sheet = model.objects.get(id=obj_id)
21 | sheet.country_region = CountryRegion.objects.get(country=sheet.country)
22 | sheet.save()
23 | self.stdout.write("%s %s" % (sheet_type, obj_id))
24 |
--------------------------------------------------------------------------------
/gmmp/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.contrib.auth.admin import UserAdmin
3 | from django.contrib.auth.models import User
4 | from django.utils.translation import gettext_lazy as _
5 |
6 | from . import models
7 |
8 | # Define an inline admin descriptor for Employee model
9 | # which acts a bit like a singleton
10 | class MonitorInline(admin.TabularInline):
11 | model = models.Monitor
12 | can_delete = False
13 | verbose_name_plural = _('Monitor Details')
14 |
15 | def monitor_country(obj):
16 | return obj.monitor.country.name
17 | monitor_country.short_description = _('Country')
18 | monitor_country.admin_order_field = 'monitor__country'
19 |
20 | # Define a new User admin
21 | class UserAdmin(UserAdmin):
22 | inlines = (MonitorInline,)
23 | list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff', monitor_country)
24 | list_filter = ('monitor__country',)
25 | search_fields = ('username', 'email', 'first_name', 'last_name', 'monitor__country')
26 |
27 | # Re-register UserAdmin
28 | admin.site.unregister(User)
29 | admin.site.register(User, UserAdmin)
30 |
--------------------------------------------------------------------------------
/gmmp/migrations/0004_specialquestions.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.15 on 2020-09-01 05:39
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 | import django_countries.fields
7 |
8 |
9 | class Migration(migrations.Migration):
10 |
11 | dependencies = [
12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
13 | ('gmmp', '0003_auto_20200114_1330'),
14 | ]
15 |
16 | operations = [
17 | migrations.CreateModel(
18 | name='SpecialQuestions',
19 | fields=[
20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21 | ('country', django_countries.fields.CountryField(default='KE', max_length=2)),
22 | ('question_1', models.TextField()),
23 | ('question_2', models.TextField()),
24 | ('question_3', models.TextField()),
25 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
26 | ],
27 | ),
28 | ]
29 |
--------------------------------------------------------------------------------
/forms/migrations/0039_auto_20200120_1536.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.9 on 2020-01-20 15:36
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('forms', '0038_auto_20200115_0622'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='radioperson',
15 | name='special_qn_1',
16 | field=models.CharField(blank=True, choices=[('Y', '(1) Yes'), ('N', '(2) No')], max_length=1, verbose_name='(17) Special question 1'),
17 | ),
18 | migrations.AlterField(
19 | model_name='radioperson',
20 | name='special_qn_2',
21 | field=models.CharField(blank=True, choices=[('Y', '(1) Yes'), ('N', '(2) No')], max_length=1, verbose_name='(18) Special question 2'),
22 | ),
23 | migrations.AlterField(
24 | model_name='radioperson',
25 | name='special_qn_3',
26 | field=models.CharField(blank=True, choices=[('Y', '(1) Yes'), ('N', '(2) No')], max_length=1, verbose_name='(19) Special question 3'),
27 | ),
28 | ]
29 |
--------------------------------------------------------------------------------
/gmmp/migrations/0006_countryuser.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.16 on 2020-09-28 09:28
2 |
3 | from django.db import migrations, models
4 | import django_countries.fields
5 | import gsheets.mixins
6 | import uuid
7 |
8 |
9 | class Migration(migrations.Migration):
10 |
11 | dependencies = [
12 | ('gmmp', '0005_special_questions_gsheet_integration'),
13 | ]
14 |
15 | operations = [
16 | migrations.CreateModel(
17 | name='CountryUser',
18 | fields=[
19 | ('guid', models.CharField(default=uuid.uuid4, max_length=255, primary_key=True, serialize=False)),
20 | ('country', django_countries.fields.CountryField(default='KE', max_length=2)),
21 | ('firstname', models.CharField(max_length=127)),
22 | ('lastname', models.CharField(max_length=127)),
23 | ('username', models.CharField(max_length=127)),
24 | ('email', models.CharField(max_length=127)),
25 | ('designation', models.CharField(max_length=127)),
26 | ],
27 | bases=(gsheets.mixins.SheetPullableMixin, models.Model),
28 | ),
29 | ]
30 |
--------------------------------------------------------------------------------
/forms/migrations/0040_auto_20200120_1538.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.9 on 2020-01-20 15:38
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('forms', '0039_auto_20200120_1536'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='televisionperson',
15 | name='special_qn_1',
16 | field=models.CharField(blank=True, choices=[('Y', '(1) Yes'), ('N', '(2) No')], max_length=1, verbose_name='(19) Special question 1'),
17 | ),
18 | migrations.AlterField(
19 | model_name='televisionperson',
20 | name='special_qn_2',
21 | field=models.CharField(blank=True, choices=[('Y', '(1) Yes'), ('N', '(2) No')], max_length=1, verbose_name='(20) Special question 2'),
22 | ),
23 | migrations.AlterField(
24 | model_name='televisionperson',
25 | name='special_qn_3',
26 | field=models.CharField(blank=True, choices=[('Y', '(1) Yes'), ('N', '(2) No')], max_length=1, verbose_name='(21) Special question 3'),
27 | ),
28 | ]
29 |
--------------------------------------------------------------------------------
/forms/migrations/0041_auto_20200123_1224.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.9 on 2020-01-23 12:24
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('forms', '0040_auto_20200120_1538'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='internetnewsperson',
15 | name='special_qn_1',
16 | field=models.CharField(blank=True, choices=[('Y', '(1) Yes'), ('N', '(2) No')], max_length=1, verbose_name='(22) Special question 1'),
17 | ),
18 | migrations.AlterField(
19 | model_name='internetnewsperson',
20 | name='special_qn_2',
21 | field=models.CharField(blank=True, choices=[('Y', '(1) Yes'), ('N', '(2) No')], max_length=1, verbose_name='(23) Special question 2'),
22 | ),
23 | migrations.AlterField(
24 | model_name='internetnewsperson',
25 | name='special_qn_3',
26 | field=models.CharField(blank=True, choices=[('Y', '(1) Yes'), ('N', '(2) No')], max_length=1, verbose_name='(24) Special question 3'),
27 | ),
28 | ]
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | ## For ubuntu
4 |
5 | ```bash
6 | sudo apt-get install libpq-dev
7 | sudo apt-get install libpython-dev
8 | ```
9 |
10 | Install a postgres db if you don't already have one
11 |
12 | ```bash
13 | sudo apt-get install postgresql postgresql-contrib
14 | sudo /etc/init.d/postgresql start
15 | ```
16 |
17 | Install the Heroku toolbelt for deployment, database backup, etc
18 |
19 | ```bash
20 | wget -qO- https://toolbelt.heroku.com/install-ubuntu.sh | sh
21 | ```
22 |
23 | Grab a backup of the database.
24 |
25 | ```bash
26 | pg_dump > /tmp/dump # You can get the url from the Heroku config. This seems to take a long time
27 | sudo su - postgres
28 | createuser gmmp -W # set password to gmmp
29 | createuser c4saadmin -W # needed to prevent error in dump file
30 | createdb gmmp --owner gmmp
31 | psql U gmmp < /tmp/dump # might get an error complaining the code4saadmin doesn't exist
32 | ```
33 |
34 | You'll need to install postgres, psycopg as your development user
35 |
36 | ```bash
37 | git clone https://github.com/Code4SA/gmmp.git
38 | cd gmmp
39 | mkdir env
40 | virtualenv env
41 | source env/bin/activate
42 | pip install -r requirements.txt
43 | ```
44 |
--------------------------------------------------------------------------------
/gmmp/templates/admin/submit_line.html:
--------------------------------------------------------------------------------
1 | {% extends "admin/submit_line.html" %}
2 |
3 | {% load static i18n admin_urls %}
4 |
5 |
19 |
--------------------------------------------------------------------------------
/reports/migrations/0003_indonesia-weights.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import migrations
5 | from django_countries import countries
6 |
7 | def populate_weights(apps, schema_editor):
8 | Weights = apps.get_model("reports", "Weights")
9 | db_alias = schema_editor.connection.alias
10 |
11 | for item in COUNTRY_WEIGHTS:
12 | country = item['Country']
13 | item.pop('Country')
14 | for media_type, weight in item.items():
15 | w = Weights.objects.using(db_alias).create(
16 | country=country,
17 | media_type=media_type,
18 | weight=weight)
19 | w.save()
20 |
21 | def backwards(apps, schema_editor):
22 | pass
23 |
24 | class Migration(migrations.Migration):
25 |
26 | dependencies = [
27 | ('reports', '0002_populate_weights'),
28 | ]
29 |
30 | operations = [
31 | migrations.RunPython(
32 | populate_weights,
33 | backwards,
34 | ),
35 | ]
36 |
37 | COUNTRY_WEIGHTS= [
38 | {'Country': 'ID',
39 | 'Internet': '0',
40 | 'Print': '11',
41 | 'Radio': '1',
42 | 'Television': '7',
43 | 'Twitter': '0'}]
44 |
--------------------------------------------------------------------------------
/forms/migrations/0045_auto_20200306_1510.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.9 on 2020-03-06 15:10
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('forms', '0044_auto_20200226_0758'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='radiosheet',
15 | name='num_female_anchors',
16 | field=models.PositiveIntegerField(help_text='The anchor (or announcer, or presenter) is the person who introduces the newscast and the individual items within it. Note: You should only include the anchors/announcers. Do not include reporters or other journalists ', verbose_name='Number of female anchors'),
17 | ),
18 | migrations.AlterField(
19 | model_name='televisionsheet',
20 | name='num_female_anchors',
21 | field=models.PositiveIntegerField(help_text='The anchor (or announcer, or presenter) is the person who introduces the newscast and the individual items within it. Note: You should only include the anchors/announcers. Do not include reporters or other journalists ', verbose_name='Number of female anchors'),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/reports/migrations/0004_transnational_weights.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import migrations
5 | from django_countries import countries
6 |
7 | def populate_weights(apps, schema_editor):
8 | Weights = apps.get_model("reports", "Weights")
9 | db_alias = schema_editor.connection.alias
10 |
11 | for item in COUNTRY_WEIGHTS:
12 | country = item['Country']
13 | item.pop('Country')
14 | for media_type, weight in item.items():
15 | w = Weights.objects.using(db_alias).create(
16 | country=country,
17 | media_type=media_type,
18 | weight=weight)
19 | w.save()
20 |
21 | def backwards(apps, schema_editor):
22 | pass
23 |
24 | class Migration(migrations.Migration):
25 |
26 | dependencies = [
27 | ('reports', '0003_indonesia-weights'),
28 | ]
29 |
30 | operations = [
31 | migrations.RunPython(
32 | populate_weights,
33 | backwards,
34 | ),
35 | ]
36 |
37 | COUNTRY_WEIGHTS= [
38 | {'Country': 'T1',
39 | 'Internet': '1',
40 | 'Print': '1',
41 | 'Radio': '1',
42 | 'Television': '1',
43 | 'Twitter': '1'}]
44 |
--------------------------------------------------------------------------------
/gmmp/management/commands/bulk_password_reset_emails.py:
--------------------------------------------------------------------------------
1 | from django.core.management.base import BaseCommand
2 | from django.contrib.auth.models import User
3 | from django.core.mail import send_mass_mail
4 | from django.template.loader import render_to_string
5 | from django.core.mail import get_connection, EmailMultiAlternatives
6 | from django.conf import settings
7 |
8 |
9 | def send_mass_html_mail(subject, message, html_message, from_email, recipient_list):
10 | emails = []
11 | for recipient in recipient_list:
12 | email = EmailMultiAlternatives(subject, message, from_email, [recipient])
13 | email.attach_alternative(html_message, 'text/html')
14 | emails.append(email)
15 | return get_connection().send_messages(emails)
16 |
17 | class Command(BaseCommand):
18 | def handle(self, *args, **options):
19 | users = User.objects.filter(last_login=None)
20 | password_reset_form = render_to_string(
21 | 'emails/welcome_password_reset.html', {'SITE_URL': settings.SITE_URL})
22 | send_mass_html_mail(
23 | 'New account on app.gmmp.ngo',
24 | '''Hi there''',
25 | password_reset_form,
26 | settings.EMAIL_FROM,
27 | [user.email for user in users]
28 | )
29 |
--------------------------------------------------------------------------------
/forms/migrations/0029_auto_20191210_2042.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('forms', '0028_auto_20191210_2033'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='newspaperperson',
16 | name='special_qn_1',
17 | field=models.CharField(blank=True, max_length=1, verbose_name='(20) Special Question 1', choices=[(b'Y', '(1) Yes'), (b'N', '(2) No')]),
18 | preserve_default=True,
19 | ),
20 | migrations.AlterField(
21 | model_name='newspaperperson',
22 | name='special_qn_2',
23 | field=models.CharField(blank=True, max_length=1, verbose_name='(21) Special Question 2', choices=[(b'Y', '(1) Yes'), (b'N', '(2) No')]),
24 | preserve_default=True,
25 | ),
26 | migrations.AlterField(
27 | model_name='newspaperperson',
28 | name='special_qn_3',
29 | field=models.CharField(blank=True, max_length=1, verbose_name='(22) Special Question 3', choices=[(b'Y', '(1) Yes'), (b'N', '(2) No')]),
30 | preserve_default=True,
31 | ),
32 | ]
33 |
--------------------------------------------------------------------------------
/forms/migrations/0030_auto_20191210_2043.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('forms', '0029_auto_20191210_2042'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='newspaperperson',
16 | name='special_qn_1',
17 | field=models.CharField(blank=True, max_length=1, verbose_name='(20) Special question 1', choices=[(b'Y', '(1) Yes'), (b'N', '(2) No')]),
18 | preserve_default=True,
19 | ),
20 | migrations.AlterField(
21 | model_name='newspaperperson',
22 | name='special_qn_2',
23 | field=models.CharField(blank=True, max_length=1, verbose_name='(21) Special question 2', choices=[(b'Y', '(1) Yes'), (b'N', '(2) No')]),
24 | preserve_default=True,
25 | ),
26 | migrations.AlterField(
27 | model_name='newspaperperson',
28 | name='special_qn_3',
29 | field=models.CharField(blank=True, max_length=1, verbose_name='(22) Special question 3', choices=[(b'Y', '(1) Yes'), (b'N', '(2) No')]),
30 | preserve_default=True,
31 | ),
32 | ]
33 |
--------------------------------------------------------------------------------
/forms/migrations/0033_auto_20191212_2003.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('forms', '0032_auto_20191212_1932'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='radiosheet',
16 | name='num_female_anchors',
17 | field=models.PositiveIntegerField(help_text='The anchor (or announcer, or presenter) is the person who introduces the newscast and the individual items within it. Note: You should only include the anchors/announcers. Do not include reporters or other ', verbose_name='Number of female anchors'),
18 | preserve_default=True,
19 | ),
20 | migrations.AlterField(
21 | model_name='televisionsheet',
22 | name='num_female_anchors',
23 | field=models.PositiveIntegerField(help_text='The anchor (or announcer, or presenter) is the person who introduces the newscast and the individual items within it. Note: You should only include the anchors/announcers. Do not include reporters or other ', verbose_name='Number of female anchors'),
24 | preserve_default=True,
25 | ),
26 | ]
27 |
--------------------------------------------------------------------------------
/reports/migrations/0008_gsheetcountryweights.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.18 on 2021-03-01 21:07
2 |
3 | from django.db import migrations, models
4 | import django_countries.fields
5 | import gsheets.mixins
6 | import uuid
7 |
8 |
9 | class Migration(migrations.Migration):
10 |
11 | dependencies = [
12 | ('reports', '0007_proxy_weights'),
13 | ]
14 |
15 | operations = [
16 | migrations.CreateModel(
17 | name='GSheetCountryWeights',
18 | fields=[
19 | ('guid', models.CharField(default=uuid.uuid4, max_length=255, primary_key=True, serialize=False)),
20 | ('country', django_countries.fields.CountryField(default='KE', max_length=2)),
21 | ('print_weight', models.DecimalField(decimal_places=2, default=0.0, max_digits=4)),
22 | ('radio_weight', models.DecimalField(decimal_places=2, default=0.0, max_digits=4)),
23 | ('tv_weight', models.DecimalField(decimal_places=2, default=0.0, max_digits=4)),
24 | ('internet_weight', models.DecimalField(decimal_places=2, default=0.0, max_digits=4)),
25 | ('twitter_weight', models.DecimalField(decimal_places=2, default=0.0, max_digits=4)),
26 | ],
27 | bases=(gsheets.mixins.SheetPullableMixin, models.Model),
28 | ),
29 | ]
30 |
--------------------------------------------------------------------------------
/forms/migrations/0006_auto_20150309_1403.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('forms', '0005_internetnewsperson_age'),
11 | ]
12 |
13 | operations = [
14 | migrations.AddField(
15 | model_name='internetnewsperson',
16 | name='is_photograph',
17 | field=models.PositiveIntegerField(default=None, verbose_name='Is there a photograph of the person in the story?', choices=[(1, 'Yes'), (2, 'No'), (3, 'Do not know')]),
18 | preserve_default=False,
19 | ),
20 | migrations.AddField(
21 | model_name='internetnewsperson',
22 | name='is_quoted',
23 | field=models.CharField(default=None, help_text='A person is directly quoted if their own words are printed, e.g. "The war against terror is our first priority" said President Bush.
If the story paraphrases what the person said, that is not a direct quote, e.g. President Bush said that top priority would be given to fighting the war against terror.
', max_length=1, verbose_name='Is the person directly quoted', choices=[(b'Y', 'Yes'), (b'N', 'No')]),
24 | preserve_default=False,
25 | ),
26 | ]
27 |
--------------------------------------------------------------------------------
/gmmp/templates/report_filter.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load static %}
3 |
4 | {% block page_content %}
5 |
6 |
7 |
8 |
9 |
Welcome to the 2020 GMMP Reporting tool.
10 |
11 |
12 |
13 |
19 |
27 |
28 |
36 | {% endblock page_content %}
37 |
--------------------------------------------------------------------------------
/reports/migrations/0005_duplicate_weights.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.18 on 2021-02-18 12:12
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | def remove_duplicate_weights(apps, schema_editor):
7 | Weights = apps.get_model("reports", "Weights")
8 | db_alias = schema_editor.connection.alias
9 |
10 | # Remove old transnational since a new one will be created with a new code
11 | Weights.objects.using(db_alias).filter(country="T1").delete()
12 |
13 | # Remove all (country, media_type) duplicates
14 | # Based on: https://stackoverflow.com/a/10290420
15 | last_seen_country = None
16 | last_seen_media_type = None
17 | weights = Weights.objects.using(db_alias).all().order_by("country", "media_type")
18 | for weight in weights:
19 | if (
20 | weight.country == last_seen_country
21 | and weight.media_type == last_seen_media_type
22 | ):
23 | weight.delete()
24 | else:
25 | last_seen_media_type = weight.media_type
26 | last_seen_country = weight.country
27 |
28 |
29 | def backwards(apps, schema_editor):
30 | pass
31 |
32 |
33 | class Migration(migrations.Migration):
34 |
35 | dependencies = [
36 | ("reports", "0004_transnational_weights"),
37 | ]
38 |
39 | operations = [
40 | migrations.RunPython(
41 | remove_duplicate_weights,
42 | backwards,
43 | ),
44 | ]
45 |
--------------------------------------------------------------------------------
/forms/migrations/0053_add_monitoring_mode_20200909_1013.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.16 on 2020-09-10 08:25
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('forms', '0052_make_fields_nullable_20200907_0936'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='internetnewssheet',
15 | name='monitor_mode',
16 | field=models.IntegerField(choices=[(1, 'Long'), (2, 'Short')], default=1),
17 | ),
18 | migrations.AddField(
19 | model_name='newspapersheet',
20 | name='monitor_mode',
21 | field=models.IntegerField(choices=[(1, 'Long'), (2, 'Short')], default=1),
22 | ),
23 | migrations.AddField(
24 | model_name='radiosheet',
25 | name='monitor_mode',
26 | field=models.IntegerField(choices=[(1, 'Long'), (2, 'Short')], default=1),
27 | ),
28 | migrations.AddField(
29 | model_name='televisionsheet',
30 | name='monitor_mode',
31 | field=models.IntegerField(choices=[(1, 'Long'), (2, 'Short')], default=1),
32 | ),
33 | migrations.AddField(
34 | model_name='twittersheet',
35 | name='monitor_mode',
36 | field=models.IntegerField(choices=[(1, 'Long'), (2, 'Short')], default=1),
37 | ),
38 | ]
39 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | db:
4 | image: postgres:9.6
5 | ports:
6 | - "54321:5432"
7 | environment:
8 | - POSTGRES_USER=gmmp
9 | - POSTGRES_PASSWORD=gmmp
10 | - POSTGRES_DB=gmmp
11 | - PGUSER=gmmp
12 | - PGPASSWORD=gmmp
13 | web:
14 | build: .
15 | ports:
16 | - "8000:8000"
17 | volumes:
18 | - .:/app
19 | - logvolume01:/var/log
20 | depends_on:
21 | - db
22 | environment:
23 | - DATABASE_URL=postgresql://gmmp:gmmp@db:5432/gmmp
24 | - DJANGO_SECRET_KEY=somethingsecret
25 | - SITE_URL=http://localhost:8000
26 | - PGHOST=db
27 | - PGDATABASE=gmmp
28 | - PGUSER=gmmp
29 | - PGPASSWORD=gmmp
30 | - PYTHONDONTWRITEBYTECODE=True
31 | - DJANGO_DEBUG=${DJANGO_DEBUG:-True} # For testing deploys
32 | - GSHEETS_WEIGHTS_SPREADSHEET_ID=${GSHEETS_WEIGHTS_SPREADSHEET_ID:-}
33 | - GMMP_EMAIL_HOST_PASSWORD=${GMMP_EMAIL_HOST_PASSWORD:-}
34 | - GMMP_GUNICORN_TIMEOUT=${GMMP_GUNICORN_TIMEOUT:-120}
35 | - GMMP_GUNICORN_WORKERS=${GMMP_GUNICORN_WORKERS:-3}
36 | - GMMP_REPORTS_HISTORICAL_YEAR=${GMMP_REPORTS_HISTORICAL_YEAR:-2010}
37 | - OAUTHLIB_RELAX_TOKEN_SCOPE=1 # https://stackoverflow.com/a/51643134
38 | command: [
39 | "/cmd.sh",
40 | "--log-level=debug",
41 | "--reload",
42 | "gmmp.wsgi:application",
43 | ]
44 |
45 | volumes:
46 | logvolume01: {}
47 |
--------------------------------------------------------------------------------
/gmmp/management/commands/sync_users.py:
--------------------------------------------------------------------------------
1 | import os
2 | from django.core.management.base import BaseCommand
3 | from django.contrib.auth.models import User, Group
4 | from django.core.management import call_command
5 | from django.conf import settings
6 | from gmmp.models import Monitor, CountryUser
7 |
8 | class Command(BaseCommand):
9 | def handle(self, *args, **options):
10 | # Remove all users in the system
11 | CountryUser.objects.all().delete()
12 | User.objects.filter().delete()
13 |
14 | # Pull all gmmp users
15 | try:
16 | call_command('syncgsheets')
17 | except Exception:
18 | # TODO check why an exception is being raised despite all users being pulled
19 | pass
20 |
21 | country_users = CountryUser.objects.all()
22 | for country_user in country_users:
23 | # Create users
24 | group, _ = Group.objects.get_or_create(name=country_user.designation)
25 | user, _ = User.objects.get_or_create(email=country_user.email,
26 | first_name=country_user.firstname, last_name=country_user.lastname, username=country_user.username)
27 | user.set_password(settings.COUNTRY_USER_DEFAULT_PASSWORD)
28 | user.groups.add(group)
29 |
30 | user.is_staff = True
31 | user.save()
32 | monitor, _ = Monitor.objects.get_or_create(user=user)
33 | monitor.country = country_user.country
34 | monitor.save()
35 |
--------------------------------------------------------------------------------
/gmmp/templatetags/i18n_switcher.py:
--------------------------------------------------------------------------------
1 | from django import template
2 | from django.conf import settings
3 | from django.template.defaultfilters import stringfilter
4 |
5 | register = template.Library()
6 |
7 |
8 | def switch_lang_code(path, language):
9 |
10 | # Get the supported language codes
11 | lang_codes = [c for (c, name) in settings.LANGUAGES]
12 |
13 | # Validate the inputs
14 | if path == "":
15 | raise Exception("URL path for language switch is empty")
16 | elif path[0] != "/":
17 | raise Exception('URL path for language switch does not start with "/"')
18 | elif language not in lang_codes:
19 | raise Exception("%s is not a supported language code" % language)
20 |
21 | # Split the parts of the path
22 | parts = path.split("/")
23 |
24 | # Add or substitute the new language prefix
25 | if parts[1] in lang_codes:
26 | if language == "en":
27 | parts.pop(1)
28 | else:
29 | parts[1] = language
30 | elif not language == "en":
31 | parts[0] = "/" + language
32 |
33 | # Return the full new path
34 | return "/".join(parts)
35 |
36 |
37 | @register.filter
38 | @stringfilter
39 | def switch_i18n_prefix(path, language):
40 | """takes in a string path"""
41 | return switch_lang_code(path, language)
42 |
43 |
44 | @register.filter
45 | def switch_i18n(request, language):
46 | """takes in a request object and gets the path from it"""
47 | return switch_lang_code(request.get_full_path(), language)
48 |
--------------------------------------------------------------------------------
/gmmp/static/css/3-col-portfolio.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Start Bootstrap - 3 Col Portfolio HTML Template (http://startbootstrap.com)
3 | * Code licensed under the Apache License v2.0.
4 | * For details, see http://www.apache.org/licenses/LICENSE-2.0.
5 | */
6 |
7 | body {
8 | padding-top: 30px;
9 | }
10 |
11 | .portfolio-item {
12 | margin-bottom: 25px;
13 | }
14 |
15 | footer {
16 | margin: 50px 0;
17 | }
18 |
19 | h2 {
20 | color: rgb(51, 122, 183);
21 | }
22 |
23 | .box a, .box a:hover, .box a:active {
24 | text-decoration: none;
25 | }
26 |
27 | .box {
28 | width: 360px;
29 | height: 206px;
30 | font-size: 25px;
31 | background-color: #efefef;
32 | text-align: center;
33 | position: relative;
34 | text-decoration: none;
35 | }
36 |
37 | .box div {
38 | top: 85px;
39 | position:relative;
40 | -webkit-touch-callout: none;
41 | -webkit-user-select: none;
42 | -khtml-user-select: none;
43 | -moz-user-select: none;
44 | -ms-user-select: none;
45 | user-select: none;
46 | color: #454545;
47 | }
48 |
49 | .box i {
50 | top: 120px;
51 | left: 150px;
52 | color: #454545;
53 | }
54 |
55 | .box div:hover, .box i:hover {
56 | cursor: pointer;
57 | text-decoration: none;
58 | }
59 |
60 | .box div:active, .box i:active {
61 | cursor: pointer;
62 | color: #acacac;
63 | }
64 |
65 | .user-greeting img {
66 | margin-bottom: 3px;
67 | margin-left: 3px;
68 | }
69 |
70 | .header-instructions {
71 | color: rgb(51, 122, 183);
72 | font-size: 20px;
73 | }
74 |
--------------------------------------------------------------------------------
/gmmp/static/wazimap/js.cba122a9.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["stories.styles.css","barchart.css"],"names":[],"mappings":"AAAA,WACI,qBAAuB,CACvB,WAAY,CACZ,YAAa,CACb,eAAgB,CAChB,gBACJ,CAKA,QACI,aAAc,CACd,WAAY,CACZ,yBAA8B,CAC9B,UAAW,CACX,iBAAkB,CAClB,cAAe,CACf,mBACJ,CAGA,gBAOI,eAAgB,CAIhB,eAAkB,CAClB,QAEJ,CAGA,iCAhBI,qBAAsB,CACtB,cAAe,CACf,cAAe,CACf,UAAW,CACX,aAAc,CACd,oBAAyB,CAEzB,iBAAkB,CAClB,iBAAkB,CAIlB,MAkBJ,CAdA,iBAOI,eAAgB,CAIhB,eAAkB,CAClB,WAEJ,CCrDA,qCACI,YACJ","file":"js.cba122a9.css","sourceRoot":"../src","sourcesContent":[".container {\n border: 1px solid black;\n width: 800px;\n height: 100px;\n margin-top: 50px;\n margin-left: 50px;\n}\n\n.bar {\n}\n\n.d3-tip {\n line-height: 1;\n padding: 8px;\n background: rgba(0, 0, 0, 0.8);\n color: #fff;\n border-radius: 4px;\n font-size: 16px;\n pointer-events: none;\n}\n\n/* Style northward tooltips specifically */\n.d3-tip.n:after {\n box-sizing: border-box;\n display: inline;\n font-size: 10px;\n width: 100%;\n line-height: 1;\n color: rgba(0, 0, 0, 0.8);\n content: \"\\25BC\";\n position: absolute;\n text-align: center;\n\n margin: -2px 0 0 0;\n top: 100%;\n left: 0;\n}\n\n/* Style southward tooltips specifically */\n.d3-tip.s:before {\n box-sizing: border-box;\n display: inline;\n font-size: 10px;\n width: 100%;\n line-height: 1;\n color: rgba(0, 0, 0, 0.8);\n content: \"\\25B2\";\n position: absolute;\n text-align: center;\n\n margin: 0 0 -2px 0;\n bottom: 100%;\n left: 0;\n}\n",".indicator__chart_container rect.bar {\n fill: #E4653D;\n}\n"]}
--------------------------------------------------------------------------------
/reports/management/commands/generate_dataset.py:
--------------------------------------------------------------------------------
1 | import os
2 | import csv
3 | from django.core.management.base import BaseCommand
4 |
5 | from reports.report_builder import XLSXReportBuilder
6 | from reports.report_dataset import generate_dataset_desc
7 |
8 | from reports.forms import GlobalForm
9 |
10 |
11 | class Command(BaseCommand):
12 | help = "Generates dataset from the GMMP report"
13 |
14 | def add_arguments(self, parser):
15 | parser.add_argument("-w","--worksheets", nargs="+", help="Worksheets to generate dataset", required=False)
16 | parser.add_argument("-d", "--dataset-details", action="store_true", help="Filename to store the selected worksheet details")
17 |
18 | def handle(self, *args, **options):
19 | # Create the dataset directory if it doesn't exist
20 | os.makedirs("dataset", exist_ok=True)
21 | if options['worksheets']:
22 | dataset_sheets = options['worksheets']
23 | else:
24 | dataset_sheets = ["ws_05", "ws_06", "ws_09", "ws_15",
25 | "ws_28b", "ws_28c", "ws_30", "ws_38", "ws_41", "ws_47", "ws_48",
26 | "ws_83", "ws_85", "ws_92", "ws_93", "ws_97", "ws_100", "ws_101",
27 | "ws_102", "ws_104"]
28 |
29 | chart_filename = options.get("dataset-details") if options.get("dataset-details") else "gmmp_dataset"
30 | generate_dataset_desc(chart_filename, dataset_sheets)
31 |
32 | form = GlobalForm()
33 | xlsx = XLSXReportBuilder(form).build(dataset_sheets=dataset_sheets)
34 |
--------------------------------------------------------------------------------
/forms/migrations/0013_auto_20150324_0700.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('forms', '0012_auto_20150312_1400'),
11 | ]
12 |
13 | operations = [
14 | migrations.RemoveField(
15 | model_name='internetnewssheet',
16 | name='country',
17 | ),
18 | migrations.RemoveField(
19 | model_name='internetnewssheet',
20 | name='monitor',
21 | ),
22 | migrations.RemoveField(
23 | model_name='newspapersheet',
24 | name='country',
25 | ),
26 | migrations.RemoveField(
27 | model_name='newspapersheet',
28 | name='monitor',
29 | ),
30 | migrations.RemoveField(
31 | model_name='radiosheet',
32 | name='country',
33 | ),
34 | migrations.RemoveField(
35 | model_name='radiosheet',
36 | name='monitor',
37 | ),
38 | migrations.RemoveField(
39 | model_name='televisionsheet',
40 | name='country',
41 | ),
42 | migrations.RemoveField(
43 | model_name='televisionsheet',
44 | name='monitor',
45 | ),
46 | migrations.RemoveField(
47 | model_name='twittersheet',
48 | name='country',
49 | ),
50 | migrations.RemoveField(
51 | model_name='twittersheet',
52 | name='monitor',
53 | ),
54 | ]
55 |
--------------------------------------------------------------------------------
/forms/migrations/0012_auto_20150312_1400.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 | import django_countries.fields
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('forms', '0011_auto_20150312_1358'),
12 | ]
13 |
14 | operations = [
15 | migrations.AlterField(
16 | model_name='internetnewssheet',
17 | name='country',
18 | field=django_countries.fields.CountryField(max_length=2, null=True),
19 | preserve_default=True,
20 | ),
21 | migrations.AlterField(
22 | model_name='newspapersheet',
23 | name='country',
24 | field=django_countries.fields.CountryField(max_length=2, null=True),
25 | preserve_default=True,
26 | ),
27 | migrations.AlterField(
28 | model_name='radiosheet',
29 | name='country',
30 | field=django_countries.fields.CountryField(max_length=2, null=True),
31 | preserve_default=True,
32 | ),
33 | migrations.AlterField(
34 | model_name='televisionsheet',
35 | name='country',
36 | field=django_countries.fields.CountryField(max_length=2, null=True),
37 | preserve_default=True,
38 | ),
39 | migrations.AlterField(
40 | model_name='twittersheet',
41 | name='country',
42 | field=django_countries.fields.CountryField(max_length=2, null=True),
43 | preserve_default=True,
44 | ),
45 | ]
46 |
--------------------------------------------------------------------------------
/forms/migrations/0011_auto_20150312_1358.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 | import django_countries.fields
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('forms', '0010_televisionsheet_comments'),
12 | ]
13 |
14 | operations = [
15 | migrations.AddField(
16 | model_name='internetnewssheet',
17 | name='country',
18 | field=django_countries.fields.CountryField(default=None, max_length=2),
19 | preserve_default=False,
20 | ),
21 | migrations.AddField(
22 | model_name='newspapersheet',
23 | name='country',
24 | field=django_countries.fields.CountryField(default=None, max_length=2),
25 | preserve_default=False,
26 | ),
27 | migrations.AddField(
28 | model_name='radiosheet',
29 | name='country',
30 | field=django_countries.fields.CountryField(default=None, max_length=2),
31 | preserve_default=False,
32 | ),
33 | migrations.AddField(
34 | model_name='televisionsheet',
35 | name='country',
36 | field=django_countries.fields.CountryField(default=None, max_length=2),
37 | preserve_default=False,
38 | ),
39 | migrations.AddField(
40 | model_name='twittersheet',
41 | name='country',
42 | field=django_countries.fields.CountryField(default=None, max_length=2),
43 | preserve_default=False,
44 | ),
45 | ]
46 |
--------------------------------------------------------------------------------
/forms/migrations/0057_add_monitor_code.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.16 on 2020-09-17 11:54
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('forms', '0056_allow_blank_and_none'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='internetnewssheet',
15 | name='monitor_code',
16 | field=models.CharField(default='', max_length=255, verbose_name='Monitor Code'),
17 | preserve_default=False,
18 | ),
19 | migrations.AddField(
20 | model_name='newspapersheet',
21 | name='monitor_code',
22 | field=models.CharField(default='', max_length=255, verbose_name='Monitor Code'),
23 | preserve_default=False,
24 | ),
25 | migrations.AddField(
26 | model_name='radiosheet',
27 | name='monitor_code',
28 | field=models.CharField(default='', max_length=255, verbose_name='Monitor Code'),
29 | preserve_default=False,
30 | ),
31 | migrations.AddField(
32 | model_name='televisionsheet',
33 | name='monitor_code',
34 | field=models.CharField(default='', max_length=255, verbose_name='Monitor Code'),
35 | preserve_default=False,
36 | ),
37 | migrations.AddField(
38 | model_name='twittersheet',
39 | name='monitor_code',
40 | field=models.CharField(default='', max_length=255, verbose_name='Monitor Code'),
41 | preserve_default=False,
42 | ),
43 | ]
44 |
--------------------------------------------------------------------------------
/forms/migrations/0054_monitoring_mode_messages_20200910_1441.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.16 on 2020-09-10 14:41
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('forms', '0053_add_monitoring_mode_20200909_1013'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='internetnewssheet',
15 | name='monitor_mode',
16 | field=models.IntegerField(choices=[(1, 'Full monitoring'), (2, 'Short monitoring')], default=1, verbose_name='Format'),
17 | ),
18 | migrations.AlterField(
19 | model_name='newspapersheet',
20 | name='monitor_mode',
21 | field=models.IntegerField(choices=[(1, 'Full monitoring'), (2, 'Short monitoring')], default=1, verbose_name='Format'),
22 | ),
23 | migrations.AlterField(
24 | model_name='radiosheet',
25 | name='monitor_mode',
26 | field=models.IntegerField(choices=[(1, 'Full monitoring'), (2, 'Short monitoring')], default=1, verbose_name='Format'),
27 | ),
28 | migrations.AlterField(
29 | model_name='televisionsheet',
30 | name='monitor_mode',
31 | field=models.IntegerField(choices=[(1, 'Full monitoring'), (2, 'Short monitoring')], default=1, verbose_name='Format'),
32 | ),
33 | migrations.AlterField(
34 | model_name='twittersheet',
35 | name='monitor_mode',
36 | field=models.IntegerField(choices=[(1, 'Full monitoring'), (2, 'Short monitoring')], default=1, verbose_name='Format'),
37 | ),
38 | ]
39 |
--------------------------------------------------------------------------------
/forms/migrations/0017_auto_20150331_1815.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('forms', '0016_auto_20150330_1413'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='radiosheet',
16 | name='item_number',
17 | field=models.PositiveIntegerField(help_text='Write in the number that describes the position of the story within the newscast. E.g. the first story in the newscast is item 1; the seventh story is item 7.', verbose_name='(1) Item Number', choices=[(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7), (8, 8), (9, 9), (10, 10), (11, 11), (12, 12), (13, 13), (14, 14), (15, 15), (16, 16), (17, 17), (18, 18), (19, 19), (20, 20), (21, 21), (22, 22), (23, 23), (24, 24), (25, 25), (26, 26), (27, 27), (28, 28), (29, 29)]),
18 | preserve_default=True,
19 | ),
20 | migrations.AlterField(
21 | model_name='televisionsheet',
22 | name='item_number',
23 | field=models.PositiveIntegerField(help_text='Write in the number that describes the position of the story within the newscast. E.g. the first story in the newscast is item 1; the seventh story is item 7.', verbose_name='(1) Item Number', choices=[(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7), (8, 8), (9, 9), (10, 10), (11, 11), (12, 12), (13, 13), (14, 14), (15, 15), (16, 16), (17, 17), (18, 18), (19, 19), (20, 20), (21, 21), (22, 22), (23, 23), (24, 24), (25, 25), (26, 26), (27, 27), (28, 28), (29, 29)]),
24 | preserve_default=True,
25 | ),
26 | ]
27 |
--------------------------------------------------------------------------------
/forms/migrations/0060_add_moldova_to_country_region.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 | COUNTRY_REGION = [
7 | ("Moldova", "Europe"),
8 | ]
9 |
10 | def populate_country_region(apps, schema_editor):
11 | from django_countries import countries
12 | CountryRegion = apps.get_model("forms", "CountryRegion")
13 | db_alias = schema_editor.connection.alias
14 |
15 | country_region_objs = CountryRegion.objects.using(db_alias).all()
16 | region_map = {}
17 |
18 | # Map country codes to regions
19 | for country_region in COUNTRY_REGION:
20 | code = countries.by_name(country_region[0])
21 | if code:
22 | if country_region[1] in region_map:
23 | region_map[country_region[1]].append(code)
24 | else:
25 | region_map[country_region[1]] = [code]
26 |
27 | # Create CountryRegion objects for supplied pairs
28 | for region, country_list in region_map.items():
29 | for country in country_list:
30 | # Is this check necessary?
31 | if not country_region_objs.filter(country=country):
32 | CountryRegion.objects.using(db_alias).create(
33 | country=country,
34 | region=region)
35 |
36 |
37 | def backwards(apps, schema_editor):
38 | """
39 | Table gets dropped, so no need to delete the rows
40 | """
41 | pass
42 |
43 |
44 | class Migration(migrations.Migration):
45 |
46 | dependencies = [
47 | ("forms", "0059_update_country_region"),
48 | ]
49 |
50 | operations = [
51 | migrations.RunPython(populate_country_region, backwards),
52 | ]
53 |
--------------------------------------------------------------------------------
/gmmp/management/commands/map_weights_to_codes.py:
--------------------------------------------------------------------------------
1 | import csv
2 | from pprint import pprint
3 |
4 | from django.core.management.base import BaseCommand
5 |
6 | from django_countries import countries
7 | from forms.modelutils import CountryRegion
8 |
9 | class Command(BaseCommand):
10 | args = 'input_file output_file'
11 | help = 'Maps the given country names to there codes and regions.'
12 | def handle(self, *args, **options):
13 | country_weightings = {}
14 | with open(args[1], 'wb') as output:
15 | with open(args[0]) as csvfile:
16 | writer = csv.writer(output)
17 | reader = csv.DictReader(csvfile)
18 | writer.writerow(['Country', 'Region', 'Print', 'Radio', 'TV', 'Online'])
19 | for row in reader:
20 | if row['Country'] == "Ivory Coast":
21 | row['Country'] = u"C\xf4te d'Ivoire"
22 | code = countries.by_name(row['Country'])
23 | region = CountryRegion.objects.get(country=code).region
24 | if not code:
25 | self.stdout.write('Country not found %s' % row['Country'])
26 | break
27 | writer.writerow([
28 | code, region, row['Print'], row['Radio'], row['TV'], row['Online']
29 | ])
30 | country_weightings[code] = {
31 | 'Region': region,
32 | 'Print': row['Print'],
33 | 'Radio': row['Radio'],
34 | 'Television': row['TV'],
35 | 'Internet': row['Online']
36 | }
37 | pprint(country_weightings)
38 |
--------------------------------------------------------------------------------
/gmmp/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.conf import settings
3 | from django.contrib.auth.models import User
4 | from django_countries.fields import CountryField
5 | from gsheets.mixins import SheetPullableMixin
6 | from uuid import uuid4
7 |
8 | class Monitor(models.Model):
9 | user = models.OneToOneField(User, on_delete=models.CASCADE)
10 | country = CountryField(default='KE')
11 |
12 | def __unicode__(self):
13 | return "%s" % self.country
14 |
15 | class SpecialQuestions(SheetPullableMixin, models.Model):
16 | spreadsheet_id = settings.GSHEETS_SPECIAL_QUESTIONS['SPREADSHEET_ID']
17 | sheet_name = settings.GSHEETS_SPECIAL_QUESTIONS['SHEET_NAME']
18 | model_id_field = 'guid'
19 | sheet_id_field = 'Platform ID'
20 |
21 | guid = models.CharField(primary_key=True, max_length=255, default=uuid4)
22 |
23 | country = CountryField(default='KE')
24 | question_1 = models.TextField()
25 | question_2 = models.TextField()
26 | question_3 = models.TextField()
27 |
28 | def __str__(self):
29 | return f"{self.country} Special Questions"
30 |
31 | class CountryUser(SheetPullableMixin, models.Model):
32 | spreadsheet_id = settings.GSHEET_COUNTRY_USERS['SPREADSHEET_ID']
33 | sheet_name = settings.GSHEET_COUNTRY_USERS['SHEET_NAME']
34 | model_id_field = 'guid'
35 | sheet_id_field = 'Platform ID'
36 |
37 | guid = models.CharField(primary_key=True, max_length=255, default=uuid4)
38 |
39 | country = CountryField(default="KE")
40 | firstname = models.CharField(max_length=127)
41 | lastname = models.CharField(max_length=127)
42 | username = models.CharField(max_length=127)
43 | email = models.CharField(max_length=127)
44 | designation = models.CharField(max_length=127)
45 |
--------------------------------------------------------------------------------
/forms/migrations/0036_auto_20200114_1337.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.9 on 2020-01-14 13:37
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('forms', '0035_auto_20200114_1330'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='internetnewssheet',
15 | name='stereotypes',
16 | field=models.PositiveIntegerField(choices=[(1, '(1) Agree'), (2, '(2) Disagree')], verbose_name='(9) This story clearly challenges gender stereotypes'),
17 | ),
18 | migrations.AlterField(
19 | model_name='newspapersheet',
20 | name='stereotypes',
21 | field=models.PositiveIntegerField(choices=[(1, '(1) Agree'), (2, '(2) Disagree')], verbose_name='(8) This story clearly challenges gender stereotypes'),
22 | ),
23 | migrations.AlterField(
24 | model_name='radiosheet',
25 | name='stereotypes',
26 | field=models.PositiveIntegerField(choices=[(1, '(1) Agree'), (2, '(2) Disagree')], verbose_name='(7) This story clearly challenges gender stereotypes'),
27 | ),
28 | migrations.AlterField(
29 | model_name='televisionsheet',
30 | name='stereotypes',
31 | field=models.PositiveIntegerField(choices=[(1, '(1) Agree'), (2, '(2) Disagree')], verbose_name='(7) This story clearly challenges gender stereotypes'),
32 | ),
33 | migrations.AlterField(
34 | model_name='twittersheet',
35 | name='stereotypes',
36 | field=models.PositiveIntegerField(choices=[(1, '(1) Agree'), (2, '(2) Disagree')], verbose_name='(6) This story clearly challenges gender stereotypes'),
37 | ),
38 | ]
39 |
--------------------------------------------------------------------------------
/gmmp/static/wazimap/normalize.83e55e48.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:none}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}
2 | /*# sourceMappingURL=normalize.83e55e48.css.map */
--------------------------------------------------------------------------------
/forms/migrations/0022_assign_country_region_to_sheet_models.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 | def assign_country_region_to_sheet(apps, schema_editor):
7 | from forms.models import sheet_models
8 |
9 | CountryRegion = apps.get_model("forms", "CountryRegion")
10 | db_alias = schema_editor.connection.alias
11 |
12 | for name, model in sheet_models.items():
13 | sheets_model = apps.get_model("forms", model._meta.object_name)
14 | sheets = sheets_model.objects.using(db_alias).all()
15 | for sheet in sheets:
16 | try:
17 | country_region = CountryRegion.objects.using(db_alias).get(country=sheet.country.code)
18 | sheet.country_region = country_region
19 | sheet.save()
20 | except CountryRegion.DoesNotExist:
21 | # Assign to unmapped CountryRegion object
22 | country_region = CountryRegion.objects.using(db_alias).get(region='Unmapped')
23 | sheet.country_region = country_region
24 | sheet.save()
25 |
26 | def backwards(apps, schema_editor):
27 | from forms.models import sheet_models
28 | db_alias = schema_editor.connection.alias
29 |
30 | for name, model in sheet_models.items():
31 | sheets_model = apps.get_model("forms", model._meta.object_name)
32 | sheets = sheets_model.objects.using(db_alias).all()
33 | for sheet in sheets:
34 | sheet.country_region = None
35 | sheet.save()
36 |
37 | class Migration(migrations.Migration):
38 |
39 | dependencies = [
40 | ('forms', '0021_auto_20150511_1414'),
41 | ]
42 |
43 | operations = [
44 | migrations.RunPython(
45 | assign_country_region_to_sheet,
46 | backwards,
47 | ),
48 | ]
49 |
--------------------------------------------------------------------------------
/gmmp/signals.py:
--------------------------------------------------------------------------------
1 | from django.core.exceptions import ObjectDoesNotExist
2 | from django.db.models.signals import pre_save, post_save
3 | from django.dispatch import receiver
4 | from django.contrib.auth.models import User, Group
5 | from django_countries.fields import countries
6 | from gsheets.signals import sheet_row_processed
7 | from .models import Monitor, SpecialQuestions, CountryUser
8 |
9 | @receiver(post_save, sender=User)
10 | def create_admin_group(sender, instance, signal, created, **kwargs):
11 | try:
12 | monitor = instance.monitor
13 | except:
14 | monitor, created = Monitor.objects.get_or_create(user=instance)
15 | monitor.save()
16 | Group.objects.get_or_create(name='%s_admin' % monitor.country)
17 |
18 | @receiver(sheet_row_processed, sender=SpecialQuestions)
19 | def match_special_questions_columns_to_fields(instance=None, created=None, row_data=None, **kwargs):
20 | try:
21 | instance.country = countries.alpha2(row_data['Country'])
22 | instance.question_1 = row_data['Question 1'].strip()
23 | instance.question_2 = row_data['Question 2'].strip()
24 | instance.question_3 = row_data['Question 3'].strip()
25 | instance.save()
26 | except (ObjectDoesNotExist, KeyError):
27 | pass
28 |
29 | @receiver(sheet_row_processed, sender=CountryUser)
30 | def match_country_users_columns_to_fields(instance=None, created=None, row_data=None, **kwargs):
31 | try:
32 | instance.country = countries.alpha2(row_data['Country'])
33 | instance.firstname = row_data['Firstname'].strip()
34 | instance.lastname = row_data['Lastname'].strip()
35 | instance.username = row_data['Username'].strip()
36 | instance.email = row_data['Email'].strip()
37 | instance.designation = row_data['Designation'].strip()
38 | instance.save()
39 | except (ObjectDoesNotExist, KeyError):
40 | pass
41 |
--------------------------------------------------------------------------------
/reports/signals.py:
--------------------------------------------------------------------------------
1 | from django.core.exceptions import ObjectDoesNotExist
2 | from django.dispatch import receiver
3 |
4 | from django_countries import countries
5 | from gsheets.signals import sheet_row_processed
6 |
7 | from .models import Weights, GSheetCountryWeights
8 |
9 |
10 | @receiver(sheet_row_processed, sender=GSheetCountryWeights)
11 | def update_or_create_weights_from_gsheetweights(
12 | instance=None, created=None, row_data=None, **kwargs
13 | ):
14 | try:
15 | country = countries.alpha2(row_data["Country"])
16 | print_weight = row_data["Print"]
17 | radio_weight = row_data["Radio"]
18 | television_weight = row_data["Television"]
19 | internet_weight = row_data["Internet"]
20 | twitter_weight = row_data["Twitter"]
21 | Weights.objects.update_or_create(
22 | country=country, media_type="Print", defaults={"weight": print_weight}
23 | )
24 | Weights.objects.update_or_create(
25 | country=country, media_type="Radio", defaults={"weight": radio_weight}
26 | )
27 | Weights.objects.update_or_create(
28 | country=country,
29 | media_type="Television",
30 | defaults={"weight": television_weight},
31 | )
32 | Weights.objects.update_or_create(
33 | country=country, media_type="Internet", defaults={"weight": internet_weight}
34 | )
35 | Weights.objects.update_or_create(
36 | country=country, media_type="Twitter", defaults={"weight": twitter_weight}
37 | )
38 | instance.country = country
39 | instance.print_weight = print_weight
40 | instance.radio_weight = radio_weight
41 | instance.tv_weight = television_weight
42 | instance.internet_weight = internet_weight
43 | instance.twitter_weight = twitter_weight
44 | instance.save()
45 | except (ObjectDoesNotExist, KeyError):
46 | pass
47 |
--------------------------------------------------------------------------------
/reports/models.py:
--------------------------------------------------------------------------------
1 | from uuid import uuid4
2 |
3 | from django.conf import settings
4 | from django.db import models
5 |
6 | from django_countries.fields import CountryField
7 | from gsheets.mixins import SheetPullableMixin
8 |
9 |
10 | class Weights(models.Model):
11 | country = CountryField()
12 | media_type = models.CharField(max_length=32)
13 | weight = models.DecimalField(max_digits=4, decimal_places=2)
14 |
15 | def __str__(self):
16 | return f"{self.country} {self.media_type} {self.weight}"
17 |
18 | class Meta:
19 | constraints = [
20 | models.UniqueConstraint(
21 | fields=["country", "media_type"], name="country_media_type_key"
22 | )
23 | ]
24 |
25 |
26 | class GSheetCountryWeights(SheetPullableMixin, models.Model):
27 | spreadsheet_id = settings.GSHEETS_WEIGHTS["SPREADSHEET_ID"]
28 | sheet_name = settings.GSHEETS_WEIGHTS["GLOBAL_WEIGHTS_SHEET_NAME"]
29 | model_id_field = "guid"
30 | sheet_id_field = "Platform ID"
31 |
32 | guid = models.CharField(primary_key=True, max_length=255, default=uuid4)
33 |
34 | # NOTE(kilemensi): gsheets performs default insert for new rows before
35 | # doing an update with correct data. This means the model
36 | # must have valid default values for all fields.
37 | country = CountryField(default="KE")
38 | print_weight = models.DecimalField(max_digits=4, decimal_places=2, default=0.0)
39 | radio_weight = models.DecimalField(max_digits=4, decimal_places=2, default=0.0)
40 | tv_weight = models.DecimalField(max_digits=4, decimal_places=2, default=0.0)
41 | internet_weight = models.DecimalField(max_digits=4, decimal_places=2, default=0.0)
42 | twitter_weight = models.DecimalField(max_digits=4, decimal_places=2, default=0.0)
43 |
44 | def __str__(self):
45 | return f"{self.country} [{self.print_weight}, {self.radio_weight}, {self.tv_weight}, {self.internet_weight}, {self.twitter_weight}]"
46 |
--------------------------------------------------------------------------------
/reports/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from reports.report_details import get_countries, get_regions
3 |
4 |
5 | class GlobalForm(forms.Form):
6 | pass
7 |
8 | class CountryForm(forms.Form):
9 | """
10 | Due to the system checks added in django 1.9 https://docs.djangoproject.com/en/3.1/releases/1.9/#urls,
11 | this form gets imported automatically when the url imports happen. This in turn causes the COUNTRIES = get_countries()
12 | variable to be called causing database query errors since migrations haven't been applied yet.
13 | Removing COUNTRIES = get_countries() and replacing it with get_form_countries ensures that the get_countries function
14 | will now only be explicitly called as opposed to being automatically called.
15 | """
16 | # Only show countries for which data has been submitted
17 |
18 | country = forms.ChoiceField(
19 | label='Country',
20 | choices=get_countries)
21 |
22 | def get_form_countries(self):
23 | return get_countries()
24 |
25 | def filter_countries(self):
26 | if self.cleaned_data['country'] == 'ALL':
27 | return get_countries()
28 | else:
29 | return [(code, country) for code, country in get_countries() if code == self.cleaned_data['country']]
30 |
31 | class RegionForm(forms.Form):
32 | """
33 | Due to the system checks added in django 1.9 https://docs.djangoproject.com/en/3.1/releases/1.9/#urls,
34 | this form gets imported automatically when the url imports happen. This in turn causes the REGIONS = get_regions()
35 | variable to be called causing database query errors since migrations haven't been applied yet.
36 | Removing REGIONS = get_regions() and replacing it with get_form_regions ensures that the get_regions function
37 | will now only be explicitly called as opposed to being automatically called.
38 | """
39 |
40 | region = forms.ChoiceField(
41 | label='Region',
42 | choices=get_regions)
43 |
44 | def get_form_regions(self):
45 | return get_regions()
46 |
47 |
--------------------------------------------------------------------------------
/forms/migrations/0044_auto_20200226_0758.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.9 on 2020-02-26 07:58
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('forms', '0043_update_country_region'),
10 | ]
11 |
12 | operations = [
13 | migrations.RenameField(
14 | model_name='televisionsheet',
15 | old_name='television_channel',
16 | new_name='channel',
17 | ),
18 | migrations.RemoveField(
19 | model_name='televisionsheet',
20 | name='station_name',
21 | ),
22 | migrations.AlterField(
23 | model_name='internetnewssheet',
24 | name='shared_on_facebook',
25 | field=models.CharField(choices=[('Y', '(1) Yes'), ('N', '(2) No')], help_text="Has this story been shared by the media house on its Facebook Page?\n\n Scroll down the media house's Facebook page to check.", max_length=1, verbose_name='(5) Shared on Facebook'),
26 | ),
27 | migrations.AlterField(
28 | model_name='internetnewssheet',
29 | name='shared_via_twitter',
30 | field=models.CharField(choices=[('Y', '(1) Yes'), ('N', '(2) No')], help_text='Has this story been shared by the media house via Twitter?\n\n Enter the exact URL of the story into https://twitter.com - answer yes if the media house\'s name appears in the search results.', max_length=1, verbose_name='(4) Shared via twitter?'),
31 | ),
32 | migrations.AlterField(
33 | model_name='radiosheet',
34 | name='station_name',
35 | field=models.CharField(help_text="Be as specific as possible. E.g. if the radio company is called RRI, and if the newscast is broadcast on its third channel, write in 'RRI-3'.", max_length=255, verbose_name='Channel'),
36 | ),
37 | migrations.RenameField(
38 | model_name='radiosheet',
39 | old_name='station_name',
40 | new_name='channel',
41 | ),
42 | ]
43 |
--------------------------------------------------------------------------------
/gmmp/urls.py:
--------------------------------------------------------------------------------
1 | import debug_toolbar
2 | from django.conf import settings
3 | from django.conf.urls.i18n import i18n_patterns
4 | from django.conf.urls.static import static
5 | from django.contrib import admin
6 | from django.contrib.auth import views as auth_views
7 | from django.urls import include, path, re_path
8 | from django.views.generic.base import RedirectView
9 |
10 | from gmmp import settings
11 | from gmmp.views import CustomPassowrdResetView, CustomPasswordResetDoneView
12 | from reports.views import WazimapView
13 |
14 | admin.site.site_header = settings.ADMIN_SITE_SITE_HEADER
15 | admin.site.site_title = settings.ADMIN_SITE_SITE_TITLE
16 | admin.site.site_url = settings.ADMIN_SITE_SITE_URL
17 | admin.site.index_title = settings.ADMIN_SITE_INDEX_TITLE
18 |
19 | urlpatterns = (
20 | i18n_patterns(
21 | # Django JET URLS
22 | re_path(r"^jet/", include("jet.urls", "jet")),
23 | # Django JET dashboard URLS
24 | re_path(r"^jet/dashboard/", include("jet.dashboard.urls", "jet-dashboard")),
25 | path(
26 | "admin/password_reset/",
27 | CustomPassowrdResetView.as_view(),
28 | name="admin_password_reset",
29 | ),
30 | path(
31 | "admin/password_reset/done/",
32 | CustomPasswordResetDoneView.as_view(),
33 | name="password_reset_done",
34 | ),
35 | path(
36 | "reset///",
37 | auth_views.PasswordResetConfirmView.as_view(),
38 | name="password_reset_confirm",
39 | ),
40 | path(
41 | "reset/done/",
42 | auth_views.PasswordResetCompleteView.as_view(),
43 | name="password_reset_complete",
44 | ),
45 | # Admin site URLS
46 | path("admin/", admin.site.urls),
47 | re_path(r"^$", RedirectView.as_view(url="/admin"), name="go-to-admin"),
48 | path('reports/', include('reports.urls')),
49 | path('genmap', WazimapView.as_view(), name='genmap'),
50 | prefix_default_language=False,
51 | )
52 | + [path('__debug__/', include(debug_toolbar.urls))]
53 | + [path("", include("gsheets.urls"))]
54 | + [path("i18n/", include("django.conf.urls.i18n"))]
55 | + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
56 | )
57 |
--------------------------------------------------------------------------------
/gmmp/templates/admin/login.html:
--------------------------------------------------------------------------------
1 | {% extends "admin/base_site.html" %}
2 | {% load i18n admin_static %}
3 |
4 | {% block extrastyle %}
5 | {{ block.super }}
6 |
7 | {% endblock %}
8 |
9 | {% block bodyclass %}{{ block.super }} login{% endblock %}
10 |
11 | {% block usertools %}{% endblock %}
12 |
13 | {% block nav-global %}{% endblock %}
14 |
15 | {% block content_title %} {% endblock %}
16 |
17 | {% block breadcrumbs %}{% endblock %}
18 |
19 |
20 | {% block content %}
21 | {% if form.errors and not form.non_field_errors %}
22 |
23 | {% if form.errors.items|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %}
24 |
25 | {% endif %}
26 |
27 | {% if form.non_field_errors %}
28 | {% for error in form.non_field_errors %}
29 |
30 | {{ error }}
31 |
32 | {% endfor %}
33 | {% endif %}
34 |
35 |
36 |
57 |
58 |
59 |
62 |
63 | {% endblock %}
64 |
65 | {% block footer %}
66 | {% endblock %}
67 |
--------------------------------------------------------------------------------
/forms/migrations/0025_auto_20191210_1924.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('forms', '0024_auto_20191210_1805'),
11 | ]
12 |
13 | operations = [
14 | migrations.RemoveField(
15 | model_name='newspapersheet',
16 | name='person_secondary',
17 | ),
18 | migrations.AlterField(
19 | model_name='newspapersheet',
20 | name='comments',
21 | field=models.TextField(verbose_name='(23) Describe any photographs included in the story and the conclusions you draw from them.', blank=True),
22 | preserve_default=True,
23 | ),
24 | migrations.AlterField(
25 | model_name='newspapersheet',
26 | name='further_analysis',
27 | field=models.CharField(help_text=" A story warrants further analysis if it clearly perpetuates or clearly challenges gender stereotypes, if it includes women's opinions in a remarkable way, if it contributes to an understanding of inequalities between women and men, if it mentions or calls attention to women's human rights, etc. Consult the guide for further explanation", max_length=1, verbose_name='(24) Does this story warrant further analysis?', choices=[(b'Y', '(1) Yes'), (b'N', '(2) No')]),
28 | preserve_default=True,
29 | ),
30 | migrations.AlterField(
31 | model_name='newspapersheet',
32 | name='inequality_women',
33 | field=models.PositiveIntegerField(verbose_name='(7) This story clearly highlights issues of inequality between women and men', choices=[(1, '(1) Agree'), (2, '(2) Disagree'), (3, '(3) Neither agree nor disagree'), (4, '(4) Do not know')]),
34 | preserve_default=True,
35 | ),
36 | migrations.AlterField(
37 | model_name='newspapersheet',
38 | name='stereotypes',
39 | field=models.PositiveIntegerField(help_text='This story clearly challenges gender stereotypes', verbose_name='(8) Challenges Stereotypes', choices=[(1, '(1) Agree'), (2, '(2) Disagree'), (3, '(3) Neither agree nor disagree'), (4, '(4) Do not know')]),
40 | preserve_default=True,
41 | ),
42 | ]
43 |
--------------------------------------------------------------------------------
/forms/migrations/0002_auto_20150309_1227.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('forms', '0001_initial'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='internetnewssheet',
16 | name='equality_rights',
17 | field=models.CharField(help_text="Scan the full news story and code 'Yes' if it quotes or makes reference to any piece of legislation or policy that promotes gender equality or human rights.", max_length=1, verbose_name='Reference to gender equality / human rights legislation/ policy', choices=[(b'Y', 'Yes'), (b'N', 'No')]),
18 | preserve_default=True,
19 | ),
20 | migrations.AlterField(
21 | model_name='newspapersheet',
22 | name='equality_rights',
23 | field=models.CharField(help_text="Scan the full news story and code 'Yes' if it quotes or makes reference to any piece of legislation or policy that promotes gender equality or human rights.", max_length=1, verbose_name='Reference to gender equality / human rights legislation/ policy', choices=[(b'Y', 'Yes'), (b'N', 'No')]),
24 | preserve_default=True,
25 | ),
26 | migrations.AlterField(
27 | model_name='radiosheet',
28 | name='equality_rights',
29 | field=models.CharField(help_text="Scan the full news story and code 'Yes' if it quotes or makes reference to any piece of legislation or policy that promotes gender equality or human rights.", max_length=1, verbose_name='Reference to gender equality / human rights legislation/ policy', choices=[(b'Y', 'Yes'), (b'N', 'No')]),
30 | preserve_default=True,
31 | ),
32 | migrations.AlterField(
33 | model_name='televisionsheet',
34 | name='equality_rights',
35 | field=models.CharField(help_text="Scan the full news story and code 'Yes' if it quotes or makes reference to any piece of legislation or policy that promotes gender equality or human rights.", max_length=1, verbose_name='Reference to gender equality / human rights legislation/ policy', choices=[(b'Y', 'Yes'), (b'N', 'No')]),
36 | preserve_default=True,
37 | ),
38 | ]
39 |
--------------------------------------------------------------------------------
/forms/migrations/0009_auto_20150312_1347.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('forms', '0008_auto_20150309_1902'),
11 | ]
12 |
13 | operations = [
14 | migrations.RemoveField(
15 | model_name='internetnewssheet',
16 | name='comments',
17 | ),
18 | migrations.AddField(
19 | model_name='radiosheet',
20 | name='comments',
21 | field=models.TextField(verbose_name='Describe any photographs included in the story and the conclusions you draw from them.', blank=True),
22 | preserve_default=True,
23 | ),
24 | migrations.AlterField(
25 | model_name='internetnewsperson',
26 | name='family_role',
27 | field=models.CharField(help_text="Code yes only if the word 'wife', 'husband' etc is actually used to describe the person.", max_length=1, verbose_name='Family Role Given?', choices=[(b'Y', 'Yes'), (b'N', 'No')]),
28 | preserve_default=True,
29 | ),
30 | migrations.AlterField(
31 | model_name='newspaperperson',
32 | name='family_role',
33 | field=models.CharField(help_text="Code yes only if the word 'wife', 'husband' etc is actually used to describe the person.", max_length=1, verbose_name='Family Role Given?', choices=[(b'Y', 'Yes'), (b'N', 'No')]),
34 | preserve_default=True,
35 | ),
36 | migrations.AlterField(
37 | model_name='radioperson',
38 | name='family_role',
39 | field=models.CharField(help_text="Code yes only if the word 'wife', 'husband' etc is actually used to describe the person.", max_length=1, verbose_name='Family Role Given?', choices=[(b'Y', 'Yes'), (b'N', 'No')]),
40 | preserve_default=True,
41 | ),
42 | migrations.AlterField(
43 | model_name='televisionperson',
44 | name='family_role',
45 | field=models.CharField(help_text="Code yes only if the word 'wife', 'husband' etc is actually used to describe the person.", max_length=1, verbose_name='Family Role Given?', choices=[(b'Y', 'Yes'), (b'N', 'No')]),
46 | preserve_default=True,
47 | ),
48 | ]
49 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Multi-stage build
2 |
3 | ###############################################################################
4 | ## Python base image
5 | ###############################################################################
6 | FROM python:3.8-slim AS python-base
7 |
8 | ### Arg
9 | #### See: https://stackoverflow.com/a/56569081
10 | ARG DEBIAN_FRONTEND=noninteractive
11 |
12 | ### Env
13 | ENV APP_HOST=.
14 | ENV APP_DOCKER=/app
15 | ENV PYTHONDONTWRITEBYTECODE 1
16 | ENV PYTHONUNBUFFERED 1
17 |
18 | ### Dependencies
19 | #### System
20 | #### We need libpq-dev in both build and final runtime image
21 | RUN apt-get update \
22 | && apt-get -y upgrade \
23 | && apt-get install gettext libpq-dev --no-install-recommends -y \
24 | && apt-get clean
25 |
26 | ###############################################################################
27 | ## Python builder base image
28 | ###############################################################################
29 | FROM python-base AS python-builder-base
30 |
31 | ### Dependencies
32 | #### System
33 |
34 | RUN apt-get install gcc python-dev --no-install-recommends -y \
35 | && apt-get clean \
36 | && pip install --upgrade pip
37 |
38 | ###############################################################################
39 | ## Python builder image
40 | ###############################################################################
41 |
42 | FROM python-builder-base AS python-builder
43 |
44 | ### Env
45 | ENV PATH=/root/.local/bin:$PATH
46 |
47 | ### Dependencies
48 | #### Python
49 | COPY ${APP_HOST}/requirements-all.txt ${APP_HOST}/requirements.txt /tmp/
50 | RUN pip install --user -r /tmp/requirements.txt
51 |
52 | ###############################################################################
53 | ## App image
54 | ###############################################################################
55 | FROM python-base AS app
56 |
57 | ### Env
58 | ENV PATH=/root/.local/bin:$PATH
59 |
60 | ### Dependencies
61 | #### Python (copy from python-builder)
62 | COPY --from=python-builder /root/.local /root/.local
63 |
64 | ### Volumes
65 | WORKDIR ${APP_DOCKER}
66 | RUN mkdir media static logs
67 | VOLUME ["${APP_DOCKER}/media/", "${APP_DOCKER}/logs/"]
68 |
69 | # Expose server port
70 | EXPOSE 8000
71 |
72 | ### Setup app
73 | COPY ${APP_HOST} ${APP_DOCKER}
74 | COPY ${APP_HOST}/contrib/docker/*.sh /
75 | RUN chmod +x /entrypoint.sh \
76 | && chmod +x /cmd.sh \
77 | && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
78 |
79 | ### Run app
80 | ENTRYPOINT ["/entrypoint.sh"]
81 | CMD ["/cmd.sh", "gmmp.wsgi:application"]
82 |
--------------------------------------------------------------------------------
/forms/migrations/0050_add_created_and_updated_at.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.15 on 2020-08-26 06:23
2 |
3 | from django.db import migrations, models
4 | import django.utils.timezone
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('forms', '0049_auto_20200820_0739'),
11 | ]
12 |
13 | operations = [
14 | migrations.AddField(
15 | model_name='internetnewssheet',
16 | name='created_at',
17 | field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
18 | preserve_default=False,
19 | ),
20 | migrations.AddField(
21 | model_name='internetnewssheet',
22 | name='updated_at',
23 | field=models.DateTimeField(auto_now=True),
24 | ),
25 | migrations.AddField(
26 | model_name='newspapersheet',
27 | name='created_at',
28 | field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
29 | preserve_default=False,
30 | ),
31 | migrations.AddField(
32 | model_name='newspapersheet',
33 | name='updated_at',
34 | field=models.DateTimeField(auto_now=True),
35 | ),
36 | migrations.AddField(
37 | model_name='radiosheet',
38 | name='created_at',
39 | field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
40 | preserve_default=False,
41 | ),
42 | migrations.AddField(
43 | model_name='radiosheet',
44 | name='updated_at',
45 | field=models.DateTimeField(auto_now=True),
46 | ),
47 | migrations.AddField(
48 | model_name='televisionsheet',
49 | name='created_at',
50 | field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
51 | preserve_default=False,
52 | ),
53 | migrations.AddField(
54 | model_name='televisionsheet',
55 | name='updated_at',
56 | field=models.DateTimeField(auto_now=True),
57 | ),
58 | migrations.AddField(
59 | model_name='twittersheet',
60 | name='created_at',
61 | field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
62 | preserve_default=False,
63 | ),
64 | migrations.AddField(
65 | model_name='twittersheet',
66 | name='updated_at',
67 | field=models.DateTimeField(auto_now=True),
68 | ),
69 | ]
70 |
--------------------------------------------------------------------------------
/static/js/tabs.js:
--------------------------------------------------------------------------------
1 | (function($){
2 | var set_button_navigation = function() {
3 | var last_tab = $('.changeform-tabs li:last-child').hasClass('selected');
4 | if(last_tab) {
5 | $('input[name=_addanother]').show();
6 | $('input[name=_save]').show();
7 | $('input[name=_continue]').hide();
8 | }else{
9 | $('input[name=_addanother]').hide();
10 | $('input[name=_save]').hide();
11 | $('input[name=_continue]').show();
12 | }
13 | };
14 |
15 | $(document).ready(function() {
16 | var newspapersheet_form = $('#newspapersheet_form').length > 0;
17 | var radiosheet_form = $('#radiosheet_form').length > 0;
18 | var televisionsheet_form = $('#televisionsheet_form').length > 0;
19 | var internetnewssheet_form = $('#internetnewssheet_form').length > 0;
20 | var twittersheet_form = $('#twittersheet_form').length > 0;
21 |
22 | if(newspapersheet_form || twittersheet_form || radiosheet_form || televisionsheet_form || internetnewssheet_form){
23 | // Rename the save and continue button to Next
24 | var continueButton$ = $('input[name=_continue]');
25 | continueButton$.val('Next Tab');
26 | continueButton$.click((function(e) {
27 | e.preventDefault();
28 | // Move to next tab unless we are on the last tab
29 | var selectedTab$ = $('.changeform-tabs li.selected');
30 |
31 | if ($('.changeform-tabs li.selected:last-child').length === 0){
32 | selectedTab$.removeClass('selected').next().addClass('selected');
33 | var module = selectedTab$.index();
34 | $('.module_'+module).removeClass('selected');
35 | $('.module_'+(module+1)).addClass('selected');
36 | set_button_navigation();
37 | }
38 | }));
39 | $('.changeform-tabs li').click(function (e) {
40 | $('.changeform-tabs li').removeClass('selected');
41 | $(e.currentTarget).addClass('selected');
42 |
43 | set_button_navigation();
44 | });
45 | if($('.success').length){
46 | var selectedTab$ = $('.changeform-tabs li.selected');
47 | var module = selectedTab$.index();
48 | selectedTab$.removeClass('selected');
49 | $('.changeform-tabs li:first-child').addClass('selected');
50 | $('.module_'+module).removeClass('selected');
51 | $('.module_0').addClass('selected');
52 | }
53 | set_button_navigation();
54 | }
55 | });
56 | }(jet.jQuery));
57 |
--------------------------------------------------------------------------------
/gmmp/templates/admin/base_site.html:
--------------------------------------------------------------------------------
1 | {% extends 'admin/base_site.html' %}
2 | {% load static i18n i18n_switcher jet_tags %}
3 |
4 | {% block extrastyle %}
5 | {{ block.super }}
6 |
7 | {% endblock %}
8 |
9 | {% block extrahead %}
10 | {{ block.super }}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | {% endblock %}
20 |
21 | {% block blockbots %}
22 | {{ block.super }}
23 |
24 |
26 |
27 | {% endblock %}
28 |
29 | {% block userlinks %}
30 | {{ block.super }}
31 | {% get_available_languages as LANGUAGES %}
32 | {% get_language_info_list for LANGUAGES as languages %}
33 | {% for language in languages %}
34 |
35 | {{ language.name_local }}
36 |
37 | {% endfor %}
38 | {% endblock %}
39 |
40 | {% block welcome-msg %}
41 | {% trans 'Welcome,' %}
42 | {{ user.monitor.country.alpha3 }} / {% firstof user.get_short_name user.get_username %}
43 | {% endblock %}
44 |
45 | {% block footer %}
46 |
49 |
67 | {% endblock %}
68 |
--------------------------------------------------------------------------------
/reports/management/commands/import-historical.py:
--------------------------------------------------------------------------------
1 | from optparse import make_option
2 |
3 | from django.conf import settings
4 | from django.core.management.base import BaseCommand, CommandError
5 |
6 | from reports.historical import Historical
7 | from reports.report_details import REGION_COUNTRY_MAP, get_countries
8 |
9 |
10 | class Command(BaseCommand):
11 | help = "Import historical GMMP data from an XLSX file"
12 |
13 | def add_arguments(self, parser):
14 | parser.add_argument("filename", nargs="+", help="Excel file(s) to import")
15 | parser.add_argument("--global", action="store_true", help="Coverage is global")
16 | parser.add_argument(
17 | "--region",
18 | action="store",
19 | dest="region",
20 | help="Import historical data for a region. One of: {}".format(
21 | ", ".join(sorted(REGION_COUNTRY_MAP.keys()))
22 | ),
23 | )
24 | parser.add_argument(
25 | "--country",
26 | action="store",
27 | dest="country",
28 | help="Import historical data for a country. One of: {}".format(
29 | ", ".join(sorted(c[0] for c in get_countries()))
30 | ),
31 | )
32 | parser.add_argument(
33 | "--year",
34 | action="store",
35 | dest="year",
36 | help="Historical GMMP year the Excel file(s) belongs to. One of: 2010, 2015",
37 | )
38 |
39 | def handle(self, *args, **options):
40 | historical = Historical()
41 | coverage = None
42 | region = None
43 | country = None
44 | year = options["year"] or settings.REPORTS_HISTORICAL_YEAR
45 | filenames = options["filename"]
46 |
47 | if year not in ["2010", "2015"]:
48 | raise ValueError("Invalid historical GMMP year: {}".format(year))
49 |
50 | if options["global"]:
51 | coverage = "global"
52 | elif options["region"]:
53 | coverage = "region"
54 | region = options["region"]
55 | if region not in REGION_COUNTRY_MAP:
56 | raise ValueError("Unknown region: %s" % region)
57 | elif options["country"]:
58 | coverage = "country"
59 | country = options["country"]
60 | countries = {c: n for c, n in get_countries()}
61 | if not country.upper() in countries:
62 | for code, name in countries.items():
63 | if name.upper() == country:
64 | country = code
65 | break
66 | if not country in countries:
67 | raise ValueError("Unknown country: %s" % country)
68 | else:
69 | raise CommandError("Must specify --global or --region")
70 |
71 | for fname in filenames:
72 | historical.import_from_file(
73 | fname, coverage, region=region, country=country, year=year
74 | )
75 |
76 | historical.save()
77 |
--------------------------------------------------------------------------------
/forms/migrations/0049_auto_20200820_0739.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.15 on 2020-08-20 07:39
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('forms', '0048_auto_20200820_0646'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='internetnewssheet',
15 | name='covid19',
16 | field=models.PositiveIntegerField(choices=[(1, '(1) Yes'), (2, '(2) No')], default=2, help_text='Note: For the question below it is important NOT to code COVID19-related stories under topic 23 but to choose the most relevant secondary topic theme in order to ensure results that can be compared with those from previous GMMPs.', verbose_name='(z) Is this story related to coronavirus Covid-19?'),
17 | ),
18 | migrations.AlterField(
19 | model_name='newspapersheet',
20 | name='covid19',
21 | field=models.PositiveIntegerField(choices=[(1, '(1) Yes'), (2, '(2) No')], default=2, help_text='Note: For the question below it is important NOT to code COVID19-related stories under topic 23 but to choose the most relevant secondary topic theme in order to ensure results that can be compared with those from previous GMMPs.', verbose_name='(z) Is this story related to coronavirus Covid-19?'),
22 | ),
23 | migrations.AlterField(
24 | model_name='radiosheet',
25 | name='covid19',
26 | field=models.PositiveIntegerField(choices=[(1, '(1) Yes'), (2, '(2) No')], default=2, help_text='Note: For the question below it is important NOT to code COVID19-related stories under topic 23 but to choose the most relevant secondary topic theme in order to ensure results that can be compared with those from previous GMMPs.', verbose_name='(z) Is this story related to coronavirus Covid-19?'),
27 | ),
28 | migrations.AlterField(
29 | model_name='televisionsheet',
30 | name='covid19',
31 | field=models.PositiveIntegerField(choices=[(1, '(1) Yes'), (2, '(2) No')], default=2, help_text='Note: For the question below it is important NOT to code COVID19-related stories under topic 23 but to choose the most relevant secondary topic theme in order to ensure results that can be compared with those from previous GMMPs.', verbose_name='(z) Is this story related to coronavirus Covid-19?'),
32 | ),
33 | migrations.AlterField(
34 | model_name='twittersheet',
35 | name='covid19',
36 | field=models.PositiveIntegerField(choices=[(1, '(1) Yes'), (2, '(2) No')], default=2, help_text='Note: For the question below it is important NOT to code COVID19-related stories under topic 23 but to choose the most relevant secondary topic theme in order to ensure results that can be compared with those from previous GMMPs.', verbose_name='(z) Is this story related to coronavirus Covid-19?'),
37 | ),
38 | ]
39 |
--------------------------------------------------------------------------------
/forms/migrations/0059_update_country_region.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 | COUNTRY_REGION = [
7 | ("Myanmar", "Asia"),
8 | ("Angola","Africa"),
9 | ("Cambodia","Asia"),
10 | ("Cayman Islands","Caribbean"),
11 | ("Dominica","Caribbean"),
12 | ("Greenland","Europe"),
13 | ("Honduras","Latin America"),
14 | ("Hong Kong","Asia"),
15 | ("Iraq","Middle East"),
16 | ("Jordan","Middle East"),
17 | ("Macao","Asia"),
18 | ("Papua New Guinea","Pacific"),
19 | ("Russia","Europe"),
20 | ("Rwanda","Africa"),
21 | ("Seychelles","Africa"),
22 | ("Timor-Leste","Asia"),
23 | ("Uzbekistan","Asia"),
24 | ]
25 |
26 | def get_region_map(CountryRegion):
27 | from django_countries import countries
28 |
29 | region_map = {}
30 |
31 | # Map new country code(s) to regions
32 | for country_region in COUNTRY_REGION:
33 | code = countries.by_name(country_region[0])
34 | if code:
35 | if country_region[1] in region_map:
36 | region_map[country_region[1]].append(code)
37 | else:
38 | region_map[country_region[1]] = [code]
39 |
40 | return region_map
41 |
42 | def code(apps, schema_editor):
43 | CountryRegion = apps.get_model("forms", "CountryRegion")
44 | db_alias = schema_editor.connection.alias
45 |
46 | # Update countries regions
47 | CountryRegion.objects.using(db_alias).filter(country="CY").update(region="Europe")
48 | CountryRegion.objects.using(db_alias).filter(country="KZ").update(region="Asia")
49 | CountryRegion.objects.using(db_alias).filter(country="PR").update(region="Caribbean")
50 | CountryRegion.objects.using(db_alias).filter(country="VU").update(region="Pacific")
51 |
52 | # Create CountryRegion objects for supplied pairs
53 | region_map = get_region_map(CountryRegion)
54 | for region, country_code_list in region_map.items():
55 | for country_code in country_code_list:
56 | CountryRegion.objects.using(db_alias).create(
57 | country=country_code, region=region
58 | )
59 |
60 | def reverse_code(apps, schema_editor):
61 | CountryRegion = apps.get_model("forms", "CountryRegion")
62 | db_alias = schema_editor.connection.alias
63 | # Reverse Update regions
64 | CountryRegion.objects.using(db_alias).filter(country="CY").update(region="Middle East")
65 | CountryRegion.objects.using(db_alias).filter(country="KZ").update(region="Europe")
66 |
67 | # Delete CountryRegion objects for supplied pairs
68 | region_map = get_region_map(CountryRegion)
69 | for region, country_code_list in region_map.items():
70 | for country_code in country_code_list:
71 | CountryRegion.objects.using(db_alias).filter(
72 | country=country_code, region=region
73 | ).delete()
74 |
75 |
76 | class Migration(migrations.Migration):
77 |
78 | dependencies = [
79 | ("forms", "0058_add_deleted_attribute"),
80 | ]
81 |
82 | operations = [
83 | migrations.RunPython(code, reverse_code),
84 | ]
85 |
--------------------------------------------------------------------------------
/forms/migrations/0043_update_country_region.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 | COUNTRY_REGION = [
7 | ("North Ireland", "Europe"),
8 | ("International", "Global"),
9 | ]
10 |
11 |
12 | def get_region_map(CountryRegion):
13 | from django_countries import countries
14 |
15 | region_map = {}
16 |
17 | # Map new country code(s) to regions
18 | for country_region in COUNTRY_REGION:
19 | code = countries.by_name(country_region[0])
20 | if code:
21 | if country_region[1] in region_map:
22 | region_map[country_region[1]].append(code)
23 | else:
24 | region_map[country_region[1]] = [code]
25 |
26 | return region_map
27 |
28 |
29 | def code(apps, schema_editor):
30 | CountryRegion = apps.get_model("forms", "CountryRegion")
31 | db_alias = schema_editor.connection.alias
32 |
33 | country_region_objs = CountryRegion.objects.using(db_alias).all()
34 |
35 | # Update old custom country codes to ISO use-assignable codes
36 | CountryRegion.objects.using(db_alias).filter(country="B1").update(country="QM")
37 | CountryRegion.objects.using(db_alias).filter(country="B2").update(country="QN")
38 | CountryRegion.objects.using(db_alias).filter(country="EN").update(country="QO")
39 | CountryRegion.objects.using(db_alias).filter(country="SQ").update(country="QQ")
40 | CountryRegion.objects.using(db_alias).filter(country="WL").update(country="QR")
41 |
42 | # Create CountryRegion objects for supplied pairs
43 | region_map = get_region_map(CountryRegion)
44 | for region, country_code_list in region_map.items():
45 | for country_code in country_code_list:
46 | CountryRegion.objects.using(db_alias).create(
47 | country=country_code, region=region
48 | )
49 |
50 |
51 | def reverse_code(apps, schema_editor):
52 | CountryRegion = apps.get_model("forms", "CountryRegion")
53 | db_alias = schema_editor.connection.alias
54 |
55 | # Revert ISO use-assignable codes to old custom country codes
56 | CountryRegion.objects.using(db_alias).filter(country="QM").update(country="B1")
57 | CountryRegion.objects.using(db_alias).filter(country="QN").update(country="B2")
58 | CountryRegion.objects.using(db_alias).filter(country="QO").update(country="EN")
59 | CountryRegion.objects.using(db_alias).filter(country="QQ").update(country="SQ")
60 | CountryRegion.objects.using(db_alias).filter(country="QR").update(country="WL")
61 |
62 | # Delete CountryRegion objects for supplied pairs
63 | region_map = get_region_map(CountryRegion)
64 | for region, country_code_list in region_map.items():
65 | for country_code in country_code_list:
66 | CountryRegion.objects.using(db_alias).filter(
67 | country=country_code, region=region
68 | ).delete()
69 |
70 |
71 | class Migration(migrations.Migration):
72 |
73 | dependencies = [
74 | ("forms", "0042_auto_20200220_1247"),
75 | ]
76 |
77 | operations = [
78 | migrations.RunPython(code, reverse_code),
79 | ]
80 |
--------------------------------------------------------------------------------
/reports/views.py:
--------------------------------------------------------------------------------
1 | # Python
2 | from datetime import date
3 |
4 | # Django
5 | from django.views import View
6 | from django.shortcuts import render
7 | from django.http import HttpResponse
8 | from django.contrib.auth.decorators import login_required, user_passes_test
9 | from django.utils.decorators import method_decorator
10 |
11 | # Project
12 | from reports.report_builder import XLSXReportBuilder
13 |
14 | from reports.export_builder import XLSXDataExportBuilder
15 | from reports.forms import GlobalForm, CountryForm, RegionForm
16 |
17 |
18 | class ReportView(View):
19 | template_name = 'report_filter.html'
20 |
21 | @method_decorator(login_required)
22 | def dispatch(self, *args, **kwargs):
23 | return super(ReportView, self).dispatch(*args, **kwargs)
24 |
25 | def get(self, request, *args, **kwargs):
26 | country_form = CountryForm()
27 | region_form = RegionForm()
28 | context = {
29 | 'country_form': country_form,
30 | 'region_form': region_form}
31 | return render(
32 | request,
33 | self.template_name,
34 | context)
35 |
36 | def post(self, request, *args, **kwargs):
37 | if 'country_form' in request.POST:
38 | form = CountryForm(request.POST)
39 | choice = [country for code, country in form.get_form_countries() if code == request.POST['country']][0]
40 | elif 'region_form' in request.POST:
41 | form = RegionForm(request.POST)
42 | choice = [region for id, region in form.get_form_regions() if id == int(request.POST['region'])][0]
43 | else:
44 | form = GlobalForm(request.POST)
45 | choice = 'Global'
46 |
47 | if form.is_valid():
48 | xlsx = XLSXReportBuilder(form).build()
49 | filename = 'GMMP Report: %s - %s' % (choice, date.today())
50 |
51 | response = HttpResponse(xlsx, content_type='application/vnd.ms-excel')
52 | response['Content-Disposition'] = f'attachment; filename="{filename}.xlsx"'
53 | return response
54 | # context = {'form' : filter_form}
55 | # return render(
56 | # request,
57 | # self.template_name,
58 | # context)
59 |
60 | report_filter = CountryForm()
61 | context = {'form' : form}
62 | return render(
63 | request,
64 | self.template_name,
65 | context)
66 |
67 |
68 | @user_passes_test(lambda u: u.is_superuser)
69 | def data_export(request):
70 | if request.method == 'POST':
71 | xlsx = XLSXDataExportBuilder(request).build()
72 | filename = 'GMMP Data export'
73 |
74 | response = HttpResponse(xlsx, content_type='application/vnd.ms-excel')
75 | response['Content-Disposition'] = 'attachment; filename=%s.xlsx' % filename
76 |
77 | return response
78 |
79 | context = {}
80 | return render(
81 | request,
82 | 'data_export.html',
83 | context)
84 |
85 |
86 | class WazimapView(View):
87 | template_name = 'index.html'
88 |
89 | @method_decorator(login_required)
90 | def get(self, request, *args, **kwargs):
91 | return render(
92 | request,
93 | self.template_name, {})
94 |
--------------------------------------------------------------------------------
/gmmp/templates/base.html:
--------------------------------------------------------------------------------
1 | {% load static %}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Global Media Monitoring Project
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
66 |
67 |
68 |
69 |
70 |
71 | {% block page_content %}
72 | {% endblock page_content %}
73 |
74 |
75 |
76 |
77 |
Copyright © Global Media Monitoring Project 2020
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/reports/historical/historical.py:
--------------------------------------------------------------------------------
1 | import openpyxl
2 | import json
3 | import os
4 | import logging
5 | import re
6 |
7 | from reports.report_details import WS_INFO
8 |
9 | from .canon import canon
10 | from ._gmmp_2010_importer import GMMP2010ReportImporter
11 | from ._gmmp_2015_importer import GMMP2015ReportImporter
12 |
13 |
14 | class Historical(object):
15 | log = logging.getLogger(__name__)
16 |
17 | def __init__(self, historical_file="historical.json"):
18 | self.fname = historical_file
19 | self.all_data = {}
20 |
21 | def load(self):
22 | if os.path.exists(self.fname):
23 | with open(self.fname, 'r') as f:
24 | self.all_data = json.load(f)
25 |
26 | def save(self):
27 | with open(self.fname, "w") as f:
28 | json.dump(self.all_data, f, indent=2, sort_keys=True)
29 |
30 | def get(self, new_ws, coverage, year, region=None, country=None):
31 | if coverage == "region":
32 | key = canon(region)
33 | elif coverage == "country":
34 | key = canon(country)
35 | elif coverage == "global":
36 | key = "global"
37 | else:
38 | raise ValueError("Unknown coverage %s" % coverage)
39 |
40 | sheet = WS_INFO["ws_" + new_ws][year]
41 |
42 | if "historical" not in sheet:
43 | raise KeyError(
44 | "New worksheet %s is not linked to an historical worksheet" % new_ws
45 | )
46 | old_ws = sheet["historical"]
47 |
48 | if old_ws not in self.all_data[key]:
49 | raise KeyError(
50 | "Old worksheet %s does not have any historical data" % old_ws
51 | )
52 |
53 | return self.all_data[key][sheet["historical"]]
54 |
55 | def import_from_file(self, fname, coverage, region=None, country=None, year=None):
56 | wb = openpyxl.load_workbook(fname, data_only=True)
57 |
58 | key = coverage
59 | if region:
60 | self.log.info("Importing for region %s", region)
61 | key = canon(region)
62 | elif country:
63 | self.log.info("Importing for country %s", country)
64 | key = canon(country)
65 | report_importer_by_year = {
66 | "2010": GMMP2010ReportImporter,
67 | "2015": GMMP2015ReportImporter,
68 | }
69 | report_importer = report_importer_by_year[year]()
70 | for sheet in self.historical_sheets(coverage, year):
71 | # find matching sheet name
72 | ws = report_importer.get_work_sheet(wb, sheet)
73 | if not ws:
74 | self.log.warn(
75 | "Couldn't find historical sheet %s; only have these sheets available: %s",
76 | sheet.get("historical"),
77 | ", ".join(sorted(wb.sheetnames)),
78 | )
79 | continue
80 |
81 | self.log.info(
82 | "Importing sheet %s of the %s report", sheet.get("historical"), year
83 | )
84 | data = report_importer.import_sheet(sheet)
85 | self.all_data.setdefault(key, {})[sheet.get("historical")] = data
86 | self.log.info(
87 | "Imported sheet %s of the %s report", sheet.get("historical"), year
88 | )
89 |
90 | def historical_sheets(self, coverage, year):
91 | sheets = []
92 | for sheets_by_year in WS_INFO.values():
93 | sheet = sheets_by_year.get(year)
94 | if sheet and ("historical" in sheet and coverage in sheet["reports"]):
95 | sheets.append(sheet)
96 | return sheets
97 |
--------------------------------------------------------------------------------
/forms/migrations/0058_add_deleted_attribute.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.16 on 2020-10-01 06:05
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('forms', '0057_add_monitor_code'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='internetnewsjournalist',
15 | name='deleted',
16 | field=models.BooleanField(default=False, help_text='Mark this Journalist as deleted.'),
17 | ),
18 | migrations.AddField(
19 | model_name='internetnewsperson',
20 | name='deleted',
21 | field=models.BooleanField(default=False, help_text='Mark this person as deleted.'),
22 | ),
23 | migrations.AddField(
24 | model_name='internetnewssheet',
25 | name='deleted',
26 | field=models.BooleanField(default=False, help_text='Mark this sheet as deleted.'),
27 | ),
28 | migrations.AddField(
29 | model_name='newspaperjournalist',
30 | name='deleted',
31 | field=models.BooleanField(default=False, help_text='Mark this Journalist as deleted.'),
32 | ),
33 | migrations.AddField(
34 | model_name='newspaperperson',
35 | name='deleted',
36 | field=models.BooleanField(default=False, help_text='Mark this person as deleted.'),
37 | ),
38 | migrations.AddField(
39 | model_name='newspapersheet',
40 | name='deleted',
41 | field=models.BooleanField(default=False, help_text='Mark this sheet as deleted.'),
42 | ),
43 | migrations.AddField(
44 | model_name='radiojournalist',
45 | name='deleted',
46 | field=models.BooleanField(default=False, help_text='Mark this Journalist as deleted.'),
47 | ),
48 | migrations.AddField(
49 | model_name='radioperson',
50 | name='deleted',
51 | field=models.BooleanField(default=False, help_text='Mark this person as deleted.'),
52 | ),
53 | migrations.AddField(
54 | model_name='radiosheet',
55 | name='deleted',
56 | field=models.BooleanField(default=False, help_text='Mark this sheet as deleted.'),
57 | ),
58 | migrations.AddField(
59 | model_name='televisionjournalist',
60 | name='deleted',
61 | field=models.BooleanField(default=False, help_text='Mark this Journalist as deleted.'),
62 | ),
63 | migrations.AddField(
64 | model_name='televisionperson',
65 | name='deleted',
66 | field=models.BooleanField(default=False, help_text='Mark this person as deleted.'),
67 | ),
68 | migrations.AddField(
69 | model_name='televisionsheet',
70 | name='deleted',
71 | field=models.BooleanField(default=False, help_text='Mark this sheet as deleted.'),
72 | ),
73 | migrations.AddField(
74 | model_name='twitterjournalist',
75 | name='deleted',
76 | field=models.BooleanField(default=False, help_text='Mark this Journalist as deleted.'),
77 | ),
78 | migrations.AddField(
79 | model_name='twitterperson',
80 | name='deleted',
81 | field=models.BooleanField(default=False, help_text='Mark this person as deleted.'),
82 | ),
83 | migrations.AddField(
84 | model_name='twittersheet',
85 | name='deleted',
86 | field=models.BooleanField(default=False, help_text='Mark this sheet as deleted.'),
87 | ),
88 | ]
89 |
--------------------------------------------------------------------------------
/forms/migrations/0003_auto_20150309_1340.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('forms', '0002_auto_20150309_1227'),
11 | ]
12 |
13 | operations = [
14 | migrations.AddField(
15 | model_name='televisionsheet',
16 | name='person_secondary',
17 | field=models.PositiveIntegerField(default=None, help_text=" \n Select ''Secondary Source'' only if the story is based solely on information from a report, article, or other piece of written information. \nCode information for: \n - Any person whom the story is about even if they are not interviewed or quoted \n - Each person who is interviewed \n - Each person in the story who is quoted, either directly or indirectly. Code only individual people. \n \nDo not code: \n - Groups (e.g. a group of nurses, a group of soldiers);\n - Organisations, companies, collectivities (e.g. political parties);\n - Characters in novels or movies (unless the story is about them);\n - Deceased historical figures (unless the story is about them);\n - Interpreters (Code the person being interviewed as if they spoke without an interpreter).\n", verbose_name='Source', choices=[(1, 'Person'), (2, 'Secondary Source')]),
18 | preserve_default=False,
19 | ),
20 | migrations.AlterField(
21 | model_name='radiosheet',
22 | name='num_female_anchors',
23 | field=models.PositiveIntegerField(help_text='The anchor (or announcer, or presenter) is the person who introduces the newscast and the individual items within it. Note: You should only include the anchors/announcers. Do not include reporters or other', verbose_name='Number of female anchors', choices=[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7), (8, 8), (9, 9)]),
24 | preserve_default=True,
25 | ),
26 | migrations.AlterField(
27 | model_name='radiosheet',
28 | name='num_male_anchors',
29 | field=models.PositiveIntegerField(help_text='The anchor (or announcer, or presenter) is the person who introduces the newscast and the individual items within it. Note: You should only include the anchors/announcers. Do not include reporters or other journalists ', verbose_name='Number of male anchors', choices=[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7), (8, 8), (9, 9)]),
30 | preserve_default=True,
31 | ),
32 | migrations.AlterField(
33 | model_name='televisionsheet',
34 | name='num_female_anchors',
35 | field=models.PositiveIntegerField(help_text='The anchor (or announcer, or presenter) is the person who introduces the newscast and the individual items within it. Note: You should only include the anchors/announcers. Do not include reporters or other', verbose_name='Number of female anchors', choices=[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7), (8, 8), (9, 9)]),
36 | preserve_default=True,
37 | ),
38 | migrations.AlterField(
39 | model_name='televisionsheet',
40 | name='num_male_anchors',
41 | field=models.PositiveIntegerField(help_text='The anchor (or announcer, or presenter) is the person who introduces the newscast and the individual items within it. Note: You should only include the anchors/announcers. Do not include reporters or other journalists ', verbose_name='Number of male anchors', choices=[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7), (8, 8), (9, 9)]),
42 | preserve_default=True,
43 | ),
44 | ]
45 |
--------------------------------------------------------------------------------
/forms/migrations/0018_auto_20150416_0855.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('forms', '0017_auto_20150331_1815'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='internetnewssheet',
16 | name='webpage_layer_no',
17 | field=models.PositiveIntegerField(help_text='Webpage Layer Number. Homepage=1, One click away=2, Five clicks away= 5, etc. Note that if a story appears on the front page, code with 1', verbose_name='(1) Webpage Layer Number'),
18 | preserve_default=True,
19 | ),
20 | migrations.AlterField(
21 | model_name='newspapersheet',
22 | name='page_number',
23 | field=models.PositiveIntegerField(help_text='Write in the number of the page on which the story begins. Story appears on first page = 1, Seventh page = 7, etc.', verbose_name='(1) Page Number'),
24 | preserve_default=True,
25 | ),
26 | migrations.AlterField(
27 | model_name='radiosheet',
28 | name='item_number',
29 | field=models.PositiveIntegerField(help_text='Write in the number that describes the position of the story within the newscast. E.g. the first story in the newscast is item 1; the seventh story is item 7.', verbose_name='(1) Item Number'),
30 | preserve_default=True,
31 | ),
32 | migrations.AlterField(
33 | model_name='radiosheet',
34 | name='num_female_anchors',
35 | field=models.PositiveIntegerField(help_text='The anchor (or announcer, or presenter) is the person who introduces the newscast and the individual items within it. Note: You should only include the anchors/announcers. Do not include reporters or other', verbose_name='Number of female anchors'),
36 | preserve_default=True,
37 | ),
38 | migrations.AlterField(
39 | model_name='radiosheet',
40 | name='num_male_anchors',
41 | field=models.PositiveIntegerField(help_text='The anchor (or announcer, or presenter) is the person who introduces the newscast and the individual items within it. Note: You should only include the anchors/announcers. Do not include reporters or other journalists ', verbose_name='Number of male anchors'),
42 | preserve_default=True,
43 | ),
44 | migrations.AlterField(
45 | model_name='televisionsheet',
46 | name='item_number',
47 | field=models.PositiveIntegerField(help_text='Write in the number that describes the position of the story within the newscast. E.g. the first story in the newscast is item 1; the seventh story is item 7.', verbose_name='(1) Item Number'),
48 | preserve_default=True,
49 | ),
50 | migrations.AlterField(
51 | model_name='televisionsheet',
52 | name='num_female_anchors',
53 | field=models.PositiveIntegerField(help_text='The anchor (or announcer, or presenter) is the person who introduces the newscast and the individual items within it. Note: You should only include the anchors/announcers. Do not include reporters or other', verbose_name='Number of female anchors'),
54 | preserve_default=True,
55 | ),
56 | migrations.AlterField(
57 | model_name='televisionsheet',
58 | name='num_male_anchors',
59 | field=models.PositiveIntegerField(help_text='The anchor (or announcer, or presenter) is the person who introduces the newscast and the individual items within it. Note: You should only include the anchors/announcers. Do not include reporters or other journalists ', verbose_name='Number of male anchors'),
60 | preserve_default=True,
61 | ),
62 | ]
63 |
--------------------------------------------------------------------------------
/forms/migrations/0019_auto_20150506_1052.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('forms', '0018_auto_20150416_0855'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='internetnewssheet',
16 | name='stereotypes',
17 | field=models.PositiveIntegerField(help_text='This story clearly challenges gender stereotypes', verbose_name='(23) Challenges Stereotypes', choices=[(1, '(1) Agree'), (2, '(2) Disagree'), (3, '(3) Neither agree nor disagree'), (4, '(4) Do not know')]),
18 | preserve_default=True,
19 | ),
20 | migrations.AlterField(
21 | model_name='newspapersheet',
22 | name='space',
23 | field=models.PositiveIntegerField(verbose_name='(4) Space', choices=[(1, '(1) Full page'), (2, '(2) Half page'), (3, '(3) One third page'), (4, '(4) Quarter page'), (5, '(5) Less than quarter page')]),
24 | preserve_default=True,
25 | ),
26 | migrations.AlterField(
27 | model_name='newspapersheet',
28 | name='stereotypes',
29 | field=models.PositiveIntegerField(help_text='This story clearly challenges gender stereotypes', verbose_name='(21) Challenges Stereotypes', choices=[(1, '(1) Agree'), (2, '(2) Disagree'), (3, '(3) Neither agree nor disagree'), (4, '(4) Do not know')]),
30 | preserve_default=True,
31 | ),
32 | migrations.AlterField(
33 | model_name='radiojournalist',
34 | name='role',
35 | field=models.PositiveIntegerField(verbose_name='Role', choices=[(1, '(1) Anchor, announcer or presenter: Usually in the television studio'), (2, '(2) Reporter: Usually outside the studio. Include reporters who do not appear on screen, but whose voice is heard (e.g. as voice-over).'), (3, '(3) Other journalist: Sportscaster, weather forecaster, commentator/analyst etc.')]),
36 | preserve_default=True,
37 | ),
38 | migrations.AlterField(
39 | model_name='radiosheet',
40 | name='stereotypes',
41 | field=models.PositiveIntegerField(help_text='This story clearly challenges gender stereotypes', verbose_name='(16) Challenges Stereotypes', choices=[(1, '(1) Agree'), (2, '(2) Disagree'), (3, '(3) Neither agree nor disagree'), (4, '(4) Do not know')]),
42 | preserve_default=True,
43 | ),
44 | migrations.AlterField(
45 | model_name='televisionjournalist',
46 | name='role',
47 | field=models.PositiveIntegerField(verbose_name='Role', choices=[(1, '(1) Anchor, announcer or presenter: Usually in the television studio'), (2, '(2) Reporter: Usually outside the studio. Include reporters who do not appear on screen, but whose voice is heard (e.g. as voice-over).'), (3, '(3) Other journalist: Sportscaster, weather forecaster, commentator/analyst etc.')]),
48 | preserve_default=True,
49 | ),
50 | migrations.AlterField(
51 | model_name='televisionsheet',
52 | name='stereotypes',
53 | field=models.PositiveIntegerField(help_text='This story clearly challenges gender stereotypes', verbose_name='(18) Challenges Stereotypes', choices=[(1, '(1) Agree'), (2, '(2) Disagree'), (3, '(3) Neither agree nor disagree'), (4, '(4) Do not know')]),
54 | preserve_default=True,
55 | ),
56 | migrations.AlterField(
57 | model_name='twittersheet',
58 | name='stereotypes',
59 | field=models.PositiveIntegerField(help_text='This tweet clearly challenges gender stereotypes', verbose_name='(8) Challenges Stereotypes', choices=[(1, '(1) Agree'), (2, '(2) Disagree'), (3, '(3) Neither agree nor disagree'), (4, '(4) Do not know')]),
60 | preserve_default=True,
61 | ),
62 | ]
63 |
--------------------------------------------------------------------------------
/gmmp/templates/index.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load static %}
3 |
4 | {% block page_content %}
5 |
6 |
7 |
8 |
9 |
Welcome to the 2020 GMMP Database.
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
24 |
27 |
Internet News: Capture here news that are online. Remember to only capture national (and if necessary, local) major websites.
28 |
29 |
30 |
36 |
39 |
Newspapers: Newspaper news are news published on print. The number of newspapers you code will depend on the number of newspapers in your country.
40 |
41 |
42 |
48 |
51 |
Twitter: Only capture tweets that are specific posts/feeds by news media on twitter. Remember to capture only national (and if necessary, local) media house Twitter feeds.
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
65 |
68 |
Radio News: Capture here news that are broadcast on the radio. The number of newscasts you code will depend on the number of radio channels that broadcast news in your country.
69 |
70 |
71 |
77 |
80 |
Televison News: Capture here news that are broadcast on television. The number of newscast you code will depend on the number of television channels that broadcast news in your country.
81 |
82 |
90 |
91 | {% endblock page_content %}
92 |
--------------------------------------------------------------------------------
/forms/migrations/0004_auto_20150309_1358.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('forms', '0003_auto_20150309_1340'),
11 | ]
12 |
13 | operations = [
14 | migrations.RemoveField(
15 | model_name='internetnewsperson',
16 | name='age',
17 | ),
18 | migrations.RemoveField(
19 | model_name='internetnewsperson',
20 | name='is_photograph',
21 | ),
22 | migrations.RemoveField(
23 | model_name='internetnewsperson',
24 | name='is_quoted',
25 | ),
26 | migrations.RemoveField(
27 | model_name='internetnewsperson',
28 | name='occupation_other',
29 | ),
30 | migrations.RemoveField(
31 | model_name='internetnewsperson',
32 | name='survivor_comments',
33 | ),
34 | migrations.RemoveField(
35 | model_name='internetnewsperson',
36 | name='victim_comments',
37 | ),
38 | migrations.RemoveField(
39 | model_name='newspaperperson',
40 | name='occupation_other',
41 | ),
42 | migrations.RemoveField(
43 | model_name='newspaperperson',
44 | name='survivor_comments',
45 | ),
46 | migrations.RemoveField(
47 | model_name='newspaperperson',
48 | name='victim_comments',
49 | ),
50 | migrations.RemoveField(
51 | model_name='radioperson',
52 | name='age',
53 | ),
54 | migrations.RemoveField(
55 | model_name='radioperson',
56 | name='is_photograph',
57 | ),
58 | migrations.RemoveField(
59 | model_name='radioperson',
60 | name='is_quoted',
61 | ),
62 | migrations.RemoveField(
63 | model_name='radioperson',
64 | name='occupation_other',
65 | ),
66 | migrations.RemoveField(
67 | model_name='radioperson',
68 | name='survivor_comments',
69 | ),
70 | migrations.RemoveField(
71 | model_name='radioperson',
72 | name='victim_comments',
73 | ),
74 | migrations.RemoveField(
75 | model_name='televisionperson',
76 | name='is_photograph',
77 | ),
78 | migrations.RemoveField(
79 | model_name='televisionperson',
80 | name='is_quoted',
81 | ),
82 | migrations.RemoveField(
83 | model_name='televisionperson',
84 | name='occupation_other',
85 | ),
86 | migrations.RemoveField(
87 | model_name='televisionperson',
88 | name='survivor_comments',
89 | ),
90 | migrations.RemoveField(
91 | model_name='televisionperson',
92 | name='victim_comments',
93 | ),
94 | migrations.RemoveField(
95 | model_name='twitterperson',
96 | name='age',
97 | ),
98 | migrations.RemoveField(
99 | model_name='twitterperson',
100 | name='family_role',
101 | ),
102 | migrations.RemoveField(
103 | model_name='twitterperson',
104 | name='function',
105 | ),
106 | migrations.RemoveField(
107 | model_name='twitterperson',
108 | name='is_quoted',
109 | ),
110 | migrations.RemoveField(
111 | model_name='twitterperson',
112 | name='occupation',
113 | ),
114 | migrations.RemoveField(
115 | model_name='twitterperson',
116 | name='occupation_other',
117 | ),
118 | migrations.RemoveField(
119 | model_name='twitterperson',
120 | name='survivor_comments',
121 | ),
122 | migrations.RemoveField(
123 | model_name='twitterperson',
124 | name='survivor_of',
125 | ),
126 | migrations.RemoveField(
127 | model_name='twitterperson',
128 | name='victim_comments',
129 | ),
130 | migrations.RemoveField(
131 | model_name='twitterperson',
132 | name='victim_of',
133 | ),
134 | migrations.RemoveField(
135 | model_name='twitterperson',
136 | name='victim_or_survivor',
137 | ),
138 | ]
139 |
--------------------------------------------------------------------------------
/forms/migrations/0051_increate_people_in_the_news_age.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.16 on 2020-09-07 06:08
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('forms', '0050_add_created_and_updated_at'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='internetnewsperson',
15 | name='age',
16 | field=models.PositiveIntegerField(choices=[(0, '(0) Do not know'), (1, '(1) 12 and under'), (2, '(2) 13-18'), (3, '(3) 19-34'), (4, '(4) 35-49'), (5, '(5) 50-64'), (6, '(6) 65-79'), (7, '(7) 80 years or more')], verbose_name='(13) Age (person appears)'),
17 | ),
18 | migrations.AlterField(
19 | model_name='internetnewssheet',
20 | name='covid19',
21 | field=models.PositiveIntegerField(choices=[(1, '(1) Yes'), (2, '(2) No')], help_text='Note: For the question below it is important NOT to code COVID19-related stories under topic 23 but to choose the most relevant secondary topic theme in order to ensure results that can be compared with those from previous GMMPs.', verbose_name='(z) Is this story related to coronavirus Covid-19?'),
22 | ),
23 | migrations.AlterField(
24 | model_name='newspaperperson',
25 | name='age',
26 | field=models.PositiveIntegerField(choices=[(0, '(0) Do not know'), (1, '(1) 12 and under'), (2, '(2) 13-18'), (3, '(3) 19-34'), (4, '(4) 35-49'), (5, '(5) 50-64'), (6, '(6) 65-79'), (7, '(7) 80 years or more')], verbose_name='(11) Age (person appears)'),
27 | ),
28 | migrations.AlterField(
29 | model_name='newspapersheet',
30 | name='covid19',
31 | field=models.PositiveIntegerField(choices=[(1, '(1) Yes'), (2, '(2) No')], help_text='Note: For the question below it is important NOT to code COVID19-related stories under topic 23 but to choose the most relevant secondary topic theme in order to ensure results that can be compared with those from previous GMMPs.', verbose_name='(z) Is this story related to coronavirus Covid-19?'),
32 | ),
33 | migrations.AlterField(
34 | model_name='radiosheet',
35 | name='covid19',
36 | field=models.PositiveIntegerField(choices=[(1, '(1) Yes'), (2, '(2) No')], help_text='Note: For the question below it is important NOT to code COVID19-related stories under topic 23 but to choose the most relevant secondary topic theme in order to ensure results that can be compared with those from previous GMMPs.', verbose_name='(z) Is this story related to coronavirus Covid-19?'),
37 | ),
38 | migrations.AlterField(
39 | model_name='televisionperson',
40 | name='age',
41 | field=models.PositiveIntegerField(choices=[(0, '(0) Do not know'), (1, '(1) 12 and under'), (2, '(2) 13-18'), (3, '(3) 19-34'), (4, '(4) 35-49'), (5, '(5) 50-64'), (6, '(6) 65-79'), (7, '(7) 80 years or more')], verbose_name='(12) Age (person appears)'),
42 | ),
43 | migrations.AlterField(
44 | model_name='televisionsheet',
45 | name='covid19',
46 | field=models.PositiveIntegerField(choices=[(1, '(1) Yes'), (2, '(2) No')], help_text='Note: For the question below it is important NOT to code COVID19-related stories under topic 23 but to choose the most relevant secondary topic theme in order to ensure results that can be compared with those from previous GMMPs.', verbose_name='(z) Is this story related to coronavirus Covid-19?'),
47 | ),
48 | migrations.AlterField(
49 | model_name='twitterperson',
50 | name='age',
51 | field=models.PositiveIntegerField(choices=[(0, '(0) Do not know'), (1, '(1) 12 and under'), (2, '(2) 13-18'), (3, '(3) 19-34'), (4, '(4) 35-49'), (5, '(5) 50-64'), (6, '(6) 65-79'), (7, '(7) 80 years or more')], verbose_name='(10) Age (person appears)'),
52 | ),
53 | migrations.AlterField(
54 | model_name='twittersheet',
55 | name='covid19',
56 | field=models.PositiveIntegerField(choices=[(1, '(1) Yes'), (2, '(2) No')], help_text='Note: For the question below it is important NOT to code COVID19-related stories under topic 23 but to choose the most relevant secondary topic theme in order to ensure results that can be compared with those from previous GMMPs.', verbose_name='(z) Is this story related to coronavirus Covid-19?'),
57 | ),
58 | ]
59 |
--------------------------------------------------------------------------------
/forms/migrations/0046_auto_20200312_0949.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.11 on 2020-03-12 09:49
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('forms', '0045_auto_20200306_1510'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='internetnewsperson',
15 | name='is_quoted',
16 | field=models.CharField(choices=[('Y', '(1) Yes'), ('N', '(2) No')], help_text='A person is directly quoted if their own words are printed, e.g. "The war against terror is our first priority" said President Bush. If the story paraphrases what the person said, that is not a direct quote, e.g. President Bush said that top priority would be given to fighting the war against terror.', max_length=1, verbose_name='(20) Is the person directly quoted'),
17 | ),
18 | migrations.AlterField(
19 | model_name='internetnewsperson',
20 | name='victim_or_survivor',
21 | field=models.CharField(choices=[('Y', '(1) Yes'), ('N', '(2) No')], help_text="You should code a person as a victim either if the word 'victim' is used to describe her/him, or if the story Implies that the person is a victim - e.g. by using language or images that evoke particular emotions such as shock, horror, pity for the person. You should code a person as a survivor either if the word 'survivor' is used to describe her/him, or if the story implies that the person is a survivor - e.g. by using language or images that evoke particular emotions such as admiration or respect for the person.", max_length=1, verbose_name='(17) Does the story identify the person as either a victim or survivor?'),
22 | ),
23 | migrations.AlterField(
24 | model_name='newspaperperson',
25 | name='is_quoted',
26 | field=models.CharField(choices=[('Y', '(1) Yes'), ('N', '(2) No')], help_text='A person is directly quoted if their own words are printed, e.g. "The war against terror is our first priority" said President Bush. If the story paraphrases what the person said, that is not a direct quote, e.g. President Bush said that top priority would be given to fighting the war against terror.', max_length=1, verbose_name='(18) Is the person directly quoted'),
27 | ),
28 | migrations.AlterField(
29 | model_name='newspaperperson',
30 | name='victim_or_survivor',
31 | field=models.CharField(choices=[('Y', '(1) Yes'), ('N', '(2) No')], help_text="You should code a person as a victim either if the word 'victim' is used to describe her/him, or if the story Implies that the person is a victim - e.g. by using language or images that evoke particular emotions such as shock, horror, pity for the person. You should code a person as a survivor either if the word 'survivor' is used to describe her/him, or if the story implies that the person is a survivor - e.g. by using language or images that evoke particular emotions such as admiration or respect for the person.", max_length=1, verbose_name='(15) Does the story identify the person as either a victim or survivor?'),
32 | ),
33 | migrations.AlterField(
34 | model_name='radioperson',
35 | name='victim_or_survivor',
36 | field=models.CharField(choices=[('Y', '(1) Yes'), ('N', '(2) No')], help_text="You should code a person as a victim either if the word 'victim' is used to describe her/him, or if the story Implies that the person is a victim - e.g. by using language or images that evoke particular emotions such as shock, horror, pity for the person. You should code a person as a survivor either if the word 'survivor' is used to describe her/him, or if the story implies that the person is a survivor - e.g. by using language or images that evoke particular emotions such as admiration or respect for the person.", max_length=1, verbose_name='(14) Does the story identify the person as either a victim or survivor?'),
37 | ),
38 | migrations.AlterField(
39 | model_name='televisionperson',
40 | name='victim_or_survivor',
41 | field=models.CharField(choices=[('Y', '(1) Yes'), ('N', '(2) No')], help_text="You should code a person as a victim either if the word 'victim' is used to describe her/him, or if the story Implies that the person is a victim - e.g. by using language or images that evoke particular emotions such as shock, horror, pity for the person. You should code a person as a survivor either if the word 'survivor' is used to describe her/him, or if the story implies that the person is a survivor - e.g. by using language or images that evoke particular emotions such as admiration or respect for the person.", max_length=1, verbose_name='(16) Does the story identify the person as either a victim or survivor?'),
42 | ),
43 | ]
44 |
--------------------------------------------------------------------------------
/forms/migrations/0042_auto_20200220_1247.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.9 on 2020-02-20 12:47
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('forms', '0041_auto_20200123_1224'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterModelOptions(
14 | name='internetnewssheet',
15 | options={'verbose_name': 'Internet'},
16 | ),
17 | migrations.AlterModelOptions(
18 | name='newspapersheet',
19 | options={'verbose_name': 'Newspaper'},
20 | ),
21 | migrations.AlterModelOptions(
22 | name='radiosheet',
23 | options={'verbose_name': 'Radio'},
24 | ),
25 | migrations.AlterModelOptions(
26 | name='televisionsheet',
27 | options={'verbose_name': 'Television'},
28 | ),
29 | migrations.AlterModelOptions(
30 | name='twittersheet',
31 | options={'verbose_name': 'Twitter'},
32 | ),
33 | migrations.AlterField(
34 | model_name='internetnewsperson',
35 | name='special_qn_1',
36 | field=models.CharField(blank=True, choices=[('Y', '(1) Yes'), ('N', '(2) No')], max_length=1, verbose_name='(22) Special question (1)'),
37 | ),
38 | migrations.AlterField(
39 | model_name='internetnewsperson',
40 | name='special_qn_2',
41 | field=models.CharField(blank=True, choices=[('Y', '(1) Yes'), ('N', '(2) No')], max_length=1, verbose_name='(23) Special question (2)'),
42 | ),
43 | migrations.AlterField(
44 | model_name='internetnewsperson',
45 | name='special_qn_3',
46 | field=models.CharField(blank=True, choices=[('Y', '(1) Yes'), ('N', '(2) No')], max_length=1, verbose_name='(24) Special question (3)'),
47 | ),
48 | migrations.AlterField(
49 | model_name='newspaperperson',
50 | name='special_qn_1',
51 | field=models.CharField(blank=True, choices=[('Y', '(1) Yes'), ('N', '(2) No')], max_length=1, verbose_name='(20) Special question (1)'),
52 | ),
53 | migrations.AlterField(
54 | model_name='newspaperperson',
55 | name='special_qn_2',
56 | field=models.CharField(blank=True, choices=[('Y', '(1) Yes'), ('N', '(2) No')], max_length=1, verbose_name='(21) Special question (2)'),
57 | ),
58 | migrations.AlterField(
59 | model_name='newspaperperson',
60 | name='special_qn_3',
61 | field=models.CharField(blank=True, choices=[('Y', '(1) Yes'), ('N', '(2) No')], max_length=1, verbose_name='(22) Special question (3)'),
62 | ),
63 | migrations.AlterField(
64 | model_name='radioperson',
65 | name='special_qn_1',
66 | field=models.CharField(blank=True, choices=[('Y', '(1) Yes'), ('N', '(2) No')], max_length=1, verbose_name='(17) Special question (1)'),
67 | ),
68 | migrations.AlterField(
69 | model_name='radioperson',
70 | name='special_qn_2',
71 | field=models.CharField(blank=True, choices=[('Y', '(1) Yes'), ('N', '(2) No')], max_length=1, verbose_name='(18) Special question (2)'),
72 | ),
73 | migrations.AlterField(
74 | model_name='radioperson',
75 | name='special_qn_3',
76 | field=models.CharField(blank=True, choices=[('Y', '(1) Yes'), ('N', '(2) No')], max_length=1, verbose_name='(19) Special question (3)'),
77 | ),
78 | migrations.AlterField(
79 | model_name='televisionperson',
80 | name='special_qn_1',
81 | field=models.CharField(blank=True, choices=[('Y', '(1) Yes'), ('N', '(2) No')], max_length=1, verbose_name='(19) Special question (1)'),
82 | ),
83 | migrations.AlterField(
84 | model_name='televisionperson',
85 | name='special_qn_2',
86 | field=models.CharField(blank=True, choices=[('Y', '(1) Yes'), ('N', '(2) No')], max_length=1, verbose_name='(20) Special question (2)'),
87 | ),
88 | migrations.AlterField(
89 | model_name='televisionperson',
90 | name='special_qn_3',
91 | field=models.CharField(blank=True, choices=[('Y', '(1) Yes'), ('N', '(2) No')], max_length=1, verbose_name='(21) Special question (3)'),
92 | ),
93 | migrations.AlterField(
94 | model_name='twitterperson',
95 | name='special_qn_1',
96 | field=models.CharField(blank=True, choices=[('Y', '(1) Yes'), ('N', '(2) No')], max_length=1, verbose_name='(14) Special question (1)'),
97 | ),
98 | migrations.AlterField(
99 | model_name='twitterperson',
100 | name='special_qn_2',
101 | field=models.CharField(blank=True, choices=[('Y', '(1) Yes'), ('N', '(2) No')], max_length=1, verbose_name='(15) Special question (2)'),
102 | ),
103 | migrations.AlterField(
104 | model_name='twitterperson',
105 | name='special_qn_3',
106 | field=models.CharField(blank=True, choices=[('Y', '(1) Yes'), ('N', '(2) No')], max_length=1, verbose_name='(16) Special question (3)'),
107 | ),
108 | ]
109 |
--------------------------------------------------------------------------------
/reports/historical/_base_importer.py:
--------------------------------------------------------------------------------
1 | """
2 | Holds the base import class which holds methods which are
3 | common to all year import classes. It also holds utility functions
4 | """
5 | import re
6 |
7 | from .canon import canon
8 |
9 |
10 | def v(v):
11 | """
12 | Try to interpret a value as an percentage
13 | """
14 | try:
15 | basestring
16 | except NameError:
17 | basestring = str
18 | if not isinstance(v, basestring):
19 | return v
20 |
21 | m = re.match(r"^(\d+(\.\d+)?)(%)?$", v)
22 | if m:
23 | if not m.group(2):
24 | # int
25 | v = int(m.group(1))
26 | else:
27 | # float
28 | v = float(m.group(1))
29 |
30 | if m.group(3):
31 | v = v / 100.0
32 | return v
33 |
34 |
35 | class BaseReportImporter(object):
36 | def __init__(self):
37 | self.ws = None
38 |
39 | def get_work_sheet(self, wb, sheet):
40 | for name in wb.sheetnames:
41 | if name == sheet.get("historical"):
42 | self.ws = wb[name]
43 | return self.ws
44 |
45 | def import_sheet(self, sheet):
46 | return getattr(self, "import_%s" % sheet.get("historical"))(sheet)
47 |
48 | def slurp_secondary_col_table(
49 | self,
50 | ws,
51 | data,
52 | col_start,
53 | cols_per_group,
54 | cols,
55 | row_end,
56 | row_start=5,
57 | major_col_heading_row=4,
58 | row_heading_col=5,
59 | ):
60 | """
61 | Get values from a table with two levels of column headings.
62 |
63 | eg.
64 | Major 1 | Major 2
65 | Col 1 | Col 2 | Col 1 | Col 2
66 | row1
67 | row2
68 | row3
69 | """
70 | for icol in range(col_start, col_start + cols * cols_per_group, cols_per_group):
71 | major_col_heading = canon(
72 | ws.cell(column=icol, row=major_col_heading_row).value
73 | )
74 | if not major_col_heading:
75 | continue
76 | major_col_data = {}
77 | data[major_col_heading] = major_col_data
78 |
79 | self.slurp_table(
80 | ws,
81 | major_col_data,
82 | icol,
83 | icol + cols_per_group - 1,
84 | row_end,
85 | row_start=row_start,
86 | col_heading_row=major_col_heading_row + 1,
87 | row_heading_col=row_heading_col,
88 | )
89 |
90 | def slurp_table(
91 | self,
92 | ws,
93 | data,
94 | col_start,
95 | col_end,
96 | row_end,
97 | row_start,
98 | col_heading_row,
99 | row_heading_col,
100 | ):
101 | """
102 | Grab values from a simple table with column and row titles.
103 |
104 | eg.
105 | Cat 1 | Cat 2 | N
106 | row1
107 | row2
108 | row3
109 | """
110 | for icol in range(col_start, col_end + 1):
111 | col_heading = canon(ws.cell(column=icol, row=col_heading_row).value)
112 | if not col_heading:
113 | continue
114 | col_data = {}
115 | data[col_heading] = col_data
116 |
117 | for irow in range(row_start, row_end + 1):
118 | row_heading = canon(ws.cell(column=row_heading_col, row=irow).value)
119 | col_data[row_heading] = v(ws.cell(column=icol, row=irow).value)
120 |
121 | def slurp_year_grouped_table(
122 | self,
123 | ws,
124 | all_data,
125 | col_start,
126 | cols_per_group,
127 | cols,
128 | row_end,
129 | row_start=5,
130 | year_heading_row=4,
131 | col_heading_row=3,
132 | row_heading_col=5,
133 | skip_years=[],
134 | ):
135 | """
136 | Slurp a table where each category contains a range of years.
137 |
138 | eg.
139 | Category 1 | Category 2 |
140 | 2005 | 2010 | N | 2005 | 2010 | N |
141 | row1
142 | row2
143 | row3
144 | """
145 | for icol in range(col_start, col_start + cols * cols_per_group, cols_per_group):
146 | col_heading = canon(ws.cell(column=icol, row=col_heading_row).value)
147 |
148 | for iyear in range(icol, icol + cols_per_group):
149 | year = ws.cell(column=iyear, row=year_heading_row).value
150 | if year in ["N", "N-F"]:
151 | year = 2010
152 | effective_col_heading = canon("N")
153 | else:
154 | year = int(year)
155 | effective_col_heading = col_heading
156 |
157 | if year not in skip_years:
158 | data = all_data.setdefault(year, {})
159 | col_data = data.setdefault(effective_col_heading, {})
160 |
161 | for irow in range(row_start, row_end + 1):
162 | row_heading = canon(
163 | ws.cell(column=row_heading_col, row=irow).value
164 | )
165 | col_data[row_heading] = v(ws.cell(column=iyear, row=irow).value)
166 |
--------------------------------------------------------------------------------
/gmmp/dashboard_modules.py:
--------------------------------------------------------------------------------
1 | from django.urls import reverse
2 | from django.utils.translation import gettext_lazy as _
3 | from jet.dashboard.modules import DashboardModule
4 |
5 | from forms.models import (
6 | InternetNewsSheet,
7 | NewspaperSheet,
8 | RadioSheet,
9 | TelevisionSheet,
10 | TwitterSheet,
11 | )
12 |
13 |
14 | class AddInternetNewsSubmission(DashboardModule):
15 | title = _("Internet")
16 | url = reverse("admin:forms_internetnewssheet_add")
17 | template = "gmmp/dashboard_modules/add_submission.html"
18 |
19 | def init_with_context(self, context):
20 | self.children = [
21 | {
22 | "title": _("Code Story"),
23 | "url": reverse("admin:forms_internetnewssheet_add"),
24 | "description": _('''Code all online news content from the home page or 'first layer' of the site that are not designated as health/sports/entertainment/business news unless it is apparent that they are
25 | uncharacteristically important stories that day (i.e. would appear in the front page section of a newspaper instead of the appropriate sub-section).'''),
26 | },
27 | ]
28 |
29 |
30 | class AddNewspaperSubmission(DashboardModule):
31 | title = _("Newspapers")
32 | url = reverse("admin:forms_newspapersheet_add")
33 | template = "gmmp/dashboard_modules/add_submission.html"
34 |
35 | def init_with_context(self, context):
36 | self.children = [
37 | {
38 | "title": _("Code Story"),
39 | "url": reverse("admin:forms_newspapersheet_add"),
40 | "description": _('''Begin with the main news page (usually Page 1). Code all the news stories on this page. Then go to the next major news page.
41 | Code regular news stories only - not editorials, commentaries, letters to the editor.'''),
42 | },
43 | ]
44 |
45 |
46 | class AddRadioSubmission(DashboardModule):
47 | title = _("Radio")
48 | url = reverse("admin:forms_radiosheet_add")
49 | template = "gmmp/dashboard_modules/add_submission.html"
50 |
51 | def init_with_context(self, context):
52 | self.children = [
53 | {
54 | "title": _("Code Story"),
55 | "url": reverse("admin:forms_radiosheet_add"),
56 | "description": _('''Code all the stories in the newscasts that you selected, including: All types of news — politics, local stories, international stories, reports on education, medicine, business, entertainment, and so on.
57 | Sports reports — code only if they are part of the newscast. (Do not code a programme if it is
58 | entirely about sports.)'''),
59 | },
60 | ]
61 |
62 |
63 | class AddTelevisionSubmission(DashboardModule):
64 | title = _("Television")
65 | url = reverse("admin:forms_televisionsheet_add")
66 | template = "gmmp/dashboard_modules/add_submission.html"
67 |
68 | def init_with_context(self, context):
69 | self.children = [
70 | {
71 | "title": _("Code Story"),
72 | "url": reverse("admin:forms_televisionsheet_add"),
73 | "description": _('''Code all the stories in the newscasts that you selected, including: All types of news — politics, local stories, international stories, reports on education, medicine, business, entertainment, and so on.
74 | Sports reports — code only if they are part of the newscast. (Do not code a programme if it is
75 | entirely about sports.)'''),
76 | },
77 | ]
78 |
79 |
80 | class AddTwitterSubmission(DashboardModule):
81 | title = _("Twitter")
82 | url = reverse("admin:forms_twittersheet_add")
83 | template = "gmmp/dashboard_modules/add_submission.html"
84 |
85 | def init_with_context(self, context):
86 | self.children = [
87 | {
88 | "title": _("Code Tweet"),
89 | "url": reverse("admin:forms_twittersheet_add"),
90 | "description": _('''Begin coding after 6.30 p.m. on the global monitoring day. Code every third tweet time stamped 6.30 p.m. or earlier published on the media monitoring day up to 15 – 20 tweets. If the Twitter news feed provider you have chosen does not yield 15 to 20 Tweets by taking every third item, take every second item. If they provide less than 15 tweets per day, they are not appropriate for inclusion in the monitoring.'''),
91 | },
92 | ]
93 |
94 |
95 | class Submissions(DashboardModule):
96 | title = "Coded"
97 | template = "gmmp/dashboard_modules/submissions.html"
98 |
99 | def _get_count(self, model_, user):
100 | return model_.objects.count() if user.is_superuser else model_.objects.filter(country=user.monitor.country).count()
101 |
102 | def init_with_context(self, context):
103 | self.children = [
104 | {
105 | "name": _("Newspapers"),
106 | "count": self._get_count(NewspaperSheet, context.request.user),
107 | "url": reverse("admin:forms_newspapersheet_changelist"),
108 | },
109 | {
110 | "name": _("Radio"),
111 | "count": self._get_count(RadioSheet, context.request.user),
112 | "url": reverse("admin:forms_radiosheet_changelist"),
113 | },
114 | {
115 | "name": _("Television"),
116 | "count": self._get_count(TelevisionSheet, context.request.user),
117 | "url": reverse("admin:forms_televisionsheet_changelist"),
118 | },
119 | {
120 | "name": _("Internet"),
121 | "count": self._get_count(InternetNewsSheet, context.request.user),
122 | "url": reverse("admin:forms_internetnewssheet_changelist"),
123 | },
124 | {
125 | "name": _("Twitter"),
126 | "count": self._get_count(TwitterSheet, context.request.user),
127 | "url": reverse("admin:forms_twittersheet_changelist"),
128 | },
129 | ]
130 |
--------------------------------------------------------------------------------
/gmmp/locale/en/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: PACKAGE VERSION\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-09-18 10:15+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
20 |
21 | #: gmmp/admin.py:13
22 | msgid "Monitor Details"
23 | msgstr ""
24 |
25 | #: gmmp/admin.py:17
26 | msgid "Country"
27 | msgstr ""
28 |
29 | #: gmmp/dashboard_modules.py:15 gmmp/dashboard_modules.py:117
30 | #: gmmp/settings.py:232
31 | msgid "Internet"
32 | msgstr ""
33 |
34 | #: gmmp/dashboard_modules.py:22 gmmp/dashboard_modules.py:38
35 | #: gmmp/dashboard_modules.py:54 gmmp/dashboard_modules.py:71
36 | msgid "Code Story"
37 | msgstr ""
38 |
39 | #: gmmp/dashboard_modules.py:24
40 | msgid ""
41 | "Code all online news content from the home page or 'first layer' of the site "
42 | "that are not designated as health/sports/entertainment/business news unless "
43 | "it is apparent that they are\n"
44 | "uncharacteristically important stories that day (i.e. would appear in the "
45 | "front page section of a newspaper instead of the appropriate sub-section)."
46 | msgstr ""
47 |
48 | #: gmmp/dashboard_modules.py:31 gmmp/dashboard_modules.py:102
49 | #: gmmp/settings.py:217
50 | msgid "Newspapers"
51 | msgstr ""
52 |
53 | #: gmmp/dashboard_modules.py:40
54 | msgid ""
55 | "Begin with the main news page (usually Page 1). Code all the news stories on "
56 | "this page. Then go to the next major news page.\n"
57 | "Code regular news stories only - not editorials, commentaries, letters to "
58 | "the editor."
59 | msgstr ""
60 |
61 | #: gmmp/dashboard_modules.py:47 gmmp/dashboard_modules.py:107
62 | #: gmmp/settings.py:222
63 | msgid "Radio"
64 | msgstr ""
65 |
66 | #: gmmp/dashboard_modules.py:56 gmmp/dashboard_modules.py:73
67 | msgid ""
68 | "Code all the stories in the newscasts that you selected, including: All "
69 | "types of news — politics, local stories, international stories, reports on "
70 | "education, medicine, business, entertainment, and so on.\n"
71 | "Sports reports — code only if they are part of the newscast. (Do not code a "
72 | "programme if it is\n"
73 | "entirely about sports.)"
74 | msgstr ""
75 |
76 | #: gmmp/dashboard_modules.py:64 gmmp/dashboard_modules.py:112
77 | #: gmmp/settings.py:227
78 | msgid "Television"
79 | msgstr ""
80 |
81 | #: gmmp/dashboard_modules.py:81 gmmp/dashboard_modules.py:122
82 | #: gmmp/settings.py:237
83 | msgid "Twitter"
84 | msgstr ""
85 |
86 | #: gmmp/dashboard_modules.py:88
87 | msgid "Code Tweet"
88 | msgstr ""
89 |
90 | #: gmmp/dashboard_modules.py:90
91 | msgid ""
92 | "Begin coding after 6.30 p.m. on the global monitoring day. Code every third "
93 | "tweet time stamped 6.30 p.m. or earlier published on the media monitoring "
94 | "day up to 15 – 20 tweets. If the Twitter news feed provider you have chosen "
95 | "does not yield 15 to 20 Tweets by taking every third item, take every second "
96 | "item. If they provide less than 15 tweets per day, they are not appropriate "
97 | "for inclusion in the monitoring."
98 | msgstr ""
99 |
100 | #: gmmp/settings.py:127
101 | msgid "English"
102 | msgstr ""
103 |
104 | #: gmmp/settings.py:128
105 | msgid "Spanish"
106 | msgstr ""
107 |
108 | #: gmmp/settings.py:129
109 | msgid "French"
110 | msgstr ""
111 |
112 | #: gmmp/settings.py:192
113 | msgid "Belgium - French"
114 | msgstr ""
115 |
116 | #: gmmp/settings.py:193
117 | msgid "Belgium - Flemish"
118 | msgstr ""
119 |
120 | #: gmmp/settings.py:195
121 | msgid "England"
122 | msgstr ""
123 |
124 | #: gmmp/settings.py:196
125 | msgid "North Ireland"
126 | msgstr ""
127 |
128 | #: gmmp/settings.py:197
129 | msgid "Scotland"
130 | msgstr ""
131 |
132 | #: gmmp/settings.py:198
133 | msgid "Wales"
134 | msgstr ""
135 |
136 | #: gmmp/settings.py:199
137 | msgid "International"
138 | msgstr ""
139 |
140 | #: gmmp/settings.py:203
141 | msgid "GMMP Database"
142 | msgstr ""
143 |
144 | #: gmmp/settings.py:214
145 | msgid "Code"
146 | msgstr ""
147 |
148 | #: gmmp/settings.py:244
149 | msgid "Coded"
150 | msgstr ""
151 |
152 | #: gmmp/settings.py:254
153 | msgid "Access Control"
154 | msgstr ""
155 |
156 | #: gmmp/settings.py:258
157 | msgid "General"
158 | msgstr ""
159 |
160 | #: gmmp/settings.py:261
161 | msgid "Help"
162 | msgstr ""
163 |
164 | #: gmmp/settings.py:263
165 | msgid "Facebook Group"
166 | msgstr ""
167 |
168 | #: gmmp/settings.py:268
169 | msgid "Methodology Guides & Coding Tools"
170 | msgstr ""
171 |
172 | #: gmmp/settings.py:275
173 | msgid "Other Links"
174 | msgstr ""
175 |
176 | #: gmmp/settings.py:278
177 | msgid "Who Makes The News"
178 | msgstr ""
179 |
180 | #: gmmp/settings.py:283
181 | msgid "Facebook"
182 | msgstr ""
183 |
184 | #: gmmp/templates/admin/base_site.html:41
185 | msgid "Welcome,"
186 | msgstr ""
187 |
188 | #: gmmp/templates/admin/change_form.html:6
189 | msgid "Home"
190 | msgstr ""
191 |
192 | #: gmmp/templates/admin/change_form.html:9
193 | msgid "Add Article"
194 | msgstr ""
195 |
196 | #: gmmp/templates/admin/login.html:23
197 | msgid "Please correct the error below."
198 | msgstr ""
199 |
200 | #: gmmp/templates/admin/login.html:23
201 | msgid "Please correct the errors below."
202 | msgstr ""
203 |
204 | #: gmmp/templates/admin/login.html:50
205 | msgid "Forgotten your password or username?"
206 | msgstr ""
207 |
208 | #: gmmp/templates/admin/login.html:54
209 | msgid "Log in"
210 | msgstr ""
211 |
212 | #: gmmp/templates/admin/submit_line.html:9
213 | msgid "Delete"
214 | msgstr ""
215 |
216 | #: gmmp/templates/admin/submit_line.html:11
217 | msgid "Save as new"
218 | msgstr ""
219 |
220 | #: gmmp/templates/admin/submit_line.html:12
221 | msgid "Save and continue editing"
222 | msgstr ""
223 |
224 | #: gmmp/templates/admin/submit_line.html:12
225 | msgid "Save and view"
226 | msgstr ""
227 |
228 | #: gmmp/templates/admin/submit_line.html:13
229 | msgid "Save and add another"
230 | msgstr ""
231 |
232 | #: gmmp/templates/admin/submit_line.html:14
233 | msgid "Save"
234 | msgstr ""
235 |
236 | #: gmmp/templates/admin/submit_line.html:15
237 | msgid "Close"
238 | msgstr ""
239 |
--------------------------------------------------------------------------------
/forms/migrations/0020_auto_20150506_1136.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 | import django_countries.fields
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('gmmp', '0001_initial'),
12 | ('forms', '0019_auto_20150506_1052'),
13 | ]
14 |
15 | operations = [
16 | migrations.AddField(
17 | model_name='internetnewssheet',
18 | name='country',
19 | field=django_countries.fields.CountryField(max_length=2, null=True),
20 | preserve_default=True,
21 | ),
22 | migrations.AddField(
23 | model_name='internetnewssheet',
24 | name='monitor',
25 | field=models.ForeignKey(to='gmmp.Monitor', null=True, on_delete=models.SET_NULL),
26 | preserve_default=True,
27 | ),
28 | migrations.AddField(
29 | model_name='newspapersheet',
30 | name='country',
31 | field=django_countries.fields.CountryField(max_length=2, null=True),
32 | preserve_default=True,
33 | ),
34 | migrations.AddField(
35 | model_name='newspapersheet',
36 | name='monitor',
37 | field=models.ForeignKey(to='gmmp.Monitor', null=True, on_delete=models.SET_NULL),
38 | preserve_default=True,
39 | ),
40 | migrations.AddField(
41 | model_name='radiosheet',
42 | name='country',
43 | field=django_countries.fields.CountryField(max_length=2, null=True),
44 | preserve_default=True,
45 | ),
46 | migrations.AddField(
47 | model_name='radiosheet',
48 | name='monitor',
49 | field=models.ForeignKey(to='gmmp.Monitor', null=True, on_delete=models.SET_NULL),
50 | preserve_default=True,
51 | ),
52 | migrations.AddField(
53 | model_name='televisionsheet',
54 | name='country',
55 | field=django_countries.fields.CountryField(max_length=2, null=True),
56 | preserve_default=True,
57 | ),
58 | migrations.AddField(
59 | model_name='televisionsheet',
60 | name='monitor',
61 | field=models.ForeignKey(to='gmmp.Monitor', null=True, on_delete=models.SET_NULL),
62 | preserve_default=True,
63 | ),
64 | migrations.AddField(
65 | model_name='twittersheet',
66 | name='country',
67 | field=django_countries.fields.CountryField(max_length=2, null=True),
68 | preserve_default=True,
69 | ),
70 | migrations.AddField(
71 | model_name='twittersheet',
72 | name='monitor',
73 | field=models.ForeignKey(to='gmmp.Monitor', null=True, on_delete=models.SET_NULL),
74 | preserve_default=True,
75 | ),
76 |
77 | migrations.RunSQL(
78 | """
79 | update forms_internetnewssheet
80 |
81 | set country = monitor.country,
82 | monitor_id = monitor.id
83 |
84 | from
85 | forms_internetnewssheet as sheet
86 | inner join
87 | (select distinct object_pk, content_type_id, user_id from guardian_userobjectpermission) as perms on perms.object_pk = cast(sheet.id as varchar)
88 | inner join django_content_type on django_content_type.model = 'internetnewssheet' and perms.content_type_id = django_content_type.id
89 | inner join gmmp_monitor monitor on monitor.user_id = perms.user_id
90 |
91 | where forms_internetnewssheet.id = sheet.id
92 | ;
93 |
94 | update forms_newspapersheet
95 |
96 | set country = monitor.country,
97 | monitor_id = monitor.id
98 |
99 | from
100 | forms_newspapersheet as sheet
101 | inner join
102 | (select distinct object_pk, content_type_id, user_id from guardian_userobjectpermission) as perms on perms.object_pk = cast(sheet.id as varchar)
103 | inner join django_content_type on django_content_type.model = 'newspapersheet' and perms.content_type_id = django_content_type.id
104 | inner join gmmp_monitor monitor on monitor.user_id = perms.user_id
105 |
106 | where forms_newspapersheet.id = sheet.id
107 | ;
108 |
109 | update forms_televisionsheet
110 |
111 | set country = monitor.country,
112 | monitor_id = monitor.id
113 |
114 | from
115 | forms_televisionsheet as sheet
116 | inner join
117 | (select distinct object_pk, content_type_id, user_id from guardian_userobjectpermission) as perms on perms.object_pk = cast(sheet.id as varchar)
118 | inner join django_content_type on django_content_type.model = 'televisionsheet' and perms.content_type_id = django_content_type.id
119 | inner join gmmp_monitor monitor on monitor.user_id = perms.user_id
120 |
121 | where forms_televisionsheet.id = sheet.id
122 | ;
123 |
124 | update forms_radiosheet
125 |
126 | set country = monitor.country,
127 | monitor_id = monitor.id
128 |
129 | from
130 | forms_radiosheet as sheet
131 | inner join
132 | (select distinct object_pk, content_type_id, user_id from guardian_userobjectpermission) as perms on perms.object_pk = cast(sheet.id as varchar)
133 | inner join django_content_type on django_content_type.model = 'radiosheet' and perms.content_type_id = django_content_type.id
134 | inner join gmmp_monitor monitor on monitor.user_id = perms.user_id
135 |
136 | where forms_radiosheet.id = sheet.id
137 | ;
138 |
139 | update forms_twittersheet
140 |
141 | set country = monitor.country,
142 | monitor_id = monitor.id
143 |
144 | from
145 | forms_twittersheet as sheet
146 | inner join
147 | (select distinct object_pk, content_type_id, user_id from guardian_userobjectpermission) as perms on perms.object_pk = cast(sheet.id as varchar)
148 | inner join django_content_type on django_content_type.model = 'twittersheet' and perms.content_type_id = django_content_type.id
149 | inner join gmmp_monitor monitor on monitor.user_id = perms.user_id
150 |
151 | where forms_twittersheet.id = sheet.id
152 | ;
153 | """,
154 | """
155 | select 1;
156 | """
157 | )
158 | ]
159 |
--------------------------------------------------------------------------------