diff --git a/src/events/admin.py b/src/events/admin.py index 79e283794..e9c3b9e3c 100644 --- a/src/events/admin.py +++ b/src/events/admin.py @@ -116,13 +116,25 @@ class EndTimeRangeFilter(EventTimeRangeFilter): field_name = 'end_time' +class EventLocationAdminMixin: + + @admin.display(description=_('location'), ordering='location') + def display_location(self, instance): + value = instance.location + field = instance._meta.get_field('location') + if value and value not in dict(field.flatchoices): + return _('Custom: %(value)s') % {'value': value} + + return instance.get_location_display() + + @admin.register(CustomEvent) -class CustomEventAdmin(ImportExportMixin, admin.ModelAdmin): +class CustomEventAdmin(EventLocationAdminMixin, ImportExportMixin, admin.ModelAdmin): form = CustomEventForm search_fields = ['title'] list_display = [ - 'title', 'begin_time', 'end_time', 'location', 'break_event', + 'title', 'begin_time', 'end_time', 'display_location', 'break_event', 'description', 'link_path', 'get_edit_link', ] list_filter = [ @@ -141,7 +153,7 @@ def get_edit_link(self, instance): @admin.register(KeynoteEvent) -class KeynoteEventAdmin(TranslationAdmin): +class KeynoteEventAdmin(EventLocationAdminMixin, TranslationAdmin): fields = [ 'conference', 'speaker_name', 'speaker_bio', 'speaker_photo', 'session_title', 'session_description', 'session_slides', @@ -150,12 +162,14 @@ class KeynoteEventAdmin(TranslationAdmin): 'location', 'is_remote', ] search_fields = ['speaker_name'] - list_display = ['speaker_name', 'begin_time', 'end_time', 'location', 'is_remote'] + list_display = [ + 'speaker_name', 'begin_time', 'end_time', 'display_location', 'is_remote', + ] list_filter = [BeginTimeRangeFilter, EndTimeRangeFilter, 'location', 'is_remote'] @admin.register(JobListingsEvent) -class JobListingEventAdmin(admin.ModelAdmin): +class JobListingEventAdmin(EventLocationAdminMixin, admin.ModelAdmin): fields = [ 'conference', 'sponsor', @@ -163,35 +177,39 @@ class JobListingEventAdmin(admin.ModelAdmin): ] list_display = [ 'sponsor', - 'begin_time', 'end_time', 'location'] + 'begin_time', 'end_time', 'display_location'] list_filter = [BeginTimeRangeFilter, EndTimeRangeFilter, 'location'] raw_id_fields = ['sponsor'] @admin.register(ProposedTalkEvent) -class ProposedTalkEventAdmin(admin.ModelAdmin): +class ProposedTalkEventAdmin(EventLocationAdminMixin, admin.ModelAdmin): fields = [ 'conference', 'proposal', 'begin_time', 'end_time', 'location', 'is_remote', 'youtube_id' ] - list_display = ['proposal', 'begin_time', 'end_time', 'location', 'is_remote'] + list_display = [ + 'proposal', 'begin_time', 'end_time', 'display_location', 'is_remote', + ] list_filter = [BeginTimeRangeFilter, EndTimeRangeFilter, 'location', 'is_remote'] raw_id_fields = ['proposal'] @admin.register(ProposedTutorialEvent) -class ProposedTutorialEventAdmin(admin.ModelAdmin): +class ProposedTutorialEventAdmin(EventLocationAdminMixin, admin.ModelAdmin): fields = [ 'conference', 'proposal', 'begin_time', 'end_time', 'location', 'is_remote', 'youtube_id' ] - list_display = ['proposal', 'begin_time', 'end_time', 'location', 'is_remote'] + list_display = [ + 'proposal', 'begin_time', 'end_time', 'display_location', 'is_remote', + ] list_filter = [BeginTimeRangeFilter, EndTimeRangeFilter, 'location', 'is_remote'] raw_id_fields = ['proposal'] @admin.register(SponsoredEvent) -class SponsoredEventAdmin(admin.ModelAdmin): +class SponsoredEventAdmin(EventLocationAdminMixin, admin.ModelAdmin): fields = [ 'conference', 'host', 'title', 'slug', 'category', 'language', 'abstract', 'python_level', 'detailed_description', @@ -199,7 +217,7 @@ class SponsoredEventAdmin(admin.ModelAdmin): 'slido_embed_link', 'hackmd_embed_link', 'youtube_id', 'begin_time', 'end_time', 'location', ] - list_display = ['title', 'begin_time', 'end_time', 'location'] + list_display = ['title', 'begin_time', 'end_time', 'display_location'] list_filter = [ BeginTimeRangeFilter, EndTimeRangeFilter, 'location', 'category', 'language', 'python_level', diff --git a/src/events/tests/test_admin.py b/src/events/tests/test_admin.py index 00b0050a3..e68548fa4 100644 --- a/src/events/tests/test_admin.py +++ b/src/events/tests/test_admin.py @@ -2,6 +2,7 @@ from django.contrib.admin import site from django.contrib.admin.views.main import ChangeList from django.contrib.auth.models import AnonymousUser +from django.urls import reverse from django.utils.encoding import force_str from events.admin import ( @@ -57,6 +58,19 @@ def conv_choice(choice): ) +@pytest.mark.django_db +def test_event_admin_displays_custom_location(admin_client): + CustomEvent.objects.create(title='Off-site event', location='lobby') + + response = admin_client.get(reverse('admin:events_customevent_changelist')) + + assert response.status_code == 200 + assert ( + b'Custom: lobby' + in response.content + ) + + @pytest.fixture def time_admin(): return TimeAdmin(Time, site) diff --git a/src/locale/en_US/LC_MESSAGES/django.po b/src/locale/en_US/LC_MESSAGES/django.po index 2ef0f7079..d669d8424 100644 --- a/src/locale/en_US/LC_MESSAGES/django.po +++ b/src/locale/en_US/LC_MESSAGES/django.po @@ -13,7 +13,7 @@ msgid "" msgstr "" "Project-Id-Version: PyCon TW\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-08-16 19:10+0800\n" +"POT-Creation-Date: 2026-08-17 03:14+0800\n" "PO-Revision-Date: 2022-07-11 02:23+0800\n" "Last-Translator: Tom Chen \n" "Language-Team: English (United States) (http://www.transifex.com/pycon-" @@ -49,32 +49,32 @@ msgstr "attendee" msgid "attendees" msgstr "attendees" -#: ccip/views.py:215 +#: ccip/views.py:209 msgctxt "CCIP event type" msgid "event" msgstr "event" -#: ccip/views.py:223 +#: ccip/views.py:217 msgctxt "CCIP event type" msgid "break" msgstr "break" -#: ccip/views.py:231 +#: ccip/views.py:225 msgctxt "CCIP event type" msgid "keynote" msgstr "keynote" -#: ccip/views.py:237 +#: ccip/views.py:231 msgctxt "CCIP event type" msgid "sponsored" msgstr "sponsored" -#: ccip/views.py:245 +#: ccip/views.py:239 msgctxt "CCIP event type" msgid "tutorial" msgstr "tutorial" -#: ccip/views.py:257 +#: ccip/views.py:251 msgctxt "CCIP event type" msgid "talk" msgstr "talk" @@ -234,7 +234,7 @@ msgstr "{model_name_cap} creation requires a request object." msgid "conference" msgstr "conference" -#: core/models.py:93 events/models.py:202 +#: core/models.py:93 events/models.py:181 msgid "title" msgstr "title" @@ -278,7 +278,7 @@ msgstr "slide link" msgid "slido embed link" msgstr "Slido embed link" -#: core/models.py:156 events/models.py:275 +#: core/models.py:156 events/models.py:246 msgid "HackMD embed link" msgstr "HackMD embed link" @@ -319,93 +319,97 @@ msgstr "Tokens" msgid "time value" msgstr "time value" -#: events/admin.py:108 events/models.py:160 +#: events/admin.py:108 events/models.py:161 msgid "begin time" msgstr "begin time" -#: events/admin.py:114 events/models.py:169 +#: events/admin.py:114 events/models.py:170 msgid "end time" msgstr "end time" -#: events/admin.py:120 events/admin.py:155 events/forms.py:11 -#: events/models.py:152 events/models.py:198 +#: events/admin.py:121 events/fields.py:59 events/models.py:153 msgid "location" msgstr "Location" -#: events/admin.py:162 +#: events/admin.py:126 +#, python-format +msgid "Custom: %(value)s" +msgstr "Custom: %(value)s" + +#: events/admin.py:148 #: templates/default/users/_includes/dashboard_proposal_table.html:32 msgid "Edit" msgstr "Edit" -#: events/forms.py:15 +#: events/fields.py:22 msgid "custom location" msgstr "Custom location" -#: events/forms.py:36 +#: events/fields.py:53 msgid "Custom…" msgstr "Custom…" -#: events/forms.py:56 +#: events/fields.py:75 msgid "Enter a custom location." msgstr "Enter a custom location." -#: events/models.py:71 +#: events/models.py:72 msgid "value" msgstr "value" -#: events/models.py:78 +#: events/models.py:79 msgid "time" msgstr "time" -#: events/models.py:79 +#: events/models.py:80 msgid "times" msgstr "times" -#: events/models.py:129 +#: events/models.py:130 msgid "All rooms" msgstr "All rooms" -#: events/models.py:130 +#: events/models.py:131 msgid "R0 (Simulcast)" msgstr "R0 (Simulcast)" -#: events/models.py:131 +#: events/models.py:132 msgid "R0" msgstr "R0" -#: events/models.py:134 +#: events/models.py:135 msgid "R1" msgstr "R1" -#: events/models.py:137 +#: events/models.py:138 msgid "R2" msgstr "R2" -#: events/models.py:140 +#: events/models.py:141 msgid "R3" msgstr "R3" -#: events/models.py:141 +#: events/models.py:142 msgid "R4" msgstr "R4" -#: events/models.py:142 +#: events/models.py:143 msgid "Sprint / Open Space" msgstr "Sprint" -#: events/models.py:143 templates/pycontw-2016/_includes/nav/front_nav.html:46 +#: events/models.py:144 templates/pycontw-2016/_includes/nav/front_nav.html:46 msgid "Tutorial" msgstr "Tutorial" -#: events/models.py:144 +#: events/models.py:145 msgid "Young Inspire / Poster Session" msgstr "Poster Session" -#: events/models.py:206 +#: events/models.py:185 msgid "is break event" msgstr "is break event" -#: events/models.py:209 +#: events/models.py:188 msgid "" "Whether this event is displays as a break. A break can be visually " "distinguished from \"real\" conference sessions, such as keynotes, talks, " @@ -414,71 +418,71 @@ msgstr "" "Whether this event is displays as a break. A break can be visually " "distinguished from “real” conference sessions, such as keynotes, talks, etc." -#: events/models.py:215 +#: events/models.py:194 msgid "event description" msgstr "event description" -#: events/models.py:218 +#: events/models.py:197 msgid "link path" msgstr "link path" -#: events/models.py:223 +#: events/models.py:202 msgid "custom event" msgstr "custom event" -#: events/models.py:224 +#: events/models.py:203 msgid "custom events" msgstr "custom events" -#: events/models.py:241 users/models.py:125 +#: events/models.py:212 users/models.py:125 msgid "speaker name" msgstr "speaker name" -#: events/models.py:246 +#: events/models.py:217 msgid "speaker bio" msgstr "speaker bio" -#: events/models.py:250 +#: events/models.py:221 msgid "speaker photo" msgstr "speaker photo" -#: events/models.py:254 +#: events/models.py:225 msgid "Raster format of the speaker's photo, e.g. PNG, JPEG." msgstr "Raster format of the speaker's photo, e.g. PNG, JPEG." -#: events/models.py:258 +#: events/models.py:229 msgid "keynote session title" msgstr "keynote session title" -#: events/models.py:263 +#: events/models.py:234 msgid "keynote session description" msgstr "keynote session description" -#: events/models.py:267 +#: events/models.py:238 msgid "session slides" msgstr "session slides" -#: events/models.py:271 +#: events/models.py:242 msgid "slido" msgstr "Slido" -#: events/models.py:280 +#: events/models.py:251 msgid "social linkedin" msgstr "Linkedin" -#: events/models.py:284 +#: events/models.py:255 msgid "social twitter" msgstr "Twitter" -#: events/models.py:288 +#: events/models.py:259 msgid "social github" msgstr "Github" -#: events/models.py:292 events/models.py:377 +#: events/models.py:263 events/models.py:348 msgid "slug" msgstr "slug" -#: events/models.py:294 +#: events/models.py:265 #, python-brace-format msgid "" "This is used to link to the speaker's introduction on the Keynote page, e.g. " @@ -487,77 +491,77 @@ msgstr "" "This is used to link to the speaker's introduction on the Keynote page, e.g. " "'liang2' will link to '{link}#keynote-speaker-liang2'." -#: events/models.py:301 events/models.py:420 events/models.py:456 +#: events/models.py:272 events/models.py:391 events/models.py:427 msgid "is remote" msgstr "is remote" -#: events/models.py:305 events/models.py:380 events/models.py:424 -#: events/models.py:460 +#: events/models.py:276 events/models.py:351 events/models.py:395 +#: events/models.py:431 msgid "youtube id" msgstr "youtube id" -#: events/models.py:311 +#: events/models.py:282 msgid "keynote event" msgstr "keynote event" -#: events/models.py:312 +#: events/models.py:283 msgid "keynote events" msgstr "keynote events" -#: events/models.py:315 +#: events/models.py:286 #, python-brace-format msgid "Keynote: {speaker}" msgstr "Keynote: {speaker}" -#: events/models.py:354 sponsors/models.py:107 sponsors/models.py:123 +#: events/models.py:325 sponsors/models.py:107 sponsors/models.py:123 msgid "sponsor" msgstr "sponsor" -#: events/models.py:359 events/models.py:360 +#: events/models.py:330 events/models.py:331 #: templates/pycontw-2020/_includes/menu.html:104 #: templates/pycontw-2020/contents/_default/events/job-listings.html:33 msgid "Job Listings" msgstr "Job Listings" -#: events/models.py:363 +#: events/models.py:334 #, python-brace-format msgid "Open Role of Sponsor: {sponsor}" msgstr "Open Role of Sponsor: {sponsor}" -#: events/models.py:372 +#: events/models.py:343 msgid "host" msgstr "host" -#: events/models.py:386 +#: events/models.py:357 msgid "sponsored event" msgstr "sponsored event" -#: events/models.py:387 +#: events/models.py:358 msgid "sponsored events" msgstr "sponsored events" -#: events/models.py:415 events/models.py:446 proposals/models.py:364 +#: events/models.py:386 events/models.py:417 proposals/models.py:364 #: reviews/models.py:75 reviews/models.py:196 msgid "proposal" msgstr "proposal" -#: events/models.py:432 +#: events/models.py:403 msgid "talk event" msgstr "talk event" -#: events/models.py:433 +#: events/models.py:404 msgid "talk events" msgstr "talk events" -#: events/models.py:451 +#: events/models.py:422 msgid "registration link" msgstr "registration link" -#: events/models.py:468 +#: events/models.py:439 msgid "tutorial event" msgstr "tutorial event" -#: events/models.py:469 +#: events/models.py:440 msgid "tutorial events" msgstr "tutorial events" diff --git a/src/locale/zh_Hant/LC_MESSAGES/django.po b/src/locale/zh_Hant/LC_MESSAGES/django.po index 1d9526f0c..317a83092 100644 --- a/src/locale/zh_Hant/LC_MESSAGES/django.po +++ b/src/locale/zh_Hant/LC_MESSAGES/django.po @@ -20,7 +20,7 @@ msgid "" msgstr "" "Project-Id-Version: PyCon TW\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-08-16 19:10+0800\n" +"POT-Creation-Date: 2026-08-17 03:14+0800\n" "PO-Revision-Date: 2022-07-11 02:25+0800\n" "Last-Translator: Tom Chen \n" "Language-Team: Chinese Traditional (http://www.transifex.com/pycon-taiwan/" @@ -56,32 +56,32 @@ msgstr "會眾" msgid "attendees" msgstr "會眾" -#: ccip/views.py:215 +#: ccip/views.py:209 msgctxt "CCIP event type" msgid "event" msgstr "活動" -#: ccip/views.py:223 +#: ccip/views.py:217 msgctxt "CCIP event type" msgid "break" msgstr "休息" -#: ccip/views.py:231 +#: ccip/views.py:225 msgctxt "CCIP event type" msgid "keynote" msgstr "基調演講" -#: ccip/views.py:237 +#: ccip/views.py:231 msgctxt "CCIP event type" msgid "sponsored" msgstr "贊助" -#: ccip/views.py:245 +#: ccip/views.py:239 msgctxt "CCIP event type" msgid "tutorial" msgstr "專業課程" -#: ccip/views.py:257 +#: ccip/views.py:251 msgctxt "CCIP event type" msgid "talk" msgstr "演講" @@ -241,7 +241,7 @@ msgstr "建立 {model_name_cap} 需要一個 request 物件。" msgid "conference" msgstr "會議" -#: core/models.py:93 events/models.py:202 +#: core/models.py:93 events/models.py:181 msgid "title" msgstr "題目" @@ -285,7 +285,7 @@ msgstr "投影片連結" msgid "slido embed link" msgstr "Slido 嵌入連結" -#: core/models.py:156 events/models.py:275 +#: core/models.py:156 events/models.py:246 msgid "HackMD embed link" msgstr "HackMD 嵌入連結" @@ -326,93 +326,97 @@ msgstr "驗證碼" msgid "time value" msgstr "時間值" -#: events/admin.py:108 events/models.py:160 +#: events/admin.py:108 events/models.py:161 msgid "begin time" msgstr "開始時間" -#: events/admin.py:114 events/models.py:169 +#: events/admin.py:114 events/models.py:170 msgid "end time" msgstr "結束時間" -#: events/admin.py:120 events/admin.py:155 events/forms.py:11 -#: events/models.py:152 events/models.py:198 +#: events/admin.py:121 events/fields.py:59 events/models.py:153 msgid "location" msgstr "地點" -#: events/admin.py:162 +#: events/admin.py:126 +#, python-format +msgid "Custom: %(value)s" +msgstr "自訂:%(value)s" + +#: events/admin.py:148 #: templates/default/users/_includes/dashboard_proposal_table.html:32 msgid "Edit" msgstr "編輯" -#: events/forms.py:15 +#: events/fields.py:22 msgid "custom location" msgstr "自訂地點" -#: events/forms.py:36 +#: events/fields.py:53 msgid "Custom…" msgstr "自訂…" -#: events/forms.py:56 +#: events/fields.py:75 msgid "Enter a custom location." msgstr "請輸入自訂地點。" -#: events/models.py:71 +#: events/models.py:72 msgid "value" msgstr "值" -#: events/models.py:78 +#: events/models.py:79 msgid "time" msgstr "時間" -#: events/models.py:79 +#: events/models.py:80 msgid "times" msgstr "時間" -#: events/models.py:129 +#: events/models.py:130 msgid "All rooms" msgstr "所有會議室" -#: events/models.py:130 +#: events/models.py:131 msgid "R0 (Simulcast)" msgstr "R0 (聯播)" -#: events/models.py:131 +#: events/models.py:132 msgid "R0" msgstr "R0" -#: events/models.py:134 +#: events/models.py:135 msgid "R1" msgstr "R1" -#: events/models.py:137 +#: events/models.py:138 msgid "R2" msgstr "R2" -#: events/models.py:140 +#: events/models.py:141 msgid "R3" msgstr "R3" -#: events/models.py:141 +#: events/models.py:142 msgid "R4" msgstr "R4" -#: events/models.py:142 +#: events/models.py:143 msgid "Sprint / Open Space" msgstr "開放空間" -#: events/models.py:143 templates/pycontw-2016/_includes/nav/front_nav.html:46 +#: events/models.py:144 templates/pycontw-2016/_includes/nav/front_nav.html:46 msgid "Tutorial" msgstr "專業課程" -#: events/models.py:144 +#: events/models.py:145 msgid "Young Inspire / Poster Session" msgstr "海報展" -#: events/models.py:206 +#: events/models.py:185 msgid "is break event" msgstr "休息" -#: events/models.py:209 +#: events/models.py:188 msgid "" "Whether this event is displays as a break. A break can be visually " "distinguished from \"real\" conference sessions, such as keynotes, talks, " @@ -421,71 +425,71 @@ msgstr "" "此活動是否應該被顯示為休息時間。休息時間的顯示會與「真的」會議時段,例如基調" "演講、一般演講不同。" -#: events/models.py:215 +#: events/models.py:194 msgid "event description" msgstr "活動說明" -#: events/models.py:218 +#: events/models.py:197 msgid "link path" msgstr "連結路徑" -#: events/models.py:223 +#: events/models.py:202 msgid "custom event" msgstr "自訂活動" -#: events/models.py:224 +#: events/models.py:203 msgid "custom events" msgstr "自訂活動" -#: events/models.py:241 users/models.py:125 +#: events/models.py:212 users/models.py:125 msgid "speaker name" msgstr "演講者" -#: events/models.py:246 +#: events/models.py:217 msgid "speaker bio" msgstr "演講者介紹" -#: events/models.py:250 +#: events/models.py:221 msgid "speaker photo" msgstr "演講者照片" -#: events/models.py:254 +#: events/models.py:225 msgid "Raster format of the speaker's photo, e.g. PNG, JPEG." msgstr "演講者照片使用點陣格式,例如PNG, JPEG" -#: events/models.py:258 +#: events/models.py:229 msgid "keynote session title" msgstr "演講標題" -#: events/models.py:263 +#: events/models.py:234 msgid "keynote session description" msgstr "演講介紹" -#: events/models.py:267 +#: events/models.py:238 msgid "session slides" msgstr "演講投影片" -#: events/models.py:271 +#: events/models.py:242 msgid "slido" msgstr "Slido" -#: events/models.py:280 +#: events/models.py:251 msgid "social linkedin" msgstr "Linkedin" -#: events/models.py:284 +#: events/models.py:255 msgid "social twitter" msgstr "Twitter" -#: events/models.py:288 +#: events/models.py:259 msgid "social github" msgstr "Github" -#: events/models.py:292 events/models.py:377 +#: events/models.py:263 events/models.py:348 msgid "slug" msgstr "網址文字" -#: events/models.py:294 +#: events/models.py:265 #, python-brace-format msgid "" "This is used to link to the speaker's introduction on the Keynote page, e.g. " @@ -494,77 +498,77 @@ msgstr "" "用來連結至基調演講頁面上的講者簡介,如「liang2」可連結至「{link}#keynote-" "speaker-liang2」。" -#: events/models.py:301 events/models.py:420 events/models.py:456 +#: events/models.py:272 events/models.py:391 events/models.py:427 msgid "is remote" msgstr "遠端演講" -#: events/models.py:305 events/models.py:380 events/models.py:424 -#: events/models.py:460 +#: events/models.py:276 events/models.py:351 events/models.py:395 +#: events/models.py:431 msgid "youtube id" msgstr "youtube id" -#: events/models.py:311 +#: events/models.py:282 msgid "keynote event" msgstr "基調演講" -#: events/models.py:312 +#: events/models.py:283 msgid "keynote events" msgstr "基調演講" -#: events/models.py:315 +#: events/models.py:286 #, python-brace-format msgid "Keynote: {speaker}" msgstr "基調演講:{speaker}" -#: events/models.py:354 sponsors/models.py:107 sponsors/models.py:123 +#: events/models.py:325 sponsors/models.py:107 sponsors/models.py:123 msgid "sponsor" msgstr "贊助" -#: events/models.py:359 events/models.py:360 +#: events/models.py:330 events/models.py:331 #: templates/pycontw-2020/_includes/menu.html:104 #: templates/pycontw-2020/contents/_default/events/job-listings.html:33 msgid "Job Listings" msgstr "徵才資訊" -#: events/models.py:363 +#: events/models.py:334 #, python-brace-format msgid "Open Role of Sponsor: {sponsor}" msgstr "徵才中贊助商: {sponsor}" -#: events/models.py:372 +#: events/models.py:343 msgid "host" msgstr "主持人" -#: events/models.py:386 +#: events/models.py:357 msgid "sponsored event" msgstr "贊助演講" -#: events/models.py:387 +#: events/models.py:358 msgid "sponsored events" msgstr "贊助演講" -#: events/models.py:415 events/models.py:446 proposals/models.py:364 +#: events/models.py:386 events/models.py:417 proposals/models.py:364 #: reviews/models.py:75 reviews/models.py:196 msgid "proposal" msgstr "提案" -#: events/models.py:432 +#: events/models.py:403 msgid "talk event" msgstr "演講" -#: events/models.py:433 +#: events/models.py:404 msgid "talk events" msgstr "演講" -#: events/models.py:451 +#: events/models.py:422 msgid "registration link" msgstr "報名連結" -#: events/models.py:468 +#: events/models.py:439 msgid "tutorial event" msgstr "專業課程" -#: events/models.py:469 +#: events/models.py:440 msgid "tutorial events" msgstr "專業課程"