diff --git a/CHANGES b/CHANGES
index 53fd1bad0..df03e5143 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,11 @@
+1.5
+---
+* add .xlsx format support
+* add ability to invalide all cache at startup (set `INVALIDATE_CACHE` env variable)
+* add ability to create default users at startup (set `AUTOCREATE_USERS` env variable)
+* enable Azure login without email
+* add partner.name to Intervention endpoint
+
1.4.1
-----
* fixes dependencies
diff --git a/MANIFEST.in b/MANIFEST.in
index af113d81e..268d68760 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -22,6 +22,7 @@ exclude Makefile
exclude manage.py
exclude manage.py
exclude tests
+exclude ROADMAP
prune .circleci
prune db
diff --git a/Pipfile b/Pipfile
index 5a3a7f32b..3c429e8ed 100644
--- a/Pipfile
+++ b/Pipfile
@@ -34,7 +34,6 @@ pyparsing = "*"
raven = "*"
sqlparse = "*"
whitenoise = "*"
-django-monthfield = "*"
django-model-utils = "*"
python-social-auth = "*"
social-auth-app-django = "*"
@@ -44,6 +43,7 @@ cryptography = "*"
"django-rest-framework-social-oauth2" = "*"
django-countries = "*"
django-filter = "*"
+drf-renderer-xlsx = "*"
[dev-packages]
"flake8" = ">=3.6.0"
@@ -62,7 +62,6 @@ pytest-coverage = "*"
pytest-django = "*"
pytest-echo = "*"
pytest-pythonpath = "*"
-#tox-pipenv = "==1.6.0"
yapf = "*"
vcrpy = "*"
diff --git a/Pipfile.lock b/Pipfile.lock
index b670cd10e..674d7233f 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "77175119807c7c5bbd7ff9d8b0e59eea0e19233b2e5d85d125232fa342d2f428"
+ "sha256": "4ebe763c2d5e81b5c2291549170c2ff2329dac4cafccf66b9b12e18589c19e12"
},
"pipfile-spec": 6,
"requires": {
@@ -264,13 +264,6 @@
"index": "pypi",
"version": "==3.1.2"
},
- "django-monthfield": {
- "hashes": [
- "sha256:981031ade748fa9a6fc5b1ded60b4a95c02af7a50ddbb69b734a564deb14683b"
- ],
- "index": "pypi",
- "version": "==0.1.1"
- },
"django-oauth-toolkit": {
"hashes": [
"sha256:ad1b76275950ebbff708222cec57bbdb879f89bac7df6b9dee0f4b9db485c264"
@@ -366,6 +359,14 @@
"index": "pypi",
"version": "==0.4.0"
},
+ "drf-renderer-xlsx": {
+ "hashes": [
+ "sha256:1fad2c299f444a68b2ff963a28df11697427afc582485bab26e8efacf1596bfb",
+ "sha256:b08c55b4a0c75578457fbfcaf75fce081cce0f46c84ddce0d86abf01dbce8c27"
+ ],
+ "index": "pypi",
+ "version": "==0.3.0"
+ },
"drf-yasg": {
"extras": [
"validation"
@@ -377,6 +378,13 @@
"index": "pypi",
"version": "==1.11.0"
},
+ "et-xmlfile": {
+ "hashes": [
+ "sha256:614d9722d572f6246302c4491846d2c393c199cfa4edc9af593437691683335b",
+ "sha256:a6de963569df3b3bf5a3427e2d40495e6ce81006dacb2b2b79670a0f42a8b689"
+ ],
+ "version": "==1.0.1"
+ },
"flex": {
"hashes": [
"sha256:17a58b3c0ca6524dbc79e1b266dcb8fc39d18060fbe8072ae297f14249ee0909",
@@ -426,6 +434,13 @@
],
"version": "==1.1.0"
},
+ "jdcal": {
+ "hashes": [
+ "sha256:948fb8d079e63b4be7a69dd5f0cd618a0a57e80753de8248fd786a8a20658a07",
+ "sha256:ea0a5067c5f0f50ad4c7bdc80abad3d976604f6fb026b0b3a17a9d84bb9046c9"
+ ],
+ "version": "==1.4"
+ },
"jinja2": {
"hashes": [
"sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
@@ -469,6 +484,12 @@
],
"version": "==2.1.0"
},
+ "openpyxl": {
+ "hashes": [
+ "sha256:022c0f3fa1e873cc0ba20651c54dd5e6276fc4ff150b4060723add4fc448645e"
+ ],
+ "version": "==2.5.9"
+ },
"psutil": {
"hashes": [
"sha256:1c19957883e0b93d081d41687089ad630e370e26dc49fd9df6951d6c891c4736",
diff --git a/ROADMAP b/ROADMAP
new file mode 100644
index 000000000..3a6e0fe01
--- /dev/null
+++ b/ROADMAP
@@ -0,0 +1 @@
+Features
diff --git a/src/etools_datamart/__init__.py b/src/etools_datamart/__init__.py
index 58a977a8c..8ff6ef56b 100644
--- a/src/etools_datamart/__init__.py
+++ b/src/etools_datamart/__init__.py
@@ -1,3 +1,3 @@
NAME = 'etools-datamart'
-VERSION = __version__ = '1.4.1'
+VERSION = __version__ = '1.5'
__author__ = ''
diff --git a/src/etools_datamart/api/endpoints/common.py b/src/etools_datamart/api/endpoints/common.py
index 8f09637cc..0f5fce262 100644
--- a/src/etools_datamart/api/endpoints/common.py
+++ b/src/etools_datamart/api/endpoints/common.py
@@ -7,6 +7,7 @@
from django.db import connections
from django.http import Http404
from drf_querystringfilter.exceptions import QueryFilterException
+from drf_renderer_xlsx.renderers import XLSXRenderer
from dynamic_serializer.core import DynamicSerializerMixin
from rest_framework.exceptions import NotAuthenticated, PermissionDenied
from rest_framework.filters import OrderingFilter
@@ -59,6 +60,7 @@ class APIReadOnlyModelViewSet(ReadOnlyModelViewSet):
renderer_classes = [JSONRenderer,
APIBrowsableAPIRenderer,
CSVRenderer,
+ XLSXRenderer,
]
filter_backends = [SystemFilterBackend,
DatamartQueryStringFilterBackend,
diff --git a/src/etools_datamart/api/filtering.py b/src/etools_datamart/api/filtering.py
index b315d92b6..4ade0d55e 100644
--- a/src/etools_datamart/api/filtering.py
+++ b/src/etools_datamart/api/filtering.py
@@ -15,63 +15,6 @@
'oct', 'nov', 'dec']
-# class SchemaFilterBackend(BaseFilterBackend):
-# @lru_cache(100)
-# def get_schema_fields(self, view):
-# return [coreapi.Field(
-# name='country_name',
-# required=False,
-# location='query',
-# schema=coreschema.String(
-# title='country_name',
-# description="""case insensitive, comma separated list of country names
-# {c}
-# """.format(c=", ".join([c.name for c in connections['etools'].get_tenants()]))
-# )
-# )]
-#
-# def filter_queryset(self, request, queryset, view):
-# value = request.GET.get('country_name', None)
-# assert queryset.model._meta.app_label == 'etools'
-# conn = connections['etools']
-# if not value:
-# if request.user.is_superuser:
-# conn.set_all_schemas()
-# else:
-# allowed = get_etools_allowed_schemas(request.user)
-# if not allowed:
-# raise PermissionDenied("You don't have enabled schemas")
-# conn.set_schemas(get_etools_allowed_schemas(request.user))
-# else:
-# value = set(value.split(","))
-# validate_schemas(*value)
-# if not request.user.is_superuser:
-# user_schemas = get_etools_allowed_schemas(request.user)
-# if not user_schemas.issuperset(value):
-# raise NotAuthorizedSchema(",".join(sorted(value - user_schemas)))
-# conn.set_schemas(value)
-# return queryset
-# class CountryFilterBackend(SchemaFilterBackend):
-# def filter_queryset(self, request, queryset, view):
-# value = request.GET.get('country_name', None)
-# assert queryset.model._meta.app_label != 'etools'
-# if not value:
-# if not request.user.is_superuser:
-# allowed = get_etools_allowed_schemas(request.user)
-# if not allowed:
-# raise PermissionDenied("You don't have enabled schemas")
-# queryset.filter(country_name__in=allowed)
-# else:
-# value = set(value.split(","))
-# validate_schemas(*value)
-# if not request.user.is_superuser:
-# user_schemas = get_etools_allowed_schemas(request.user)
-# if not user_schemas.issuperset(value):
-# raise NotAuthorizedSchema(",".join(sorted(value - user_schemas)))
-# queryset.filter(country_name__in=value)
-# return queryset
-
-
class CountryNameProcessor:
def process_country_name(self, efilters, eexclude, field, value, request,
op, param, negate, **payload):
@@ -99,16 +42,6 @@ def process_country_name(self, efilters, eexclude, field, value, request,
class CountryNameProcessorTenantModel(CountryNameProcessor):
pass
- # def process_country_name(self, filters, exclude, field, value, request, **payload):
- # _f, _e = super().process_country_name({}, {}, field, value, request, **payload)
- # # assert queryset.model._meta.app_label == 'etools'
- # conn = connections['etools']
- # if 'country_name__iregex' in _f:
- # conn.set_schemas(_f['country_name__iregex'])
- # else:
- # conn.set_all_schemas()
- #
- # return filters, exclude
class MonthProcessor:
diff --git a/src/etools_datamart/apps/data/admin.py b/src/etools_datamart/apps/data/admin.py
index 92b717e29..129a0e643 100644
--- a/src/etools_datamart/apps/data/admin.py
+++ b/src/etools_datamart/apps/data/admin.py
@@ -123,6 +123,6 @@ class FAMIndicatorAdmin(DataModelAdmin):
@register(models.UserStats)
class UserStatsAdmin(DataModelAdmin):
list_display = ('country_name', 'schema_name', 'month', 'total', 'unicef', 'logins', 'unicef_logins')
- list_filter = (SchemaFilter, 'month',)
+ list_filter = (SchemaFilter, 'month')
load_handler = load_user_report
date_hierarchy = 'month'
diff --git a/src/etools_datamart/apps/data/migrations/0002_intervention_partner_name.py b/src/etools_datamart/apps/data/migrations/0002_intervention_partner_name.py
new file mode 100644
index 000000000..05810c918
--- /dev/null
+++ b/src/etools_datamart/apps/data/migrations/0002_intervention_partner_name.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.1.3 on 2018-11-06 13:55
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('data', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='intervention',
+ name='partner_name',
+ field=models.CharField(max_length=200, null=True),
+ ),
+ ]
diff --git a/src/etools_datamart/apps/data/models/intervention.py b/src/etools_datamart/apps/data/models/intervention.py
index 3e4fa9207..ddda23684 100644
--- a/src/etools_datamart/apps/data/models/intervention.py
+++ b/src/etools_datamart/apps/data/models/intervention.py
@@ -34,6 +34,7 @@ class Intervention(DataMartModel):
unicef_signatory_last_name = models.CharField(max_length=30, null=True)
unicef_signatory_email = models.CharField(max_length=254, null=True)
+ partner_name = models.CharField(max_length=200, null=True)
partner_signatory_title = models.CharField(max_length=64, null=True)
partner_signatory_first_name = models.CharField(max_length=64, null=True)
partner_signatory_last_name = models.CharField(max_length=64, null=True)
diff --git a/src/etools_datamart/apps/etl/tasks/etl.py b/src/etools_datamart/apps/etl/tasks/etl.py
index 7a6a42f1a..73b9adfef 100644
--- a/src/etools_datamart/apps/etl/tasks/etl.py
+++ b/src/etools_datamart/apps/etl/tasks/etl.py
@@ -104,7 +104,7 @@ def load_intervention():
end_date=record.end,
review_date_prc=record.review_date_prc,
prc_review_document=record.prc_review_document,
-
+ partner_name=record.agreement.partner.name,
agreement_id=record.agreement.pk,
partner_authorized_officer_signatory_id=get_attr(record,
'partner_authorized_officer_signatory.pk'),
diff --git a/src/etools_datamart/apps/init/management/commands/init-setup.py b/src/etools_datamart/apps/init/management/commands/init-setup.py
index 2ee6f08fa..d483ad8bb 100644
--- a/src/etools_datamart/apps/init/management/commands/init-setup.py
+++ b/src/etools_datamart/apps/init/management/commands/init-setup.py
@@ -1,5 +1,6 @@
import os
import sys
+import warnings
from django.conf import settings
from django.contrib.auth import get_user_model
@@ -11,6 +12,7 @@
from django_celery_beat.models import CrontabSchedule, PeriodicTask
from humanize import naturaldelta
from strategy_field.utils import fqn
+from unicef_rest_framework.models.acl import GroupAccessControl
from etools_datamart.apps.etl.models import TaskLog
from etools_datamart.celery import app
@@ -83,6 +85,7 @@ def handle(self, *args, **options):
"is_staff": True,
"password": make_password(pwd)})
Group.objects.get_or_create(name='Guests')
+ all_access, __ = Group.objects.get_or_create(name='Endpoints all access')
if created: # pragma: no cover
self.stdout.write(f"Created superuser `{admin}` with password `{pwd}`")
@@ -92,6 +95,35 @@ def handle(self, *args, **options):
from unicef_rest_framework.models import Service
created, deleted, total = Service.objects.load_services()
self.stdout.write(f"{total} services found. {created} new. {deleted} deleted")
+ if os.environ.get('INVALIDATE_CACHE'):
+ Service.objects.invalidate_cache()
+
+ for service in Service.objects.all():
+ GroupAccessControl.objects.get_or_create(
+ group=all_access,
+ service=service,
+ serializers=['*'],
+ policy=GroupAccessControl.POLICY_ALLOW
+ )
+
+ if os.environ.get('AUTOCREATE_USERS'):
+ self.stdout.write("Found 'AUTOCREATE_USERS' environment variable")
+ self.stdout.write("Going to create new users")
+ try:
+ for entry in os.environ.get('AUTOCREATE_USERS').split(','):
+ user, pwd = entry.split('|')
+ User = get_user_model()
+ u, created = User.objects.get_or_create(username=user)
+ if created:
+ self.stdout.write(f"Created user {u}")
+ u.set_password(pwd)
+ u.save()
+ u.groups.add(all_access)
+ else:
+ self.stdout.write(f"User {u} already exists.")
+
+ except Exception as e:
+ warnings.warn(f"Unable to create default users. {e}")
if options['tasks'] or _all or options['refresh']:
midnight, __ = CrontabSchedule.objects.get_or_create(minute=0, hour=0)
diff --git a/src/etools_datamart/apps/web/static/favicon.ico b/src/etools_datamart/apps/web/static/favicon.ico
new file mode 100644
index 000000000..1f67ff427
Binary files /dev/null and b/src/etools_datamart/apps/web/static/favicon.ico differ
diff --git a/src/etools_datamart/apps/web/templates/base.html b/src/etools_datamart/apps/web/templates/base.html
index 30d812757..c4e351edc 100644
--- a/src/etools_datamart/apps/web/templates/base.html
+++ b/src/etools_datamart/apps/web/templates/base.html
@@ -4,6 +4,7 @@