Skip to content

Commit

Permalink
Merge pull request #7 from City-of-Turku/feature/option-other
Browse files Browse the repository at this point in the history
Feature/option other
  • Loading branch information
juuso-j authored Feb 8, 2024
2 parents ec5c961 + bd70664 commit 550e63e
Showing 10 changed files with 179 additions and 13 deletions.
41 changes: 40 additions & 1 deletion profiles/admin.py
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@

from profiles.models import (
Answer,
AnswerOther,
Option,
PostalCode,
PostalCodeResult,
@@ -58,7 +59,46 @@ class Meta:
model = Option


@admin.register(Answer)
class AnswerAdmin(DisableDeleteAdminMixin, ReadOnlyFieldsAdminMixin, admin.ModelAdmin):
def get_queryset(self, request):
qs = super().get_queryset(request)
qs = qs.filter(option__is_other=False)
return qs

class Meta:
model = Answer


@admin.register(AnswerOther)
class AnswerOtherAdmin(
DisableDeleteAdminMixin, ReadOnlyFieldsAdminMixin, admin.ModelAdmin
):
list_display = (
"question_description",
"sub_question_description",
"other",
)

def get_queryset(self, request):
qs = super().get_queryset(request)
qs = qs.filter(option__is_other=True)
return qs

def other(self, obj):
return obj.option.other

def question_description(self, obj):
if obj.question:
return obj.question.question_en
else:
return obj.sub_question.question.question_en

def sub_question_description(self, obj):
if obj.sub_question:
return obj.sub_question.description_en
return None

class Meta:
model = Answer

@@ -90,7 +130,6 @@ class Meta:
admin.site.register(SubQuestionCondition, SubQuestionConditionAdmin)
admin.site.register(Option, OptionAdmin)
admin.site.register(Result, ResultAdmin)
admin.site.register(Answer, AnswerAdmin)
admin.site.register(PostalCode, PostalCodeAdmin)
admin.site.register(PostalCodeType, PostalCodeTypeAdmin)
admin.site.register(PostalCodeResult, PostalCodeResultAdmin)
1 change: 1 addition & 0 deletions profiles/api/serializers.py
Original file line number Diff line number Diff line change
@@ -78,6 +78,7 @@ class SubQuestionRequestSerializer(serializers.Serializer):
class AnswerRequestSerializer(QuestionRequestSerializer):
option = serializers.IntegerField()
sub_question = serializers.IntegerField(required=False)
other = serializers.CharField(required=False)


class InConditionResponseSerializer(serializers.Serializer):
14 changes: 11 additions & 3 deletions profiles/api/views.py
Original file line number Diff line number Diff line change
@@ -506,10 +506,11 @@ def get_permissions(self):
responses={
201: OpenApiResponse(description="created"),
400: OpenApiResponse(
description="'option' or 'question' argument not given"
description="'option' or 'question' not found in body or for if 'is_other' is true for option"
" 'other' field is missing in body"
),
404: OpenApiResponse(
description="'option', 'question' or 'sub_question' not found"
description="'option', 'question' or 'sub_question' not found"
),
405: OpenApiResponse(
description="Question or sub question condition not met,"
@@ -566,7 +567,6 @@ def create(self, request, *args, **kwargs):
f"Option {option_id} not found or wrong related question.",
status=status.HTTP_404_NOT_FOUND,
)

question_condition_qs = QuestionCondition.objects.filter(question=question)
if question_condition_qs.count() > 0:
if not question_condition_met(question_condition_qs, user):
@@ -586,6 +586,14 @@ def create(self, request, *args, **kwargs):
)
if user:
filter = {"user": user, "question": question, "sub_question": sub_question}
if option.is_other:
other = request.data.get("other", None)
if not other:
return Response(
"'other' not found in body, required if is_other field is true for option.",
status=status.HTTP_400_BAD_REQUEST,
)
filter["other"] = other
queryset = Answer.objects.filter(**filter)
if queryset.count() == 0:
filter["option"] = option
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 4.1.10 on 2024-02-05 14:45

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("profiles", "0009_subquestioncondition"),
]

operations = [
migrations.RemoveConstraint(
model_name="answer",
name="unique_user_and_option",
),
migrations.AlterField(
model_name="answer",
name="option",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="answers",
to="profiles.option",
),
),
]
18 changes: 18 additions & 0 deletions profiles/migrations/0011_answer_other.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.1.10 on 2024-02-05 14:51

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("profiles", "0010_remove_answer_unique_user_and_option_and_more"),
]

operations = [
migrations.AddField(
model_name="answer",
name="other",
field=models.TextField(blank=True, null=True),
),
]
18 changes: 18 additions & 0 deletions profiles/migrations/0012_option_is_other.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.1.10 on 2024-02-06 11:46

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("profiles", "0011_answer_other"),
]

operations = [
migrations.AddField(
model_name="option",
name="is_other",
field=models.BooleanField(default=False, verbose_name="is other textfield"),
),
]
23 changes: 23 additions & 0 deletions profiles/migrations/0013_answerother.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 4.1.10 on 2024-02-07 07:14

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("profiles", "0012_option_is_other"),
]

operations = [
migrations.CreateModel(
name="AnswerOther",
fields=[],
options={
"proxy": True,
"indexes": [],
"constraints": [],
},
bases=("profiles.answer",),
),
]
17 changes: 9 additions & 8 deletions profiles/models.py
Original file line number Diff line number Diff line change
@@ -47,6 +47,7 @@ class Option(models.Model):
)
results = models.ManyToManyField("Result", related_name="options")
order_number = models.PositiveSmallIntegerField(null=True)
is_other = models.BooleanField(default=False, verbose_name="is other textfield")

class Meta:
ordering = ["question__number", "sub_question__question__number"]
@@ -72,8 +73,10 @@ class Answer(models.Model):
"account.User", related_name="answers", on_delete=models.CASCADE
)
option = models.ForeignKey(
"Option", related_name="answers", on_delete=models.CASCADE
"Option", related_name="answers", null=True, on_delete=models.CASCADE
)
other = models.TextField(null=True, blank=True)

question = models.ForeignKey(
"Question", related_name="answers", null=True, on_delete=models.CASCADE
)
@@ -84,15 +87,13 @@ class Answer(models.Model):
created = models.DateTimeField(auto_now_add=True)

class Meta:
constraints = [
models.UniqueConstraint(
fields=["user", "option"], name="unique_user_and_option"
)
]
ordering = ["id"]

def __str__(self):
return f"{self.option.value}"

class AnswerOther(Answer):
# Proxy model that allows registerin Answer model twice to the Admin
class Meta:
proxy = True


class QuestionCondition(models.Model):
25 changes: 25 additions & 0 deletions profiles/tests/api/test_answer.py
Original file line number Diff line number Diff line change
@@ -37,6 +37,31 @@ def test_post_answer(api_client_authenticated, users, questions, options):
assert user.result.value == "negative result"


@pytest.mark.django_db
def test_post_answer_with_other_option(api_client, users, answers, questions, options):
user = users.get(username="no answers user")
token = Token.objects.create(user=user)
api_client.credentials(HTTP_AUTHORIZATION="Token " + token.key)
option = options.get(value="other")
question3 = questions.get(number="3")
answer_url = reverse("profiles:answer-list")
response = api_client.post(
answer_url,
{"option": option.id, "question": question3.id, "other": "test data"},
)
assert response.status_code == 201
answers_qs = Answer.objects.filter(user=user)
assert answers_qs.count() == 1
assert answers_qs.first().other == "test data"
# Test posting without 'other' field to a option where is_other is True
response = api_client.post(
answer_url, {"option": option.id, "question": question3.id}
)
assert response.status_code == 400
answers_qs = Answer.objects.filter(user=user)
assert answers_qs.count() == 1


@pytest.mark.django_db
def test_post_answer_answer_is_updated(
api_client_authenticated, users, answers, questions, options
7 changes: 6 additions & 1 deletion profiles/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -56,6 +56,7 @@ def sub_questions(questions):
SubQuestion.objects.create(
question=question, description="Do you drive yourself?", order_number=0
)

return SubQuestion.objects.all()


@@ -69,7 +70,6 @@ def options(questions, sub_questions, results):
option_no.results.add(negative_result)
option_yes = Option.objects.create(value="yes", question=question1)
option_yes.results.add(positive_result)

train_sub_q = sub_questions.get(description="train")
car_sub_q = sub_questions.get(description="car")
Option.objects.create(value="never", sub_question=train_sub_q)
@@ -81,6 +81,8 @@ def options(questions, sub_questions, results):
question3 = Question.objects.get(number="3")
Option.objects.create(value="fast", question=question3)
Option.objects.create(value="easy", question=question3)
Option.objects.create(value="other", question=question3, is_other=True)

Option.objects.create(
value="yes I drive",
sub_question=SubQuestion.objects.get(description="Do you drive yourself?"),
@@ -145,6 +147,9 @@ def users():
Profile.objects.create(user=user)
user = User.objects.create(username="car and train user")
Profile.objects.create(user=user)
user = User.objects.create(username="no answers user")
Profile.objects.create(user=user)

return User.objects.all()


0 comments on commit 550e63e

Please sign in to comment.