{% load static %}
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Моя карьера</title>
<link href="{% static 'career/css/style.css' %}" rel="stylesheet">
</head>
<body class="body">
<div class="container">
<h1 class="title">МОЯ КАРЬЕРА</h1>
<form method="post" class="form-section">
{% csrf_token %}
<div>
<h3>Я СЕЙЧАС РАБОТАЮ (ВЫБЕРИ ДОЛЖНОСТЬ)</h3>
<select name="current_position" required>
<option value="">-- Выберите текущую должность --</option>
{% for position in positions %}
<option value="{{ position.id }}">{{ position.name }} (Уровень {{ position.level }})</option>
{% endfor %}
</select>
</div>
<div>
<h3>Я ХОЧУ РАБОТАТЬ</h3>
<select name="target_position" required>
<option value="">-- Выберите целевую должность --</option>
{% for position in positions %}
<option value="{{ position.id }}">{{ position.name }} (Уровень {{ position.level }})</option>
{% endfor %}
</select>
</div>
<button type="submit" class="btn">ПОСТРОИТЬ КАРЬЕРУ</button>
</form>
{% if target_position and current_position %}
{% if is_too_far %}
<div class="warning">
<h3>ВНИМАНИЕ!</h3>
<p>
Вы выбрали должность, которая находится на слишком высокой ступеньке
и при этом относится к другой сфере деятельности. Это слишком большой шаг для текущего уровня.
</p>
<p>
Рекомендуем выбрать должность, которая ближе к вашей текущей сфере и уровню,
чтобы постепенно развивать свои навыки.
</p>
</div>
{% else %}
{% if level_difference > 1 %}
<div class="warning">
<h3>ВНИМАНИЕ!</h3>
<p>Вы выбрали сложную должность на {{ level_difference }} ступенек выше.</p>
<p>Её будет освоить не так-то просто. Потребуется:</p>
<ul>
<li>Прочитать много литературы</li>
<li>Освоить различные технические средства работы</li>
<li>Самостоятельное изучение материалов</li>
</ul>
{% endif %}
{% if required_skills %}
<h2>Необходимые навыки:</h2>
<ul>
{% for skill in required_skills %}
<li>{{ skill.name }}</li>
{% endfor %}
</ul>
<form method="post">
{% csrf_token %}
<input type="hidden" name="current_position" value="{{ current_position.id }}">
<input type="hidden" name="target_position" value="{{ target_position.id }}">
<button type="submit" name="download_pdf" class="btn btn_pdf">Скачать PDF</button>
</form>
{% else %}
<p>Навыков нет или они не были загружены.</p>
{% endif %}
</div>
{% endif %}
{% endif %}
</div>
</body>
<script src="{% static 'career/js/main.js' %}"></script>
</html>
from django.contrib import admin
from .models import Position, Skill, PositionGroup
admin.site.site_header = 'Администрирование' # Заголовок в админке
admin.site.site_title = 'Администрирование' # Заголовок вкладки браузера
admin.site.index_title = 'Панель управления' # Заголовок на главной странице админки
class SkillInline(admin.TabularInline):
model = Skill.positions.through
extra = 1
class PositionInline(admin.TabularInline):
model = Position
extra = 1
fields = ('name', 'level')
show_change_link = True
list_filter = ('level',)
ordering = ('name',)
@admin.register(Position)
class PositionAdmin(admin.ModelAdmin):
list_display = ('name', 'level')
list_filter = ('level',)
search_fields = ('name',)
inlines = [SkillInline]
@admin.register(Skill)
class SkillAdmin(admin.ModelAdmin):
list_display = ('name',)
search_fields = ('name',)
filter_horizontal = ('positions',)
@admin.register(PositionGroup)
class PositionGroupAdmin(admin.ModelAdmin):
list_display = ('name',)
search_fields = ('name',)
inlines = [PositionInline]
from django.apps import AppConfig
class CareerConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'career'
verbose_name = '📊 Карьера'
from django.db import models
class PositionGroup(models.Model):
name = models.CharField(max_length=255, verbose_name="Название группы")
class Meta:
verbose_name = "Группа должностей"
verbose_name_plural = "Группы должностей"
def __str__(self):
return self.name
class Position(models.Model):
name = models.CharField(max_length=255, verbose_name='Название должности')
level = models.PositiveSmallIntegerField(verbose_name='Уровень должности')
group = models.ForeignKey(PositionGroup, null=True, on_delete=models.CASCADE, related_name="positions", verbose_name="Группа")
class Meta:
verbose_name = 'Должность'
verbose_name_plural = 'Должности'
def __str__(self):
return f"{self.name} (Уровень {self.level})"
class Skill(models.Model):
name = models.CharField(max_length=255, verbose_name='Название навыка')
positions = models.ManyToManyField(Position, related_name='skills', verbose_name='Должности')
class Meta:
verbose_name = 'Умение'
verbose_name_plural = 'Умения'
def __str__(self):
return self.name
from django.urls import path
from . import views
urlpatterns = [
path('', views.career_plan, name='career_plan'),
]
from io import BytesIO
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from django.http import HttpResponse
from config.settings import FONT_PATH
def generate_pdf_response(skills, position_name):
# Регистрирует шрифт Arial для поддержки кириллицы
pdfmetrics.registerFont(TTFont('Arial', FONT_PATH))
# Создает буфер для PDF
buffer = BytesIO()
# Создает PDF-документ
p = canvas.Canvas(buffer)
# Настройки документа
p.setFont("Arial", 16)
p.setTitle(f"Навыки на должность {position_name}")
# Заголовок
p.drawString(50, 800, f"Необходимые навыки для {position_name}:")
p.setFont("Arial", 12) # Уменьшает размер шрифта для основного текста
# Начальная позиция для текста
y_position = 770
# Список навыков
for i, skill in enumerate(skills, 1):
p.drawString(70, y_position, f"{i}. {skill.name}")
y_position -= 25 # Переходит на следующую строку
# Проверка на конец страницы
if y_position < 50:
p.showPage() # Создает новую страницу
y_position = 800 # Сбрасывает позицию
p.setFont("Arial", 12) # Устанавливает шрифт для новой страницы
# Закрывает PDF
p.showPage()
p.save()
# Получает содержимое PDF
buffer.seek(0)
response = HttpResponse(buffer, content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename="required_skills.pdf"'
return response
from django.shortcuts import render
from .models import Position
from .utils import generate_pdf_response
def career_plan(request):
positions = Position.objects.all().order_by('level')
context = {'positions': positions} # Базовый контекст
if request.method == 'POST':
current_position_id = request.POST.get('current_position')
target_position_id = request.POST.get('target_position')
current_position = Position.objects.get(id=current_position_id)
target_position = Position.objects.get(id=target_position_id)
level_difference = target_position.level - current_position.level
is_too_far = (
target_position.level > 1 and
current_position.group != target_position.group
)
if 'download_pdf' in request.POST:
return generate_pdf_response(target_position.skills.all(), target_position.name)
# Обновляет контекст
context.update({
'current_position': current_position,
'target_position': target_position,
'level_difference': level_difference,
'is_too_far': is_too_far,
# если нельзя показывать скилы, то не достает их из бд
'required_skills': target_position.skills.all() if not is_too_far else None,
})
return render(request, 'career/plan.html', context)
"""
Django settings for config project.
Generated by 'django-admin startproject' using Django 4.1.5.
For more information on this file, see
https://docs.djangoproject.com/en/4.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.1/ref/settings/
"""
import os
from pathlib import Path
from dotenv.main import load_dotenv
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
load_dotenv(BASE_DIR / '.env')
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.getenv('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.getenv('DEBUG')
FONT_PATH = os.path.join(BASE_DIR, 'fonts', 'arial.ttf')
ALLOWED_HOSTS = ['195.161.62.128', '127.0.0.1', 'localhost']
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'users',
'career.apps.CareerConfig',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'config.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'config.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.1/topics/i18n/
LANGUAGE_CODE = 'ru-RU'
TIME_ZONE = 'Europe/Moscow'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.1/howto/static-files/
STATIC_URL = 'static/'
# Отвечает за место на диске, откуда необходимо подгружать статику
STATICFILES_DIRS = (
BASE_DIR / 'static',
)
# Default primary key field type
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
AUTH_USER_MODEL = 'users.User'
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
"""config URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.1/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('career.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
"""
WSGI config for config project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
application = get_wsgi_application()
from django.apps import AppConfig
class UsersConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'users'
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
username = None
email = models.EmailField(unique=True, verbose_name='email')
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
def __str__(self):
return self.email
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
asgiref==3.8.1
attrs==25.1.0
chardet==5.2.0
Django==5.1.6
djangorestframework==3.15.2
dotenv-python==0.0.1
drf-spectacular==0.28.0
inflection==0.5.1
jsonschema==4.23.0
jsonschema-specifications==2024.10.1
pillow==11.1.0
psycopg2==2.9.10
python-dotenv==1.0.1
PyYAML==6.0.2
referencing==0.36.2
reportlab==4.3.1
rpds-py==0.22.3
sqlparse==0.5.3
typing_extensions==4.12.2
tzdata==2025.1
uritemplate==4.1.1