Skip to content
Commits on Source (7)
[bumpversion]
current_version = 0.6.0
current_version = 0.7.0
commit = True
[bumpversion:file:web/opdm_service/__init__.py]
......
......@@ -9,8 +9,6 @@ stages:
- deploy
variables:
# IMAGE: $CI_REGISTRY/$CI_PROJECT_PATH:$CI_COMMIT_REF_NAME
# RELEASE_IMAGE: $CI_REGISTRY/$CI_PROJECT_PATH:latest
CONTAINER_IMAGE: ${CI_REGISTRY}/${CI_PROJECT_PATH}:latest
DOCKER_DRIVER: overlay2
......@@ -53,3 +51,28 @@ rancher_staging:
restart opdm-service/nginx
only:
- develop
rancher_production:
stage: deploy
image: tagip/rancher-cli
environment:
name: production
url: http://service.opdm.openpolis.io/v1
script:
- echo "Deploy to production server"
- export SECRET_KEY="${PRODUCTION_SECRET_KEY}"
- export ALLOWED_HOSTS=staging.service.opdm.openpolis.io
- rancher
--url ${RANCHER_URL}
--access-key ${RANCHER_PRODUCTION_ACCESS_KEY}
--secret-key ${RANCHER_PRODUCTION_SECRET_KEY}
--env op-production --debug
up -d --pull --confirm-upgrade --stack opdm-service --force-recreate web
- rancher
--url ${RANCHER_URL}
--access-key ${RANCHER_PRODUCTION_ACCESS_KEY}
--secret-key ${RANCHER_PRODUCTION_SECRET_KEY}
--env op-production --debug
restart opdm-service/nginx
only:
- master
......@@ -5,6 +5,26 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.7.0]
### Added
- home page redirects to `/about` page
- `/about` page shows useful links for developers
- `/about` page contains a detailed description of the project's
technical details, and basic statistics on data
- markdown content within django templates are now possible,
through `markdown_tags.markdown` template tag
### Fixed
- bug in locations extraction with limit and offset fixed
### Changed
- gitlab CI deploy to production added (master only)
## [0.6.0]
### Fixed
- problem in `REG` context for `posts_and_persons` import solved
......@@ -17,6 +37,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Fixed
- `get_or_create` criterion while creating global organizations corrected
## [0.5.1]
### Fixed
- one label in Post was corrected, for posts imported from openpolis
......
......@@ -79,9 +79,9 @@ It uses:
The web image must be built and pushed to the gitlab registry:
docker build -t registry.gitlab.depp.it/depp/open-bilanci-armonizzati:latest web
docker build -t registry.gitlab.depp.it/openpolis/opdm-service:latest web
docker login registry.gitlab.depp.it
docker push registry.gitlab.depp.it/depp/open-bilanci-armonizzati:latest
docker push registry.gitlab.depp.it/openpolis/opdm-service:latest
To deploy on a local or on a remote machine (using `docker-machine`):
......
This diff is collapsed.
"The Openpolis Data Manager service package (the backend)"
__version__ = '0.6.0'
__version__ = '0.7.0'
......@@ -196,7 +196,7 @@ class OpAPIImporter(object):
if locations_limit:
info = info[locations_offset:locations_offset + locations_limit]
else:
info = info[locations_offset]
info = info[locations_offset:]
return info
......
......@@ -210,7 +210,6 @@ DJANGO_APPS = (
# Admin panel and documentation:
'django.contrib.admin',
# 'django.contrib.admindocs',
# Django helper
'django_extensions',
......@@ -218,6 +217,8 @@ DJANGO_APPS = (
'rest_framework',
'rest_framework_swagger',
'django_filters',
'django.contrib.humanize',
)
# Apps specific for this project go here.
......
from django import template
from django.conf import settings
from django.utils.safestring import mark_safe
try:
from django.utils.encoding import force_text
except ImportError:
from django.utils.encoding import force_unicode as force_text
import mistune
register = template.Library()
@register.tag(name="markdown")
def markdown_tag(parser, token):
nodelist = parser.parse(('endmarkdown',))
parser.delete_first_token() # consume '{% endmarkdown %}'
return MarkdownNode(nodelist)
class MarkdownNode(template.Node):
def __init__(self, nodelist):
self.nodelist = nodelist
def render(self, context):
value = self.nodelist.render(context)
try:
return mark_safe(mistune.markdown(value, escape=False))
except ImportError:
if settings.DEBUG:
raise template.TemplateSyntaxError("Error in `markdown` tag: "
"The mistune library isn't installed.")
return force_text(value)
......@@ -2,9 +2,8 @@
"""
from django.conf import settings
from django.conf.urls import include, url
from django.conf.urls.static import static
from django.contrib import admin
from django.views.generic import TemplateView
from django.views.generic import TemplateView, RedirectView
from rest_framework import routers
from rest_framework_jwt.views import obtain_jwt_token, refresh_jwt_token
......@@ -12,8 +11,7 @@ from rest_framework.schemas import get_schema_view
from rest_framework.documentation import include_docs_urls
from opdm_service.views import AreaViewSet, OrganizationViewSet, \
MembershipViewSet, PersonViewSet, PostViewSet
import popolo.urls
MembershipViewSet, PersonViewSet, PostViewSet, AboutView
admin.autodiscover()
......@@ -41,13 +39,24 @@ urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api-token-auth/', obtain_jwt_token, name='obtain-jwt'),
url(r'^api-token-refresh/', refresh_jwt_token, name='refresh-jwt'),
url(r'^api-auth/', include('rest_framework.urls',
namespace='rest_framework')),
url(r'^api-auth/',
include('rest_framework.urls', namespace='rest_framework')
),
url(r'^$', RedirectView.as_view(url="about"), name='home'),
url(r'^about/',
AboutView.as_view(template_name="about.html"),
name='about'
),
url(r'^(?P<version>v1)/', include(router.urls)),
url(r'^schema/', schema_view),
url(r'^docs/', include_docs_urls(title='DataManager API')),
url(r'^403$', TemplateView.as_view(template_name="403.html"), name='tampering-403'),
url(r'^popolo/', include(popolo.urls)),
url(
r'^docs/',
include_docs_urls(title='DataManager API'),
),
url(r'^403$',
TemplateView.as_view(template_name="403.html"),
name='tampering-403'
),
]
if settings.DEBUG_TOOLBAR:
......
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# ViewSets define the view behavior.
from django.db.models import Count
from django.views.generic import TemplateView
from django_filters import rest_framework as filters
from rest_framework import viewsets, permissions
......@@ -19,6 +19,15 @@ from opdm_service.serializers import AreaListSerializer, AreaSerializer, \
MembershipListSerializer, PersonListSerializer, PersonSerializer, \
PostListSerializer, MembershipSerializer
class AboutView(TemplateView):
def get_context_data(self, **kwargs):
return {
'n_memberships': Membership.objects.count(),
'n_persons': Person.objects.count(),
'n_organizations': Organization.objects.count(),
'n_current_areas': Area.objects.current().count(),
'n_past_areas': Area.objects.past().count()
}
class AreaViewSet(viewsets.ReadOnlyModelViewSet):
"""
......@@ -192,20 +201,27 @@ class OrganizationViewSet(viewsets.ReadOnlyModelViewSet):
"""
self.serializer_class = MembershipListSerializer
instance = self.get_object()
queryset = instance.memberships.all().prefetch_related(
self.serializer_class = MembershipListSerializer
membership_vieset = MembershipViewSet(request=request)
self.search_fields = membership_vieset.search_fields
self.ordering_fields = membership_vieset.ordering_fields
self.filter_backends = membership_vieset.filter_backends
self.filter_class = membership_vieset.filter_class
self.queryset = instance.memberships.all().prefetch_related(
'person', 'organization',
'post', 'area',
)
queryset = MembershipViewSet(request=request).filter_queryset(queryset)
self.queryset = membership_vieset.filter_queryset(self.queryset)
page = self.paginate_queryset(queryset)
page = self.paginate_queryset(self.queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
serializer = self.get_serializer(self.queryset, many=True)
return Response(serializer.data)
......
{% extends "base.html" %}
{% load markdown_tags %}
{% load staticfiles i18n %}
{% load humanize %}
{% block title %}OPDM about page{% endblock %}
{% block page_title %}About - OPDM{% endblock page_title %}
{% block content %}
{% markdown %}
# Openpolis Data Manager
## Introduzione
Openpolis DataManager contiene tutti i dati Openpolis
sul *potere in Italia*.
Sono mappati attualmente {{ n_memberships|intcomma }} **incarichi pubblici**
per {{ n_persons|intcomma }} **persone** in
{{ n_organizations|intcomma }} **organizzazioni**
di diritto o a controllo pubblico,
distribuite su {{ n_current_areas|intcomma }} **aree geografiche** attuali e
{{ n_past_areas|intcomma }} passate.
## Dettagli tecnici
Attualmente è implementata una [API REST][apiroot],
che permette l'accesso libero in sola lettura.
L'API REST è sviluppata secondo lo standard [Core API][coreapi] e la
[documentazione][apidocs], interattiva e generata e aggiornata automaticamente,
è disponibile,con esempi di utilizzo per shell, python e javascript.
L'API fornisce risposte in formato JSON o in formato HTML *navigabile* ed
è quindi esplorabile da browser (con filtri, motore di ricerca e ordinamento dei
risultati).
Le risposte sono *paginate*, con 25 risultati per pagina di default, estendibili
fino a 1000. I link per caricare le pagine successive o precedenti si trovano
sotto forma di URL nella stessa pagina dei risultati.
Le modifiche future all'API sono versionate (attualmente v1),
in modo da garantire la continuità di esercizio agli applicativi
che la utilizzino per tutto il tempo necessario all'adozione
delle nuove versioni.
Gli accessi sono attualmente limitati a 10000 per giorno per utenti anonimi,
e illimitati per utenti autorizzati.
L'autorizzazione può essere fatta attraverso [JSON Web Token][jwt] o
HTTP Basic Auth.
Tutti gli endpoint rispondono esclusivamente su porta HTTPS, in modo che
l'invio dei parametri di autorizzazione non avvenga in chiaro.
E' previsto, a breve, il rilascio di un endpoint [Graphql][graphql], per semplificare
il processo di sviluppo di applicativi basati su questi dati.
[apiroot]: {% url 'api-root' 'v1' %}
[coreapi]: http://www.coreapi.org/
[apidocs]: {% url 'api-docs:docs-index' %}
[jwt]: https://jwt.io/
[graphql]: http://graphql.org/
## Modello dei dati di dominio (sintesi)
![Il diagramma del modello del dominio][domain_model]
[domain_model]: {% static 'images/domain_model.png' %} "Diagramma modello del dominio"
Il modello è un'istanza di [Popolo][popolo], uno standard internazionale
per gli open government data, sviluppato da un consorzio di cui Openpolis
è parte.
[popolo]: http://www.popoloproject.com/
Gli oggetti principali (Person, Membership, Organization, Area, Post),
hanno una data inizio (`start_date`) e una data fine (`end_date`).
A seconda del contesto, le date possono avere nomi e significati differenti,
ad esempio, nel caso di una persona, `birth_date` o `death_date`, e nel caso
di un'organizzazione, `funding_date` e `dissolution_date`.
Per tutti gli oggetti principali, sono comunque disponibili filtri
su queste date, attraverso i parametri:
| | |
| -----------------:|:----------------------------------------- |
| `status` | oggetti attuali o passati (current, past) |
| `active_at` | oggetti attivi alla data indicata |
| `active_from` | oggetti ... |
| `active_to` | oggetti ... |
Questi parametri sono indicati in modo esplicito nella documentazione.
**Esempio**: *elenco dei soli comuni attivi in data orierna*.
```
http://service.opdm.openpolis.io/v1/areas?istat_classification=COM&status=current
```
Per ciascuna istanza degli oggetti, sono registrati i timestamp di creazione
e di ultimo aggiornamento. In questo modo è possibile usare il filtro
`updated_after`, per richiedere gli elenchi di oggetti aggiornati dopo un
dato istante.
**Esempio**: *elenco dei soli comuni aggiornati dopo le 12 del 24 novembre 2017*.
```
http://service.opdm.openpolis.io/v1/areas?istat_classification=COM&updated_after=2017-11-24T12:00:00
```
## Reference del modello
Di seguito sono elencati gli oggetti principali, con i loro campi/relazioni:
### Membership (incarico)
Descrive un incarico specifico, legato a un'Organizzazione reale,
con una data di inizio e fine.
| Denominazione | Descrizione |
| -----------------:|:--------------------------------- |
| `label` | Descrizione dell'incarico |
| `role` | Deleghe o altri dettagli |
### Person
- `name`
- ...
### Organization
### Area
### Post (tipo di incarico)
## Stack software utilizzato
I dati sono su un DB relazionale (postgres 9.6) e l'applicazione è sviluppata
con django/python.
Le api REST sono sviluppate con django-rest-framework, l'endpoint graphql
con graphene-django.
L'applicazione web è servita attraverso Nginx + Gunicorn.
Il sistema operativo è Linux, distribuzione Ubuntu 16.04.
Il codice sorgente dell'applicazione è disponibile liberamente sul [repository
gitlab di openpolis][gitlab].
[gitlab]: https://gitlab.depp.it/openpolis/opdm/opdm-service
## Licenze di utilizzo dei dati
I dati esposti sono liberamente utilizzabili per fini non commerciali, a patto
di citare la fonte, secondo quanto indicato dalla licenza [CC-BY-NC][cc].
[cc]: https://creativecommons.org/licenses/by-nc/3.0/it/
## Contatti
Per informazioni sull'utilizzo dei dati e/o eventuali segnalazioni di errori:
info@openpolis.it.
Per informazioni sulle tecnologie: guglielmo@openpolis.it.
{% endmarkdown %}
{% endblock content %}
......@@ -2,9 +2,9 @@
<html lang="en">
<head>
<meta charset="utf-8">
<title>{% block page_title %}mosic2-area-riservata{% endblock %}</title>
<title>{% block page_title %}opdm-api{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="{% block page_description %}Area riservata per condivisione documenti Mosic2{% endblock %}">
<meta name="description" content="{% block page_description %}Openpolis Data Manager API{% endblock %}">
<meta name="author" content="{% block page_author %}Guglielmo Celata{% endblock %}">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
......