diff --git a/Dockerfile b/Dockerfile index 8176851..11cf538 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,4 +27,4 @@ WORKDIR /src # root is used as a hotfix for package introspection problem # https://intellij-support.jetbrains.com/hc/en-us/community/posts/115000373944/comments/7286554132370 -USER root +USER root \ No newline at end of file diff --git a/Makefile b/Makefile index ef96780..8bfb7b3 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ lint: # запуск автоматических тестов test: - docker compose run app ./manage.py test + docker compose run app python manage.py test # запуск всех функций поддержки качества кода -all: format lint test +all: format lint test docs-html diff --git a/docs/source/code.rst b/docs/source/code.rst index 9dc7ee3..58558db 100644 --- a/docs/source/code.rst +++ b/docs/source/code.rst @@ -5,3 +5,4 @@ portfolio jobs blog + author diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/author/__init__.py b/src/author/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/author/admin.py b/src/author/admin.py new file mode 100644 index 0000000..a1fa90a --- /dev/null +++ b/src/author/admin.py @@ -0,0 +1,22 @@ +# Register your models here. +from django.contrib import admin + +from author.models import Author + + +@admin.register(Author) +class AuthorAdmin(admin.ModelAdmin): + list_display = ( + "resume_url", + "github_url", + "email", + "created_at", + "updated_at", + ) + + search_fields = ("email",) + + list_filter = ( + "created_at", + "updated_at", + ) diff --git a/src/author/apps.py b/src/author/apps.py new file mode 100644 index 0000000..ed859f9 --- /dev/null +++ b/src/author/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class AuthorConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "author" + verbose_name = "Автор" diff --git a/src/author/migrations/0001_initial.py b/src/author/migrations/0001_initial.py new file mode 100644 index 0000000..b00b03c --- /dev/null +++ b/src/author/migrations/0001_initial.py @@ -0,0 +1,61 @@ +# Generated by Django 4.1.7 on 2023-02-25 18:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="Author", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "created_at", + models.DateTimeField( + auto_now_add=True, verbose_name="Время создания записи" + ), + ), + ( + "updated_at", + models.DateTimeField( + auto_now=True, verbose_name="Время обновления записи" + ), + ), + ( + "resume_url", + models.URLField( + help_text="Ссылка на резюме автора", verbose_name="Резюме" + ), + ), + ( + "github_url", + models.URLField( + help_text="Ссылка на гитхаб автора", verbose_name="GitHub" + ), + ), + ( + "email", + models.EmailField( + help_text="Почта автора", max_length=254, verbose_name="Почта" + ), + ), + ], + options={ + "verbose_name": "Данные об авторе", + "verbose_name_plural": "Данные об авторе", + }, + ), + ] diff --git a/src/author/migrations/__init__.py b/src/author/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/author/models.py b/src/author/models.py new file mode 100644 index 0000000..3ae92a9 --- /dev/null +++ b/src/author/models.py @@ -0,0 +1,32 @@ +# Create your models here. + +from django.db import models + +from base.models import TimeStampMixin + + +class Author(TimeStampMixin): + """ + Модель для хранения данных об авторе. + """ + + resume_url = models.URLField( + verbose_name="Резюме", + help_text="Ссылка на резюме автора", + ) + github_url = models.URLField( + verbose_name="GitHub", + help_text="Ссылка на гитхаб автора", + ) + + email = models.EmailField( + verbose_name="Почта", + help_text="Почта автора", + ) + + class Meta: + verbose_name = "Данные об авторе" + verbose_name_plural = "Данные об авторе" + + def __str__(self) -> str: + return f'Объект "Автор" (id={self.pk})' diff --git a/src/author/templates/author/author_detail.html b/src/author/templates/author/author_detail.html new file mode 100644 index 0000000..14737b2 --- /dev/null +++ b/src/author/templates/author/author_detail.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} + +{% load static %} + +{% block author_content %} + +{% endblock %} \ No newline at end of file diff --git a/src/author/templates/author/author_list.html b/src/author/templates/author/author_list.html new file mode 100644 index 0000000..f1a803d --- /dev/null +++ b/src/author/templates/author/author_list.html @@ -0,0 +1,13 @@ +{% extends 'base.html' %} + +{% load static %} + +{% block author_content %} + {% for author in object_list %} + + {% endfor %} +{% endblock %} diff --git a/src/author/tests.py b/src/author/tests.py new file mode 100644 index 0000000..a39e78b --- /dev/null +++ b/src/author/tests.py @@ -0,0 +1,32 @@ +from django.test import TestCase + +from author.models import Author + + +class JobTestCase(TestCase): + """ + Тестирование функций работы + """ + + def setUp(self) -> None: + """ + Настройка перед тестированием. + :return: + """ + + Author.objects.create( + resume_url="https://test.test", + github_url="https://test.test", + email="test@test.test", + ) + + def test_job_messages_creation(self) -> None: + """ + Тестирование моделей работы. + :return: + """ + + author = Author.objects.get(resume_url="https://test.test") + self.assertEqual(author.github_url, "https://test.test") + self.assertEqual(author.email, "test@test.test") + self.assertEqual(str(author), f'Объект "Автор" (id={author.pk})') diff --git a/src/author/urls.py b/src/author/urls.py new file mode 100644 index 0000000..dbc2a52 --- /dev/null +++ b/src/author/urls.py @@ -0,0 +1,9 @@ +from django.urls import path + +from author.views import AutorListView +from author.views import AutorDetailView + +urlpatterns = [ + path("", AutorListView.as_view(), name="authors"), + path("/", AutorDetailView.as_view(), name="author"), +] diff --git a/src/author/views.py b/src/author/views.py new file mode 100644 index 0000000..650808a --- /dev/null +++ b/src/author/views.py @@ -0,0 +1,12 @@ +# Create your views here. +from django.views.generic import DetailView, ListView + +from author.models import Author + + +class AuthorListView(ListView): + model = Author + + +class AuthorDetailView(DetailView): + model = Author diff --git a/src/blog/migrations/0001_initial.py b/src/blog/migrations/0001_initial.py index 3f830e8..d385322 100644 --- a/src/blog/migrations/0001_initial.py +++ b/src/blog/migrations/0001_initial.py @@ -1,6 +1,6 @@ -# Generated by Django 4.1.1 on 2022-09-20 15:45 -from typing import List +# Generated by Django 4.1.7 on 2023-02-25 12:45 +import ckeditor_uploader.fields from django.db import migrations, models @@ -8,7 +8,7 @@ class Migration(migrations.Migration): initial = True - dependencies: List = [] + dependencies = [] operations = [ migrations.CreateModel( @@ -36,7 +36,12 @@ class Migration(migrations.Migration): ), ), ("title", models.CharField(max_length=255, verbose_name="Заголовок")), - ("content", models.TextField(verbose_name="Содержимое сообщения")), + ( + "content", + ckeditor_uploader.fields.RichTextUploadingField( + verbose_name="Содержимое сообщения" + ), + ), ( "image", models.ImageField(upload_to="images/", verbose_name="Изображение"), @@ -47,7 +52,8 @@ class Migration(migrations.Migration): ), ], options={ - "abstract": False, + "verbose_name": "Сообщение блога", + "verbose_name_plural": "Сообщения блога", }, ), ] diff --git a/src/blog/migrations/0002_alter_blog_options_alter_blog_content.py b/src/blog/migrations/0002_alter_blog_options_alter_blog_content.py deleted file mode 100644 index aab0535..0000000 --- a/src/blog/migrations/0002_alter_blog_options_alter_blog_content.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 4.1.1 on 2022-09-26 13:57 - -import ckeditor_uploader.fields -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ("blog", "0001_initial"), - ] - - operations = [ - migrations.AlterModelOptions( - name="blog", - options={ - "verbose_name": "Сообщение блога", - "verbose_name_plural": "Сообщения блога", - }, - ), - migrations.AlterField( - model_name="blog", - name="content", - field=ckeditor_uploader.fields.RichTextUploadingField( - verbose_name="Содержимое сообщения" - ), - ), - ] diff --git a/src/jobs/admin.py b/src/jobs/admin.py index 2eec2ff..fb4e336 100644 --- a/src/jobs/admin.py +++ b/src/jobs/admin.py @@ -16,7 +16,7 @@ class JobAdmin(admin.ModelAdmin): "updated_at", ) - search_fields = ("description",) + search_fields = ("description", "content") list_filter = ( "created_at", diff --git a/src/jobs/migrations/0001_initial.py b/src/jobs/migrations/0001_initial.py index 78f13cc..9820603 100644 --- a/src/jobs/migrations/0001_initial.py +++ b/src/jobs/migrations/0001_initial.py @@ -1,6 +1,6 @@ -# Generated by Django 4.1.1 on 2022-09-20 15:45 -from typing import List +# Generated by Django 4.1.7 on 2023-02-25 12:45 +import ckeditor_uploader.fields from django.db import migrations, models @@ -8,7 +8,7 @@ class Migration(migrations.Migration): initial = True - dependencies: List = [] + dependencies: list = [] operations = [ migrations.CreateModel( @@ -51,6 +51,12 @@ class Migration(migrations.Migration): verbose_name="Описание", ), ), + ( + "content", + ckeditor_uploader.fields.RichTextUploadingField( + verbose_name="Подробное описание" + ), + ), ], options={ "verbose_name": "Выполненная работа", diff --git a/src/jobs/models.py b/src/jobs/models.py index a5525d1..ee75694 100644 --- a/src/jobs/models.py +++ b/src/jobs/models.py @@ -1,7 +1,7 @@ """ Модели для приложения "Jobs" (выполненные работы). """ - +from ckeditor_uploader.fields import RichTextUploadingField from django.db import models from base.models import TimeStampMixin @@ -22,6 +22,9 @@ class Job(TimeStampMixin): verbose_name="Описание", help_text="Краткое описание выполненной работы", ) + content = RichTextUploadingField( + verbose_name="Подробное описание", + ) class Meta: verbose_name = "Выполненная работа" @@ -29,3 +32,11 @@ class Meta: def __str__(self) -> str: return f'Объект "Выполненная работа" (id={self.pk})' + + def summary(self) -> str: + """ + Краткое содержание описания работы. + + :return: + """ + return self.content[:100] + "..." diff --git a/src/jobs/templates/jobs/job_detail.html b/src/jobs/templates/jobs/job_detail.html index 6b40e3f..a6b1911 100644 --- a/src/jobs/templates/jobs/job_detail.html +++ b/src/jobs/templates/jobs/job_detail.html @@ -3,11 +3,11 @@ {% block content %}
-

{{ blog.title }}

-

{{ blog.pub_date }}

-

{{ blog.title }}

-

{{ blog.content }}

- Вернуться в блог +

{{ job.title }}

+

{{ job.pub_date }}

+

{{ job.description }}

+ {{ job.content|safe }} + На главную
{% endblock %} diff --git a/src/jobs/templates/jobs/job_list.html b/src/jobs/templates/jobs/job_list.html index 5765bc2..9b69ce6 100644 --- a/src/jobs/templates/jobs/job_list.html +++ b/src/jobs/templates/jobs/job_list.html @@ -34,9 +34,10 @@

{{ job.description }}

+

{{ job.summary|safe }}

{{ job.created_at|date:"d E Y" }}

diff --git a/src/jobs/tests.py b/src/jobs/tests.py index e69de29..b24da08 100644 --- a/src/jobs/tests.py +++ b/src/jobs/tests.py @@ -0,0 +1,29 @@ +from django.test import TestCase + +from jobs.models import Job + + +class JobTestCase(TestCase): + """ + Тестирование функций работы + """ + + def setUp(self) -> None: + """ + Настройка перед тестированием. + :return: + """ + + Job.objects.create(image="test", description="test", content="test" * 100) + + def test_job_messages_creation(self) -> None: + """ + Тестирование моделей работы. + :return: + """ + + job = Job.objects.get(description="test") + + content = "test" * 100 + self.assertEqual(job.summary(), content[:100] + "...") + self.assertEqual(str(job), f'Объект "Выполненная работа" (id={job.pk})') diff --git a/src/jobs/urls.py b/src/jobs/urls.py new file mode 100644 index 0000000..10af16e --- /dev/null +++ b/src/jobs/urls.py @@ -0,0 +1,12 @@ +from django.urls import path + +from jobs.views import IndexJobsListView +from jobs.views import JobDetailView + +urlpatterns = [ + path("", IndexJobsListView.as_view(), name="jobs"), + path("/", JobDetailView.as_view(), name="job"), + # path('/', views.DetailView.as_view(), name='detail'), + # path('/results/', views.ResultsView.as_view(), name='results'), + # path('/vote/', views.vote, name='vote'), +] diff --git a/src/jobs/views.py b/src/jobs/views.py index 17fb444..a1f4330 100644 --- a/src/jobs/views.py +++ b/src/jobs/views.py @@ -1,7 +1,11 @@ -from django.views.generic import ListView +from django.views.generic import ListView, DetailView from jobs.models import Job class IndexJobsListView(ListView): model = Job + + +class JobDetailView(DetailView): + model = Job diff --git a/src/portfolio/migrations/__init__.py b/src/portfolio/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/portfolio/settings.py b/src/portfolio/settings.py index dbe6dbd..94a6fb5 100644 --- a/src/portfolio/settings.py +++ b/src/portfolio/settings.py @@ -45,6 +45,7 @@ # созданные приложения "jobs.apps.JobsConfig", "blog.apps.BlogConfig", + "author.apps.AuthorConfig", # установленные приложения "ckeditor", "ckeditor_uploader", diff --git a/src/portfolio/templates/base.html b/src/portfolio/templates/base.html index 42b2f67..2104ddb 100644 --- a/src/portfolio/templates/base.html +++ b/src/portfolio/templates/base.html @@ -35,11 +35,8 @@

Обо мне

Мои контакты

- + {% block author_content %} + {% endblock %}
diff --git a/src/portfolio/urls.py b/src/portfolio/urls.py index 11ab691..398be18 100644 --- a/src/portfolio/urls.py +++ b/src/portfolio/urls.py @@ -22,12 +22,17 @@ from django.urls import path, include from jobs.views import IndexJobsListView +from author.views import AuthorListView + from portfolio import settings + urlpatterns = [ path("admin/", admin.site.urls), path("ckeditor/", include("ckeditor_uploader.urls")), - path("", IndexJobsListView.as_view(), name="home"), + path("", AuthorListView.as_view(), name="authors_content"), + path("", IndexJobsListView.as_view(), name="content"), + path("job/", include("jobs.urls")), path("blog/", include("blog.urls")), ] urlpatterns.extend(static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT))