diff --git a/association/models.py b/association/models.py index 00dd566b8..372c3e3ce 100644 --- a/association/models.py +++ b/association/models.py @@ -10,9 +10,10 @@ from organization.models import Organization from location.models import Location from sfm_pc.models import GetComplexFieldNameMixin +from source.mixins import SourcesMixin -class Association(models.Model, BaseModel, GetComplexFieldNameMixin): +class Association(models.Model, BaseModel, SourcesMixin, GetComplexFieldNameMixin): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.startdate = ComplexFieldContainer(self, AssociationStartDate) diff --git a/composition/models.py b/composition/models.py index 8aef0bb38..c2648445a 100644 --- a/composition/models.py +++ b/composition/models.py @@ -9,9 +9,10 @@ from complex_fields.models import ComplexField, ComplexFieldContainer from complex_fields.base_models import BaseModel from sfm_pc.models import GetComplexFieldNameMixin +from source.mixins import SourcesMixin -class Composition(models.Model, BaseModel, GetComplexFieldNameMixin): +class Composition(models.Model, BaseModel, SourcesMixin, GetComplexFieldNameMixin): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.parent = ComplexFieldContainer(self, CompositionParent) diff --git a/configs/sfm.conf.production.nginx.gpg b/configs/sfm.conf.production.nginx.gpg index aa13411b6..0155f709e 100644 Binary files a/configs/sfm.conf.production.nginx.gpg and b/configs/sfm.conf.production.nginx.gpg differ diff --git a/emplacement/models.py b/emplacement/models.py index a48ea8442..035ca8f51 100644 --- a/emplacement/models.py +++ b/emplacement/models.py @@ -14,9 +14,10 @@ from organization.models import Organization from location.models import Location from sfm_pc.models import GetComplexFieldNameMixin +from source.mixins import SourcesMixin -class Emplacement(models.Model, BaseModel, GetComplexFieldNameMixin): +class Emplacement(models.Model, BaseModel, SourcesMixin, GetComplexFieldNameMixin): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.startdate = ComplexFieldContainer(self, EmplacementStartDate) diff --git a/membershiporganization/models.py b/membershiporganization/models.py index eb88cc901..d8c873727 100644 --- a/membershiporganization/models.py +++ b/membershiporganization/models.py @@ -11,9 +11,10 @@ from complex_fields.base_models import BaseModel from organization.models import Organization from sfm_pc.models import GetComplexFieldNameMixin +from source.mixins import SourcesMixin -class MembershipOrganization(models.Model, BaseModel, GetComplexFieldNameMixin): +class MembershipOrganization(models.Model, BaseModel, SourcesMixin, GetComplexFieldNameMixin): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.member = ComplexFieldContainer(self, MembershipOrganizationMember) diff --git a/membershipperson/models.py b/membershipperson/models.py index 578e49dd3..bcccb8d7e 100644 --- a/membershipperson/models.py +++ b/membershipperson/models.py @@ -12,9 +12,10 @@ from person.models import Person from organization.models import Organization from sfm_pc.models import GetComplexFieldNameMixin +from source.mixins import SourcesMixin -class MembershipPerson(models.Model, BaseModel, GetComplexFieldNameMixin): +class MembershipPerson(models.Model, BaseModel, SourcesMixin, GetComplexFieldNameMixin): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.member = ComplexFieldContainer(self, MembershipPersonMember) diff --git a/organization/models.py b/organization/models.py index e0f3f224f..d0b983207 100644 --- a/organization/models.py +++ b/organization/models.py @@ -17,6 +17,8 @@ from sfm_pc.utils import VersionsMixin from sfm_pc.models import GetComplexFieldNameMixin +from source.mixins import SourcesMixin + VERSION_RELATED_FIELDS = [ 'associationorganization_set', @@ -38,7 +40,7 @@ @reversion.register(follow=VERSION_RELATED_FIELDS) -class Organization(models.Model, BaseModel, VersionsMixin, GetComplexFieldNameMixin): +class Organization(models.Model, BaseModel, SourcesMixin, VersionsMixin, GetComplexFieldNameMixin): uuid = models.UUIDField(default=uuid.uuid4, editable=False, diff --git a/organization/urls.py b/organization/urls.py index 903c1e890..fbd30ca13 100644 --- a/organization/urls.py +++ b/organization/urls.py @@ -1,10 +1,11 @@ from django.conf.urls import url +from django.views.decorators.cache import cache_page from organization import views urlpatterns = [ url(r'^view/(?P[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/$', - views.OrganizationDetail.as_view(), + cache_page(60 * 60 * 24)(views.OrganizationDetail.as_view()), name="view-organization"), url(r'name/autocomplete', views.organization_autocomplete, diff --git a/organization/views.py b/organization/views.py index bdd6abad8..94e0fb35e 100644 --- a/organization/views.py +++ b/organization/views.py @@ -1,9 +1,12 @@ +from datetime import date +from itertools import chain import json from django.http import HttpResponse, HttpResponseRedirect from django.core.urlresolvers import reverse, reverse_lazy from django.db.models import Q from django.core.serializers import serialize +from django.contrib.sitemaps import Sitemap from emplacement.models import Emplacement @@ -43,6 +46,29 @@ class OrganizationDetail(BaseDetailView): template_name = 'organization/view.html' slug_field = 'uuid' + def get_sources(self, context): + sources = list(context['organization'].sources) + + original_list = sources.copy() + + related_entities = ( + 'person_members', + 'org_members', + 'memberships', + 'subsidiaries', + 'events', + 'emplacements', + 'associations', + 'parents', + ) + + for relation in related_entities: + sources += list( + chain.from_iterable(entity.sources for entity in context[relation]) + ) + + return sorted(set(sources), key=lambda x: x.get_published_date() or date.min, reverse=True) + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -190,6 +216,8 @@ def get_context_data(self, **kwargs): context['parents_list'].append(org_data) + context['sources'] = self.get_sources(context) + return context @@ -678,3 +706,12 @@ def organization_autocomplete(request): response['results'].append(result) return HttpResponse(json.dumps(response), content_type='application/json') + + +class OrganizationSitemap(Sitemap): + + def items(self): + return Organization.objects.filter(published=True).order_by('id') + + def location(self, obj): + return reverse('view-organization', args=[obj.uuid]) diff --git a/person/models.py b/person/models.py index eac40a1f8..ce54afe3f 100644 --- a/person/models.py +++ b/person/models.py @@ -16,6 +16,7 @@ from sfm_pc.utils import VersionsMixin from sfm_pc.models import GetComplexFieldNameMixin +from source.mixins import SourcesMixin VERSION_RELATED_FIELDS = [ @@ -31,7 +32,7 @@ @reversion.register(follow=VERSION_RELATED_FIELDS) -class Person(models.Model, BaseModel, VersionsMixin, GetComplexFieldNameMixin): +class Person(models.Model, BaseModel, SourcesMixin, VersionsMixin, GetComplexFieldNameMixin): uuid = models.UUIDField(default=uuid.uuid4, editable=False, diff --git a/person/urls.py b/person/urls.py index 3592e282a..e41fbf955 100644 --- a/person/urls.py +++ b/person/urls.py @@ -23,7 +23,7 @@ views.PersonDeletePostingView.as_view(), name='delete-person-posting'), url(r'^view/(?P[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/$', - views.PersonDetail.as_view(), + cache_page(60 * 60 * 24)(views.PersonDetail.as_view()), name="view-person"), url(r'autocomplete/$', views.person_autocomplete, diff --git a/person/views.py b/person/views.py index 7d9fab673..5ec1fc8d5 100644 --- a/person/views.py +++ b/person/views.py @@ -1,11 +1,12 @@ import json - from datetime import date from collections import namedtuple +from itertools import chain -from django.http import HttpResponse, HttpResponseRedirect -from django.db import connection +from django.contrib.sitemaps import Sitemap from django.core.urlresolvers import reverse, reverse_lazy +from django.db import connection +from django.http import HttpResponse, HttpResponseRedirect from django.utils.translation import ugettext as _ from person.models import Person @@ -21,6 +22,23 @@ class PersonDetail(BaseDetailView): template_name = 'person/view.html' slug_field = 'uuid' + def get_sources(self, context): + sources = list(context['person'].sources) + + sources += list( + chain.from_iterable(m.sources for m in context['memberships']) + ) + + sources += list( + chain.from_iterable(sub['commander'].sources for sub in context['subordinates']) + ) + + sources += list( + chain.from_iterable(sub['commander'].sources for sub in context['superiors']) + ) + + return sorted(set(sources), key=lambda x: x.get_published_date() or date.min, reverse=True) + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -125,6 +143,8 @@ def get_context_data(self, **kwargs): for event in events: context['events'].append(event.object_ref) + context['sources'] = list(self.get_sources(context)) + return context @@ -474,3 +494,12 @@ def delete(self, request, *args, **kwargs): organization.object_ref_saved() return response + + +class PersonSitemap(Sitemap): + + def items(self): + return Person.objects.filter(published=True).order_by('id') + + def location(self, obj): + return reverse('view-person', args=[obj.uuid]) diff --git a/sfm_pc/base_views.py b/sfm_pc/base_views.py index 3c12a896b..b91b75e27 100644 --- a/sfm_pc/base_views.py +++ b/sfm_pc/base_views.py @@ -20,7 +20,7 @@ from extra_views import FormSetView -from source.models import AccessPoint +from source.models import AccessPoint, Source from association.models import Association from emplacement.models import Emplacement from person.models import Person @@ -67,7 +67,9 @@ def get_context_data(self, *args, **kwargs): 'Person': Person, 'MembershipPerson': MembershipPerson, 'Violation': Violation, - 'Organization': Organization + 'Organization': Organization, + 'Source': Source, + 'AccessPoint': AccessPoint, } return context diff --git a/sfm_pc/management/commands/import_google_doc.py b/sfm_pc/management/commands/import_google_doc.py index 3a8d6568f..80394c899 100644 --- a/sfm_pc/management/commands/import_google_doc.py +++ b/sfm_pc/management/commands/import_google_doc.py @@ -12,6 +12,7 @@ from tqdm import tqdm +from django.core.cache import cache from django.core.exceptions import ValidationError from django.core.management.base import BaseCommand from django.db import models, IntegrityError @@ -228,7 +229,11 @@ def handle(self, *args, **options): data_src = options['folder'] if options.get('folder') else options['doc_id'] self.stdout.write(self.style.SUCCESS('Successfully imported data from {}'.format(data_src))) - # connect post save signals + + # Clear cached detail and command chart views + cache.clear() + + # Connect post save signals self.connectSignals() def create_locations(self): diff --git a/sfm_pc/settings.py b/sfm_pc/settings.py index 9175b9aae..8201076fb 100644 --- a/sfm_pc/settings.py +++ b/sfm_pc/settings.py @@ -73,6 +73,7 @@ 'django.contrib.staticfiles', 'django.contrib.gis', 'django.contrib.humanize', + 'django.contrib.sitemaps', 'haystack', 'django_date_extensions', 'rosetta', diff --git a/sfm_pc/templatetags/citations.py b/sfm_pc/templatetags/citations.py index 229c2a252..2cef2aaba 100644 --- a/sfm_pc/templatetags/citations.py +++ b/sfm_pc/templatetags/citations.py @@ -43,10 +43,16 @@ def get_citation_string(obj): for i, attr in enumerate(info): key, val = attr[0], attr[1] + event = '' # Wrap links in anchors if key == _('Source URL') or key == _('Archive URL'): - html_fmt = '{0}: {1}' + html_fmt = ( + '{0}: {1}' + ) + event = 'Source Link' if key == _('Source URL') else 'Archive Link' else: html_fmt = '{0}: {1}' @@ -54,7 +60,7 @@ def get_citation_string(obj): if i != 0: html_str = '
' - html_str += html_fmt.format(key, val) + html_str += html_fmt.format(key, val, event) html += html_str source_citation += html diff --git a/sfm_pc/templatetags/countries.py b/sfm_pc/templatetags/countries.py index 3840aa382..00399836b 100644 --- a/sfm_pc/templatetags/countries.py +++ b/sfm_pc/templatetags/countries.py @@ -37,7 +37,7 @@ def country_name(division_id): # so that we don't mess with the template return '' -@register.inclusion_tag('partials/location_string.html') +@register.simple_tag def render_location_string(obj, countries=True): context = {} @@ -48,9 +48,9 @@ def render_location_string(obj, countries=True): obj.adminlevel1.get_value(), obj.adminlevel2.get_value()] - context['locations'] = [loc for loc in locations if loc is not None] + locations = [loc for loc in locations if loc is not None] if countries and country_name(obj.division_id.get_value()) is not None: - context['locations'].append(country_name(obj.division_id.get_value())) + locations.append(country_name(obj.division_id.get_value())) - return context + return ', '.join(str(loc) for loc in locations) diff --git a/sfm_pc/urls.py b/sfm_pc/urls.py index 5ca6a5cb6..83459c7ed 100644 --- a/sfm_pc/urls.py +++ b/sfm_pc/urls.py @@ -4,14 +4,43 @@ from django.conf.urls.static import static from django.contrib import admin from django.contrib.auth.views import logout_then_login +from django.contrib.sitemaps import views as sitemap_views, Sitemap from django.views.decorators.cache import cache_page -from django.core.urlresolvers import reverse_lazy +from django.core.urlresolvers import reverse, reverse_lazy + +from organization.views import OrganizationSitemap +from person.views import PersonSitemap +from violation.views import ViolationSitemap from sfm_pc.views import (Dashboard, osm_autocomplete, division_autocomplete, command_chain, DownloadData, DumpChangeLog, download_zip, About, about_redirect) -urlpatterns = i18n_patterns( + +class StaticViewSitemap(Sitemap): + i18n = True + + def items(self): + return ['dashboard', 'about', 'download'] + + def location(self, item): + return reverse(item) + +sitemaps = { + 'organization': OrganizationSitemap, + 'person': PersonSitemap, + 'violation': ViolationSitemap, + 'static': StaticViewSitemap, +} + +urlpatterns = [ + # sitemap + url(r'^sitemap\.xml$', sitemap_views.index, {'sitemaps': sitemaps}), + url(r'^sitemap-(?P
.+)\.xml$', sitemap_views.sitemap, {'sitemaps': sitemaps}, + name='django.contrib.sitemaps.views.sitemap'), +] + +urlpatterns += i18n_patterns( url(r'^organization/', include('organization.urls')), url(r'^person/', include('person.urls')), url(r'^source/', include('source.urls')), @@ -47,7 +76,6 @@ # auth url('^', include('django.contrib.auth.urls')), - ) # Rosetta translation app diff --git a/source/mixins.py b/source/mixins.py new file mode 100644 index 000000000..349c62a91 --- /dev/null +++ b/source/mixins.py @@ -0,0 +1,18 @@ +import itertools + +from complex_fields.models import ComplexFieldContainer, ComplexFieldListContainer + + +class SourcesMixin: + @property + def sources(self): + sources = [] + + for field in itertools.chain(self.complex_fields, self.complex_lists): + if isinstance(field, ComplexFieldListContainer): + field = field.get_complex_field(None) + assert field + + sources += [accesspoint.source for accesspoint in field.get_sources()] + + return set(sources) diff --git a/static/css/custom.css b/static/css/custom.css index bd81b89e9..63a5659e0 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -224,6 +224,10 @@ li { border-color: #d8731e !important; /* override bootstrap default */ } +.label { + white-space: normal; +} + /* ====== * * facets * * ====== */ @@ -372,7 +376,6 @@ a.collapsed .show-less { line-height: 0; vertical-align: middle; text-decoration: none; - background-color: #d9230f; color: inherit; cursor: pointer; } @@ -396,28 +399,24 @@ a.collapsed .show-less { display: inline; } -.citation { - visibility: hidden; -} - .low-confidence, .medium-confidence, .high-confidence { display: inline-block; } -.low-confidence .popover-title, -.low-confidence .source-footnote { +.low-confidence .popover-title/*, +.low-confidence .source-footnote*/ { background-color: #f5d7cd; } -.medium-confidence .popover-title, -.medium-confidence .source-footnote { +.medium-confidence .popover-title/*, +.medium-confidence .source-footnote*/ { background-color: #f5f3cd; } -.high-confidence .popover-title, -.high-confidence .source-footnote { +.high-confidence .popover-title/*, +.high-confidence .source-footnote*/ { background-color: #dcf3dc; } @@ -544,3 +543,809 @@ a.collapsed .show-less { margin-left: 15px; } } + +@font-face { + font-family: 'Glyphicons Halflings'; + + src: url('../fonts/glyphicons-halflings-regular.eot'); + src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); +} +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.glyphicon-asterisk:before { + content: "\002a"; +} +.glyphicon-plus:before { + content: "\002b"; +} +.glyphicon-euro:before, +.glyphicon-eur:before { + content: "\20ac"; +} +.glyphicon-minus:before { + content: "\2212"; +} +.glyphicon-cloud:before { + content: "\2601"; +} +.glyphicon-envelope:before { + content: "\2709"; +} +.glyphicon-pencil:before { + content: "\270f"; +} +.glyphicon-glass:before { + content: "\e001"; +} +.glyphicon-music:before { + content: "\e002"; +} +.glyphicon-search:before { + content: "\e003"; +} +.glyphicon-heart:before { + content: "\e005"; +} +.glyphicon-star:before { + content: "\e006"; +} +.glyphicon-star-empty:before { + content: "\e007"; +} +.glyphicon-user:before { + content: "\e008"; +} +.glyphicon-film:before { + content: "\e009"; +} +.glyphicon-th-large:before { + content: "\e010"; +} +.glyphicon-th:before { + content: "\e011"; +} +.glyphicon-th-list:before { + content: "\e012"; +} +.glyphicon-ok:before { + content: "\e013"; +} +.glyphicon-remove:before { + content: "\e014"; +} +.glyphicon-zoom-in:before { + content: "\e015"; +} +.glyphicon-zoom-out:before { + content: "\e016"; +} +.glyphicon-off:before { + content: "\e017"; +} +.glyphicon-signal:before { + content: "\e018"; +} +.glyphicon-cog:before { + content: "\e019"; +} +.glyphicon-trash:before { + content: "\e020"; +} +.glyphicon-home:before { + content: "\e021"; +} +.glyphicon-file:before { + content: "\e022"; +} +.glyphicon-time:before { + content: "\e023"; +} +.glyphicon-road:before { + content: "\e024"; +} +.glyphicon-download-alt:before { + content: "\e025"; +} +.glyphicon-download:before { + content: "\e026"; +} +.glyphicon-upload:before { + content: "\e027"; +} +.glyphicon-inbox:before { + content: "\e028"; +} +.glyphicon-play-circle:before { + content: "\e029"; +} +.glyphicon-repeat:before { + content: "\e030"; +} +.glyphicon-refresh:before { + content: "\e031"; +} +.glyphicon-list-alt:before { + content: "\e032"; +} +.glyphicon-lock:before { + content: "\e033"; +} +.glyphicon-flag:before { + content: "\e034"; +} +.glyphicon-headphones:before { + content: "\e035"; +} +.glyphicon-volume-off:before { + content: "\e036"; +} +.glyphicon-volume-down:before { + content: "\e037"; +} +.glyphicon-volume-up:before { + content: "\e038"; +} +.glyphicon-qrcode:before { + content: "\e039"; +} +.glyphicon-barcode:before { + content: "\e040"; +} +.glyphicon-tag:before { + content: "\e041"; +} +.glyphicon-tags:before { + content: "\e042"; +} +.glyphicon-book:before { + content: "\e043"; +} +.glyphicon-bookmark:before { + content: "\e044"; +} +.glyphicon-print:before { + content: "\e045"; +} +.glyphicon-camera:before { + content: "\e046"; +} +.glyphicon-font:before { + content: "\e047"; +} +.glyphicon-bold:before { + content: "\e048"; +} +.glyphicon-italic:before { + content: "\e049"; +} +.glyphicon-text-height:before { + content: "\e050"; +} +.glyphicon-text-width:before { + content: "\e051"; +} +.glyphicon-align-left:before { + content: "\e052"; +} +.glyphicon-align-center:before { + content: "\e053"; +} +.glyphicon-align-right:before { + content: "\e054"; +} +.glyphicon-align-justify:before { + content: "\e055"; +} +.glyphicon-list:before { + content: "\e056"; +} +.glyphicon-indent-left:before { + content: "\e057"; +} +.glyphicon-indent-right:before { + content: "\e058"; +} +.glyphicon-facetime-video:before { + content: "\e059"; +} +.glyphicon-picture:before { + content: "\e060"; +} +.glyphicon-map-marker:before { + content: "\e062"; +} +.glyphicon-adjust:before { + content: "\e063"; +} +.glyphicon-tint:before { + content: "\e064"; +} +.glyphicon-edit:before { + content: "\e065"; +} +.glyphicon-share:before { + content: "\e066"; +} +.glyphicon-check:before { + content: "\e067"; +} +.glyphicon-move:before { + content: "\e068"; +} +.glyphicon-step-backward:before { + content: "\e069"; +} +.glyphicon-fast-backward:before { + content: "\e070"; +} +.glyphicon-backward:before { + content: "\e071"; +} +.glyphicon-play:before { + content: "\e072"; +} +.glyphicon-pause:before { + content: "\e073"; +} +.glyphicon-stop:before { + content: "\e074"; +} +.glyphicon-forward:before { + content: "\e075"; +} +.glyphicon-fast-forward:before { + content: "\e076"; +} +.glyphicon-step-forward:before { + content: "\e077"; +} +.glyphicon-eject:before { + content: "\e078"; +} +.glyphicon-chevron-left:before { + content: "\e079"; +} +.glyphicon-chevron-right:before { + content: "\e080"; +} +.glyphicon-plus-sign:before { + content: "\e081"; +} +.glyphicon-minus-sign:before { + content: "\e082"; +} +.glyphicon-remove-sign:before { + content: "\e083"; +} +.glyphicon-ok-sign:before { + content: "\e084"; +} +.glyphicon-question-sign:before { + content: "\e085"; +} +.glyphicon-info-sign:before { + content: "\e086"; +} +.glyphicon-screenshot:before { + content: "\e087"; +} +.glyphicon-remove-circle:before { + content: "\e088"; +} +.glyphicon-ok-circle:before { + content: "\e089"; +} +.glyphicon-ban-circle:before { + content: "\e090"; +} +.glyphicon-arrow-left:before { + content: "\e091"; +} +.glyphicon-arrow-right:before { + content: "\e092"; +} +.glyphicon-arrow-up:before { + content: "\e093"; +} +.glyphicon-arrow-down:before { + content: "\e094"; +} +.glyphicon-share-alt:before { + content: "\e095"; +} +.glyphicon-resize-full:before { + content: "\e096"; +} +.glyphicon-resize-small:before { + content: "\e097"; +} +.glyphicon-exclamation-sign:before { + content: "\e101"; +} +.glyphicon-gift:before { + content: "\e102"; +} +.glyphicon-leaf:before { + content: "\e103"; +} +.glyphicon-fire:before { + content: "\e104"; +} +.glyphicon-eye-open:before { + content: "\e105"; +} +.glyphicon-eye-close:before { + content: "\e106"; +} +.glyphicon-warning-sign:before { + content: "\e107"; +} +.glyphicon-plane:before { + content: "\e108"; +} +.glyphicon-calendar:before { + content: "\e109"; +} +.glyphicon-random:before { + content: "\e110"; +} +.glyphicon-comment:before { + content: "\e111"; +} +.glyphicon-magnet:before { + content: "\e112"; +} +.glyphicon-chevron-up:before { + content: "\e113"; +} +.glyphicon-chevron-down:before { + content: "\e114"; +} +.glyphicon-retweet:before { + content: "\e115"; +} +.glyphicon-shopping-cart:before { + content: "\e116"; +} +.glyphicon-folder-close:before { + content: "\e117"; +} +.glyphicon-folder-open:before { + content: "\e118"; +} +.glyphicon-resize-vertical:before { + content: "\e119"; +} +.glyphicon-resize-horizontal:before { + content: "\e120"; +} +.glyphicon-hdd:before { + content: "\e121"; +} +.glyphicon-bullhorn:before { + content: "\e122"; +} +.glyphicon-bell:before { + content: "\e123"; +} +.glyphicon-certificate:before { + content: "\e124"; +} +.glyphicon-thumbs-up:before { + content: "\e125"; +} +.glyphicon-thumbs-down:before { + content: "\e126"; +} +.glyphicon-hand-right:before { + content: "\e127"; +} +.glyphicon-hand-left:before { + content: "\e128"; +} +.glyphicon-hand-up:before { + content: "\e129"; +} +.glyphicon-hand-down:before { + content: "\e130"; +} +.glyphicon-circle-arrow-right:before { + content: "\e131"; +} +.glyphicon-circle-arrow-left:before { + content: "\e132"; +} +.glyphicon-circle-arrow-up:before { + content: "\e133"; +} +.glyphicon-circle-arrow-down:before { + content: "\e134"; +} +.glyphicon-globe:before { + content: "\e135"; +} +.glyphicon-wrench:before { + content: "\e136"; +} +.glyphicon-tasks:before { + content: "\e137"; +} +.glyphicon-filter:before { + content: "\e138"; +} +.glyphicon-briefcase:before { + content: "\e139"; +} +.glyphicon-fullscreen:before { + content: "\e140"; +} +.glyphicon-dashboard:before { + content: "\e141"; +} +.glyphicon-paperclip:before { + content: "\e142"; +} +.glyphicon-heart-empty:before { + content: "\e143"; +} +.glyphicon-link:before { + content: "\e144"; +} +.glyphicon-phone:before { + content: "\e145"; +} +.glyphicon-pushpin:before { + content: "\e146"; +} +.glyphicon-usd:before { + content: "\e148"; +} +.glyphicon-gbp:before { + content: "\e149"; +} +.glyphicon-sort:before { + content: "\e150"; +} +.glyphicon-sort-by-alphabet:before { + content: "\e151"; +} +.glyphicon-sort-by-alphabet-alt:before { + content: "\e152"; +} +.glyphicon-sort-by-order:before { + content: "\e153"; +} +.glyphicon-sort-by-order-alt:before { + content: "\e154"; +} +.glyphicon-sort-by-attributes:before { + content: "\e155"; +} +.glyphicon-sort-by-attributes-alt:before { + content: "\e156"; +} +.glyphicon-unchecked:before { + content: "\e157"; +} +.glyphicon-expand:before { + content: "\e158"; +} +.glyphicon-collapse-down:before { + content: "\e159"; +} +.glyphicon-collapse-up:before { + content: "\e160"; +} +.glyphicon-log-in:before { + content: "\e161"; +} +.glyphicon-flash:before { + content: "\e162"; +} +.glyphicon-log-out:before { + content: "\e163"; +} +.glyphicon-new-window:before { + content: "\e164"; +} +.glyphicon-record:before { + content: "\e165"; +} +.glyphicon-save:before { + content: "\e166"; +} +.glyphicon-open:before { + content: "\e167"; +} +.glyphicon-saved:before { + content: "\e168"; +} +.glyphicon-import:before { + content: "\e169"; +} +.glyphicon-export:before { + content: "\e170"; +} +.glyphicon-send:before { + content: "\e171"; +} +.glyphicon-floppy-disk:before { + content: "\e172"; +} +.glyphicon-floppy-saved:before { + content: "\e173"; +} +.glyphicon-floppy-remove:before { + content: "\e174"; +} +.glyphicon-floppy-save:before { + content: "\e175"; +} +.glyphicon-floppy-open:before { + content: "\e176"; +} +.glyphicon-credit-card:before { + content: "\e177"; +} +.glyphicon-transfer:before { + content: "\e178"; +} +.glyphicon-cutlery:before { + content: "\e179"; +} +.glyphicon-header:before { + content: "\e180"; +} +.glyphicon-compressed:before { + content: "\e181"; +} +.glyphicon-earphone:before { + content: "\e182"; +} +.glyphicon-phone-alt:before { + content: "\e183"; +} +.glyphicon-tower:before { + content: "\e184"; +} +.glyphicon-stats:before { + content: "\e185"; +} +.glyphicon-sd-video:before { + content: "\e186"; +} +.glyphicon-hd-video:before { + content: "\e187"; +} +.glyphicon-subtitles:before { + content: "\e188"; +} +.glyphicon-sound-stereo:before { + content: "\e189"; +} +.glyphicon-sound-dolby:before { + content: "\e190"; +} +.glyphicon-sound-5-1:before { + content: "\e191"; +} +.glyphicon-sound-6-1:before { + content: "\e192"; +} +.glyphicon-sound-7-1:before { + content: "\e193"; +} +.glyphicon-copyright-mark:before { + content: "\e194"; +} +.glyphicon-registration-mark:before { + content: "\e195"; +} +.glyphicon-cloud-download:before { + content: "\e197"; +} +.glyphicon-cloud-upload:before { + content: "\e198"; +} +.glyphicon-tree-conifer:before { + content: "\e199"; +} +.glyphicon-tree-deciduous:before { + content: "\e200"; +} +.glyphicon-cd:before { + content: "\e201"; +} +.glyphicon-save-file:before { + content: "\e202"; +} +.glyphicon-open-file:before { + content: "\e203"; +} +.glyphicon-level-up:before { + content: "\e204"; +} +.glyphicon-copy:before { + content: "\e205"; +} +.glyphicon-paste:before { + content: "\e206"; +} +.glyphicon-alert:before { + content: "\e209"; +} +.glyphicon-equalizer:before { + content: "\e210"; +} +.glyphicon-king:before { + content: "\e211"; +} +.glyphicon-queen:before { + content: "\e212"; +} +.glyphicon-pawn:before { + content: "\e213"; +} +.glyphicon-bishop:before { + content: "\e214"; +} +.glyphicon-knight:before { + content: "\e215"; +} +.glyphicon-baby-formula:before { + content: "\e216"; +} +.glyphicon-tent:before { + content: "\26fa"; +} +.glyphicon-blackboard:before { + content: "\e218"; +} +.glyphicon-bed:before { + content: "\e219"; +} +.glyphicon-apple:before { + content: "\f8ff"; +} +.glyphicon-erase:before { + content: "\e221"; +} +.glyphicon-hourglass:before { + content: "\231b"; +} +.glyphicon-lamp:before { + content: "\e223"; +} +.glyphicon-duplicate:before { + content: "\e224"; +} +.glyphicon-piggy-bank:before { + content: "\e225"; +} +.glyphicon-scissors:before { + content: "\e226"; +} +.glyphicon-bitcoin:before { + content: "\e227"; +} +.glyphicon-btc:before { + content: "\e227"; +} +.glyphicon-xbt:before { + content: "\e227"; +} +.glyphicon-yen:before { + content: "\00a5"; +} +.glyphicon-jpy:before { + content: "\00a5"; +} +.glyphicon-ruble:before { + content: "\20bd"; +} +.glyphicon-rub:before { + content: "\20bd"; +} +.glyphicon-scale:before { + content: "\e230"; +} +.glyphicon-ice-lolly:before { + content: "\e231"; +} +.glyphicon-ice-lolly-tasted:before { + content: "\e232"; +} +.glyphicon-education:before { + content: "\e233"; +} +.glyphicon-option-horizontal:before { + content: "\e234"; +} +.glyphicon-option-vertical:before { + content: "\e235"; +} +.glyphicon-menu-hamburger:before { + content: "\e236"; +} +.glyphicon-modal-window:before { + content: "\e237"; +} +.glyphicon-oil:before { + content: "\e238"; +} +.glyphicon-grain:before { + content: "\e239"; +} +.glyphicon-sunglasses:before { + content: "\e240"; +} +.glyphicon-text-size:before { + content: "\e241"; +} +.glyphicon-text-color:before { + content: "\e242"; +} +.glyphicon-text-background:before { + content: "\e243"; +} +.glyphicon-object-align-top:before { + content: "\e244"; +} +.glyphicon-object-align-bottom:before { + content: "\e245"; +} +.glyphicon-object-align-horizontal:before { + content: "\e246"; +} +.glyphicon-object-align-left:before { + content: "\e247"; +} +.glyphicon-object-align-vertical:before { + content: "\e248"; +} +.glyphicon-object-align-right:before { + content: "\e249"; +} +.glyphicon-triangle-right:before { + content: "\e250"; +} +.glyphicon-triangle-left:before { + content: "\e251"; +} +.glyphicon-triangle-bottom:before { + content: "\e252"; +} +.glyphicon-triangle-top:before { + content: "\e253"; +} +.glyphicon-console:before { + content: "\e254"; +} +.glyphicon-superscript:before { + content: "\e255"; +} +.glyphicon-subscript:before { + content: "\e256"; +} +.glyphicon-menu-left:before { + content: "\e257"; +} +.glyphicon-menu-right:before { + content: "\e258"; +} +.glyphicon-menu-down:before { + content: "\e259"; +} +.glyphicon-menu-up:before { + content: "\e260"; +} \ No newline at end of file diff --git a/static/fonts/glyphicons-halflings-regular.eot b/static/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 000000000..b93a4953f Binary files /dev/null and b/static/fonts/glyphicons-halflings-regular.eot differ diff --git a/static/fonts/glyphicons-halflings-regular.svg b/static/fonts/glyphicons-halflings-regular.svg new file mode 100644 index 000000000..94fb5490a --- /dev/null +++ b/static/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/fonts/glyphicons-halflings-regular.ttf b/static/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 000000000..1413fc609 Binary files /dev/null and b/static/fonts/glyphicons-halflings-regular.ttf differ diff --git a/static/fonts/glyphicons-halflings-regular.woff b/static/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 000000000..9e612858f Binary files /dev/null and b/static/fonts/glyphicons-halflings-regular.woff differ diff --git a/static/fonts/glyphicons-halflings-regular.woff2 b/static/fonts/glyphicons-halflings-regular.woff2 new file mode 100644 index 000000000..64539b54c Binary files /dev/null and b/static/fonts/glyphicons-halflings-regular.woff2 differ diff --git a/static/js/increment_footnotes.js b/static/js/increment_footnotes.js index dd502a0bd..ed255096b 100644 --- a/static/js/increment_footnotes.js +++ b/static/js/increment_footnotes.js @@ -2,15 +2,6 @@ var Footnotes = Footnotes || {}; var Footnotes = { - counter: 1, - - increment: function() { - $('.source-footnote-counter').each(function() { - $(this).html(Footnotes.counter); - Footnotes.counter ++; - }); - }, - initPopovers: function() { $('[data-toggle="popover"]').popover({ @@ -20,19 +11,8 @@ var Footnotes = { } }); - $('[data-toggle="tooltip"]').tooltip(); - // Show footnotes on hover - $('.citation-container, .cited').on('mouseover', function() { - $(this).find('.citation').css('visibility', 'visible'); - }) - - $('.citation-container, .cited').on('mouseout', function() { - // Hide the citation if a popover is not visible - if ($(this).has('.popover').length === 0) { - $(this).find('.citation').css('visibility', 'hidden'); - } - }) + $('[data-toggle="tooltip"]').tooltip(); $(document).on('shown.bs.popover', function(e) { diff --git a/static/js/piwik_tracker.js b/static/js/piwik_tracker.js index 18091cd98..b3f8f30cc 100644 --- a/static/js/piwik_tracker.js +++ b/static/js/piwik_tracker.js @@ -3,3 +3,4 @@ // The good stuff is in piwik_tracker_production.js. We decrypt that one and // move it over here when we deploy to production, to avoid sending dev data // to Piwik. +var _paq = []; diff --git a/templates/base.html b/templates/base.html index 21b8f5664..56dcd3bc7 100644 --- a/templates/base.html +++ b/templates/base.html @@ -7,7 +7,7 @@ - WhoWasInCommand + {% block page_title_prefix %}{% endblock %}WhoWasInCommand{% block page_title_suffix %}{% endblock %} @@ -138,10 +138,8 @@

{% trans "Site contents" %}


// Init popovers var fn = Footnotes; - fn.increment(); fn.initPopovers(); - // Get language code in url, prepend to array, and remove duplicate dataArray = JSON.parse('{{request.LANGUAGE_CODE|create_select2_data|safe}}'); diff --git a/templates/organization/view.html b/templates/organization/view.html index 0ed183f50..2cd9957a7 100644 --- a/templates/organization/view.html +++ b/templates/organization/view.html @@ -6,11 +6,17 @@ {% load help %} {% load model_meta %} -{% if associations or emplacements %} - {% block extra_head %} - - {% endblock %} -{% endif %} +{% block page_title_prefix %}{{ organization.name.get_value }} - {% endblock %} + +{% block extra_head %} + + {% if associations or emplacements %} + + {% endif %} + {% if sources %} + + {% endif %} +{% endblock %} {% block header %}

@@ -68,7 +74,7 @@

{% if subsidiaries %}
  • {% trans "Subsidiaries" %}
  • {% endif %} {% if person_members %}
  • {% trans "Personnel" %}
  • {% endif %} {% if events %}
  • {% trans "Incidents" %}
  • {% endif %} - {% if sources %}
  • {% trans "Sources" %}
  • {% endif %} + {% if sources %}
  • {% trans "Sources" %}
  • {% endif %} {% endblock %} @@ -84,7 +90,7 @@

    {% if subsidiaries %}
  • {% trans "Subsidiaries" %}
  • {% endif %} {% if person_members %}
  • {% trans "Personnel" %}
  • {% endif %} {% if events %}
  • {% trans "Incidents" %}
  • {% endif %} - {% if sources %}
  • {% trans "Sources" %}
  • {% endif %} + {% if sources %}
  • {% trans "Sources" %}
  • {% endif %} {% endblock %} @@ -513,6 +519,10 @@

    {% endif %} + {% if sources %} + {% include 'partials/source_overview_table.html' %} + {% endif %} + {% endblock %} {% block extra_js %} @@ -558,4 +568,21 @@

    }); {% endif %} + + {% if sources %} + + + + {% endif %} {% endblock %} diff --git a/templates/partials/location_string.html b/templates/partials/location_string.html deleted file mode 100644 index eb865cfb9..000000000 --- a/templates/partials/location_string.html +++ /dev/null @@ -1,3 +0,0 @@ -{% for location in locations %} - {{ location }}{% if not forloop.last %}, {% endif %} -{% endfor %} diff --git a/templates/partials/source_and_confidence.html b/templates/partials/source_and_confidence.html index c10dcf98f..61c6cc1a2 100644 --- a/templates/partials/source_and_confidence.html +++ b/templates/partials/source_and_confidence.html @@ -19,8 +19,9 @@ data-trigger="focus" data-delay='{ "show": 0, "hide": 250 }' tabindex="1" - aria-label="{% trans 'Sources and confidence for this information' %}"> - + aria-label="{% trans 'Sources and confidence for this information' %}" + onclick="_paq.push(['trackEvent', 'Citation Interaction', 'Citation Expand', '{{ object_value }}']);"> + [+] diff --git a/templates/partials/source_overview_table.html b/templates/partials/source_overview_table.html new file mode 100644 index 000000000..65571b485 --- /dev/null +++ b/templates/partials/source_overview_table.html @@ -0,0 +1,72 @@ +{% load citations countries help i18n model_meta %} + +
    +
    +

    + + {% trans "Sources" %} + {% help href='sources.html' %} +

    +
    + + + + + + + + + + + + {% for source in sources %} + + {% with published_date=source.get_published_date %} + + {% endwith %} + + + {% with access_date=source.accesspoint_set.get.accessed_on %} + + {% endwith %} + + + {% endfor %} + +
    {{ models.Source|verbose_field_name:"published_date" }}{{ models.Source|verbose_field_name:"publication" }}{{ models.Source|verbose_field_name:"title" }}{{ models.AccessPoint|verbose_field_name:"accessed_on" }}Permalink
    + {% if published_date.month and published_date.day %} + {{ published_date|date:"d F Y" }} + {% elif published_date.month %} + {{ published_date|date:"F Y" }} + {% else %} + {{ published_date }} + {% endif %} + + {{ source.publication }} + + + {{ source.title }} + + + {% if access_date.month and access_date.day %} + {{ access_date|date:"d F Y" }} + {% elif published_date.month %} + {{ access_date|date:"F Y" }} + {% else %} + {{ access_date|default:'' }} + {% endif %} + + + + +
    +
    +
    \ No newline at end of file diff --git a/templates/person/view.html b/templates/person/view.html index 0ba15b809..5bf5ea1a3 100644 --- a/templates/person/view.html +++ b/templates/person/view.html @@ -6,6 +6,19 @@ {% load help %} {% load model_meta %} +{% block page_title_prefix %}{{ person.name.get_value }} - {% endblock %} + +{% block extra_head %} + {% if memberships %} + {% with membership=memberships|first %} + + {% endwith %} + {% endif %} + {% if sources %} + + {% endif %} +{% endblock %} + {% block header %}

    @@ -50,6 +63,8 @@

    {% if command_chain %}
  • {% trans "Chain of command" %}
  • {% endif %} {% if superiors %}
  • {% trans "Superiors" %}
  • {% endif %} {% if subordinates %}
  • {% trans "Subordinates" %}
  • {% endif %} + {% if events %}
  • {% trans "Incidents" %}
  • {% endif %} + {% if sources %}
  • {% trans "Sources" %}
  • {% endif %} {% endblock %} {# This is exactly the same as the previous sidebar #} @@ -60,6 +75,8 @@

    {% if command_chain %}
  • {% trans "Chain of command" %}
  • {% endif %} {% if superiors %}
  • {% trans "Superiors" %}
  • {% endif %} {% if subordinates %}
  • {% trans "Subordinates" %}
  • {% endif %} + {% if events %}
  • {% trans "Incidents" %}
  • {% endif %} + {% if sources %}
  • {% trans "Sources" %}
  • {% endif %} {% endblock %} {% block details %} @@ -283,7 +300,7 @@

    {% if events %}
    -

    {% trans "Incidents" %}

    +

    {% trans "Incidents" %}


    {% for event in events %}
    @@ -300,6 +317,10 @@

    {% trans "Incidents" %}

    {% endif %} + {% if sources %} + {% include 'partials/source_overview_table.html' %} + {% endif %} + {% endblock %} {% block extra_js %} @@ -329,4 +350,21 @@

    {% trans "Incidents" %}

    {% endif %} + + {% if sources %} + + + + {% endif %} {% endblock %} diff --git a/templates/sfm/about.html b/templates/sfm/about.html index 70842e024..127a17312 100644 --- a/templates/sfm/about.html +++ b/templates/sfm/about.html @@ -1,5 +1,7 @@ {% extends "base.html" %} {% load i18n %} + +{% block page_title_prefix %}{% trans "About" %} - {% endblock %} {% block content %}
    diff --git a/templates/sfm/dashboard.html b/templates/sfm/dashboard.html index e32baf9dd..1db6f57ff 100644 --- a/templates/sfm/dashboard.html +++ b/templates/sfm/dashboard.html @@ -1,5 +1,6 @@ {% extends "base.html" %} {% load i18n %} +{% block page_title_suffix %}: {% trans "Public database of police, military and other security and defence forces" %}{% endblock %} {% block content %}
    diff --git a/templates/violation/view.html b/templates/violation/view.html index 544be3135..456d517c5 100644 --- a/templates/violation/view.html +++ b/templates/violation/view.html @@ -6,11 +6,16 @@ {% load help %} {% load model_meta %} -{% if location %} - {% block extra_head %} - - {% endblock %} -{% endif %} +{% block page_title_prefix %}{{ page_title }} - {% endblock %} +{% block extra_head %} + {% render_location_string violation as location_string %} + {% if violation.description or location_string %} + + {% endif %} + {% if location %} + + {% endif %} +{% endblock %} {% block header %}

    @@ -89,7 +94,7 @@

    {% if violation.description %}
  • {% trans "Description" %}
  • {% endif %} {% if perpetrators %}
  • {% trans "Perpetrators" %}
  • {% endif %} {% if perpetrator_organizations %}
  • {% trans "Perpetrator units" %}
  • {% endif %} - {% if sources %}
  • {% trans "Sources" %}
  • {% endif %} + {% if sources %}
  • {% trans "Sources" %}
  • {% endif %} {% endblock %} @@ -101,7 +106,7 @@

    {% if violation.description %}
  • {% trans "Description" %}
  • {% endif %} {% if perpetrators %}
  • {% trans "Perpetrators" %}
  • {% endif %} {% if perpetrator_organizations %}
  • {% trans "Perpetrator units" %}
  • {% endif %} - {% if sources %}
  • {% trans "Sources" %}
  • {% endif %} + {% if sources %}
  • {% trans "Sources" %}
  • {% endif %} {% endblock %} @@ -240,6 +245,10 @@

    {% endif %} + {% if sources %} + {% include 'partials/source_overview_table.html' %} + {% endif %} + {% endblock %} {% if location %} diff --git a/violation/models.py b/violation/models.py index ab7dddf5d..c5bb049c2 100644 --- a/violation/models.py +++ b/violation/models.py @@ -18,6 +18,7 @@ from organization.models import Organization from sfm_pc.utils import VersionsMixin from sfm_pc.models import GetComplexFieldNameMixin +from source.mixins import SourcesMixin from location.models import Location @@ -45,7 +46,7 @@ @reversion.register(follow=VERSION_RELATED_FIELDS) -class Violation(models.Model, BaseModel, VersionsMixin, GetComplexFieldNameMixin): +class Violation(models.Model, BaseModel, SourcesMixin, VersionsMixin, GetComplexFieldNameMixin): uuid = models.UUIDField(default=uuid.uuid4, editable=False, db_index=True) diff --git a/violation/urls.py b/violation/urls.py index 09e2b108e..3116334d0 100644 --- a/violation/urls.py +++ b/violation/urls.py @@ -6,7 +6,7 @@ urlpatterns = [ url(r'csv/', views.violation_csv, name='violation_csv'), url(r'^view/(?P[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/$', - views.ViolationDetail.as_view(), + cache_page(60 * 60 * 24)(views.ViolationDetail.as_view()), name="view-violation"), url(r'^edit/(?P[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/$', views.ViolationEditBasicsView.as_view(), diff --git a/violation/views.py b/violation/views.py index b6a6aec54..042c53aa7 100644 --- a/violation/views.py +++ b/violation/views.py @@ -1,9 +1,12 @@ -import json import csv +from datetime import date +from itertools import chain +import json -from django.http import HttpResponse, HttpResponseRedirect +from django.contrib.sitemaps import Sitemap from django.core.urlresolvers import reverse, reverse_lazy -from django.utils.translation import get_language +from django.http import HttpResponse, HttpResponseRedirect +from django.utils.translation import get_language, ugettext as _ from complex_fields.models import ComplexFieldContainer @@ -12,11 +15,40 @@ from .models import Violation, ViolationType, ViolationPerpetratorClassification from .forms import ViolationBasicsForm, ViolationCreateBasicsForm, ViolationLocationsForm + class ViolationDetail(BaseDetailView): model = Violation template_name = 'violation/view.html' slug_field = 'uuid' + def get_sources(self, context): + return sorted( + context['violation'].sources, + key=lambda x: x.get_published_date() or date.min, + reverse=True + ) + + def get_page_title(self): + title = _('Incident') + + if self.object.osmname.get_value(): + title += ' {0} {1}'.format(_('in'), self.object.osmname.get_value()) + + start = self.object.startdate.get_value() + end = self.object.enddate.get_value() + + if start and end: + if start.value == end.value: + title += ' {0} {1}'.format(_('on'), start.value) + else: + title += ' {0} {1} {2} {3}'.format(_('between'), start.value, _('and'), end.value) + elif start: + title += ' {0} {1}'.format(_('starting'), start.value) + elif end: + title += ' {0} {1}'.format(_('ending'), end.value) + + return title + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -44,6 +76,9 @@ def get_context_data(self, **kwargs): else: context['perpetrator_organizations'] = context['violation'].violationperpetratororganization_set.filter(value__published=True) + context['sources'] = self.get_sources(context) + context['page_title'] = self.get_page_title() + return context @@ -214,3 +249,11 @@ def violation_csv(request): return response + +class ViolationSitemap(Sitemap): + + def items(self): + return Violation.objects.filter(published=True).order_by('id') + + def location(self, obj): + return reverse('view-violation', args=[obj.uuid])