diff --git a/pythonpro/modules/admin.py b/pythonpro/modules/admin.py index 63eb9d9e..d4ec764b 100644 --- a/pythonpro/modules/admin.py +++ b/pythonpro/modules/admin.py @@ -1,7 +1,7 @@ from django.contrib import admin from ordered_model.admin import OrderedModelAdmin -from pythonpro.modules.models import Section, Module, Chapter +from pythonpro.modules.models import Section, Module, Chapter, Topic class ModuleAdmin(OrderedModelAdmin): @@ -16,6 +16,11 @@ class ChapterAdmin(OrderedModelAdmin): list_display = 'title slug section order move_up_down_links'.split() +class TopicAdmin(OrderedModelAdmin): + list_display = 'title slug chapter order move_up_down_links'.split() + + admin.site.register(Module, ModuleAdmin) admin.site.register(Section, SectionAdmin) admin.site.register(Chapter, ChapterAdmin) +admin.site.register(Topic, TopicAdmin) diff --git a/pythonpro/modules/facade.py b/pythonpro/modules/facade.py index dfc0bf1f..de018f88 100644 --- a/pythonpro/modules/facade.py +++ b/pythonpro/modules/facade.py @@ -1,6 +1,6 @@ from django.db.models import Prefetch -from pythonpro.modules.models import Section as _Section, Module as _Module, Chapter as _Chapter +from pythonpro.modules.models import (Section as _Section, Module as _Module, Chapter as _Chapter, Topic as _Topic) def get_all_modules(): @@ -11,7 +11,7 @@ def get_all_modules(): return tuple(_Module.objects.order_by('order')) -def get_module_with_sections_and_chapters(slug): +def get_module_with_contents(slug): """ Search for a module with respective sections and chapters :param slug: module's slug @@ -32,7 +32,7 @@ def get_module_with_sections_and_chapters(slug): ).get() -def get_section_with_module_and_chapters(slug): +def get_section_with_contents(slug): """ Search for a section with respective module and chapters :param slug: section's slug @@ -49,8 +49,24 @@ def get_section_with_module_and_chapters(slug): def get_chapter_with_contents(slug): """ - Search for a chapter respective to slug with it's module and section + Search for a chapter respective to slug with it's module, section and topics :param slug: chapter's slug :return: Chapter """ - return _Chapter.objects.filter(slug=slug).select_related('section').select_related('section__module').get() + return _Chapter.objects.filter(slug=slug).select_related('section').select_related( + 'section__module').prefetch_related( + Prefetch( + 'topic_set', + queryset=_Topic.objects.order_by('order'), + to_attr='topics' + )).get() + + +def get_topic_with_contents(slug): + """ + Search for a topic respective to slug with it's module, section anc chapter + :param slug: topic's slug + :return: Topic + """ + return _Topic.objects.filter(slug=slug).select_related('chapter').select_related('chapter__section').select_related( + 'chapter__section__module').get() diff --git a/pythonpro/modules/fixtures/pythonpro_topics.json b/pythonpro/modules/fixtures/pythonpro_topics.json new file mode 100644 index 00000000..e1dbf704 --- /dev/null +++ b/pythonpro/modules/fixtures/pythonpro_topics.json @@ -0,0 +1 @@ +[{"model": "modules.topic", "pk": 1, "fields": {"order": 0, "title": "Curso Python Pro", "description": "V\u00eddeo explicando como funciona o curso Python Pro.", "slug": "curso-python-pro", "chapter": 1, "vimeo_id": "258681672"}}] \ No newline at end of file diff --git a/pythonpro/modules/migrations/0007_topic.py b/pythonpro/modules/migrations/0007_topic.py new file mode 100644 index 00000000..cfc042f1 --- /dev/null +++ b/pythonpro/modules/migrations/0007_topic.py @@ -0,0 +1,29 @@ +# Generated by Django 2.0.3 on 2018-03-18 16:33 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('modules', '0006_chapter'), + ] + + operations = [ + migrations.CreateModel( + name='Topic', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('order', models.PositiveIntegerField(db_index=True, editable=False)), + ('title', models.CharField(max_length=50)), + ('description', models.TextField()), + ('slug', models.SlugField(unique=True)), + ('vimeo_id', models.CharField(max_length=11)), + ('chapter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='modules.Chapter')), + ], + options={ + 'ordering': ['chapter', 'order'], + }, + ), + ] diff --git a/pythonpro/modules/models.py b/pythonpro/modules/models.py index 469b18e4..5ae27a3b 100644 --- a/pythonpro/modules/models.py +++ b/pythonpro/modules/models.py @@ -83,3 +83,18 @@ def get_absolute_url(self): def parent(self): return self.section + + +class Topic(Content): + chapter = models.ForeignKey('Chapter', on_delete=models.CASCADE) + vimeo_id = models.CharField(max_length=11, db_index=False) + order_with_respect_to = 'chapter' + + class Meta: + ordering = ['chapter', 'order'] + + def get_absolute_url(self): + return reverse('topics:detail', kwargs={'slug': self.slug}) + + def parent(self): + return self.chapter diff --git a/pythonpro/modules/modules_views.py b/pythonpro/modules/modules_views.py index 89275f26..6514b15c 100644 --- a/pythonpro/modules/modules_views.py +++ b/pythonpro/modules/modules_views.py @@ -2,12 +2,12 @@ from django.shortcuts import render # Create your views here. -from pythonpro.modules.facade import get_module_with_sections_and_chapters, get_all_modules +from pythonpro.modules.facade import get_module_with_contents, get_all_modules @login_required def detail(request, slug): - module = get_module_with_sections_and_chapters(slug) + module = get_module_with_contents(slug) return render(request, 'modules/module_detail.html', {'module': module}) diff --git a/pythonpro/modules/sections_views.py b/pythonpro/modules/sections_views.py index 56bd86d4..669292c9 100644 --- a/pythonpro/modules/sections_views.py +++ b/pythonpro/modules/sections_views.py @@ -4,5 +4,5 @@ def detail(request, slug): - ctx = {'section': facade.get_section_with_module_and_chapters(slug=slug)} + ctx = {'section': facade.get_section_with_contents(slug=slug)} return render(request, 'sections/section_detail.html', ctx) diff --git a/pythonpro/modules/templates/chapters/chapter_detail.html b/pythonpro/modules/templates/chapters/chapter_detail.html index de3604d8..1125e76b 100644 --- a/pythonpro/modules/templates/chapters/chapter_detail.html +++ b/pythonpro/modules/templates/chapters/chapter_detail.html @@ -25,6 +25,16 @@

{{ chapter.title }}

  • {{ chapter.description }}
  • +
    Tópicos
    +
    +
      + {% for topic in chapter.topics %} +
    1. {{ topic.title }}
    2. + {% empty %} +
    3. Nenhum Tópico definido ainda
    4. + {% endfor %} +
    +
    diff --git a/pythonpro/modules/templates/topics/topic_detail.html b/pythonpro/modules/templates/topics/topic_detail.html new file mode 100644 index 00000000..29b89a1d --- /dev/null +++ b/pythonpro/modules/templates/topics/topic_detail.html @@ -0,0 +1,30 @@ +{% extends 'core/base.html' %} +{% load static %} + +{% block title %}{{ topic.title }}{% endblock %} + +{% block body %} + +
    +
    +
    +

    {{ topic.title }}

    +

    {{ topic.description }}

    +
    + +
    +
    +
    +
    +{% endblock body %} diff --git a/pythonpro/modules/tests/test_chapters_view.py b/pythonpro/modules/tests/test_chapters_view.py index 9da58d3e..74ff3ec5 100644 --- a/pythonpro/modules/tests/test_chapters_view.py +++ b/pythonpro/modules/tests/test_chapters_view.py @@ -66,10 +66,10 @@ def test_breadcrumb_section(resp, section): resp, f'' ) -# -# -# def test_breadcrumb_current(resp, section): -# dj_assert_contains( -# resp, -# f'' -# ) + + +def test_breadcrumb_current(resp, chapter): + dj_assert_contains( + resp, + f'' + ) diff --git a/pythonpro/modules/tests/test_topics_view.py b/pythonpro/modules/tests/test_topics_view.py new file mode 100644 index 00000000..1f921715 --- /dev/null +++ b/pythonpro/modules/tests/test_topics_view.py @@ -0,0 +1,96 @@ +import pytest +from django.urls import reverse +from model_mommy import mommy + +from pythonpro.django_assertions import dj_assert_contains +from pythonpro.modules.models import Section, Module, Chapter, 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, 2, chapter=chapter) + + +@pytest.fixture +def topic(chapter): + return mommy.make(Topic, chapter=chapter) + + +@pytest.fixture +def resp_chapter(client, django_user_model, chapter, topics): + user = mommy.make(django_user_model) + client.force_login(user) + return client.get(reverse('chapters:detail', kwargs={'slug': chapter.slug})) + + +def test_topic_title_on_chapter(resp_chapter, topics): + for topic in topics: + dj_assert_contains(resp_chapter, topic.title) + + +def test_topic_url_on_section(resp_chapter, topics): + for topic in topics: + dj_assert_contains(resp_chapter, topic.get_absolute_url()) + + +@pytest.fixture +def resp(client, topic, django_user_model): + user = mommy.make(django_user_model) + client.force_login(user) + return client.get(reverse('topics:detail', kwargs={'slug': topic.slug})) + + +def test_status_code(resp): + assert resp.status_code == 200 + + +def test_vimeo_video(resp, topic): + dj_assert_contains(resp, f'