Skip to content
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
2 changes: 1 addition & 1 deletion Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions pythonpro/modules/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class Content(OrderedModel):
description = models.TextField()
slug = models.SlugField(unique=True)
_next_content_cache = _NoneCache
_previous_content_cache = _NoneCache

class Meta:
abstract = True
Expand Down Expand Up @@ -55,6 +56,24 @@ def _next_content_query_set(self):
"""Must provide a query set for next content"""
raise NotImplementedError()

def previous_content(self):
if self._previous_content_cache is not _NoneCache:
return self._previous_content_cache
previous = self
while previous is not None:
try:
self._previous_content_cache = previous._previous_content_query_set().get()
break
except ObjectDoesNotExist:
previous = previous.parent()
else:
self._previous_content_cache = None
return self._previous_content_cache

def _previous_content_query_set(self):
"""Must provide a query set for previous content"""
raise NotImplementedError()

def module_slug(self):
if self.parent() is None:
return self.slug
Expand Down Expand Up @@ -95,6 +114,9 @@ def get_absolute_url(self):
def _next_content_query_set(self):
return Module.objects.filter(order=self.order + 1)

def _previous_content_query_set(self):
return Module.objects.filter(order=self.order - 1)


class Section(Content):
module = models.ForeignKey('Module', on_delete=models.CASCADE)
Expand All @@ -112,6 +134,9 @@ def parent(self):
def _next_content_query_set(self):
return Section.objects.filter(module=self.module, order=self.order + 1)

def _previous_content_query_set(self):
return Section.objects.filter(module=self.module, order=self.order - 1)


class Chapter(Content):
section = models.ForeignKey('Section', on_delete=models.CASCADE)
Expand All @@ -129,6 +154,9 @@ def parent(self):
def _next_content_query_set(self):
return Chapter.objects.filter(section=self.section, order=self.order + 1)

def _previous_content_query_set(self):
return Chapter.objects.filter(section=self.section, order=self.order - 1)


class Topic(Content):
chapter = models.ForeignKey('Chapter', on_delete=models.CASCADE)
Expand All @@ -147,3 +175,6 @@ def parent(self):

def _next_content_query_set(self):
return Topic.objects.filter(chapter=self.chapter, order=self.order + 1)

def _previous_content_query_set(self):
return Topic.objects.filter(chapter=self.chapter, order=self.order - 1)
15 changes: 12 additions & 3 deletions pythonpro/modules/templates/topics/topic_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,22 @@ <h1 class="mt-4 mb-3">{{ topic.title }}</h1>
frameborder="0"
webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
</div>
<div class="row mt-5 mb-5">
{% if topic.previous_content %}
<div class="col-md text-md-left text-center">
<a class="btn btn-success" href="{{ topic.previous_content.get_absolute_url }}">
&laquo; <strong>{{ topic.previous_content.title }}</strong>
</a>
</div>
{% endif %}
{% if topic.next_content %}
<div class="mt-5 mb-5" style="float: right">
<a class="btn btn-success" href="{{ topic.next_content.get_absolute_url }}">
Próximo Conteúdo: <strong>{{ topic.next_content.title }}</strong> &raquo;
<div class="col-md text-md-right text-center mt-md-0 mt-2">
<a class="btn btn-primary" href="{{ topic.next_content.get_absolute_url }}">
<strong>{{ topic.next_content.title }}</strong> &raquo;
</a>
</div>
{% endif %}
</div>
<div id='discourse-comments'></div>
<script type="text/javascript">
DiscourseEmbed = {
Expand Down
95 changes: 95 additions & 0 deletions pythonpro/modules/tests/test_previous_content.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import pytest
from django.urls import reverse
from model_mommy import mommy

from pythonpro.django_assertions import dj_assert_contains, dj_assert_not_contains
from pythonpro.modules.models import Chapter, Module, Section, Topic


@pytest.fixture
def module(db):
return mommy.make(Module)


@pytest.fixture
def section(module):
return mommy.make(Section, module=module)


@pytest.fixture
def chapter(section):
return mommy.make(Chapter, section=section)


@pytest.fixture
def topics(chapter):
return [mommy.make(Topic, chapter=chapter, order=i) for i in range(2)]


@pytest.fixture
def resp(client_with_member, django_user_model, topics):
next_topic = topics[1]
return client_with_member.get(
reverse('modules:topic_detail',
kwargs={'module_slug': next_topic.module_slug(), 'topic_slug': next_topic.slug}),
secure=True)


@pytest.fixture
def resp_first_topic(client_with_member, topics, logged_user):
first_topic = topics[0]
yield client_with_member.get(
reverse(
'modules:topic_detail', kwargs={'module_slug': first_topic.module_slug(), 'topic_slug': first_topic.slug}
),
secure=True
)


def test_topic_with_previous_topic(resp, topics):
previous_topic, _ = topics
dj_assert_contains(resp, previous_topic.get_absolute_url())


def test_first_topic(resp_first_topic):
dj_assert_not_contains(resp_first_topic, 'Conteúdo Anterior')


def test_first_topic_previous_chapter(section):
"""Assert previous Chapter as previous content for the first Topic"""
previous_chapter, next_chapter = [mommy.make(Chapter, section=section, order=order) for order in range(2)]
topic = mommy.make(Topic, chapter=next_chapter)
assert previous_chapter == topic.previous_content()


def test_topic_previous_section(module):
"""Assert previous Section as previous content for the first Topic"""
previous_section, next_section = [mommy.make(Section, module=module, order=order) for order in range(2)]
chapter = mommy.make(Chapter, section=next_section)
topic = mommy.make(Topic, chapter=chapter)
assert previous_section == topic.previous_content()


@pytest.mark.django_db
def test_topic_previous_module():
"""Assert previous Module as previous content for the first Topic"""
previous_module, next_module = [mommy.make(Module, order=order) for order in range(2)]
section = mommy.make(Section, module=next_module)
chapter = mommy.make(Chapter, section=section)
topic = mommy.make(Topic, chapter=chapter)
assert previous_module == topic.previous_content()


def test_previous_topic_first_none(chapter):
"""Assert None as previous content for the First Topic of First Chapter of First Section of First Module"""
topic = mommy.make(Topic, chapter=chapter)
assert topic.previous_content() is None


def test_cache(chapter, mocker):
"""Assert cache is used when calling previous content multiple times"""
topic = mommy.make(Topic, chapter=chapter)
mocker.spy(topic, '_previous_content_query_set')
for _ in range(3):
topic.previous_content()
assert topic._previous_content_query_set.call_count == 1