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">7Vzfc6M2EP5rPNPeTDL8tv2YxMl1Ouk1jR/a60tHBtlWDiMqRGLfX38SCDAIA65tIKQPmZhFEsvq2/12Jdkj/W6z/UyAv/4NO9AdaYqzHemzkabpqjZh/7hkF0s0XRGSFUFOLFMzwRx9h0KoCGmIHBjkGlKMXYr8vNDGngdtmpMBQvBbvtkSu/mn+mAFJcHcBq4s/RM5dC1eQ1GU7MYvEK3WyaPTOxuQtBaCYA0c/LYn0u9H+h3BmMafNts76HLzJYaJ+z0cuJtqRqBHm3R4/OPBnd0T7f5vnwa/vu1edOP1SozyCtxQvPFIs1w23m3gA49rTXfCFta/IVf1dgHsbyuCQ8+5srGLyUi/4U/xEEXAZfezluzTSvyPhlwkgmcYhFxyk9xhWi+KrZks1qEwCJEkLra/8UfCUn03gKyQF2up7P0Zir+NRlEWmDhQvIeHPRhLfeA4yFuJjqLtoVfjL7CnRv5m6BYlLipKzmju5xoLMrH8/Itq9Ng7jZ7W/VPJDk/Tac7uv2L3E1wuK3swoYzI3jvR/wr+dwW1nEba2xpROPeBza/fGHOzRmu6YbCcqewjcNGKKTpz4TLr/goJhduD1KOmhMZyAYg3kJIda7JN+TLuItIAQ1y+ZZSqJjS53mfTRAgEja/SoTOiYx8E1x3De2XEVzATdFgqIC4xoWu8wh5w7zPpbeTfkD9HYVdZm0eMfWHMF0jpTuQ1IKQ4b+r4mfxB1ZZleuGQ2LDijYT+lMEM0jrGl2eKQBdQ9JrX4+xm1w6mGyn9PwFCkY1Y+KNBTYYgpwI8rpX43mGH4dGxUYfPbK79L2ADa4Nxo+FEBnSWseY2JtVqFSJ+1+HA6F00mAwtGqiNw4HZZTxQ5YBwx/C2RDZ7OPZkR38IPTu+U5geVl35/GNAIcevDwliKnI6jURP2fVtDd6XaAuTSlS9COC1SUPETy4GeL2Tum/O4M9ymbq4/jEqv2fosxhRH7wzyf3Wd4EXecYx3X5fvECb+/IxnRJJVl2gDXIBQXSXn70yspGHqTTEP8ERij0e1fppfVxzO2zQvrSA6hnH6lYtyaZZeY5krYvFnOngSNZsSrLjTknWlAy/ZOGb9Yrid/zySc4tTQklCHiryPbt1429I06tjDjfN4jHDUF8aK5aAvFYMrygMyW6UnJ0yqafE7lMmIWpyiZCrcf3WRLBekCnoG8H0HJweOeATuJBPaC1LgGtyXT4FQaltn8EC+iWx1qbmScqajgsWdXk3ogbG+Q48dTAAH0Hi2g8Pjk+RtHKymxk3o7MWRWuxaaa6DxKd7L2Z6UCVAe94Eq51pUEZ42tLYZ74vpnYyUVZNIDL5cBpNLspEqcMGFyBPownmJ16iny6swXPBBHOZAYZo6iTSfmaY7Sgmco0gShINKYL3L+9PJzRMgW2HBCFfWRkuzw77PxGm8WYdAKE0+1AhOXrUJarRZImjG4AJMc2agNMEr5ZLUUYOTNoLhAekkKJCKW63tUG42LAO68NtKHt6emNcSv3il+dTmHHwpB6tVOwAlSM7S8I5xGl8nI+UGn+f6X41J9cAsMzZ2o03pMl9l3MPVYDKoqL1InUyMH+Cv9LG40KR20hZRUk2azPiXVuk1JVdXqXU6qT7qIPsxgZPcX739tJpdf9+/NtmLw+Gonrs4ZtaymUevUtf2o6w0hYLfXQESFw8seSmEl0VIKp3+LHYxJZQf2IdbhvG5oSW6Y80AuUZhtlE/sL9thjO4WPbHlpdpigWiU5ddlzni5/HpwG2h60wWok53sNMPL6xsyipXrSS9xLLFK90A2ZHu+dyA3znG7BbKcFZUB2eonkJtsnrUMZPO947bp5q9xoIJoB7eG9WHs3Okmu163yZ7fXt+LHX2LDT0gOXk19BnSkPDl5NwZ985tZ0nnsTu3nbwIlh5ifJ8ngCUjm1aJkcdtGtmUk4F3HmWNxlF22imbyVE2RfdAT7yPy9De6uaUNbjNVWPaEO2m0Sna5VWLOTOcxILKguuU+w5rx8TYPxCbcuHc05yiuOhQartWcwprcBtrptE0AHR6fMuUI2/h+HkldLs8Y2H0LgJYgztjYTbdaDE7XToz5Z2MqtK4+J0r5SoGfMdxuViFlAG69GtBl6O0wX311mxahZidHhoyB3z+PAZV5akhdWLlPCH5JaQTzztMSwe9/HmHxI8/3D59c287NQk6zz791Krepx8ble0vs01vyUtBQzlAaB0IslkoUIzJ+DTfv7x7m/KSUXSc6VCakWzljfQHOefo8JR9emj5Eiea2GX2a3qx5bNfJdTvfwA=</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