From 3d40e2a04d01edbb2a046e2474a2afe6f913f8b5 Mon Sep 17 00:00:00 2001 From: Daniel Pyrathon Date: Tue, 13 Jun 2017 19:44:36 -0700 Subject: [PATCH 1/2] Implemented speaker detail page and enchanced model --- pybay/forms.py | 1 + pybay/proposals/admin.py | 5 +- .../migrations/0005_auto_20170614_0036.py | 25 +++++++++ pybay/proposals/models.py | 1 + pybay/templates/frontend/speakers_detail.html | 52 +++++++++++++++++++ pybay/templates/frontend/speakers_list.html | 2 +- pybay/tests/test_views.py | 42 +++++++++++++-- pybay/urls.py | 8 +-- pybay/utils.py | 21 ++++++++ pybay/views.py | 34 ++++++++++-- requirements.txt | 2 +- 11 files changed, 178 insertions(+), 15 deletions(-) create mode 100644 pybay/proposals/migrations/0005_auto_20170614_0036.py create mode 100644 pybay/templates/frontend/speakers_detail.html create mode 100644 pybay/utils.py diff --git a/pybay/forms.py b/pybay/forms.py index dcc1a51a..86db3757 100644 --- a/pybay/forms.py +++ b/pybay/forms.py @@ -113,6 +113,7 @@ def save_to_models(self): meetup_talk=data['meetup_talk'], speaker_and_talk_history=data['speaker_and_talk_history'], talk_links=data['links_to_past_talks'], + speaker_website=data['website'], ) # Email submitter diff --git a/pybay/proposals/admin.py b/pybay/proposals/admin.py index 1fa9c758..5f7c881e 100644 --- a/pybay/proposals/admin.py +++ b/pybay/proposals/admin.py @@ -2,9 +2,10 @@ from .models import TalkProposal, TutorialProposal + class TalkProposalAdmin(admin.ModelAdmin): - list_display = ('title', 'speaker') - pass + list_display = ('title', 'speaker', 'status') + admin.site.register(TalkProposal, TalkProposalAdmin) admin.site.register(TutorialProposal) diff --git a/pybay/proposals/migrations/0005_auto_20170614_0036.py b/pybay/proposals/migrations/0005_auto_20170614_0036.py new file mode 100644 index 00000000..18d70785 --- /dev/null +++ b/pybay/proposals/migrations/0005_auto_20170614_0036.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.2 on 2017-06-14 04:36 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('proposals', '0004_auto_20170516_2218'), + ] + + operations = [ + migrations.AddField( + model_name='talkproposal', + name='speaker_website', + field=models.TextField(null=True), + ), + migrations.AddField( + model_name='tutorialproposal', + name='speaker_website', + field=models.TextField(null=True), + ), + ] diff --git a/pybay/proposals/models.py b/pybay/proposals/models.py index 7058194e..a34fe2f0 100644 --- a/pybay/proposals/models.py +++ b/pybay/proposals/models.py @@ -51,6 +51,7 @@ class Proposal(ProposalBase): what_will_attendees_learn = models.TextField() meetup_talk = models.CharField(choices=MEETUP_CHOICES, max_length=100, default="No") speaker_and_talk_history = models.TextField() + speaker_website = models.TextField(null=True) class Meta: abstract = True diff --git a/pybay/templates/frontend/speakers_detail.html b/pybay/templates/frontend/speakers_detail.html new file mode 100644 index 00000000..60e11d1c --- /dev/null +++ b/pybay/templates/frontend/speakers_detail.html @@ -0,0 +1,52 @@ +{% extends 'frontend/base.html' %} +{% load static %} +{% load thumbnail %} +{% block content %} +
+
+
+ +
+

{{ speaker.name }}

+

{{ speaker.biography_html | safe }}

+ Speaker home page +
+
+
+
+ +{% for talk in talks %} +
+
+
+
+

{{ talk.title }}

+ {{ talk.category | capfirst }}, {{ talk.get_audience_level_display | capfirst }} +
+
+ +
+
+

+

+

Description

+ {{ talk.description }} +

+

+

Abstract

+ {{ talk.abstract }} +

+     Conference Schedule     +
+
+
+
+{% endfor %} + +{% endblock content %} diff --git a/pybay/templates/frontend/speakers_list.html b/pybay/templates/frontend/speakers_list.html index 65ab68c1..1adeb285 100644 --- a/pybay/templates/frontend/speakers_list.html +++ b/pybay/templates/frontend/speakers_list.html @@ -14,7 +14,7 @@

All Speakers

{% for speaker in speakers %}
{{ speaker.name }} -

{{ speaker.name }}

+

{{ speaker.name }}

{{ speaker.biography|truncatechars:"300" }}
{% endfor %} diff --git a/pybay/tests/test_views.py b/pybay/tests/test_views.py index d03372e0..2a5c223e 100644 --- a/pybay/tests/test_views.py +++ b/pybay/tests/test_views.py @@ -1,6 +1,7 @@ from django.test import TestCase from django.core.urlresolvers import reverse -from pybay.proposals.models import Proposal +from pybay.proposals.models import TalkProposal +from pybay.utils import get_accepted_speaker_by_slug from symposion.proposals.models import ProposalKind from symposion.speakers.models import Speaker from symposion.proposals.models import AdditionalSpeaker @@ -8,13 +9,48 @@ from model_mommy.random_gen import gen_image_field from symposion.reviews.models import ProposalResult + +class SpeakersModelTest(TestCase): + + def test_get_slug(self): + self.speaker = mommy.make(Speaker, name="Lorem Ipsum", + photo=gen_image_field()) + self.assertEquals(self.speaker.name_slug, + "lorem-ipsum") + + def test_get_active_speaker_by_slug(self): + s1 = mommy.make(Speaker, name="Lorem Ipsum 1", + photo=gen_image_field()) + s2 = mommy.make(Speaker, name="Lorem Ipsum 2", + photo=gen_image_field()) + s3 = mommy.make(Speaker, name="Lorem Ipsum 3", + photo=gen_image_field()) + + kind = mommy.make(ProposalKind) + p2 = TalkProposal.objects.create( + title='test this title', kind=kind, + speaker=s2, audience_level=1, + ) + mommy.make(ProposalResult, proposal=p2, status='accepted') + self.assertEquals( + get_accepted_speaker_by_slug(s2.name_slug), + s2, + ) + with self.assertRaises(Speaker.DoesNotExist): + get_accepted_speaker_by_slug(s1.name_slug) + + + class SpeakersViewTest(TestCase): def setUp(self): kind = mommy.make(ProposalKind) self.speaker = mommy.make(Speaker, photo=gen_image_field()) - self.proposal = Proposal.objects.create( - title='test this title', kind=kind, speaker=self.speaker) + self.proposal = TalkProposal.objects.create( + title='test this title', kind=kind, + speaker=self.speaker, + audience_level=1, + ) proposal_result = mommy.make(ProposalResult, proposal=self.proposal, status='accepted') diff --git a/pybay/urls.py b/pybay/urls.py index e249a091..67210313 100644 --- a/pybay/urls.py +++ b/pybay/urls.py @@ -15,9 +15,10 @@ from pybay.views import ( pybay_cfp_create, pybay_sponsors_list, pybay_speakers_list, - pybay_faq_index, FaqTemplateView, - undecided_proposals, - proposal_detail, + pybay_faq_index, FaqTemplateView, + undecided_proposals, + proposal_detail, + pybay_speakers_detail, ) @@ -56,6 +57,7 @@ # url(r"^markitup/", include("markitup.urls")), url(r"^our-sponsors/$", pybay_sponsors_list, name="pybay_sponsors_list"), url(r"^our-speakers/$", pybay_speakers_list, name="pybay_speakers_list"), + url(r"^speaker/(?P[-\w]+)/$$", pybay_speakers_detail, name="pybay_speakers_detail"), url(r"^api/undecided_proposals$", undecided_proposals, name="pybay_undecided_proposals"), url(r"^api/proposals/(?P\d+)/$", proposal_detail, name="pybay_detail_proposal"), url(r"^404$", TemplateView.as_view(template_name="404.html")), # Adding explicitly for template dev purposes diff --git a/pybay/utils.py b/pybay/utils.py new file mode 100644 index 00000000..a4522e16 --- /dev/null +++ b/pybay/utils.py @@ -0,0 +1,21 @@ +from django.template.defaultfilters import slugify + +from pybay.proposals.models import TalkProposal +from symposion.speakers.models import Speaker + + +def get_accepted_speaker_by_slug(speaker_slug): + """ + This function is purpously done do avoid touching Symposion + source code. Given the amount of approved speakers is not + substantial, it's better to iterate over speakers, slugify name, + and check equality, than create a new field in Symposion. + """ + approved_talks = TalkProposal.objects.filter( + result__status='accepted' + ).prefetch_related('speaker') + for approved_talk in approved_talks: + if approved_talk.speaker.name_slug == speaker_slug: + return approved_talk.speaker + + raise Speaker.DoesNotExist() diff --git a/pybay/views.py b/pybay/views.py index e37038a4..19845eba 100644 --- a/pybay/views.py +++ b/pybay/views.py @@ -1,17 +1,24 @@ import json from django.shortcuts import render -from django.http import HttpResponse, HttpResponseForbidden +from django.http import (HttpResponse, HttpResponseForbidden, + HttpResponseNotFound) from django.views.generic import TemplateView from .forms import CallForProposalForm from pybay.faqs.models import Faq, Category from symposion.sponsorship.models import Sponsor -from pybay.proposals.models import Proposal, TalkProposal, TutorialProposal +from pybay.proposals.models import TalkProposal, TutorialProposal +from pybay.utils import get_accepted_speaker_by_slug +from symposion.speakers.models import Speaker from collections import defaultdict from django.conf import settings +from logging import getLogger + +log = getLogger(__file__) + cfp_close_date = settings.PROJECT_DATA['cfp_close_date'] @@ -71,9 +78,26 @@ def pybay_cfp_create(request): {'form': form, 'cfp_close_date': cfp_close_date}) +def pybay_speakers_detail(request, speaker_slug): + + # Fetch speaker + try: + speaker = get_accepted_speaker_by_slug(speaker_slug) + except Speaker.DoesNotExist: + log.error("Speaker %s does not have any approved talks or does not exist", speaker_slug) + return HttpResponseNotFound() + + speaker_approved_talks = TalkProposal.objects.filter( + speaker=speaker + ).filter(result__status='accepted') + + return render(request, 'frontend/speakers_detail.html', + {'speaker': speaker, 'talks': speaker_approved_talks, + 'speaker_website': speaker_approved_talks[0].speaker_website}) + + def pybay_speakers_list(request): - accepted_proposals = Proposal.objects.filter( - result__status='accepted') + accepted_proposals = TalkProposal.objects.filter(result__status='accepted') speakers = [] for proposal in accepted_proposals: speakers += list(proposal.speakers()) @@ -86,8 +110,8 @@ def pybay_speakers_list(request): 'speakers': speakers }) -def undecided_proposals(request): +def undecided_proposals(request): api_token = request.GET.get('token') if api_token != settings.PYBAY_API_TOKEN: return HttpResponseForbidden() diff --git a/requirements.txt b/requirements.txt index d9d1d17c..6b34e60a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ django-user-accounts==2.0 Django==1.9.2 easy-thumbnails==2.3 eventlog==0.8.0 -git+https://github.com/pybay/symposion.git#egg=symposion-1.0b2.dev3 +git+https://github.com/pybay/symposion.git#egg=symposion html5lib==0.9999999 Markdown==2.6.5 metron==1.3.5 From 70c4d7d0569c2af74bf2273b9d66abf30fcabee0 Mon Sep 17 00:00:00 2001 From: Daniel Pyrathon Date: Tue, 20 Jun 2017 19:47:39 -0700 Subject: [PATCH 2/2] addressed comments --- pybay/proposals/models.py | 2 +- pybay/templates/frontend/speakers_detail.html | 4 ++-- pybay/urls.py | 2 +- pybay/utils.py | 2 +- pybay/views.py | 3 ++- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pybay/proposals/models.py b/pybay/proposals/models.py index a34fe2f0..5df834c2 100644 --- a/pybay/proposals/models.py +++ b/pybay/proposals/models.py @@ -51,7 +51,7 @@ class Proposal(ProposalBase): what_will_attendees_learn = models.TextField() meetup_talk = models.CharField(choices=MEETUP_CHOICES, max_length=100, default="No") speaker_and_talk_history = models.TextField() - speaker_website = models.TextField(null=True) + speaker_website = models.TextField(null=True, blank=True) class Meta: abstract = True diff --git a/pybay/templates/frontend/speakers_detail.html b/pybay/templates/frontend/speakers_detail.html index 60e11d1c..71155d48 100644 --- a/pybay/templates/frontend/speakers_detail.html +++ b/pybay/templates/frontend/speakers_detail.html @@ -6,7 +6,7 @@ diff --git a/pybay/urls.py b/pybay/urls.py index 67210313..ff7047d1 100644 --- a/pybay/urls.py +++ b/pybay/urls.py @@ -57,7 +57,7 @@ # url(r"^markitup/", include("markitup.urls")), url(r"^our-sponsors/$", pybay_sponsors_list, name="pybay_sponsors_list"), url(r"^our-speakers/$", pybay_speakers_list, name="pybay_speakers_list"), - url(r"^speaker/(?P[-\w]+)/$$", pybay_speakers_detail, name="pybay_speakers_detail"), + url(r"^speaker/(?P[-\w]+)/$", pybay_speakers_detail, name="pybay_speakers_detail"), url(r"^api/undecided_proposals$", undecided_proposals, name="pybay_undecided_proposals"), url(r"^api/proposals/(?P\d+)/$", proposal_detail, name="pybay_detail_proposal"), url(r"^404$", TemplateView.as_view(template_name="404.html")), # Adding explicitly for template dev purposes diff --git a/pybay/utils.py b/pybay/utils.py index a4522e16..b4bf1465 100644 --- a/pybay/utils.py +++ b/pybay/utils.py @@ -6,7 +6,7 @@ def get_accepted_speaker_by_slug(speaker_slug): """ - This function is purpously done do avoid touching Symposion + This function is purposely done do avoid touching Symposion source code. Given the amount of approved speakers is not substantial, it's better to iterate over speakers, slugify name, and check equality, than create a new field in Symposion. diff --git a/pybay/views.py b/pybay/views.py index 19845eba..99b0c05d 100644 --- a/pybay/views.py +++ b/pybay/views.py @@ -8,7 +8,7 @@ from .forms import CallForProposalForm from pybay.faqs.models import Faq, Category from symposion.sponsorship.models import Sponsor -from pybay.proposals.models import TalkProposal, TutorialProposal +from pybay.proposals.models import TalkProposal from pybay.utils import get_accepted_speaker_by_slug from symposion.speakers.models import Speaker @@ -87,6 +87,7 @@ def pybay_speakers_detail(request, speaker_slug): log.error("Speaker %s does not have any approved talks or does not exist", speaker_slug) return HttpResponseNotFound() + # NOTE: Cannot perform reverse lookup (speaker.talk_proposals) for some reason. speaker_approved_talks = TalkProposal.objects.filter( speaker=speaker ).filter(result__status='accepted')