Skip to content

Commit 048abb7

Browse files
authored
Merge pull request #184 from sandiegopython/davidfischer/meetup-graphql-api
Switch to using Meetup's GraphQL API
2 parents 93cd566 + 9594af6 commit 048abb7

File tree

3 files changed

+139
-182
lines changed

3 files changed

+139
-182
lines changed

pythonsd/tests/__init__.py

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -95,21 +95,24 @@ def setUp(self):
9595

9696
self.expected_events = [
9797
{
98-
"link": "https://www.meetup.com/pythonsd/events/fdzbnqyznbqb/",
99-
"name": "Saturday Study Group",
100-
"datetime": "2019-10-12T12:00:00-07:00",
101-
"venue": "UCSD Geisel Library",
98+
"id": "305930677",
99+
"link": "https://www.meetup.com/pythonsd/events/305930677/",
100+
"name": "SDPy Monthly Meetup",
101+
"datetime": "2025-05-22T19:00:00-07:00",
102+
"venue": "Qualcomm Building Q",
102103
},
103104
{
104-
"link": "https://www.meetup.com/pythonsd/events/fdzbnqyznbzb/",
105-
"name": "Saturday Study Group",
106-
"datetime": "2019-10-19T12:00:00-07:00",
107-
"venue": "UCSD Geisel Library",
105+
"id": "305930676",
106+
"link": "https://www.meetup.com/pythonsd/events/305930676/",
107+
"name": "SDPy Monthly Meetup",
108+
"datetime": "2025-06-26T19:00:00-07:00",
109+
"venue": "Qualcomm Building Q",
108110
},
109111
{
110-
"link": "https://www.meetup.com/pythonsd/events/zgtnxqyznbgc/",
111-
"name": "Monthly Meetup",
112-
"datetime": "2019-10-24T19:00:00-07:00",
112+
"id": "305930675",
113+
"link": "https://www.meetup.com/pythonsd/events/305930675/",
114+
"name": "SDPy Monthly Meetup",
115+
"datetime": "2025-07-24T19:00:00-07:00",
113116
"venue": "Qualcomm Building Q",
114117
},
115118
]
@@ -131,45 +134,56 @@ def test_preloaded_events(self):
131134
return_value=self.expected_events,
132135
) as mock_get:
133136
response = self.client.get(self.url)
134-
self.assertContains(response, "UCSD Geisel Library")
135137
self.assertContains(response, "Qualcomm Building Q")
138+
self.assertContains(response, "SDPy Monthly Meetup")
136139

137140
@responses.activate
138141
def test_html_widget(self):
139142
responses.add(
140-
responses.GET,
143+
responses.POST,
141144
UpcomingEventsView.MEETUP_EVENT_API_URL,
142145
json=self.api_response,
143146
status=200,
144147
)
145148

146149
response = self.client.get(self.url)
147-
self.assertContains(response, "UCSD Geisel Library")
148150
self.assertContains(response, "Qualcomm Building Q")
151+
self.assertContains(response, "SDPy Monthly Meetup")
149152

150153
@responses.activate
151154
def test_api_noevents(self):
152155
responses.add(
153-
responses.GET,
156+
responses.POST,
154157
UpcomingEventsView.MEETUP_EVENT_API_URL,
155-
json=[],
158+
json={"data": {"groupByUrlname": {"events": {"edges": []}}}},
156159
status=400,
157160
)
158161

159162
response = self.client.get(self.url)
160163
self.assertContains(response, "There are no upcoming events")
161164

162165
@responses.activate
163-
def test_api_error(self):
166+
def test_api_exception(self):
164167
responses.add(
165-
responses.GET,
168+
responses.POST,
166169
UpcomingEventsView.MEETUP_EVENT_API_URL,
167170
body=Exception("Error connecting..."),
168171
)
169172

170173
response = self.client.get(self.url)
171174
self.assertContains(response, "There are no upcoming events")
172175

176+
@responses.activate
177+
def test_api_error(self):
178+
responses.add(
179+
responses.POST,
180+
UpcomingEventsView.MEETUP_EVENT_API_URL,
181+
json={"errors": ["There was some error querying meetup.com"]},
182+
)
183+
184+
response = self.client.get(self.url)
185+
self.assertContains(response, "There are no upcoming events")
186+
173187

174188
class TestYouTubeRecentVideosView(test.TestCase):
175189
def setUp(self):
Lines changed: 48 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -1,146 +1,49 @@
1-
[
2-
{
3-
"status": "upcoming",
4-
"local_date": "2019-10-12",
5-
"how_to_find_us": "Parking is free on the weekends in the Hopkins parking structure. We will usually be near Audrey's Cafe. Turn right after entering the library and look near the cafe first, or check the comments below for the table location.",
6-
"group": {
7-
"who": "Python Developers",
8-
"name": "San Diego Python Users Group",
9-
"join_mode": "open",
10-
"country": "us",
11-
"region": "en_US",
12-
"created": 1326433663000,
13-
"lon": -117.19999694824219,
14-
"state": "CA",
15-
"localized_location": "San Diego, CA",
16-
"timezone": "US/Pacific",
17-
"lat": 32.7599983215332,
18-
"urlname": "pythonsd",
19-
"id": 3096872
20-
},
21-
"name": "Saturday Study Group",
22-
"created": 1544308522000,
23-
"member_pay_fee": false,
24-
"venue": {
25-
"city": "San Diego",
26-
"name": "UCSD Geisel Library",
27-
"zip": "",
28-
"repinned": true,
29-
"lon": -117.23896026611328,
30-
"localized_country_name": "USA",
31-
"state": "CA",
32-
"address_1": "Hopkins Parking Structure",
33-
"country": "us",
34-
"lat": 32.883792877197266,
35-
"id": 24990930
36-
},
37-
"updated": 1544308522000,
38-
"visibility": "public",
39-
"date_in_series_pattern": false,
40-
"yes_rsvp_count": 7,
41-
"utc_offset": -25200000,
42-
"local_time": "12:00",
43-
"time": 1570906800000,
44-
"duration": 10800000,
45-
"waitlist_count": 0,
46-
"id": "fdzbnqyznbqb",
47-
"link": "https://www.meetup.com/pythonsd/events/fdzbnqyznbqb/",
48-
"description": "<p>Every Saturday we meet up with the larger Python community (including San Diego PyLadies (<a href=\"http://www.meetup.com/sd-pyladies\" class=\"linkified\">http://www.meetup.com/sd-pyladies</a>), Open BioInformatics San Diego (<a href=\"https://www.meetup.com/Open-Bioinformatics-SanDiego/\" class=\"linkified\">https://www.meetup.com/Open-Bioinformatics-SanDiego/</a>) and San Diego Artificial Intelligence (<a href=\"https://www.meetup.com/SanDiegoAI/\" class=\"linkified\">https://www.meetup.com/SanDiegoAI/</a>)) to chat, learn, and hang out. Our study group is for everyone on their Python journey, whether you're just starting out, an experienced pro, or anywhere in between.</p> <p>There's no formal instruction, just a bunch of folks who like to code. (If you're new to Python, check out these handy resources (<a href=\"http://pythonsd.org/pages/getting-started.html\" class=\"linkified\">http://pythonsd.org/pages/getting-started.html</a>)!)</p> <p>We have a great time, come join us! Bring your laptop and any questions you have about Python. Free feel to show up at any time and stay as long as you want. Hope to see you there!</p> <p>Python Community Code of Conduct (<a href=\"http://www.pythonsd.org/pages/code-of-conduct.html\" class=\"linkified\">http://www.pythonsd.org/pages/code-of-conduct.html</a>): Python is much more than a software language. Python is a vibrant community made up of members from around the globe with a diverse set of skills, personalities, and experiences. It is through these differences that our community experiences great successes and continued growth. Overall, we're good to each other. We contribute to this community not because we have to, but because we want to.</p> "
49-
},
50-
{
51-
"status": "upcoming",
52-
"local_date": "2019-10-19",
53-
"how_to_find_us": "Parking is free on the weekends in the Hopkins parking structure. We will usually be near Audrey's Cafe. Turn right after entering the library and look near the cafe first, or check the comments below for the table location.",
54-
"group": {
55-
"who": "Python Developers",
56-
"name": "San Diego Python Users Group",
57-
"join_mode": "open",
58-
"country": "us",
59-
"region": "en_US",
60-
"created": 1326433663000,
61-
"lon": -117.19999694824219,
62-
"state": "CA",
63-
"localized_location": "San Diego, CA",
64-
"timezone": "US/Pacific",
65-
"lat": 32.7599983215332,
66-
"urlname": "pythonsd",
67-
"id": 3096872
68-
},
69-
"name": "Saturday Study Group",
70-
"created": 1544308522000,
71-
"member_pay_fee": false,
72-
"venue": {
73-
"city": "San Diego",
74-
"name": "UCSD Geisel Library",
75-
"zip": "",
76-
"repinned": true,
77-
"lon": -117.23896026611328,
78-
"localized_country_name": "USA",
79-
"state": "CA",
80-
"address_1": "Hopkins Parking Structure",
81-
"country": "us",
82-
"lat": 32.883792877197266,
83-
"id": 24990930
84-
},
85-
"updated": 1544308522000,
86-
"visibility": "public",
87-
"date_in_series_pattern": false,
88-
"yes_rsvp_count": 7,
89-
"utc_offset": -25200000,
90-
"local_time": "12:00",
91-
"time": 1571511600000,
92-
"duration": 10800000,
93-
"waitlist_count": 0,
94-
"id": "fdzbnqyznbzb",
95-
"link": "https://www.meetup.com/pythonsd/events/fdzbnqyznbzb/",
96-
"description": "<p>Every Saturday we meet up with the larger Python community (including San Diego PyLadies (<a href=\"http://www.meetup.com/sd-pyladies\" class=\"linkified\">http://www.meetup.com/sd-pyladies</a>), Open BioInformatics San Diego (<a href=\"https://www.meetup.com/Open-Bioinformatics-SanDiego/\" class=\"linkified\">https://www.meetup.com/Open-Bioinformatics-SanDiego/</a>) and San Diego Artificial Intelligence (<a href=\"https://www.meetup.com/SanDiegoAI/\" class=\"linkified\">https://www.meetup.com/SanDiegoAI/</a>)) to chat, learn, and hang out. Our study group is for everyone on their Python journey, whether you're just starting out, an experienced pro, or anywhere in between.</p> <p>There's no formal instruction, just a bunch of folks who like to code. (If you're new to Python, check out these handy resources (<a href=\"http://pythonsd.org/pages/getting-started.html\" class=\"linkified\">http://pythonsd.org/pages/getting-started.html</a>)!)</p> <p>We have a great time, come join us! Bring your laptop and any questions you have about Python. Free feel to show up at any time and stay as long as you want. Hope to see you there!</p> <p>Python Community Code of Conduct (<a href=\"http://www.pythonsd.org/pages/code-of-conduct.html\" class=\"linkified\">http://www.pythonsd.org/pages/code-of-conduct.html</a>): Python is much more than a software language. Python is a vibrant community made up of members from around the globe with a diverse set of skills, personalities, and experiences. It is through these differences that our community experiences great successes and continued growth. Overall, we're good to each other. We contribute to this community not because we have to, but because we want to.</p> "
97-
},
98-
{
99-
"status": "upcoming",
100-
"local_date": "2019-10-24",
101-
"how_to_find_us": "We are in the auditorium which is straight back through the cafeteria on the first floor. The doors are locked at 7:15 but there should be a sign posted with number to call to be let in.",
102-
"group": {
103-
"who": "Python Developers",
104-
"name": "San Diego Python Users Group",
105-
"join_mode": "open",
106-
"country": "us",
107-
"region": "en_US",
108-
"created": 1326433663000,
109-
"lon": -117.19999694824219,
110-
"state": "CA",
111-
"localized_location": "San Diego, CA",
112-
"timezone": "US/Pacific",
113-
"lat": 32.7599983215332,
114-
"urlname": "pythonsd",
115-
"id": 3096872
116-
},
117-
"name": "Monthly Meetup",
118-
"created": 1556902700000,
119-
"member_pay_fee": false,
120-
"venue": {
121-
"city": "San Diego",
122-
"name": "Qualcomm Building Q",
123-
"zip": "92121",
124-
"repinned": false,
125-
"lon": -117.20123291015625,
126-
"localized_country_name": "USA",
127-
"state": "CA",
128-
"address_1": "6455 Lusk Blvd",
129-
"country": "us",
130-
"lat": 32.90226745605469,
131-
"id": 26335029
132-
},
133-
"updated": 1556902700000,
134-
"visibility": "public",
135-
"date_in_series_pattern": false,
136-
"yes_rsvp_count": 10,
137-
"utc_offset": -25200000,
138-
"local_time": "19:00",
139-
"time": 1571968800000,
140-
"duration": 7200000,
141-
"waitlist_count": 0,
142-
"id": "zgtnxqyznbgc",
143-
"link": "https://www.meetup.com/pythonsd/events/zgtnxqyznbgc/",
144-
"description": "<p>Thanks to The Qualcomm Learning Center for hosting! This meetup will be in Qualcomm's Building Q auditorium. Cloudflare's Developer Relations will provide pizza!</p> <p>AGENDA</p> <p>We will be doing 5-7 minute lightning talks If you are interested in giving a talk, please post in the comments or get in touch with Diane (<a href=\"https://www.meetup.com/pythonsd/members/9220987/\" class=\"linkified\">https://www.meetup.com/pythonsd/members/9220987/</a>).</p> <p>Currently Scheduled talks:</p> <p>ANNOUNCEMENTS</p> <p>We want your feedback improving San Diego Python! Post it here (<a href=\"https://goo.gl/forms/NmfGY4ReTTPms9cw1\" class=\"linkified\">https://goo.gl/forms/NmfGY4ReTTPms9cw1</a>).</p> <p>San Diego Python has a code of conduct (<a href=\"http://www.pythonsd.org/pages/code-of-conduct.html\" class=\"linkified\">http://www.pythonsd.org/pages/code-of-conduct.html</a>).</p> "
1+
{
2+
"data": {
3+
"groupByUrlname": {
4+
"events": {
5+
"edges": [
6+
{
7+
"node": {
8+
"id": "305930677",
9+
"title": "SDPy Monthly Meetup",
10+
"eventUrl": "https://www.meetup.com/pythonsd/events/305930677/",
11+
"venues": [
12+
{
13+
"name": "Qualcomm Building Q"
14+
}
15+
],
16+
"dateTime": "2025-05-22T19:00:00-07:00"
17+
}
18+
},
19+
{
20+
"node": {
21+
"id": "305930676",
22+
"title": "SDPy Monthly Meetup",
23+
"eventUrl": "https://www.meetup.com/pythonsd/events/305930676/",
24+
"venues": [
25+
{
26+
"name": "Qualcomm Building Q"
27+
}
28+
],
29+
"dateTime": "2025-06-26T19:00:00-07:00"
30+
}
31+
},
32+
{
33+
"node": {
34+
"id": "305930675",
35+
"title": "SDPy Monthly Meetup",
36+
"eventUrl": "https://www.meetup.com/pythonsd/events/305930675/",
37+
"venues": [
38+
{
39+
"name": "Qualcomm Building Q"
40+
}
41+
],
42+
"dateTime": "2025-07-24T19:00:00-07:00"
43+
}
44+
}
45+
]
46+
}
47+
}
14548
}
146-
]
49+
}

pythonsd/views.py

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,11 @@ def get_context_data(self, **kwargs):
3838
class UpcomingEventsView(TemplateView):
3939
"""Get upcoming events from Meetup."""
4040

41-
MEETUP_EVENT_API_URL = "https://api.meetup.com/pythonsd/events"
41+
# https://www.meetup.com/api/guide/
42+
MEETUP_EVENT_API_URL = "https://api.meetup.com/gql-ext"
43+
44+
# https://www.meetup.com/pythonsd/
45+
MEETUP_GROUP_SLUG = "pythonsd"
4246

4347
template_name = "pythonsd/fragments/upcoming-events.html"
4448

@@ -51,35 +55,71 @@ def get_upcoming_events(self):
5155
"""Get upcoming events from Meetup."""
5256
log.debug("Requesting upcoming events from Meetup.com")
5357

54-
# https://www.meetup.com/meetup_api/docs/:urlname/events/
58+
# Fetch the next 3 events from the API
59+
# https://www.meetup.com/api/schema/#Group
60+
# https://www.meetup.com/api/guide/#p02-querying-section
61+
body = """
62+
query($urlname: String!) {
63+
groupByUrlname(urlname: $urlname) {
64+
events(first: 3) {
65+
edges {
66+
node {
67+
id
68+
title
69+
venues {
70+
name
71+
}
72+
eventUrl
73+
dateTime
74+
}
75+
}
76+
}
77+
}
78+
}
79+
"""
80+
5581
try:
56-
resp = requests.get(
57-
self.MEETUP_EVENT_API_URL,
58-
params={"photo-host": "public", "page": "3"},
82+
resp = requests.post(
83+
url=self.MEETUP_EVENT_API_URL,
84+
json={"query": body, "variables": {"urlname": self.MEETUP_GROUP_SLUG}},
5985
timeout=5,
6086
)
6187
except Exception:
6288
log.exception("Request error fetching Meetup event feed")
6389
return []
6490

6591
if resp.ok:
92+
data = resp.json()
93+
94+
if "errors" in data:
95+
log.error("GraphQL error fetching Meetup event feed: %s", data)
96+
return []
97+
6698
# Transform from meetup's API format into our format
67-
events = [
68-
{
69-
"link": e["link"],
70-
"name": e["name"],
71-
# Always show time in local San Diego time
72-
"datetime": datetime.fromtimestamp(
73-
e["time"] // 1000,
74-
tz=zoneinfo.ZoneInfo(key=settings.TIME_ZONE),
75-
),
76-
"venue": e["venue"]["name"] if "venue" in e else None,
77-
}
78-
for e in resp.json()
79-
]
99+
events = []
100+
for edge in resp.json()["data"]["groupByUrlname"]["events"]["edges"]:
101+
event = edge["node"]
102+
events.append(
103+
{
104+
"id": event["id"],
105+
"name": event["title"],
106+
"link": event["eventUrl"],
107+
# Meetup's API seems to return in our timezone
108+
# but we'll be explicit here for future-proofing
109+
"datetime": datetime.fromisoformat(
110+
event["dateTime"]
111+
).astimezone(zoneinfo.ZoneInfo(key=settings.TIME_ZONE)),
112+
# Technically an event can have multiple or no venue (it's an array)
113+
"venue": (
114+
event["venues"][0]["name"] if event["venues"] else None
115+
),
116+
}
117+
)
80118
return events
81119
else:
82-
log.error("Error fetching Meetup event feed")
120+
log.error(
121+
"Error fetching Meetup event feed (status code=%s)", resp.status_code
122+
)
83123

84124
return []
85125

0 commit comments

Comments
 (0)