Portfolio Creator
Tu información profesional suele estar dispersa: código en GitHub, proyectos en el GitLab de tu universidad o empresa, artículos en bases de datos científicas. Portfolio Creator la junta en un solo sitio, te deja elegir qué mostrar y genera tu CV listo para descargar en PDF o HTML, con resúmenes redactados por IA.
- HTML
- CSS
- Python
- Django
- Docker
- AWS
# ─── CV Profesional Unificado ──────────────────────────────
def _get_cv_data(user):
"""
Recupera el estado de la 'cesta' del CV.
Guarda los datos serializados como texto JSON en la tabla ContenidoData,
usando un recurso especial con nombre 'cv_profesional_username'.
"""
recurso_name = f"cv_profesional_{user.username}"
# Busca el registro o lo crea vacío si no existe
contenido_obj, created = ContenidoData.objects.get_or_create(
recurso=recurso_name,
defaults={'usuario': user.username, 'contenido': '{}'}
)
try:
# Deserializar JSON (texto a diccionario Python)
data = json.loads(contenido_obj.contenido)
except json.JSONDecodeError:
data = {}
if not isinstance(data, dict):
data = {}
# Estructura inicial obligatoria
if 'personal_info' not in data:
data['personal_info'] = {'photo': '', 'name': '', 'linkedin': '', 'phone': '', 'email': '', 'about': ''}
if 'items' not in data:
data['items'] = []
return contenido_obj, data
@login_required
def cv_builder(request):
"""Renderiza el panel de control del CV unificado (la vista de la 'cesta')"""
contenido_obj, data = _get_cv_data(request.user) # Lee la base de datos
if request.method == 'POST':
# El usuario ha enviado el formulario de "Información Personal"
import base64
if 'photo' in request.FILES: # Si ha subido una foto
try:
photo_file = request.FILES['photo']
# Convierte la foto a Base64: un string gigante que la incrusta directamente en el HTML
photo_b64 = base64.b64encode(photo_file.read()).decode('utf-8')
data['personal_info']['photo'] = f"data:{photo_file.content_type};base64,{photo_b64}"
except Exception:
pass
# Guarda los textos
data['personal_info']['name'] = request.POST.get('name', '').strip()
data['personal_info']['linkedin'] = request.POST.get('linkedin', '').strip()
data['personal_info']['phone'] = request.POST.get('phone', '').strip()
data['personal_info']['email'] = request.POST.get('email', '').strip()
data['personal_info']['about'] = request.POST.get('about', '').strip()
# Vuelve a convertir el diccionario a texto JSON y lo guarda en la BD
contenido_obj.contenido = json.dumps(data)
contenido_obj.save()
messages.success(request, 'Información del CV guardada correctamente.')
return redirect('cv_builder')
return render(request, 'portfolioCV/cv_builder.html', {
'cv_data': data
})
@login_required
def agregar_al_cv(request):
"""Agrega un repositorio u obra a la cesta del CV (se llama desde un formulario POST)"""
if request.method == 'POST':
# Se reciben los datos básicos del repositorio para añadirlos a la 'cesta'
platform = request.POST.get('platform')
item_id = request.POST.get('id')
name = request.POST.get('name')
if platform and item_id and name:
contenido_obj, data = _get_cv_data(request.user)
# Evitar duplicados (no añadir la misma repo dos veces)
if not any(str(item['id']) == str(item_id) and item['platform'] == platform for item in data['items']):
data['items'].append({ # Añadir al array de JSON
'platform': platform,
'id': item_id,
'name': name
})
contenido_obj.contenido = json.dumps(data) # Guardar
contenido_obj.save()
messages.success(request, f'"{name}" añadido a tu CV Profesional.')
else:
messages.info(request, f'"{name}" ya estaba en tu CV Profesional.')
# Vuelve a la página anterior (HTTP Referer) o al CV Builder si no hay
return redirect(request.META.get('HTTP_REFERER', 'cv_builder'))
return redirect('index')# ─── CV Profesional Unificado ──────────────────────────────
def _get_cv_data(user):
"""
Recupera el estado de la 'cesta' del CV.
Guarda los datos serializados como texto JSON en la tabla ContenidoData,
usando un recurso especial con nombre 'cv_profesional_username'.
"""
recurso_name = f"cv_profesional_{user.username}"
# Busca el registro o lo crea vacío si no existe
contenido_obj, created = ContenidoData.objects.get_or_create(
recurso=recurso_name,
defaults={'usuario': user.username, 'contenido': '{}'}
)
try:
# Deserializar JSON (texto a diccionario Python)
data = json.loads(contenido_obj.contenido)
except json.JSONDecodeError:
data = {}
if not isinstance(data, dict):
data = {}
# Estructura inicial obligatoria
if 'personal_info' not in data:
data['personal_info'] = {'photo': '', 'name': '', 'linkedin': '', 'phone': '', 'email': '', 'about': ''}
if 'items' not in data:
data['items'] = []
return contenido_obj, data
@login_required
def cv_builder(request):
"""Renderiza el panel de control del CV unificado (la vista de la 'cesta')"""
contenido_obj, data = _get_cv_data(request.user) # Lee la base de datos
if request.method == 'POST':
# El usuario ha enviado el formulario de "Información Personal"
import base64
if 'photo' in request.FILES: # Si ha subido una foto
try:
photo_file = request.FILES['photo']
# Convierte la foto a Base64: un string gigante que la incrusta directamente en el HTML
photo_b64 = base64.b64encode(photo_file.read()).decode('utf-8')
data['personal_info']['photo'] = f"data:{photo_file.content_type};base64,{photo_b64}"
except Exception:
pass
# Guarda los textos
data['personal_info']['name'] = request.POST.get('name', '').strip()
data['personal_info']['linkedin'] = request.POST.get('linkedin', '').strip()
data['personal_info']['phone'] = request.POST.get('phone', '').strip()
data['personal_info']['email'] = request.POST.get('email', '').strip()
data['personal_info']['about'] = request.POST.get('about', '').strip()
# Vuelve a convertir el diccionario a texto JSON y lo guarda en la BD
contenido_obj.contenido = json.dumps(data)
contenido_obj.save()
messages.success(request, 'Información del CV guardada correctamente.')
return redirect('cv_builder')
return render(request, 'portfolioCV/cv_builder.html', {
'cv_data': data
})
@login_required
def agregar_al_cv(request):
"""Agrega un repositorio u obra a la cesta del CV (se llama desde un formulario POST)"""
if request.method == 'POST':
# Se reciben los datos básicos del repositorio para añadirlos a la 'cesta'
platform = request.POST.get('platform')
item_id = request.POST.get('id')
name = request.POST.get('name')
if platform and item_id and name:
contenido_obj, data = _get_cv_data(request.user)
# Evitar duplicados (no añadir la misma repo dos veces)
if not any(str(item['id']) == str(item_id) and item['platform'] == platform for item in data['items']):
data['items'].append({ # Añadir al array de JSON
'platform': platform,
'id': item_id,
'name': name
})
contenido_obj.contenido = json.dumps(data) # Guardar
contenido_obj.save()
messages.success(request, f'"{name}" añadido a tu CV Profesional.')
else:
messages.info(request, f'"{name}" ya estaba en tu CV Profesional.')
# Vuelve a la página anterior (HTTP Referer) o al CV Builder si no hay
return redirect(request.META.get('HTTP_REFERER', 'cv_builder'))
return redirect('index')