Init
This commit is contained in:
parent
32b15cb99c
commit
b3f0d2a966
0
apps/__init__.py
Normal file
0
apps/__init__.py
Normal file
0
apps/core/__init__.py
Normal file
0
apps/core/__init__.py
Normal file
3
apps/core/admin.py
Normal file
3
apps/core/admin.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
6
apps/core/apps.py
Normal file
6
apps/core/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CoreConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.core'
|
0
apps/core/migrations/__init__.py
Normal file
0
apps/core/migrations/__init__.py
Normal file
9
apps/core/models.py
Normal file
9
apps/core/models.py
Normal file
@ -0,0 +1,9 @@
|
||||
# apps/core/models.py
|
||||
from django.db import models
|
||||
|
||||
class TimeStampedModel(models.Model):
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
updated = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
3
apps/core/tests.py
Normal file
3
apps/core/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
3
apps/core/views.py
Normal file
3
apps/core/views.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
0
apps/participants/__init__.py
Normal file
0
apps/participants/__init__.py
Normal file
9
apps/participants/admin.py
Normal file
9
apps/participants/admin.py
Normal file
@ -0,0 +1,9 @@
|
||||
# apps/participants/admin.py
|
||||
from django.contrib import admin
|
||||
from .models import Participant # ✅ Participant es el único modelo en esta app
|
||||
|
||||
|
||||
@admin.register(Participant)
|
||||
class ParticipantAdmin(admin.ModelAdmin):
|
||||
list_display = ("group_name", "score", "s_vol_over_eta")
|
||||
readonly_fields = ("score",)
|
6
apps/participants/apps.py
Normal file
6
apps/participants/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ParticipantsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.participants'
|
10
apps/participants/forms.py
Normal file
10
apps/participants/forms.py
Normal file
@ -0,0 +1,10 @@
|
||||
from django import forms
|
||||
from .models import Participant
|
||||
|
||||
class ParticipantForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Participant
|
||||
fields = ["group_name","R_diff","L_diff","Ph_diff","Pcu_diff","s_vol_over_eta"]
|
||||
widgets = {f: forms.NumberInput(attrs={"step": "0.1", "class": "input"})
|
||||
for f in fields if f!="group_name"}
|
||||
widgets["group_name"]= forms.TextInput(attrs={"class":"input"})
|
32
apps/participants/migrations/0001_initial.py
Normal file
32
apps/participants/migrations/0001_initial.py
Normal file
@ -0,0 +1,32 @@
|
||||
# Generated by Django 4.2.4 on 2025-05-12 16:10
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Participant',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('updated', models.DateTimeField(auto_now=True)),
|
||||
('group_name', models.CharField(max_length=80, unique=True)),
|
||||
('R_diff', models.FloatField()),
|
||||
('L_diff', models.FloatField()),
|
||||
('Ph_diff', models.FloatField()),
|
||||
('Pcu_diff', models.FloatField()),
|
||||
('s_vol_over_eta', models.FloatField(help_text='S*Vol/η')),
|
||||
('score', models.FloatField(default=0)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-score'],
|
||||
},
|
||||
),
|
||||
]
|
0
apps/participants/migrations/__init__.py
Normal file
0
apps/participants/migrations/__init__.py
Normal file
21
apps/participants/models.py
Normal file
21
apps/participants/models.py
Normal file
@ -0,0 +1,21 @@
|
||||
# apps/participants/models.py
|
||||
from django.db import models
|
||||
from apps.core.models import TimeStampedModel
|
||||
|
||||
class Participant(TimeStampedModel):
|
||||
group_name = models.CharField(max_length=80, unique=True)
|
||||
|
||||
# diffs en porcentaje
|
||||
R_diff = models.FloatField()
|
||||
L_diff = models.FloatField()
|
||||
Ph_diff = models.FloatField()
|
||||
Pcu_diff= models.FloatField()
|
||||
|
||||
s_vol_over_eta = models.FloatField(help_text="S*Vol/η") # métrica objetiva
|
||||
score = models.FloatField(default=0)
|
||||
|
||||
class Meta:
|
||||
ordering = ["-score"]
|
||||
|
||||
def __str__(self):
|
||||
return self.group_name
|
39
apps/participants/services.py
Normal file
39
apps/participants/services.py
Normal file
@ -0,0 +1,39 @@
|
||||
# apps/participants/services.py
|
||||
from django.db import transaction
|
||||
from .models import Participant
|
||||
from apps.settingsapp.models import GradingSettings
|
||||
|
||||
def _factor(diff: float) -> float:
|
||||
if diff <= 10:
|
||||
return 1.0
|
||||
elif diff <= 20:
|
||||
return 0.8
|
||||
return 0.6
|
||||
|
||||
def calculate_scores():
|
||||
"""Recalcula y persiste 'score' para todos los participantes"""
|
||||
settings = GradingSettings.get()
|
||||
participants = list(Participant.objects.all())
|
||||
|
||||
# 1) Base Entregable + Presentación
|
||||
for p in participants:
|
||||
p.score = settings.report_weight + settings.presentation_weight
|
||||
|
||||
# 2) Similitud
|
||||
for diff, weight in [
|
||||
(p.R_diff, settings.R_weight),
|
||||
(p.L_diff, settings.L_weight),
|
||||
(p.Ph_diff, settings.Ph_weight),
|
||||
(p.Pcu_diff,settings.Pcu_weight),
|
||||
]:
|
||||
p.score += _factor(diff) * weight
|
||||
|
||||
# 3) Objetivo (rank por métrica)
|
||||
sorted_by_obj = sorted(participants, key=lambda x: x.s_vol_over_eta)
|
||||
for idx, p in enumerate(sorted_by_obj, start=1):
|
||||
p.score += max(settings.objective_weight - (idx - 1), 0)
|
||||
|
||||
# 4) Persistimos en batch
|
||||
with transaction.atomic():
|
||||
for p in participants:
|
||||
p.save(update_fields=["score"])
|
3
apps/participants/tests.py
Normal file
3
apps/participants/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
8
apps/participants/urls.py
Normal file
8
apps/participants/urls.py
Normal file
@ -0,0 +1,8 @@
|
||||
from django.urls import path
|
||||
from .views import ParticipantCreateView, CalculateView, ResultsView
|
||||
|
||||
urlpatterns = [
|
||||
path("", ParticipantCreateView.as_view(), name="participant_new"),
|
||||
path("calcular/", CalculateView.as_view(), name="calculate"),
|
||||
path("resultados/", ResultsView.as_view(), name="results"),
|
||||
]
|
23
apps/participants/views.py
Normal file
23
apps/participants/views.py
Normal file
@ -0,0 +1,23 @@
|
||||
# apps/participants/views.py
|
||||
from django.views.generic import ListView, CreateView, View, TemplateView
|
||||
from django.urls import reverse_lazy
|
||||
from django.shortcuts import redirect
|
||||
from .models import Participant
|
||||
from .forms import ParticipantForm
|
||||
from .services import calculate_scores
|
||||
|
||||
class ParticipantCreateView(CreateView):
|
||||
model = Participant
|
||||
form_class = ParticipantForm
|
||||
template_name = "participants/form.html"
|
||||
success_url = reverse_lazy("participant_new") # vuelve a sí misma
|
||||
|
||||
class CalculateView(View):
|
||||
def post(self, request, *args, **kwargs):
|
||||
calculate_scores()
|
||||
return redirect("results")
|
||||
|
||||
class ResultsView(ListView):
|
||||
model = Participant
|
||||
template_name = "participants/results.html"
|
||||
context_object_name = "participants"
|
0
apps/scoring/__init__.py
Normal file
0
apps/scoring/__init__.py
Normal file
3
apps/scoring/admin.py
Normal file
3
apps/scoring/admin.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
6
apps/scoring/apps.py
Normal file
6
apps/scoring/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ScoringConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.scoring'
|
0
apps/scoring/migrations/__init__.py
Normal file
0
apps/scoring/migrations/__init__.py
Normal file
3
apps/scoring/models.py
Normal file
3
apps/scoring/models.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
3
apps/scoring/tests.py
Normal file
3
apps/scoring/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
3
apps/scoring/views.py
Normal file
3
apps/scoring/views.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
0
apps/settingsapp/__init__.py
Normal file
0
apps/settingsapp/__init__.py
Normal file
9
apps/settingsapp/admin.py
Normal file
9
apps/settingsapp/admin.py
Normal file
@ -0,0 +1,9 @@
|
||||
# apps/settingsapp/admin.py
|
||||
from django.contrib import admin
|
||||
from .models import GradingSettings
|
||||
|
||||
|
||||
@admin.register(GradingSettings)
|
||||
class SettingsAdmin(admin.ModelAdmin):
|
||||
def has_add_permission(self, *_):
|
||||
return False # solo debe existir una instancia
|
6
apps/settingsapp/apps.py
Normal file
6
apps/settingsapp/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class SettingsappConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.settingsapp'
|
9
apps/settingsapp/forms.py
Normal file
9
apps/settingsapp/forms.py
Normal file
@ -0,0 +1,9 @@
|
||||
from django import forms
|
||||
from .models import GradingSettings
|
||||
|
||||
class GradingSettingsForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = GradingSettings
|
||||
fields = "__all__"
|
||||
widgets = {f: forms.NumberInput(attrs={"step": "0.1", "class": "input"})
|
||||
for f in fields}
|
27
apps/settingsapp/migrations/0001_initial.py
Normal file
27
apps/settingsapp/migrations/0001_initial.py
Normal file
@ -0,0 +1,27 @@
|
||||
# Generated by Django 4.2.4 on 2025-05-12 16:10
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='GradingSettings',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('report_weight', models.FloatField(default=2)),
|
||||
('presentation_weight', models.FloatField(default=1)),
|
||||
('objective_weight', models.FloatField(default=3)),
|
||||
('R_weight', models.FloatField(default=1)),
|
||||
('L_weight', models.FloatField(default=1)),
|
||||
('Ph_weight', models.FloatField(default=1)),
|
||||
('Pcu_weight', models.FloatField(default=1)),
|
||||
],
|
||||
),
|
||||
]
|
0
apps/settingsapp/migrations/__init__.py
Normal file
0
apps/settingsapp/migrations/__init__.py
Normal file
29
apps/settingsapp/models.py
Normal file
29
apps/settingsapp/models.py
Normal file
@ -0,0 +1,29 @@
|
||||
# apps/settingsapp/models.py
|
||||
from django.db import models
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
class GradingSettings(models.Model):
|
||||
# NOTAS
|
||||
report_weight = models.FloatField(default=2) # Entregable
|
||||
presentation_weight = models.FloatField(default=1) # Presentación
|
||||
objective_weight = models.FloatField(default=3) # Objetivo max
|
||||
|
||||
# SIMILITUD
|
||||
R_weight = models.FloatField(default=1)
|
||||
L_weight = models.FloatField(default=1)
|
||||
Ph_weight = models.FloatField(default=1)
|
||||
Pcu_weight= models.FloatField(default=1)
|
||||
|
||||
def clean(self):
|
||||
if GradingSettings.objects.exclude(pk=self.pk).exists():
|
||||
raise ValidationError("Solo puede existir un GradingSettings")
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.full_clean()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def get(cls):
|
||||
# devuelve la única instancia (la crea si no existe)
|
||||
obj, _ = cls.objects.get_or_create(pk=1)
|
||||
return obj
|
3
apps/settingsapp/tests.py
Normal file
3
apps/settingsapp/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
4
apps/settingsapp/urls.py
Normal file
4
apps/settingsapp/urls.py
Normal file
@ -0,0 +1,4 @@
|
||||
from django.urls import path
|
||||
from .views import SettingsUpdateView
|
||||
|
||||
urlpatterns = [ path("", SettingsUpdateView.as_view(), name="settings") ]
|
16
apps/settingsapp/views.py
Normal file
16
apps/settingsapp/views.py
Normal file
@ -0,0 +1,16 @@
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
||||
from django.views.generic import UpdateView
|
||||
from .models import GradingSettings
|
||||
from .forms import GradingSettingsForm
|
||||
|
||||
class SettingsUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
|
||||
model = GradingSettings
|
||||
form_class = GradingSettingsForm
|
||||
template_name = "settingsapp/form.html"
|
||||
success_url = "/"
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return GradingSettings.get()
|
||||
|
||||
def test_func(self):
|
||||
return self.request.user.is_staff
|
1
dev/Trafoking.drawio
Normal file
1
dev/Trafoking.drawio
Normal file
@ -0,0 +1 @@
|
||||
<mxfile host="Electron" modified="2025-05-12T15:48:11.928Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/18.0.6 Chrome/100.0.4896.143 Electron/18.2.3 Safari/537.36" etag="mYNjJAf74T8tIM3Cm0co" version="18.0.6" type="device"><diagram id="kRK4T_47FRJ5cU9VMpoS" name="Página-1">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</diagram></mxfile>
|
BIN
dev/TrafokingBack.pdf
Normal file
BIN
dev/TrafokingBack.pdf
Normal file
Binary file not shown.
BIN
dev/TrafokingFront.png
Normal file
BIN
dev/TrafokingFront.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 678 KiB |
22
manage.py
Normal file
22
manage.py
Normal file
@ -0,0 +1,22 @@
|
||||
#!/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', 'trafoking.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()
|
7
requirements.txt
Normal file
7
requirements.txt
Normal file
@ -0,0 +1,7 @@
|
||||
# requirements.txt
|
||||
Django==4.2.4
|
||||
psycopg2-binary==2.9.*
|
||||
python-dotenv==1.0.*
|
||||
django-htmx==1.15.*
|
||||
pytest==8.1.*
|
||||
pytest-django==4.7.*
|
9
templates/auth/login.html
Normal file
9
templates/auth/login.html
Normal file
@ -0,0 +1,9 @@
|
||||
<!-- templates/auth/login.html -->
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<form method="post" class="space-y-2">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button class="px-4 py-2 bg-amber-500 rounded">Entrar</button>
|
||||
</form>
|
||||
{% endblock %}
|
14
templates/base.html
Normal file
14
templates/base.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!-- templates/base.html -->
|
||||
<!doctype html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Trafoking</title>
|
||||
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tailwindcss@3.4.1/dist/tailwind.min.css">
|
||||
</head>
|
||||
<body class="min-h-screen bg-slate-900 text-slate-100 flex flex-col items-center py-8">
|
||||
<h1 class="text-4xl font-bold mb-4">Trafoking ⚡👑</h1>
|
||||
{% block content %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
22
templates/participants/form.html
Normal file
22
templates/participants/form.html
Normal file
@ -0,0 +1,22 @@
|
||||
<!-- templates/participants/form.html -->
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
|
||||
<!-- Form: añadir grupo -->
|
||||
<form method="post" action="" class="space-y-2">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button class="px-4 py-2 bg-amber-500 rounded">
|
||||
Añadir
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Botón único: calcula + redirige al ranking -->
|
||||
<form method="post" action="{% url 'calculate' %}" class="mt-4">
|
||||
{% csrf_token %}
|
||||
<button class="px-4 py-2 bg-lime-600 rounded">
|
||||
Calcular y mostrar ranking
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
18
templates/participants/results.html
Normal file
18
templates/participants/results.html
Normal file
@ -0,0 +1,18 @@
|
||||
<!-- templates/participants/results.html -->
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<table class="table-auto border-collapse">
|
||||
<thead><tr><th>#</th><th>Grupo</th><th>Puntuación</th></tr></thead>
|
||||
<tbody>
|
||||
{% for p in participants %}
|
||||
<tr class="text-center">
|
||||
<td>{{ forloop.counter }}</td>
|
||||
<td>{{ p.group_name }}</td>
|
||||
<td>{{ p.score|floatformat:2 }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="3">Sin participantes todavía.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
10
templates/settingsapp/form.html
Normal file
10
templates/settingsapp/form.html
Normal file
@ -0,0 +1,10 @@
|
||||
<!-- templates/settingsapp/form.html -->
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<h2 class="text-2xl mb-4">Ajustes de calificación</h2>
|
||||
<form method="post" class="space-y-2">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button class="px-4 py-2 bg-amber-500 rounded">Guardar</button>
|
||||
</form>
|
||||
{% endblock %}
|
0
trafoking/__init__.py
Normal file
0
trafoking/__init__.py
Normal file
16
trafoking/asgi.py
Normal file
16
trafoking/asgi.py
Normal file
@ -0,0 +1,16 @@
|
||||
"""
|
||||
ASGI config for trafoking project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'trafoking.settings')
|
||||
|
||||
application = get_asgi_application()
|
85
trafoking/settings.py
Normal file
85
trafoking/settings.py
Normal file
@ -0,0 +1,85 @@
|
||||
# trafoking/settings.py
|
||||
import os
|
||||
from pathlib import Path
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
SECRET_KEY = os.getenv("DJANGO_SECRET_KEY")
|
||||
DEBUG = False
|
||||
ALLOWED_HOSTS = ["*"]
|
||||
|
||||
INSTALLED_APPS = [
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"django_htmx",
|
||||
"apps.core",
|
||||
"apps.participants",
|
||||
"apps.settingsapp",
|
||||
]
|
||||
|
||||
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_htmx.middleware.HtmxMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = "trafoking.urls"
|
||||
TEMPLATES = [
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [BASE_DIR / "templates"],
|
||||
"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 = "trafoking.wsgi.application"
|
||||
|
||||
DB_ENGINE = os.getenv("DB_ENGINE", "sqlite") # sqlite | postgres
|
||||
if DB_ENGINE == "postgres":
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.postgresql",
|
||||
"NAME": os.getenv("DB_NAME", "trafoking"),
|
||||
"USER": os.getenv("DB_USER", "postgres"),
|
||||
"PASSWORD": os.getenv("DB_PASSWORD", "postgres"),
|
||||
"HOST": os.getenv("DB_HOST", "db"),
|
||||
"PORT": os.getenv("DB_PORT", "5432"),
|
||||
}
|
||||
}
|
||||
else: # SQLite por defecto
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.sqlite3",
|
||||
"NAME": BASE_DIR / "db.sqlite3",
|
||||
}
|
||||
}
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
|
||||
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
|
||||
]
|
||||
|
||||
LANGUAGE_CODE = "es-es"
|
||||
TIME_ZONE = "Europe/Madrid"
|
||||
USE_I18N = USE_L10N = USE_TZ = True
|
||||
|
||||
STATIC_URL = "/static/"
|
||||
STATIC_ROOT = BASE_DIR / "staticfiles"
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||
LOGIN_URL = "/login/"
|
||||
LOGIN_REDIRECT_URL = "/"
|
11
trafoking/urls.py
Normal file
11
trafoking/urls.py
Normal file
@ -0,0 +1,11 @@
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
from django.contrib.auth.views import LoginView, LogoutView
|
||||
|
||||
urlpatterns = [
|
||||
path("admin/", admin.site.urls),
|
||||
path("login/", LoginView.as_view(template_name="auth/login.html"), name="login"),
|
||||
path("logout/", LogoutView.as_view(), name="logout"),
|
||||
path("", include("apps.participants.urls")),
|
||||
path("ajustes/", include("apps.settingsapp.urls")),
|
||||
]
|
16
trafoking/wsgi.py
Normal file
16
trafoking/wsgi.py
Normal file
@ -0,0 +1,16 @@
|
||||
"""
|
||||
WSGI config for trafoking 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.2/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'trafoking.settings')
|
||||
|
||||
application = get_wsgi_application()
|
Loading…
Reference in New Issue
Block a user