diff --git a/breccia_mapper/settings.py b/breccia_mapper/settings.py index 5cfa1ad..6627eac 100644 --- a/breccia_mapper/settings.py +++ b/breccia_mapper/settings.py @@ -104,7 +104,6 @@ The most likely required settings are: SECRET_KEY, DEBUG, ALLOWED_HOSTS, DATABAS Google Maps API key to display maps of people's locations """ -import collections import logging import logging.config import pathlib diff --git a/export/serializers/people.py b/export/serializers/people.py index ea00ef2..15dda49 100644 --- a/export/serializers/people.py +++ b/export/serializers/people.py @@ -5,6 +5,38 @@ from people import models from . import base +def underscore(slug: str) -> str: + """Replace hyphens with underscores in text.""" + return slug.replace('-', '_') + + +def underscore_dict_keys(dict_: typing.Mapping[str, typing.Any]): + return {underscore(key): value for key, value in dict_.items()} + + +class AnswerSetSerializer(base.FlattenedModelSerializer): + question_model = None + + @property + def column_headers(self) -> typing.List[str]: + headers = super().column_headers + + # Add relationship questions to columns + for question in self.question_model.objects.all(): + headers.append(underscore(question.slug)) + + return headers + + def to_representation(self, instance: models.question.AnswerSet): + rep = super().to_representation(instance) + + rep.update( + underscore_dict_keys(instance.build_question_answers(use_slugs=True, show_all=True)) + ) + + return rep + + class PersonSerializer(base.FlattenedModelSerializer): class Meta: model = models.Person @@ -14,7 +46,8 @@ class PersonSerializer(base.FlattenedModelSerializer): ] -class PersonAnswerSetSerializer(base.FlattenedModelSerializer): +class PersonAnswerSetSerializer(AnswerSetSerializer): + question_model = models.PersonQuestion person = PersonSerializer() class Meta: @@ -24,38 +57,10 @@ class PersonAnswerSetSerializer(base.FlattenedModelSerializer): 'person', 'timestamp', 'replaced_timestamp', - 'nationality', - 'country_of_residence', - 'organisation', - 'organisation_started_date', - 'job_title', 'latitude', 'longitude', ] - @property - def column_headers(self) -> typing.List[str]: - headers = super().column_headers - - # Add questions to columns - for question in models.PersonQuestion.objects.all(): - headers.append(underscore(question.slug)) - - return headers - - def to_representation(self, instance): - rep = super().to_representation(instance) - - try: - # Add relationship question answers to data - for answer in instance.question_answers.all(): - rep[underscore(answer.question.slug)] = underscore(answer.slug) - - except AttributeError: - pass - - return rep - class RelationshipSerializer(base.FlattenedModelSerializer): source = PersonSerializer() @@ -70,12 +75,8 @@ class RelationshipSerializer(base.FlattenedModelSerializer): ] -def underscore(slug: str) -> str: - """Replace hyphens with underscores in text.""" - return slug.replace('-', '_') - - -class RelationshipAnswerSetSerializer(base.FlattenedModelSerializer): +class RelationshipAnswerSetSerializer(AnswerSetSerializer): + question_model = models.RelationshipQuestion relationship = RelationshipSerializer() class Meta: @@ -86,26 +87,3 @@ class RelationshipAnswerSetSerializer(base.FlattenedModelSerializer): 'timestamp', 'replaced_timestamp', ] - - @property - def column_headers(self) -> typing.List[str]: - headers = super().column_headers - - # Add relationship questions to columns - for question in models.RelationshipQuestion.objects.all(): - headers.append(underscore(question.slug)) - - return headers - - def to_representation(self, instance): - rep = super().to_representation(instance) - - try: - # Add relationship question answers to data - for answer in instance.question_answers.all(): - rep[underscore(answer.question.slug)] = underscore(answer.slug) - - except AttributeError: - pass - - return rep diff --git a/export/views/base.py b/export/views/base.py index 8416e47..602437c 100644 --- a/export/views/base.py +++ b/export/views/base.py @@ -7,6 +7,10 @@ from django.views.generic import TemplateView from django.views.generic.list import BaseListView +class QuotedCsv(csv.excel): + quoting = csv.QUOTE_NONNUMERIC + + class CsvExportView(LoginRequiredMixin, BaseListView): model = None serializer_class = None @@ -18,10 +22,10 @@ class CsvExportView(LoginRequiredMixin, BaseListView): # Force ordering by PK - though this should be default anyway serializer = self.serializer_class(self.get_queryset().order_by('pk'), many=True) - writer = csv.DictWriter(response, fieldnames=serializer.child.column_headers) + writer = csv.DictWriter(response, dialect=QuotedCsv, fieldnames=serializer.child.column_headers) writer.writeheader() writer.writerows(serializer.data) - + return response diff --git a/export/views/people.py b/export/views/people.py index 3afa1f3..f7cea55 100644 --- a/export/views/people.py +++ b/export/views/people.py @@ -8,6 +8,7 @@ class PersonExportView(base.CsvExportView): model = models.person.Person serializer_class = serializers.people.PersonSerializer + class PersonAnswerSetExportView(base.CsvExportView): model = models.person.PersonAnswerSet serializer_class = serializers.people.PersonAnswerSetSerializer diff --git a/people/models/question.py b/people/models/question.py index b143db3..dd27eba 100644 --- a/people/models/question.py +++ b/people/models/question.py @@ -166,27 +166,34 @@ class AnswerSet(models.Model): def is_current(self) -> bool: return self.replaced_timestamp is None - def build_question_answers(self, show_all: bool = False) -> typing.Dict[str, str]: + def build_question_answers(self, + show_all: bool = False, + use_slugs: bool = False) -> typing.Dict[str, str]: """Collect answers to dynamic questions and join with commas.""" questions = self.question_model.objects.all() + if not show_all: questions = questions.filter(answer_is_public=True) question_answers = {} try: + answerset_answers = list(self.question_answers.order_by().values('text', 'question_id')) + for question in questions: + key = question.slug if use_slugs else question.text + if question.hardcoded_field: answer = getattr(self, question.hardcoded_field) if isinstance(answer, list): answer = ', '.join(map(str, answer)) - question_answers[question.text] = answer - else: - answers = self.question_answers.filter( - question=question) - question_answers[question.text] = ', '.join( - map(str, answers)) + answer = ', '.join( + answer['text'] for answer in answerset_answers + if answer['question_id'] == question.id + ) + + question_answers[key] = answer except AttributeError: # No AnswerSet yet