From de7ffdd3923fddd0d5e7d1d1873006f229013917 Mon Sep 17 00:00:00 2001 From: David Llewellyn-Jones Date: Wed, 4 Oct 2023 10:06:06 +0100 Subject: [PATCH 1/3] Restructure home page Updates te homepage layout and content following user testing and so that all of the elements work correctk (e.g. links to other parts of the site). Contributes to #586. --- server/apps/main/templates/main/home.html | 301 ++++++++++------------ server/apps/main/views.py | 27 +- static/css/main.css | 120 +++++++-- 3 files changed, 269 insertions(+), 179 deletions(-) diff --git a/server/apps/main/templates/main/home.html b/server/apps/main/templates/main/home.html index 56223010..3ce73a68 100644 --- a/server/apps/main/templates/main/home.html +++ b/server/apps/main/templates/main/home.html @@ -4,13 +4,11 @@ {% block body_attributes%} data-spy="scroll" data-target="#content-bar" {% endblock %} - {% block content %} +{% block content %} - +
- -
- +

AutSPACEs

@@ -20,135 +18,159 @@

AutSPACEs

A space for autistic people to share our stories about our senses so we can build a better world for autistic people

- - - - + + +
+
Illustration
-
-
+ + +
-
-

Increase

-

understanding of sensory processing

+
+

About AutSPACEs

+
+
+
-
-

Share

-

our strategies and experiences with each other

-
+

AutSPACEs is made by and for autistic people, researchers, and allies + of autistic people, who think that the world would be a much better place + if autistic people had more of a say in how public and private spaces are + used and designed.

-
-

Advise

-

organisations and policy-makers for real-world impact

-
+

All autistic people are different. We want to collect together lots of + stories from autistic people, so that we can better understand how + sensory processing differences affect the way autistic people navigate + the world around them. Together, we can use this information to build a + better world for autistic people.

-
-

Educate

-

non-autistic people about autistic experiences

-
+
+
- + +
-
-

How to Use The Platform

+
+

How to use the platform

-
-

RegisterRegister

-
- -
-

ShareRegister

-
- -
-

ChooseRegister

+ -
-

CheckRegister

+
+ {% if user.is_authenticated %} + + {% else %} + + {% endif %} +

+ Share +
+ +

+
-
- + +
- -
-

Get Involved

-
-
-
-

Management

-
-

Contribute to the GitHub repository.

- Contribute -
-
-
-
-
-

Development

-
-

Contribute to the GitHub repository.

- Contribute -
-
-
-
-
-

Discussion

-
-

Join us on our public Slack Channel.

- Join Us -
-
-
-
-
-

Updates

-
-

Subscribe to our monthly newsletter.

- Subscribe -
-
-
-
-
- -
-
-
-

What Autism Is

-
-
-

Autistic people often have different sensory processing to people who are not autistic - .

-

By collecting together lots of autistic people’s experiences, we can change spaces so - that - they are - better for autistic people.

- - - + +
+
+
+

Our goals

- +
+
+ +
+

Share

+

Share our strategies and experiences with each other

+
+ +
+

Influence

+

Advise organisations and policy-makers for real-world impact

+
+ +
+

Educate

+

Educate non-autistic people about autistic experiences

+
+
{% endblock %} diff --git a/server/apps/main/views.py b/server/apps/main/views.py index 91c95f70..59ca3e6f 100644 --- a/server/apps/main/views.py +++ b/server/apps/main/views.py @@ -116,11 +116,34 @@ def index(request): """ if request.user.is_authenticated: oh_member = request.user.openhumansmember + + stories = [ + { + "title": "Eating in a restaurant", + "summary": "I’ve always found it so difficult going to restaurants. When I’m hungry everything sounds even louder and feels even more intimidating. As a kid, I never understood why it was so hard and my parents would often get mad at me for ‘misbehaving’ in public. I’d just meltdown whenever we went out to eat. Waiting for the food felt like some kind of marathon and I just got so overwhelmed. But now that I’ve realised hunger exacerbates my sensory differences I know how to cope.", + "uuid": "bf48c18e-6133-11ee-8f2d-0242ac120003", + "image": "animation_a.jpg", + }, + { + "title": "Spatial awareness", + "summary": "My lack of spatial awareness is probably the most debilitating part of my autism. I’m constantly walking into door frames and tripping over the corners of furniture even in my own home. And have a terrible habit of walking backwards into old women in shopping aisles. It’s been super hard with COVID as I’ve had to be in full ‘defensive mode’ whenever I go out, making sure I’m not too near to anyone.", + "uuid": "bf48c18e-6133-11ee-8f2d-0242ac120003", + "image": "animation_b.jpg", + }, + { + "title": "Television", + "summary": "My family recently got a new TV. It’s much larger than our previous one and has limited screen settings. 3/4 are too bright for me but my family dislikes the fourth option. I don’t often watch telly with the family but I feel like I should have the option to. It just feels unfair, I get that I’m a minority but they don’t see what I see and the fact that they won’t adapt to my needs upsets me. My parents are constantly nagging me to spend less time in my room but it’s situations like this that make me do just that. I can regulate my input in my own space and I can’t elsewhere.", + "uuid": "bf48c18e-6133-11ee-8f2d-0242ac120003", + "image": "animation_c.jpg", + }, + ] + context = { "oh_id": oh_member.oh_id, "oh_member": oh_member, "oh_user": oh_member.user, "oh_proj_page": settings.OH_PROJ_PAGE, + "stories": stories, } else: auth_url = OpenHumansMember.get_auth_url() @@ -281,7 +304,7 @@ def delete_experience(request, uuid): """ Delete experience from PE databacse and OH """ - + titles = request.session.get('titles', {}) title = titles.get(uuid, "no title") @@ -342,7 +365,7 @@ def list_public_experiences(request): for trigger in triggers_to_show: trigger_check = f"check{trigger}" tts[trigger_check] = True - + if all_triggers: tts["checkall"] = True diff --git a/static/css/main.css b/static/css/main.css index 7096fe48..f31cc7d7 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -116,6 +116,7 @@ body { .section-heading { font-size: 3rem; font-weight: bold; + text-align: center; } .value-heading { font-size: 2rem; @@ -133,12 +134,16 @@ body { line-height: 1.5; font-weight: bold; text-align: center; + color: #fff; + text-decoration: none; + } + .usage-box a:hover { + text-decoration: none; } - /*Introduction Section*/ .intro { padding: 2% 5% 3%; - background: #1BB5AF; + background: #1BB5AF; color: #fff; } @@ -149,9 +154,8 @@ body { padding-bottom: 3%; } .title-image { - padding-top: 2%; - width: 75%; - margin-left: 25%; + margin-top: 20px; + width: 100%; border-radius: 5%; } @@ -159,7 +163,8 @@ body { /*Value Section*/ #value { padding: 4% 5%; - color: #309FDC; + background: #309FDC; + color: #fff; } .value-text { line-height: 2; @@ -189,16 +194,45 @@ body { padding: 4% 5%; color: #309FDC; } + .experience-content { color: #8f8f8f; line-height: 1.5; font-size: 20px; } + + .experience-half { + display: flex; + justify-content: center; + align-items: center; + padding: 20px; + overflow: hidden; + height: 400px; + } + + .experience-full { + display: flex; + justify-content: center; + align-items: center; + padding: 20px 100px; + overflow: hidden; + height: 400px; + } + + .experience-image-box { + padding: 0; + margin: 0; + overflow: hidden; + } + + .experience-text-box { + max-height: 400px; + overflow: hidden; + -webkit-mask-image: linear-gradient(180deg, #000 80%, transparent); + } + .experience-image { - width: 50%; - padding-top: 2%; - padding-right: 5%; - padding-left: 6%; + transition: transform .5s; } .carousel-item { @@ -206,6 +240,15 @@ body { height: 500px; } + .carousel-item a, .carousel-item a:hover { + text-decoration: none; + color: #309FDC; + } + + .carousel-item a:hover .experience-image { + transform: scale(1.05); + } + .carousel-control-next, .carousel-control-prev { filter: invert(100%); @@ -221,23 +264,66 @@ body { filter: invert(100%); } + /*Platform Information*/ + + #platform_info { + background: #FFFFFF; + padding: 4% 5%; + color: #1BB5AF; + } + + .platform-info-text { + line-height: 1.5; + font-size: 20px; + } + + .btn-home-about { + color: #1BB5AF; + border-color: #1BB5AF; + } + + .btn-home-about:hover { + background-color: #1BB5AF; + border-color: #1BB5AF; + } + + .btn-home-about:active { + color: #1BB5AF; + background-color: #1BB5AF; + border-color: #1BB5AF; + } + + .btn-home-about:focus { + box-shadow: 0 0 0 3px #1BB5AF80; + } + /*How to Use the Platform Section*/ + #platform_usage { - background: #F1806F; + background: #FAC145; padding: 4% 5%; color: #ffffff; } + .platform-title { padding-bottom: 5%; } - .platform-left-border { - margin-left: 8%; + margin-left: 8%; } -.usage-icon{ - color: #fff; -} + + .usage-icon{ + display: flex; + align-items: center; + justify-content: center; + transition: transform .5s; + } + + .usage-box:hover .usage-icon { + transform: scale(1.5); + } + .platform-button { margin-top: 5%; } @@ -644,4 +730,4 @@ body { .profile-section { padding: 3% 5%; -} \ No newline at end of file +} From ebd5876c387061ea92673903b04546d53b8be25f Mon Sep 17 00:00:00 2001 From: David Llewellyn-Jones Date: Wed, 4 Oct 2023 10:35:54 +0100 Subject: [PATCH 2/3] Update footer links Adds links to the items in the footer. Some of these are placeholders, with the idea that they help direct people to where they can contribute. --- .../main/templates/main/partials/footer.html | 27 ++++++++++--------- static/css/main.css | 4 +++ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/server/apps/main/templates/main/partials/footer.html b/server/apps/main/templates/main/partials/footer.html index c3db8804..ab5c1133 100644 --- a/server/apps/main/templates/main/partials/footer.html +++ b/server/apps/main/templates/main/partials/footer.html @@ -1,27 +1,28 @@ {% load static %} -
-
diff --git a/static/css/main.css b/static/css/main.css index f31cc7d7..8203e864 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -64,6 +64,10 @@ body { margin-bottom: 7%; } +.clear-hover:hover { + text-decoration: none; +} + .footer-text { text-align: left; padding-right: 2%; From 2c6b957e491cd4b1500f706c35a11b4690727a73 Mon Sep 17 00:00:00 2001 From: David Llewellyn-Jones Date: Thu, 5 Oct 2023 10:55:28 +0100 Subject: [PATCH 3/3] Add carousel selector Adds a carousel.json file that contains dummy stories for the carousel. A list of uuids to real stories can also be added to the file. The real stories are chosen in preference to the dummy stories as long as: 1. The real stories have been moderated and approved. 2. The real stories have no trigger labels. --- server/apps/main/carousel.json | 35 +++++ server/apps/main/helpers.py | 109 ++++++++++++- server/apps/main/templates/main/home.html | 13 +- .../main/templates/main/single_story.html | 4 + server/apps/main/tests/carousel-test01.json | 35 +++++ server/apps/main/tests/carousel-test02.json | 33 ++++ server/apps/main/tests/carousel-test03.json | 32 ++++ server/apps/main/tests/carousel-test04.json | 36 +++++ server/apps/main/tests/test_helpers.py | 143 ++++++++++++++++-- server/apps/main/tests/test_views.py | 19 ++- server/apps/main/views.py | 50 +++--- 11 files changed, 461 insertions(+), 48 deletions(-) create mode 100644 server/apps/main/carousel.json create mode 100644 server/apps/main/tests/carousel-test01.json create mode 100644 server/apps/main/tests/carousel-test02.json create mode 100644 server/apps/main/tests/carousel-test03.json create mode 100644 server/apps/main/tests/carousel-test04.json diff --git a/server/apps/main/carousel.json b/server/apps/main/carousel.json new file mode 100644 index 00000000..7d843ab1 --- /dev/null +++ b/server/apps/main/carousel.json @@ -0,0 +1,35 @@ +{ + "number-to-display": 3, + "max-chars-title": 48, + "max-chars-experience": 512, + "placeholders": [ + { + "title": "Eating in a restaurant", + "experience": "I’ve always found it so difficult going to restaurants. When I’m hungry everything sounds even louder and feels even more intimidating. As a kid, I never understood why it was so hard and my parents would often get mad at me for ‘misbehaving’ in public. I’d just meltdown whenever we went out to eat. Waiting for the food felt like some kind of marathon and I just got so overwhelmed. But now that I’ve realised hunger exacerbates my sensory differences I know how to cope.", + "difference": "I try to make sure I’ve eaten something before I go out and a while back I bought some earplugs which are great for small and bustling places. Looking at menus beforehand also helps as I can better prep myself and focus on adjusting to the environment rather than debating what to eat. I do wish restaurants wouldn’t play super loud music. I get it adds to the ambience but having even one section that’s quiet would be so helpful.", + "image": "animation_a.jpg" + }, + { + "title": "Spatial awareness", + "experience": "My lack of spatial awareness is probably the most debilitating part of my autism. I’m constantly walking into door frames and tripping over the corners of furniture even in my own home. And have a terrible habit of walking backwards into old women in shopping aisles. It’s been super hard with COVID as I’ve had to be in full ‘defensive mode’ whenever I go out, making sure I’m not too near to anyone.\n\nHowever, I feel it most when I’m playing sports. I think I’m marking someone but they're actually halfway across the pitch. Or I call for the ball thinking I’m in a clear space when in reality there are three players standing right behind me. It’s even harder if there’s more than one game going on as I find it hard to focus on just one sound and I tend to use my hearing as a way of working out whether people are near me or not. Because autism is an invisible disability I feel like people don’t understand and just see me as a bad player. At times this gets really upsetting. It takes so much effort for me to participate in group activities because of my social anxiety, so if I feel like I’m underperforming it sometimes gets too much.", + "difference": "I think shops need to be more aware of spatial issues. Stop putting piles of things in the middle of aisles! In social situations, I just wish people were more aware. If I’m playing football and you’re on my team, tell me where the opponents are.", + "image": "animation_b.jpg" + }, + { + "title": "Television", + "experience": "My family recently got a new TV. It’s much larger than our previous one and has limited screen settings. 3/4 are too bright for me but my family dislikes the fourth option. I don’t often watch telly with the family but I feel like I should have the option to. It just feels unfair, I get that I’m a minority but they don’t see what I see and the fact that they won’t adapt to my needs upsets me. My parents are constantly nagging me to spend less time in my room but it’s situations like this that make me do just that. I can regulate my input in my own space and I can’t elsewhere.", + "difference": "Well firstly TVs need more options and the menu should be easy to navigate. But like with so much it’s just a matter of awareness and others actually having a willingness to adapt. ", + "image": "animation_c.jpg" + } + ], + "stories": [ + { + "uuid": "bf48c18e-6133-11ee-8f2d-0242ac120003", + "image": "animation_b.jpg" + }, + { + "uuid": "", + "image": "" + } + ] +} diff --git a/server/apps/main/helpers.py b/server/apps/main/helpers.py index 3b414a93..98f62f37 100644 --- a/server/apps/main/helpers.py +++ b/server/apps/main/helpers.py @@ -646,7 +646,7 @@ def message_wrap(text, width): def experience_titles_for_session(files): """ take a member.list_files() list of files and add them to dict - of the form + of the form {"titles":{ "uuid": "title", "uuid2": "title2" @@ -657,4 +657,109 @@ def experience_titles_for_session(files): for f in files: if "uuid" in f['metadata'].keys(): titles[f['metadata']['uuid']] = f['metadata']['description'] - return titles \ No newline at end of file + return titles + +def truncate_text(text, length): + truncated = textwrap.wrap(text, length)[0] + if len(truncated) < len(text): + truncated = textwrap.wrap(text, length - 3)[0]; + truncated += "..." + return truncated + +def get_carousel_stories(filename="carousel.json"): + """ + Return the stories to use for the carousel on the home page + + Returns an array of stories, each with the following structdure: + { + "title_summary": "", + "experience_summary": "", + "title": "", + "experience": "", + "difference": "", + "image": "", + "uuid": "" + } + + The actual content of the stories is controlled by the variables in the + server/apps/main/carousel.json file. + """ + module_dir = os.path.dirname(__file__) + file_path = os.path.join(module_dir, filename) + stories = [] + try: + with open(file_path, "r") as f: + data = json.load(f) + except IOError as e: + print("Exception when opening carousel file: {}".format(str(e))) + return None + + total = data.get("number-to-display", 3) + title_max = data.get("max-chars-title", 16) + experience_max = data.get("max-chars-experience", 128) + + + # Step 1: collect together real stories + if len(stories) < total: + for story in data.get("stories", []): + try: + uuid = story["uuid"] + public_story = PublicExperience.objects.get( + experience_id=uuid + ) + if (public_story.moderation_status != "approved" + or public_story.abuse + or public_story.violence + or public_story.drug + or public_story.mentalhealth + or public_story.negbody + or public_story.other): + # This isn't an appropriate story to use so we'll skip it + continue + title = public_story.title_text + experience = public_story.experience_text + item = { + "title_summary": truncate_text(title, title_max), + "experience_summary": truncate_text(experience, experience_max), + "title": title, + "experience": experience, + "difference": public_story.difference_text, + "image": story.get("image", ""), + "uuid": uuid, + } + stories.append(item) + if len(stories) >= total: + break + except KeyError as e: + print("Exception when reading carousel dictionary: {}".format(str(e))) + except PublicExperience.DoesNotExist as e: + print("Carousel experience does not exist:: {}".format(uuid)) + + + # Step 2: collect together placeholder stories + placeholder = 0 + if len(stories) < total: + for story in data.get("placeholders", []): + try: + uuid = 'placeholder{}'.format(placeholder) + title = story["title"] + experience = story["experience"] + item = { + "title_summary": truncate_text(title, title_max), + "experience_summary": truncate_text(experience, experience_max), + "title": title, + "experience": experience, + "difference": story["difference"], + "image": story.get("image", ""), + "uuid": uuid, + } + stories.append(item) + placeholder += 1 + if len(stories) >= total: + break + except KeyError as e: + print("Exception when reading carousel dictionary: {}".format(str(e))) + + return stories + + diff --git a/server/apps/main/templates/main/home.html b/server/apps/main/templates/main/home.html index 3ce73a68..5bc8a9d8 100644 --- a/server/apps/main/templates/main/home.html +++ b/server/apps/main/templates/main/home.html @@ -18,9 +18,6 @@

AutSPACEs

A space for autistic people to share our stories about our senses so we can build a better world for autistic people

- - -
@@ -150,20 +147,20 @@

Shared stories

{% if story.image %}
- First slide +
-

{{ story.title }}

-

{{ story.summary }}

+

{{ story.title_summary }}

+

{{ story.experience_summary }}

{% else %}
-

{{ story.title }}

-

{{ story.summary }}

+

{{ story.title_summary }}

+

{{ story.experience_summary }}

{% endif %} diff --git a/server/apps/main/templates/main/single_story.html b/server/apps/main/templates/main/single_story.html index a3a34d2d..6c9b9d7b 100644 --- a/server/apps/main/templates/main/single_story.html +++ b/server/apps/main/templates/main/single_story.html @@ -45,11 +45,15 @@

Title: {% firstof experience.title_text "No Title Given" %}

+ {% if placeholder %} + Example story + {% else %} {% if experience.first_hand_authorship %} Autistic individual {% else %} {% firstof experience.authorship_relation "AutSPACEs Contributor" %} {% endif %} + {% endif %}

Experience

{% firstof experience.experience_text "No Experience Text Given" %}

diff --git a/server/apps/main/tests/carousel-test01.json b/server/apps/main/tests/carousel-test01.json new file mode 100644 index 00000000..7cb1c9a6 --- /dev/null +++ b/server/apps/main/tests/carousel-test01.json @@ -0,0 +1,35 @@ +{ + "number-to-display": 3, + "max-chars-title": 14, + "max-chars-experience": 24, + "placeholders": [ + { + "title": "title01title01title01title01title01title01title01title01title01title01title01title01", + "experience": "experience01experience01experience01experience01experience01experience01experience01experience01experience01experience01experience01experience01", + "difference": "difference01difference01difference01difference01difference01difference01difference01difference01difference01difference01difference01difference01", + "image": "animation_a.jpg" + }, + { + "title": "title02title02title02title02title02title02title02title02title02title02title02title02", + "experience": "experience02experience02experience02experience02experience02experience02experience02experience02experience02experience02experience02experience02", + "difference": "difference02difference02difference02difference02difference02difference02difference02difference02difference02difference02difference02difference02", + "image": "animation_b.jpg" + }, + { + "title": "title03title03title03title03title03title03title03title03title03title03title03title03", + "experience": "experience03experience03experience03experience03experience03experience03experience03experience03experience03experience03experience03experience03", + "difference": "difference03difference03difference03difference03difference03difference03difference03difference03difference03difference03difference03difference03", + "image": "animation_c.jpg" + } + ], + "stories": [ + { + "uuid": "1234_1", + "image": "animation_b.jpg" + }, + { + "uuid": "", + "image": "" + } + ] +} diff --git a/server/apps/main/tests/carousel-test02.json b/server/apps/main/tests/carousel-test02.json new file mode 100644 index 00000000..41f2325a --- /dev/null +++ b/server/apps/main/tests/carousel-test02.json @@ -0,0 +1,33 @@ +{ + "number-to-display": 5, + "max-chars-title": 14, + "max-chars-experience": 24, + "placeholders": [ + { + "title": "title01title01title01title01title01title01title01title01title01title01title01title01", + "experience": "experience01experience01experience01experience01experience01experience01experience01experience01experience01experience01experience01experience01", + "difference": "difference01difference01difference01difference01difference01difference01difference01difference01difference01difference01difference01difference01", + "image": "animation_a.jpg" + }, + { + "title": "title02title02title02title02title02title02title02title02title02title02title02title02", + "experience": "experience02experience02experience02experience02experience02experience02experience02experience02experience02experience02experience02experience02", + "difference": "difference02difference02difference02difference02difference02difference02difference02difference02difference02difference02difference02difference02", + "image": "animation_b.jpg" + } + ], + "stories": [ + { + "uuid": "1234_2", + "image": "animation_b.jpg" + }, + { + "uuid": "1234_2", + "image": "animation_c.jpg" + }, + { + "uuid": "", + "image": "" + } + ] +} diff --git a/server/apps/main/tests/carousel-test03.json b/server/apps/main/tests/carousel-test03.json new file mode 100644 index 00000000..005acdf3 --- /dev/null +++ b/server/apps/main/tests/carousel-test03.json @@ -0,0 +1,32 @@ +{ + "number-to-display": 3, + "max-chars-title": 14, + "max-chars-experience": 24, + "placeholders": [ + { + "title": "title01title01title01title01title01title01title01title01title01title01title01title01", + "experience": "experience01experience01experience01experience01experience01experience01experience01experience01experience01experience01experience01experience01", + "difference": "difference01difference01difference01difference01difference01difference01difference01difference01difference01difference01difference01difference01", + "image": "animation_a.jpg" + }, + { + "experience": "experience02experience02experience02experience02experience02experience02experience02experience02experience02experience02experience02experience02", + "difference": "difference02difference02difference02difference02difference02difference02difference02difference02difference02difference02difference02difference02", + "image": "animation_b.jpg" + }, + { + "title": "title03title03title03title03title03title03title03title03title03title03title03title03", + "experience": "experience03experience03experience03experience03experience03experience03experience03experience03experience03experience03experience03experience03" + } + ], + "stories": [ + { + "uuid": "1234_2", + "image": "animation_b.jpg" + }, + { + "uuid": "nonexistent", + "image": "animation_c.jpg" + } + ] +} diff --git a/server/apps/main/tests/carousel-test04.json b/server/apps/main/tests/carousel-test04.json new file mode 100644 index 00000000..99f6bb7f --- /dev/null +++ b/server/apps/main/tests/carousel-test04.json @@ -0,0 +1,36 @@ +{ + "number-to-display": 1, + "max-chars-title": 14, + "max-chars-experience": 24, + "placeholders": [ + ], + "stories": [ + { + "image": "animation_b.jpg" + }, + { + "uuid": "1234_1", + "image": "animation_b.jpg" + }, + { + "uuid": "1234_2", + "image": "animation_b.jpg" + }, + { + "uuid": "1234_3", + "image": "animation_b.jpg" + }, + { + "uuid": "1234_1", + "image": "animation_b.jpg" + }, + { + "uuid": "1234_1", + "image": "animation_b.jpg" + }, + { + "uuid": "", + "image": "" + } + ] +} diff --git a/server/apps/main/tests/test_helpers.py b/server/apps/main/tests/test_helpers.py index 4db84378..384cabc5 100644 --- a/server/apps/main/tests/test_helpers.py +++ b/server/apps/main/tests/test_helpers.py @@ -5,7 +5,7 @@ is_moderator, make_tags, extract_experience_details, delete_single_file_and_pe, delete_PE, \ public_experience_model_to_form, process_trigger_warnings, update_public_experience_db, \ get_oh_metadata, get_oh_file, get_oh_combined, moderate_page, choose_moderation_redirect, \ - extract_triggers_to_show, get_message, message_wrap + extract_triggers_to_show, get_message, message_wrap, get_carousel_stories from openhumans.models import OpenHumansMember from server.apps.main.models import PublicExperience, ExperienceHistory @@ -40,6 +40,34 @@ def setUp(self): self.moderator_group.user_set.add(self.moderator_user.user) self.moderator_user.save() + approved = { + "experience_text": "approved", + "difference_text": "approved", + "title_text": "approved", + "moderation_status": "approved", + } + approved_with_trigger = { + "experience_text": "trigger", + "difference_text": "trigger", + "title_text": "trigger", + "moderation_status": "approved", + "abuse": True, + } + rejected = { + "experience_text": "rejected", + "difference_text": "rejected", + "title_text": "rejected", + "moderation_status": "rejected", + } + self.pe_a = PublicExperience.objects.create( + open_humans_member=self.non_moderator_user, experience_id="1234_1", **approved + ) + self.pe_b = PublicExperience.objects.create( + open_humans_member=self.non_moderator_user, experience_id="1234_2", **approved_with_trigger + ) + self.pe_c = PublicExperience.objects.create( + open_humans_member=self.non_moderator_user, experience_id="1234_3", **rejected + ) def test_reformat_date(self): """ @@ -276,18 +304,18 @@ def test_update_public_experience(self): 'experience_id': 'foobar_id', 'viewable': True } - # assert no PE and PEH objects exist so far - self.assertEqual(len(PublicExperience.objects.all()),0) - self.assertEqual(len(ExperienceHistory.objects.all()),0) + # record initial PE and PEH objects in the database + initial_experiences = len(PublicExperience.objects.all()) + initial_histories = len(ExperienceHistory.objects.all()) # create new PE update_public_experience_db(self.pe_data, "foobar_id" ,self.non_moderator_user,self.non_moderator_user) # check that PE exists - self.assertEqual(len(PublicExperience.objects.all()),1) + self.assertEqual(len(PublicExperience.objects.all()),initial_experiences + 1) pe = PublicExperience.objects.get(experience_id='foobar_id') # check that moderation status is "in review" self.assertEqual(pe.moderation_status, 'not reviewed') # check that this and only this PEH object exists - self.assertEqual(len(ExperienceHistory.objects.filter(experience=pe)),1) + self.assertEqual(len(ExperienceHistory.objects.filter(experience=pe)),initial_histories + 1) # check that EH object marks creation peh = ExperienceHistory.objects.filter(experience=pe)[0] self.assertEqual(peh.change_type,"Make Public") @@ -302,7 +330,7 @@ def test_update_public_experience(self): pe = PublicExperience.objects.get(experience_id='foobar_id') self.assertEqual(pe.moderation_status, 'not reviewed') # check that two history objects exists - self.assertEqual(len(ExperienceHistory.objects.filter(experience=pe)),2) + self.assertEqual(len(ExperienceHistory.objects.filter(experience=pe)),initial_histories + 2) # get newest PEH object, check it's "Edit" peh = ExperienceHistory.objects.all().order_by('changed_at')[1] self.assertEqual(peh.change_type,"Edit") @@ -314,7 +342,7 @@ def test_update_public_experience(self): # check updated PE & latest PEH object pe = PublicExperience.objects.get(experience_id='foobar_id') self.assertEqual(pe.moderation_status, 'approved') # is now approved - self.assertEqual(len(ExperienceHistory.objects.filter(experience=pe)),3) # got 3 history entries now + self.assertEqual(len(ExperienceHistory.objects.filter(experience=pe)),initial_histories + 3) # got 3 history entries now peh = ExperienceHistory.objects.all().order_by('changed_at')[2] # check details of PEH & PE self.assertEqual(pe.open_humans_member.oh_id,str(self.non_moderator_user.oh_id)) # exp owned by owner @@ -322,8 +350,8 @@ def test_update_public_experience(self): self.assertEqual(peh.change_type,'Moderate') # last operation was moderate # check everything gets deleted if user makes story non-public update_public_experience_db(self.pe_data, "foobar_id" ,self.non_moderator_user,self.non_moderator_user) - self.assertEqual(len(PublicExperience.objects.all()),0) # should be no public experience - self.assertEqual(len(ExperienceHistory.objects.all()),0) # should be no history + self.assertEqual(len(PublicExperience.objects.all()),initial_experiences) # should be no additional public experience + self.assertEqual(len(ExperienceHistory.objects.all()),initial_histories) # should be no additional history def test_moderation_redirect(self): result = choose_moderation_redirect("not moderated") @@ -390,3 +418,98 @@ def test_message_wrap(self): assert text.count("\n") == 31 for line in text.split("\n"): assert len(line) <= length + + def test_carousel_stories_nofile(self): + """ + Test that if the carousel fils is missing None is returned. + """ + # File doesn't exist + stories = get_carousel_stories(filename="tests/carousel-nonexistent.json") + assert not stories + + def test_carousel_stories_appropriate_stories(self): + """ + Test that appropriate stories can appear in the carousel. + """ + # Request three stories, all appropriate + stories = get_carousel_stories(filename="tests/carousel-test01.json") + assert len(stories) == 3 + for story in stories: + assert len(story["title_summary"]) <= 14 + assert len(story["experience_summary"]) <= 24 + assert len(story["title_summary"]) > 0 + assert len(story["experience_summary"]) > 0 + assert len(story["title"]) > 0 + assert len(story["experience"]) > 0 + assert len(story["difference"]) > 0 + assert len(story["image"]) > 0 + assert len(story["uuid"]) > 0 + + def test_carousel_stories_inappropriate_stories(self): + """ + Test that no inappropriate stories (rejected or with trigger warnings) + will appear in the carousel. + """ + # Request five stories but only two are inappropriate + stories = get_carousel_stories(filename="tests/carousel-test02.json") + assert len(stories) == 2 + for story in stories: + assert len(story["title_summary"]) <= 14 + assert len(story["experience_summary"]) <= 24 + assert len(story["title_summary"]) > 0 + assert len(story["experience_summary"]) > 0 + assert len(story["title"]) > 0 + assert len(story["experience"]) > 0 + assert len(story["difference"]) > 0 + assert len(story["image"]) > 0 + assert len(story["uuid"]) > 0 + assert story["title"] != "rejected" + assert story["title"] != "trigger" + assert "placeholder" in story["uuid"] + + def test_carousel_stories_missing_details(self): + """ + Test that if the caoursel file has poorly formed entries they are skipped + but without impacting the well-formed entries. + """ + # Request five stories but only one is well-formed + stories = get_carousel_stories(filename="tests/carousel-test03.json") + assert len(stories) == 1 + for story in stories: + assert len(story["title_summary"]) <= 14 + assert len(story["experience_summary"]) <= 24 + assert len(story["title_summary"]) > 0 + assert len(story["experience_summary"]) > 0 + assert len(story["title"]) > 0 + assert len(story["experience"]) > 0 + assert len(story["difference"]) > 0 + assert len(story["image"]) > 0 + assert len(story["uuid"]) > 0 + assert story["title"] != "rejected" + assert story["title"] != "trigger" + assert "placeholder" in story["uuid"] + + def test_carousel_stories_too_many(self): + """ + Test that if there are too many real stories available for the carousel + the correct number is still returned. Also tests the exception case where + the uuid is missing. + """ + # Request five stories but only one is well-formed + stories = get_carousel_stories(filename="tests/carousel-test04.json") + assert len(stories) == 1 + for story in stories: + assert len(story["title_summary"]) <= 14 + assert len(story["experience_summary"]) <= 24 + assert len(story["title_summary"]) > 0 + assert len(story["experience_summary"]) > 0 + assert len(story["title"]) > 0 + assert len(story["experience"]) > 0 + assert len(story["difference"]) > 0 + assert len(story["image"]) > 0 + assert len(story["uuid"]) > 0 + assert story["title"] != "rejected" + assert story["title"] != "trigger" + assert "placeholder" not in story["uuid"] + + diff --git a/server/apps/main/tests/test_views.py b/server/apps/main/tests/test_views.py index 023adce4..43d86371 100644 --- a/server/apps/main/tests/test_views.py +++ b/server/apps/main/tests/test_views.py @@ -701,6 +701,17 @@ def test_single_story(self): r_rejected_story = c.get("/main/single_story/8765_3/") self.assertRedirects(r_rejected_story, "/") + def test_single_story_placeholder(self): + c = Client() + c.force_login(self.user_a) + + # Check placeholder story is shown + r_approved_story = c.get("/main/single_story/placeholder1/") + assert r_approved_story.status_code == 200 + for item in r_approved_story.context[0]: + if "placeholder" in item: + assert item["placeholder"] == True + def test_list_public_exp_pagination(self): c = Client() c.force_login(self.user_a) @@ -737,7 +748,7 @@ def test_list_public_exp_pagination(self): # The first item on the second page should be the 11th item overall assert response.context["experiences"][0].number == 11 - + # Test pagination in my_stories, uses separate tests for page 1 / page 2 to avoid cassette issues @vcr.use_cassette( @@ -755,7 +766,7 @@ def test_list_public_exp_pagination(self): def test_my_stories_pagination_page1(self): c = Client() c.force_login(self.user_b) - + response = c.get("/main/my_stories/") num_items_per_page = 10 # number of stories per category recorded in the casette @@ -769,7 +780,7 @@ def test_my_stories_pagination_page1(self): # check story numbering assert stories[0]['number'] == 1 # first story on page 1 has number 1 assert stories[-1]['number'] == min(num_items_per_page, n_stories) # last story on page 1 has correct number - + @vcr.use_cassette( "server/apps/main/tests/fixtures/pag_mystories.yaml", record_mode="none", @@ -792,4 +803,4 @@ def test_my_stories_pagination_page2(self): # check pagination assert len(stories) == min(num_items_per_page, n_stories) # check story numbering - assert stories[0]['number'] == num_items_per_page + 1 # first story on page 1 is continuation of page 1 \ No newline at end of file + assert stories[0]['number'] == num_items_per_page + 1 # first story on page 1 is continuation of page 1 diff --git a/server/apps/main/views.py b/server/apps/main/views.py index 59ca3e6f..b895759a 100644 --- a/server/apps/main/views.py +++ b/server/apps/main/views.py @@ -50,6 +50,7 @@ message_wrap, experience_titles_for_session, extract_authorship_details, + get_carousel_stories, ) from server.apps.users.helpers import ( @@ -114,30 +115,11 @@ def index(request): """ Starting page for app. """ + stories = get_carousel_stories() + if request.user.is_authenticated: oh_member = request.user.openhumansmember - stories = [ - { - "title": "Eating in a restaurant", - "summary": "I’ve always found it so difficult going to restaurants. When I’m hungry everything sounds even louder and feels even more intimidating. As a kid, I never understood why it was so hard and my parents would often get mad at me for ‘misbehaving’ in public. I’d just meltdown whenever we went out to eat. Waiting for the food felt like some kind of marathon and I just got so overwhelmed. But now that I’ve realised hunger exacerbates my sensory differences I know how to cope.", - "uuid": "bf48c18e-6133-11ee-8f2d-0242ac120003", - "image": "animation_a.jpg", - }, - { - "title": "Spatial awareness", - "summary": "My lack of spatial awareness is probably the most debilitating part of my autism. I’m constantly walking into door frames and tripping over the corners of furniture even in my own home. And have a terrible habit of walking backwards into old women in shopping aisles. It’s been super hard with COVID as I’ve had to be in full ‘defensive mode’ whenever I go out, making sure I’m not too near to anyone.", - "uuid": "bf48c18e-6133-11ee-8f2d-0242ac120003", - "image": "animation_b.jpg", - }, - { - "title": "Television", - "summary": "My family recently got a new TV. It’s much larger than our previous one and has limited screen settings. 3/4 are too bright for me but my family dislikes the fourth option. I don’t often watch telly with the family but I feel like I should have the option to. It just feels unfair, I get that I’m a minority but they don’t see what I see and the fact that they won’t adapt to my needs upsets me. My parents are constantly nagging me to spend less time in my room but it’s situations like this that make me do just that. I can regulate my input in my own space and I can’t elsewhere.", - "uuid": "bf48c18e-6133-11ee-8f2d-0242ac120003", - "image": "animation_c.jpg", - }, - ] - context = { "oh_id": oh_member.oh_id, "oh_member": oh_member, @@ -147,7 +129,11 @@ def index(request): } else: auth_url = OpenHumansMember.get_auth_url() - context = {"auth_url": auth_url, "oh_proj_page": settings.OH_PROJ_PAGE} + context = { + "auth_url": auth_url, + "oh_proj_page": settings.OH_PROJ_PAGE, + "stories": stories, + } return render(request, "main/home.html", context=context) def login_user(request): @@ -680,14 +666,30 @@ def single_story(request, uuid): """ # Must have both the specified UUID and be approved otherwise will redirect # Should only be one result if not redirect + experience = None + placeholder = False try: experience = PublicExperience.objects.get( experience_id=uuid, moderation_status="approved" ) + except ObjectDoesNotExist: + if uuid.startswith("placeholder"): + stories = get_carousel_stories() + for story in stories: + if story["uuid"] == uuid: + experience = PublicExperience( + title_text = story["title"], + experience_text = story["experience"], + difference_text = story["difference"], + + ) + placeholder = True + break; + if experience: title = experience.title_text exp_context = {"experience": experience} title_context = {"title": title} - context = {**exp_context, **title_context} + context = {**exp_context, **title_context, "placeholder": placeholder} return render(request, "main/single_story.html", context=context) - except ObjectDoesNotExist: + else: return redirect("index")