Skip to content

Switch to using Meetup's GraphQL API #184

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 32 additions & 18 deletions pythonsd/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,21 +95,24 @@ def setUp(self):

self.expected_events = [
{
"link": "https://www.meetup.com/pythonsd/events/fdzbnqyznbqb/",
"name": "Saturday Study Group",
"datetime": "2019-10-12T12:00:00-07:00",
"venue": "UCSD Geisel Library",
"id": "305930677",
"link": "https://www.meetup.com/pythonsd/events/305930677/",
"name": "SDPy Monthly Meetup",
"datetime": "2025-05-22T19:00:00-07:00",
"venue": "Qualcomm Building Q",
},
{
"link": "https://www.meetup.com/pythonsd/events/fdzbnqyznbzb/",
"name": "Saturday Study Group",
"datetime": "2019-10-19T12:00:00-07:00",
"venue": "UCSD Geisel Library",
"id": "305930676",
"link": "https://www.meetup.com/pythonsd/events/305930676/",
"name": "SDPy Monthly Meetup",
"datetime": "2025-06-26T19:00:00-07:00",
"venue": "Qualcomm Building Q",
},
{
"link": "https://www.meetup.com/pythonsd/events/zgtnxqyznbgc/",
"name": "Monthly Meetup",
"datetime": "2019-10-24T19:00:00-07:00",
"id": "305930675",
"link": "https://www.meetup.com/pythonsd/events/305930675/",
"name": "SDPy Monthly Meetup",
"datetime": "2025-07-24T19:00:00-07:00",
"venue": "Qualcomm Building Q",
},
]
Expand All @@ -131,45 +134,56 @@ def test_preloaded_events(self):
return_value=self.expected_events,
) as mock_get:
response = self.client.get(self.url)
self.assertContains(response, "UCSD Geisel Library")
self.assertContains(response, "Qualcomm Building Q")
self.assertContains(response, "SDPy Monthly Meetup")

@responses.activate
def test_html_widget(self):
responses.add(
responses.GET,
responses.POST,
UpcomingEventsView.MEETUP_EVENT_API_URL,
json=self.api_response,
status=200,
)

response = self.client.get(self.url)
self.assertContains(response, "UCSD Geisel Library")
self.assertContains(response, "Qualcomm Building Q")
self.assertContains(response, "SDPy Monthly Meetup")

@responses.activate
def test_api_noevents(self):
responses.add(
responses.GET,
responses.POST,
UpcomingEventsView.MEETUP_EVENT_API_URL,
json=[],
json={"data": {"groupByUrlname": {"events": {"edges": []}}}},
status=400,
)

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

@responses.activate
def test_api_error(self):
def test_api_exception(self):
responses.add(
responses.GET,
responses.POST,
UpcomingEventsView.MEETUP_EVENT_API_URL,
body=Exception("Error connecting..."),
)

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

@responses.activate
def test_api_error(self):
responses.add(
responses.POST,
UpcomingEventsView.MEETUP_EVENT_API_URL,
json={"errors": ["There was some error querying meetup.com"]},
)

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


class TestYouTubeRecentVideosView(test.TestCase):
def setUp(self):
Expand Down
193 changes: 48 additions & 145 deletions pythonsd/tests/data/meetup-events-api.json
Original file line number Diff line number Diff line change
@@ -1,146 +1,49 @@
[
{
"status": "upcoming",
"local_date": "2019-10-12",
"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.",
"group": {
"who": "Python Developers",
"name": "San Diego Python Users Group",
"join_mode": "open",
"country": "us",
"region": "en_US",
"created": 1326433663000,
"lon": -117.19999694824219,
"state": "CA",
"localized_location": "San Diego, CA",
"timezone": "US/Pacific",
"lat": 32.7599983215332,
"urlname": "pythonsd",
"id": 3096872
},
"name": "Saturday Study Group",
"created": 1544308522000,
"member_pay_fee": false,
"venue": {
"city": "San Diego",
"name": "UCSD Geisel Library",
"zip": "",
"repinned": true,
"lon": -117.23896026611328,
"localized_country_name": "USA",
"state": "CA",
"address_1": "Hopkins Parking Structure",
"country": "us",
"lat": 32.883792877197266,
"id": 24990930
},
"updated": 1544308522000,
"visibility": "public",
"date_in_series_pattern": false,
"yes_rsvp_count": 7,
"utc_offset": -25200000,
"local_time": "12:00",
"time": 1570906800000,
"duration": 10800000,
"waitlist_count": 0,
"id": "fdzbnqyznbqb",
"link": "https://www.meetup.com/pythonsd/events/fdzbnqyznbqb/",
"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> "
},
{
"status": "upcoming",
"local_date": "2019-10-19",
"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.",
"group": {
"who": "Python Developers",
"name": "San Diego Python Users Group",
"join_mode": "open",
"country": "us",
"region": "en_US",
"created": 1326433663000,
"lon": -117.19999694824219,
"state": "CA",
"localized_location": "San Diego, CA",
"timezone": "US/Pacific",
"lat": 32.7599983215332,
"urlname": "pythonsd",
"id": 3096872
},
"name": "Saturday Study Group",
"created": 1544308522000,
"member_pay_fee": false,
"venue": {
"city": "San Diego",
"name": "UCSD Geisel Library",
"zip": "",
"repinned": true,
"lon": -117.23896026611328,
"localized_country_name": "USA",
"state": "CA",
"address_1": "Hopkins Parking Structure",
"country": "us",
"lat": 32.883792877197266,
"id": 24990930
},
"updated": 1544308522000,
"visibility": "public",
"date_in_series_pattern": false,
"yes_rsvp_count": 7,
"utc_offset": -25200000,
"local_time": "12:00",
"time": 1571511600000,
"duration": 10800000,
"waitlist_count": 0,
"id": "fdzbnqyznbzb",
"link": "https://www.meetup.com/pythonsd/events/fdzbnqyznbzb/",
"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> "
},
{
"status": "upcoming",
"local_date": "2019-10-24",
"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.",
"group": {
"who": "Python Developers",
"name": "San Diego Python Users Group",
"join_mode": "open",
"country": "us",
"region": "en_US",
"created": 1326433663000,
"lon": -117.19999694824219,
"state": "CA",
"localized_location": "San Diego, CA",
"timezone": "US/Pacific",
"lat": 32.7599983215332,
"urlname": "pythonsd",
"id": 3096872
},
"name": "Monthly Meetup",
"created": 1556902700000,
"member_pay_fee": false,
"venue": {
"city": "San Diego",
"name": "Qualcomm Building Q",
"zip": "92121",
"repinned": false,
"lon": -117.20123291015625,
"localized_country_name": "USA",
"state": "CA",
"address_1": "6455 Lusk Blvd",
"country": "us",
"lat": 32.90226745605469,
"id": 26335029
},
"updated": 1556902700000,
"visibility": "public",
"date_in_series_pattern": false,
"yes_rsvp_count": 10,
"utc_offset": -25200000,
"local_time": "19:00",
"time": 1571968800000,
"duration": 7200000,
"waitlist_count": 0,
"id": "zgtnxqyznbgc",
"link": "https://www.meetup.com/pythonsd/events/zgtnxqyznbgc/",
"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> "
{
"data": {
"groupByUrlname": {
"events": {
"edges": [
{
"node": {
"id": "305930677",
"title": "SDPy Monthly Meetup",
"eventUrl": "https://www.meetup.com/pythonsd/events/305930677/",
"venues": [
{
"name": "Qualcomm Building Q"
}
],
"dateTime": "2025-05-22T19:00:00-07:00"
}
},
{
"node": {
"id": "305930676",
"title": "SDPy Monthly Meetup",
"eventUrl": "https://www.meetup.com/pythonsd/events/305930676/",
"venues": [
{
"name": "Qualcomm Building Q"
}
],
"dateTime": "2025-06-26T19:00:00-07:00"
}
},
{
"node": {
"id": "305930675",
"title": "SDPy Monthly Meetup",
"eventUrl": "https://www.meetup.com/pythonsd/events/305930675/",
"venues": [
{
"name": "Qualcomm Building Q"
}
],
"dateTime": "2025-07-24T19:00:00-07:00"
}
}
]
}
}
}
]
}
78 changes: 59 additions & 19 deletions pythonsd/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ def get_context_data(self, **kwargs):
class UpcomingEventsView(TemplateView):
"""Get upcoming events from Meetup."""

MEETUP_EVENT_API_URL = "https://api.meetup.com/pythonsd/events"
# https://www.meetup.com/api/guide/
MEETUP_EVENT_API_URL = "https://api.meetup.com/gql-ext"

# https://www.meetup.com/pythonsd/
MEETUP_GROUP_SLUG = "pythonsd"

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

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

# https://www.meetup.com/meetup_api/docs/:urlname/events/
# Fetch the next 3 events from the API
# https://www.meetup.com/api/schema/#Group
# https://www.meetup.com/api/guide/#p02-querying-section
body = """
query($urlname: String!) {
groupByUrlname(urlname: $urlname) {
events(first: 3) {
edges {
node {
id
title
venues {
name
}
eventUrl
dateTime
}
}
}
}
}
"""

try:
resp = requests.get(
self.MEETUP_EVENT_API_URL,
params={"photo-host": "public", "page": "3"},
resp = requests.post(
url=self.MEETUP_EVENT_API_URL,
json={"query": body, "variables": {"urlname": self.MEETUP_GROUP_SLUG}},
timeout=5,
)
except Exception:
log.exception("Request error fetching Meetup event feed")
return []

if resp.ok:
data = resp.json()

if "errors" in data:
log.error("GraphQL error fetching Meetup event feed: %s", data)
return []

# Transform from meetup's API format into our format
events = [
{
"link": e["link"],
"name": e["name"],
# Always show time in local San Diego time
"datetime": datetime.fromtimestamp(
e["time"] // 1000,
tz=zoneinfo.ZoneInfo(key=settings.TIME_ZONE),
),
"venue": e["venue"]["name"] if "venue" in e else None,
}
for e in resp.json()
]
events = []
for edge in resp.json()["data"]["groupByUrlname"]["events"]["edges"]:
event = edge["node"]
events.append(
{
"id": event["id"],
"name": event["title"],
"link": event["eventUrl"],
# Meetup's API seems to return in our timezone
# but we'll be explicit here for future-proofing
"datetime": datetime.fromisoformat(
event["dateTime"]
).astimezone(zoneinfo.ZoneInfo(key=settings.TIME_ZONE)),
# Technically an event can have multiple or no venue (it's an array)
"venue": (
event["venues"][0]["name"] if event["venues"] else None
),
}
)
return events
else:
log.error("Error fetching Meetup event feed")
log.error(
"Error fetching Meetup event feed (status code=%s)", resp.status_code
)

return []

Expand Down