Skip to content

Commit 66b2a41

Browse files
committed
feat(resources): add tool request demo mutation
1 parent 1585de7 commit 66b2a41

File tree

9 files changed

+227
-7
lines changed

9 files changed

+227
-7
lines changed

apps/resources/admin.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Register your models here.
22
from django.contrib import admin
33

4-
from apps.resources.models import CaseStudy, ContactRequest
4+
from apps.resources.models import CaseStudy, ContactRequest, RequestDemo
55
from apps.tool_picker.admin import ReadOnlyMixin
66

77

@@ -17,3 +17,9 @@ class CaseStudyAdmin(admin.ModelAdmin[CaseStudy]):
1717
search_fields = ("title",)
1818
list_select_related = ("tool",)
1919
autocomplete_fields = ("tool",)
20+
21+
22+
@admin.register(RequestDemo)
23+
class RequestDemoAdmin(ReadOnlyMixin, admin.ModelAdmin[RequestDemo]):
24+
list_display = ("name", "email", "national_society", "created_at")
25+
search_fields = ("name", "email")

apps/resources/graphql/inputs.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import strawberry
2+
import strawberry_django
3+
4+
from apps.resources.models import RequestDemo
25

36

47
@strawberry.input
@@ -9,3 +12,12 @@ class ContactRequestInput:
912
content: str
1013
captcha_hashkey: str
1114
captcha_code: str
15+
16+
17+
@strawberry_django.input(RequestDemo)
18+
class RequestDemoInput:
19+
name: strawberry.auto
20+
email: strawberry.auto
21+
content: strawberry.auto
22+
national_society: strawberry.auto
23+
tool: int

apps/resources/graphql/mutations.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import strawberry
22
import strawberry_django
33

4-
from apps.resources.graphql.inputs import ContactRequestInput
5-
from apps.resources.graphql.types import ContactRequestType
6-
from apps.resources.serializers import ContactRequestSerializer
4+
from apps.resources.graphql.inputs import ContactRequestInput, RequestDemoInput
5+
from apps.resources.graphql.types import ContactRequestType, RequestDemoType
6+
from apps.resources.serializers import ContactRequestSerializer, RequestDemoSerializer
77
from main.graphql.context import Info
88
from utils.graphql.mutations import ModelMutation
99
from utils.graphql.types import MutationResponseType
@@ -18,3 +18,11 @@ async def create_contact_request(
1818
data: ContactRequestInput,
1919
) -> MutationResponseType[ContactRequestType]:
2020
return await ModelMutation(ContactRequestSerializer).handle_create_mutation(data, info, None)
21+
22+
@strawberry_django.mutation()
23+
async def create_demo_request(
24+
self,
25+
info: Info,
26+
data: RequestDemoInput,
27+
) -> MutationResponseType[RequestDemoType]:
28+
return await ModelMutation(RequestDemoSerializer).handle_create_mutation(data, info, None)

apps/resources/graphql/types.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import strawberry
22
import strawberry_django
33

4-
from apps.resources.models import CaseStudy, ContactRequest
4+
from apps.resources.models import CaseStudy, ContactRequest, RequestDemo
55
from utils.graphql.types import DjangoFileType
66

77

@@ -23,3 +23,14 @@ class ContactRequestType:
2323
created_at: strawberry.auto
2424
content: strawberry.auto
2525
national_society: strawberry.auto
26+
27+
28+
@strawberry_django.type(RequestDemo)
29+
class RequestDemoType:
30+
id: strawberry.ID
31+
name: strawberry.auto
32+
email: strawberry.auto
33+
created_at: strawberry.auto
34+
content: strawberry.auto
35+
national_society: strawberry.auto
36+
tool: strawberry.auto
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Generated by Django 5.2.9 on 2026-03-17 08:23
2+
3+
import django.db.models.deletion
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('resources', '0002_casestudy_link'),
11+
('tool_picker', '0004_alter_checkboxoption_order_alter_question_order'),
12+
]
13+
14+
operations = [
15+
migrations.CreateModel(
16+
name='RequestDemo',
17+
fields=[
18+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19+
('name', models.CharField(max_length=200)),
20+
('email', models.CharField(max_length=200)),
21+
('created_at', models.DateTimeField(auto_now_add=True)),
22+
('national_society', models.CharField(max_length=200)),
23+
('content', models.TextField(blank=True, null=True)),
24+
('tool', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='request_demo_tool', to='tool_picker.tool', verbose_name='Related Tool')),
25+
],
26+
options={
27+
'ordering': ['name'],
28+
},
29+
),
30+
]

apps/resources/models.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,26 @@ class Meta(UserResource.Meta):
5252
@typing.override
5353
def __str__(self):
5454
return self.title
55+
56+
57+
class RequestDemo(models.Model):
58+
"""Model representing contact where anyone can request the tool demo to the admins."""
59+
60+
name = models.CharField[str, str](max_length=200)
61+
email = models.CharField[str, str](max_length=200)
62+
created_at = models.DateTimeField[datetime.datetime, datetime.datetime](auto_now_add=True)
63+
national_society = models.CharField[str, str](max_length=200)
64+
content = models.TextField[str, str](blank=True, null=True)
65+
tool = models.ForeignKey[Tool, Tool](
66+
Tool,
67+
related_name="request_demo_tool",
68+
verbose_name="Related Tool",
69+
on_delete=models.CASCADE,
70+
)
71+
72+
class Meta:
73+
ordering = ["name"]
74+
75+
@typing.override
76+
def __str__(self):
77+
return self.name

apps/resources/serializers.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
from captcha.serializers import CaptchaModelSerializer
44
from rest_framework import serializers
55

6-
from apps.resources.models import ContactRequest
6+
from apps.resources.models import ContactRequest, RequestDemo
7+
from apps.tool_picker.models import Tool
78

89

910
class ContactRequestSerializer(CaptchaModelSerializer):
@@ -26,3 +27,19 @@ def create(self, validated_data: dict[str, typing.Any]):
2627
validated_data.pop("captcha_code", None)
2728
validated_data.pop("captcha_hashkey", None)
2829
return super().create(validated_data)
30+
31+
32+
class RequestDemoSerializer(serializers.ModelSerializer):
33+
tool = serializers.PrimaryKeyRelatedField(
34+
queryset=Tool.objects.all(),
35+
)
36+
37+
class Meta:
38+
model = RequestDemo
39+
fields = (
40+
"name",
41+
"email",
42+
"national_society",
43+
"content",
44+
"tool",
45+
)

apps/resources/tests/mutation_test.py

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
from captcha.models import CaptchaStore
44

5-
from apps.resources.models import ContactRequest
5+
from apps.resources.models import ContactRequest, RequestDemo
6+
from apps.tool_picker.factories import ToolFactory
67
from apps.user.factories import UserFactory
78
from main.tests import TestCase
89

@@ -114,3 +115,78 @@ def test_create_contact_request_without_captcha(self):
114115
"pydantic_errors": None,
115116
},
116117
]
118+
119+
120+
class TestRequestDemoMutation(TestCase):
121+
class Mutation:
122+
CREATE_DEMO_REQUEST = """
123+
mutation createDemoRequest($data: RequestDemoInput!) {
124+
createDemoRequest(data: $data) {
125+
... on RequestDemoTypeMutationResponseType {
126+
errors
127+
ok
128+
result {
129+
id
130+
name
131+
email
132+
nationalSociety
133+
content
134+
tool{
135+
pk
136+
}
137+
}
138+
}
139+
... on OperationInfo {
140+
__typename
141+
messages {
142+
code
143+
field
144+
kind
145+
message
146+
}
147+
}
148+
}
149+
}
150+
"""
151+
152+
@typing.override
153+
@classmethod
154+
def setUpClass(cls):
155+
super().setUpClass()
156+
cls.user = UserFactory.create(email="test@gmail.com")
157+
cls.tool = ToolFactory.create()
158+
159+
def _create_tool_demo_request_mutation(self, data: dict[str, str | int], **kwargs: typing.Any):
160+
return self.query_check(
161+
query=self.Mutation.CREATE_DEMO_REQUEST,
162+
variables={
163+
"data": data,
164+
},
165+
)
166+
167+
def test_create_tool_demo_request(self):
168+
tool_demo_request_data = {
169+
"name": "John",
170+
"email": "john@test.com",
171+
"nationalSociety": "Test National Society",
172+
"content": "This is test content",
173+
"tool": self.tool.id,
174+
}
175+
176+
content = self._create_tool_demo_request_mutation(data=tool_demo_request_data)
177+
response_data = content["data"]["createDemoRequest"]
178+
179+
assert response_data["ok"] is True
180+
assert response_data["errors"] is None
181+
182+
demo_request = RequestDemo.objects.get(pk=response_data["result"]["id"])
183+
assert response_data["result"] == {
184+
"id": self.gID(demo_request.pk),
185+
"name": demo_request.name,
186+
"email": demo_request.email,
187+
"content": demo_request.content,
188+
"nationalSociety": demo_request.national_society,
189+
"tool": {
190+
"pk": self.gID(demo_request.tool.pk),
191+
},
192+
}

schema.graphql

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ type ContactRequestTypeMutationResponseType {
119119

120120
union CreateContactRequestPayload = ContactRequestTypeMutationResponseType | OperationInfo
121121

122+
union CreateDemoRequestPayload = RequestDemoTypeMutationResponseType | OperationInfo
123+
122124
union CreateUserSubmissionPayload = UserSubmissionTypeMutationResponseType | OperationInfo
123125

124126
"""A generic type to return error messages"""
@@ -133,8 +135,13 @@ type DjangoFileType {
133135
url: String!
134136
}
135137

138+
type DjangoModelType {
139+
pk: ID!
140+
}
141+
136142
type Mutation {
137143
createContactRequest(data: ContactRequestInput!): CreateContactRequestPayload!
144+
createDemoRequest(data: RequestDemoInput!): CreateDemoRequestPayload!
138145
createUserSubmission(data: UserSubmissionInput!): CreateUserSubmissionPayload!
139146
}
140147

@@ -273,6 +280,36 @@ type RecommendationResultTypeOffsetPaginated {
273280
results: [RecommendationResultType!]!
274281
}
275282

283+
"""
284+
Model representing contact where anyone can request the tool demo to the admins.
285+
"""
286+
input RequestDemoInput {
287+
name: String!
288+
email: String!
289+
content: String
290+
nationalSociety: String!
291+
tool: Int!
292+
}
293+
294+
"""
295+
Model representing contact where anyone can request the tool demo to the admins.
296+
"""
297+
type RequestDemoType {
298+
id: ID!
299+
name: String!
300+
email: String!
301+
createdAt: DateTime!
302+
content: String
303+
nationalSociety: String!
304+
tool: DjangoModelType!
305+
}
306+
307+
type RequestDemoTypeMutationResponseType {
308+
ok: Boolean!
309+
errors: CustomErrorType
310+
result: RequestDemoType
311+
}
312+
276313
"""Model representing tool's selection of question and its answer."""
277314
type ToolAnswerType {
278315
id: ID!

0 commit comments

Comments
 (0)