diff --git a/Pipfile b/Pipfile index 97afa12..2a836c9 100644 --- a/Pipfile +++ b/Pipfile @@ -14,6 +14,7 @@ django-s3-folder-storage = "*" collectfast = "*" django-debug-toolbar = "*" sentry-sdk = {extras = ["django"], version = "*"} +django-ordered-model = "*" [dev-packages] flake8 = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 5a2d86f..1843e2e 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "21b14bf45a001e786035a6f7dccdf42f409fd11ff5b052aa5d76587d1f52a0f1" + "sha256": "d745da49c5e46a28384215e726ae9a3adff66dfb16f71fcc877819ad171a869e" }, "pipfile-spec": 6, "requires": { @@ -26,19 +26,19 @@ }, "boto3": { "hashes": [ - "sha256:6e0ee12e87b37fa81133e9308d0957fce4200c1ff37c96346538dba5e857da18", - "sha256:fcdb84936b04d5f78c8c8667b65bf5b9803cf39fd25bb7fe57ba237074e36171" + "sha256:33cf93f6de5176f1188c923f4de1ae149ed723b89ed12e434f2b2f628491769e", + "sha256:9733ce811bd82feab506ad9309e375a79cabe8c6149061971c17754ce8997551" ], "markers": "python_version >= '3.8'", - "version": "==1.34.82" + "version": "==1.34.83" }, "botocore": { "hashes": [ - "sha256:2fd14676152f9d64541099090cc64973fdf8232744256454de443583e35e497d", - "sha256:8f839e9a88e7ac7185e406be4cf9926673374e8a6ecc295302f56f7e3c618692" + "sha256:0a3fbbe018416aeefa8978454fb0b8129adbaf556647b72269bf02e4bf1f4161", + "sha256:0f302aa76283d4df62b4fbb6d3d20115c1a8957fc02171257fc93904d69d5636" ], "markers": "python_version >= '3.8'", - "version": "==1.34.82" + "version": "==1.34.83" }, "certifi": { "hashes": [ @@ -83,6 +83,14 @@ "markers": "python_version >= '3.8'", "version": "==4.3.0" }, + "django-ordered-model": { + "hashes": [ + "sha256:dfcd3183fe0749dad1c9971cba1d6240ce7328742a30ddc92feca41107bb241d", + "sha256:f258b9762525c00a53009e82f8b8bf2a3aa315e8b453e281e8fdbbfe2b8cb3ba" + ], + "index": "pypi", + "version": "==3.7.4" + }, "django-s3-folder-storage": { "hashes": [ "sha256:eb5fd3994726f5b87ddc0ea1a6f1af3f598f9d743127c27d6dae111d18a79abe" diff --git a/pypro/aperitivos/migrations/0002_alter_video_slug.py b/pypro/aperitivos/migrations/0002_alter_video_slug.py new file mode 100644 index 0000000..06f114b --- /dev/null +++ b/pypro/aperitivos/migrations/0002_alter_video_slug.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.4 on 2024-04-12 13:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('aperitivos', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='video', + name='slug', + field=models.SlugField(max_length=32), + ), + ] diff --git a/pypro/base/templates/base/base.html b/pypro/base/templates/base/base.html index 9d759a4..00f0874 100644 --- a/pypro/base/templates/base/base.html +++ b/pypro/base/templates/base/base.html @@ -31,6 +31,17 @@ diff --git a/pypro/base/tests/test_home.py b/pypro/base/tests/test_home.py index 32f68fb..8561964 100644 --- a/pypro/base/tests/test_home.py +++ b/pypro/base/tests/test_home.py @@ -5,7 +5,7 @@ @pytest.fixture -def resp(client): +def resp(client, db): resp = client.get(reverse('base:home')) return resp diff --git a/pypro/modulos/__init__.py b/pypro/modulos/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pypro/modulos/admin.py b/pypro/modulos/admin.py new file mode 100644 index 0000000..539c993 --- /dev/null +++ b/pypro/modulos/admin.py @@ -0,0 +1,10 @@ +from django.contrib import admin +from ordered_model.admin import OrderedModelAdmin + +from pypro.modulos.models import Modulo + + +@admin.register(Modulo) +class ModuloAdmin(OrderedModelAdmin): + list_display = ('titulo', 'publico', 'move_up_down_links') + prepopulated_fields = {'slug': ('titulo',)} diff --git a/pypro/modulos/apps.py b/pypro/modulos/apps.py new file mode 100644 index 0000000..e5da067 --- /dev/null +++ b/pypro/modulos/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ModulosConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'pypro.modulos' diff --git a/pypro/modulos/context_processors.py b/pypro/modulos/context_processors.py new file mode 100644 index 0000000..f7365c9 --- /dev/null +++ b/pypro/modulos/context_processors.py @@ -0,0 +1,5 @@ +from pypro.modulos import facade + + +def listar_modulos(request): + return {'MODULOS': facade.listar_modulos_ordenados()} diff --git a/pypro/modulos/facade.py b/pypro/modulos/facade.py new file mode 100644 index 0000000..44b7abe --- /dev/null +++ b/pypro/modulos/facade.py @@ -0,0 +1,16 @@ +from typing import List + +from pypro.modulos.models import Modulo + + +def listar_modulos_ordenados() -> List[Modulo]: + """ + Lista módulos ordenados por títulos + :return: + """ + + return list(Modulo.objects.order_by('order').all()) + + +def encontrar_modulo(slug: str) -> Modulo: + return Modulo.objects.get(slug=slug) diff --git a/pypro/modulos/migrations/0001_initial.py b/pypro/modulos/migrations/0001_initial.py new file mode 100644 index 0000000..515ad95 --- /dev/null +++ b/pypro/modulos/migrations/0001_initial.py @@ -0,0 +1,28 @@ +# Generated by Django 5.0.4 on 2024-04-12 13:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Modulo', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('order', models.PositiveIntegerField(db_index=True, editable=False, verbose_name='order')), + ('titulo', models.CharField(max_length=64)), + ('publico', models.TextField()), + ('descricao', models.TextField()), + ], + options={ + 'ordering': ('order',), + 'abstract': False, + }, + ), + ] diff --git a/pypro/modulos/migrations/0002_modulo_slug.py b/pypro/modulos/migrations/0002_modulo_slug.py new file mode 100644 index 0000000..1eed9d6 --- /dev/null +++ b/pypro/modulos/migrations/0002_modulo_slug.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.4 on 2024-04-12 16:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('modulos', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='modulo', + name='slug', + field=models.SlugField(null=True), + ), + ] diff --git a/pypro/modulos/migrations/0003_populando_slug.py b/pypro/modulos/migrations/0003_populando_slug.py new file mode 100644 index 0000000..0c7a021 --- /dev/null +++ b/pypro/modulos/migrations/0003_populando_slug.py @@ -0,0 +1,22 @@ +# Generated by Django 5.0.4 on 2024-04-12 16:55 + +from django.db import migrations +from django.utils.text import slugify + + +def popular_slug(apps, schema_editor): + Modulo = apps.get_model('modulos', 'Modulo') + for modulo in Modulo.objects.all(): + modulo.slug = slugify(modulo.titulo) + modulo.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('modulos', '0002_modulo_slug'), + ] + + operations = [ + migrations.RunPython(popular_slug) + ] diff --git a/pypro/modulos/migrations/0004_slug_unico_e_nao_nulo.py b/pypro/modulos/migrations/0004_slug_unico_e_nao_nulo.py new file mode 100644 index 0000000..5de4088 --- /dev/null +++ b/pypro/modulos/migrations/0004_slug_unico_e_nao_nulo.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.4 on 2024-04-12 17:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('modulos', '0003_populando_slug'), + ] + + operations = [ + migrations.AlterField( + model_name='modulo', + name='slug', + field=models.SlugField(unique=True), + ), + ] diff --git a/pypro/modulos/migrations/__init__.py b/pypro/modulos/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pypro/modulos/models.py b/pypro/modulos/models.py new file mode 100644 index 0000000..ce26ac6 --- /dev/null +++ b/pypro/modulos/models.py @@ -0,0 +1,19 @@ +from django.db import models +from django.urls import reverse +from ordered_model.models import OrderedModel + + +class Modulo(OrderedModel): + titulo = models.CharField(max_length=64) + publico = models.TextField() + descricao = models.TextField() + slug = models.SlugField(unique=True) + + class Meta(OrderedModel.Meta): + pass + + def __str__(self): + return self.titulo + + def get_absolute_url(self): + return reverse("modulos:detalhe", kwargs={'slug': self.slug}) diff --git a/pypro/modulos/templates/modulos/modulo_detalhe.html b/pypro/modulos/templates/modulos/modulo_detalhe.html new file mode 100644 index 0000000..b795fc5 --- /dev/null +++ b/pypro/modulos/templates/modulos/modulo_detalhe.html @@ -0,0 +1,18 @@ +{% extends 'base/base.html' %} +{% block title %}{{ modulo.titulo }}{% endblock title %} +{% block description %}Página com detalhes do módulo {{ modulo.titulo }}{% endblock description %} +{% block body %} +
+
+
+

{{ modulo.titulo }}

+
+
Público
+
{{ modulo.publico }}
+
Descrição
+
{{ modulo.descricao }}
+
+
+
+
+{% endblock body %} \ No newline at end of file diff --git a/pypro/modulos/tests/test_aba_de_modulos.py b/pypro/modulos/tests/test_aba_de_modulos.py new file mode 100644 index 0000000..500d19d --- /dev/null +++ b/pypro/modulos/tests/test_aba_de_modulos.py @@ -0,0 +1,27 @@ +from django.urls import reverse +import pytest +from pypro.django_assertions import assert_contains +from model_bakery import baker + +from pypro.modulos.models import Modulo + + +@pytest.fixture +def modulos(db): + return baker.make(Modulo, 2) + + +@pytest.fixture +def resp(client, modulos): + resp = client.get(reverse('base:home')) + return resp + + +def test_titulos_dos_modulos(resp, modulos): + for modulo in modulos: + assert_contains(resp, modulo.titulo) + + +def test_link_dos_modulos(resp, modulos): + for modulo in modulos: + assert_contains(resp, modulo.get_absolute_url()) diff --git a/pypro/modulos/tests/test_facade.py b/pypro/modulos/tests/test_facade.py new file mode 100644 index 0000000..4fc85a3 --- /dev/null +++ b/pypro/modulos/tests/test_facade.py @@ -0,0 +1,14 @@ +import pytest +from model_bakery import baker + +from pypro.modulos import facade +from pypro.modulos.models import Modulo + + +@pytest.fixture +def modulos(db): + return [baker.make(Modulo, titulo=s) for s in 'Antes Depois'.split()] + + +def test_listar_modulos_ordenados(modulos): + assert list(sorted(modulos, key=lambda modulo: modulo.order)) == facade.listar_modulos_ordenados() diff --git a/pypro/modulos/tests/test_modulo_detalhe.py b/pypro/modulos/tests/test_modulo_detalhe.py new file mode 100644 index 0000000..47b2a10 --- /dev/null +++ b/pypro/modulos/tests/test_modulo_detalhe.py @@ -0,0 +1,29 @@ +from django.urls import reverse +import pytest +from pypro.django_assertions import assert_contains +from model_bakery import baker + +from pypro.modulos.models import Modulo + + +@pytest.fixture +def modulo(db): + return baker.make(Modulo) + + +@pytest.fixture +def resp(client, modulo): + resp = client.get(reverse('modulos:detalhe', kwargs={'slug': modulo.slug})) + return resp + + +def test_titulo(resp, modulo: Modulo): + assert_contains(resp, modulo.titulo) + + +def test_descricao(resp, modulo: Modulo): + assert_contains(resp, modulo.descricao) + + +def test_publico(resp, modulo: Modulo): + assert_contains(resp, modulo.publico) diff --git a/pypro/modulos/urls.py b/pypro/modulos/urls.py new file mode 100644 index 0000000..57b816f --- /dev/null +++ b/pypro/modulos/urls.py @@ -0,0 +1,9 @@ +from django.urls import path + +from pypro.modulos import views + + +app_name = 'modulos' +urlpatterns = [ + path('', views.detalhe, name='detalhe'), +] diff --git a/pypro/modulos/views.py b/pypro/modulos/views.py new file mode 100644 index 0000000..a3bda63 --- /dev/null +++ b/pypro/modulos/views.py @@ -0,0 +1,8 @@ +from django.shortcuts import render + +from pypro.modulos import facade + + +def detalhe(request, slug): + modulo = facade.encontrar_modulo(slug) + return render(request, 'modulos/modulo_detalhe.html', {'modulo': modulo}) diff --git a/pypro/settings.py b/pypro/settings.py index 158530f..4dd0fd6 100644 --- a/pypro/settings.py +++ b/pypro/settings.py @@ -46,6 +46,8 @@ 'django.contrib.staticfiles', 'pypro.base', 'pypro.aperitivos', + 'pypro.modulos', + 'ordered_model', ] MIDDLEWARE = [ @@ -71,6 +73,7 @@ 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', + 'pypro.modulos.context_processors.listar_modulos', ], }, }, diff --git a/pypro/urls.py b/pypro/urls.py index a6b955a..46380d2 100644 --- a/pypro/urls.py +++ b/pypro/urls.py @@ -22,6 +22,7 @@ path('admin/', admin.site.urls), path('', include('pypro.base.urls')), path('aperitivos/', include('pypro.aperitivos.urls')), + path('modulos/', include('pypro.modulos.urls')), ]