├── .dockerignore ├── python_django_blog ├── __init__.py ├── articles │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_initial.py │ ├── apps.py │ ├── forms.py │ ├── urls.py │ ├── models.py │ ├── views.py │ └── tests.py ├── locale │ └── ru │ │ └── LC_MESSAGES │ │ ├── django.mo │ │ └── django.po ├── templates │ ├── index.html │ ├── articles │ │ ├── delete.html │ │ ├── create.html │ │ ├── update.html │ │ ├── detail.html │ │ └── index.html │ ├── about.html │ └── layouts │ │ ├── confirm_delete.html │ │ ├── form.html │ │ └── application.html ├── views.py ├── fixtures │ ├── test_data.json │ └── articles.json ├── tests.py ├── asgi.py ├── wsgi.py ├── utils.py ├── urls.py └── settings.py ├── .coveragerc ├── .env.example ├── .gitignore ├── docker-compose.yml ├── ruff.toml ├── Dockerfile ├── pyproject.toml ├── manage.py ├── Makefile ├── README.md ├── .github ├── workflows │ └── pyci.yml └── dependabot.yml └── uv.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | .venv 2 | -------------------------------------------------------------------------------- /python_django_blog/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python_django_blog/articles/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = python_django_blog 3 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | SECRET_KEY=somekey 2 | DEBUG=True 3 | -------------------------------------------------------------------------------- /python_django_blog/articles/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .venv/ 2 | *.sqlite3 3 | __pycache__/ 4 | .env 5 | .coverage 6 | htmlcov/ 7 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | build: . 4 | volumes: 5 | - .:/app 6 | ports: 7 | - 8000:8000 8 | command: make start 9 | -------------------------------------------------------------------------------- /python_django_blog/locale/ru/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexlet-components/python-django-blog/HEAD/python_django_blog/locale/ru/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /python_django_blog/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'layouts/application.html' %} 2 | {% load i18n %} 3 | 4 | {% block header %} 5 | {% translate "Python Django Blog" %} 6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /python_django_blog/templates/articles/delete.html: -------------------------------------------------------------------------------- 1 | {% extends 'layouts/confirm_delete.html' %} 2 | {% load i18n %} 3 | 4 | {% block header %} 5 | {% translate "Delete article" %} 6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /python_django_blog/articles/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ArticlesConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'python_django_blog.articles' 7 | -------------------------------------------------------------------------------- /python_django_blog/views.py: -------------------------------------------------------------------------------- 1 | from django.views.generic import TemplateView 2 | 3 | 4 | class IndexView(TemplateView): 5 | template_name = 'index.html' 6 | 7 | 8 | class AboutView(TemplateView): 9 | template_name = 'about.html' 10 | -------------------------------------------------------------------------------- /python_django_blog/templates/articles/create.html: -------------------------------------------------------------------------------- 1 | {% extends 'layouts/form.html' %} 2 | {% load i18n %} 3 | 4 | {% block header %} 5 | {% translate "Create article" %} 6 | {% endblock %} 7 | 8 | {% block button_value %}{% translate "Create" %}{% endblock %} 9 | -------------------------------------------------------------------------------- /python_django_blog/templates/articles/update.html: -------------------------------------------------------------------------------- 1 | {% extends 'layouts/form.html' %} 2 | {% load i18n %} 3 | 4 | {% block header %} 5 | {% translate "Update article" %} 6 | {% endblock %} 7 | 8 | {% block button_value %}{% translate "Update" %}{% endblock %} 9 | -------------------------------------------------------------------------------- /python_django_blog/articles/forms.py: -------------------------------------------------------------------------------- 1 | from django.forms import ModelForm 2 | from python_django_blog.articles.models import Article 3 | 4 | 5 | class ArticleForm(ModelForm): 6 | 7 | class Meta: 8 | model = Article 9 | fields = ['name', 'description'] 10 | -------------------------------------------------------------------------------- /ruff.toml: -------------------------------------------------------------------------------- 1 | line-length = 80 2 | exclude = ["migrations", "settings.py", "manage.py"] 3 | 4 | [lint.per-file-ignores] 5 | # init modules can contain the local imports, logic, unused imports 6 | "__init__.py" = ["F401"] 7 | 8 | [lint] 9 | preview = true 10 | select = ["E", "F", "C90"] 11 | -------------------------------------------------------------------------------- /python_django_blog/fixtures/test_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "articles": { 3 | "new": { 4 | "name": "article new", 5 | "description": "description new" 6 | }, 7 | "existing": { 8 | "name": "article two", 9 | "description": "description two" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /python_django_blog/templates/about.html: -------------------------------------------------------------------------------- 1 | {% extends 'layouts/application.html' %} 2 | {% load i18n %} 3 | 4 | {% block header %} 5 | {% translate "About the blog" %} 6 | {% endblock %} 7 | 8 | {% block content %} 9 | {% translate "Experimenting with Django on Hexlet" %} 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.13.2-slim 2 | 3 | RUN apt-get update && apt-get install -yq make gettext 4 | 5 | COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ 6 | 7 | WORKDIR /app 8 | 9 | COPY . . 10 | 11 | RUN uv sync 12 | 13 | CMD ["bash", "-c", "uv run manage.py migrate && uv run gunicorn python_django_blog.wsgi --log-file -"] 14 | -------------------------------------------------------------------------------- /python_django_blog/templates/layouts/confirm_delete.html: -------------------------------------------------------------------------------- 1 | {% extends 'layouts/application.html' %} 2 | {% load i18n %} 3 | 4 | {% block content %} 5 |
{% translate "Are you sure you want to delete" %} {{ object }}?
6 | 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /python_django_blog/templates/layouts/form.html: -------------------------------------------------------------------------------- 1 | {% extends 'layouts/application.html' %} 2 | {% load bootstrap4 %} 3 | 4 | {% block content %} 5 | 12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /python_django_blog/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.urls import reverse 3 | 4 | 5 | class AppTest(TestCase): 6 | 7 | def test_index_page(self): 8 | response = self.client.get(reverse('root')) 9 | self.assertEqual(response.status_code, 200) 10 | 11 | def test_about_page(self): 12 | response = self.client.get(reverse('about')) 13 | self.assertEqual(response.status_code, 200) 14 | -------------------------------------------------------------------------------- /python_django_blog/templates/articles/detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'layouts/application.html' %} 2 | {% load i18n %} 3 | 4 | {% block header %} 5 | {% translate "Show article" %} 6 | {% endblock %} 7 | 8 | {% block content %} 9 |{{ article.description }}
15 || {% translate "id"|upper %} | 14 |{% translate "name"|capfirst %} | 15 |{% translate "created date"|capfirst %} | 16 |17 | |
|---|---|---|---|
| {{ article.id }} | 23 |24 | {{ article.name }} 25 | | 26 |{{ article.created_at|date:"d.m.Y H:i" }} | 27 |
28 | {% translate "Update" %}
29 | 30 | {% translate "Delete" %} 31 | |
32 |