124 lines
3.4 KiB
Markdown
124 lines
3.4 KiB
Markdown
# qualifiers-model
|
|
|
|
Erstellt und erweitert Django Models für das qualifiers-Modul (UEFA Qualifikationsturniere).
|
|
|
|
## Trigger
|
|
|
|
- Neue QNode, QMatch, QGame, QGrouping Models
|
|
- Model-Erweiterungen für Qualifiers
|
|
- "qualifiers model", "qnode", "qmatch", "qgame"
|
|
|
|
## Kontext
|
|
|
|
Das qualifiers-Modul verwaltet UEFA-Qualifikationsturniere mit folgender Hierarchie:
|
|
- **QPath** → QTier → QNode (Turnier-Struktur)
|
|
- **QNode** → QGrouping → QPosition (Gruppenbildung)
|
|
- **QNode** → QMatch → QGame (Spielpaarungen)
|
|
|
|
## Regeln
|
|
|
|
1. **Immer db_index für ForeignKeys setzen** wenn häufig gefiltert wird:
|
|
```python
|
|
scenario = models.ForeignKey('scheduler.Scenario', on_delete=models.CASCADE, db_index=True)
|
|
```
|
|
|
|
2. **Meta-Klasse mit indexes und constraints**:
|
|
```python
|
|
class Meta:
|
|
ordering = ['-tier__ypos', '-stage__xpos']
|
|
constraints = [
|
|
models.UniqueConstraint(fields=['name', 'scenario'], name='unique_qnode_name_scenario')
|
|
]
|
|
indexes = [
|
|
models.Index(fields=['scenario', 'tier', 'stage'], name='qnode_scenario_tier_stage_idx'),
|
|
]
|
|
```
|
|
|
|
3. **related_name konsistent benennen**:
|
|
- Plural für ForeignKey: `related_name='groupings'`
|
|
- Model-Name für M2M: `related_name='qnodes_seeded'`
|
|
|
|
4. **State-Machine Pattern für QNode**:
|
|
```python
|
|
NODESTATE_CHOICES = (
|
|
(0, "none"), # Warten auf Vorgänger
|
|
(1, "seeded"), # Teams zugeordnet
|
|
(2, "grouping"), # Gruppen gebildet
|
|
(3, "draw"), # Spiele ausgelost
|
|
(4, "finalized"), # Alle Ergebnisse da
|
|
)
|
|
```
|
|
|
|
5. **GameMixin für QGame verwenden**:
|
|
```python
|
|
from scheduler.models import GameMixin
|
|
|
|
class QGame(GameMixin):
|
|
# Erbt: homeGoals, awayGoals, resultEntered, season
|
|
qnode = models.ForeignKey(QNode, ...)
|
|
```
|
|
|
|
6. **Signals mit transaction.atomic()**:
|
|
```python
|
|
@receiver(post_save, sender=QGame)
|
|
def post_match_signal(sender, instance, created, **kwargs):
|
|
with transaction.atomic():
|
|
# Signal-Logik
|
|
```
|
|
|
|
## Beispiel: Neues Feld zu QNode
|
|
|
|
```python
|
|
# qualifiers/models.py
|
|
class QNode(models.Model):
|
|
# Existierende Felder...
|
|
|
|
# Neues Feld
|
|
auto_advance = models.BooleanField(
|
|
default=False,
|
|
help_text="Automatisch zum nächsten State wechseln"
|
|
)
|
|
```
|
|
|
|
## Beispiel: Neues Model
|
|
|
|
```python
|
|
# qualifiers/models.py
|
|
class QNodeViolation(models.Model):
|
|
"""Speichert Constraint-Verletzungen für einen Node."""
|
|
node = models.ForeignKey(
|
|
QNode,
|
|
on_delete=models.CASCADE,
|
|
related_name='violations',
|
|
db_index=True
|
|
)
|
|
scenario = models.ForeignKey(
|
|
'scheduler.Scenario',
|
|
on_delete=models.CASCADE,
|
|
db_index=True
|
|
)
|
|
type = models.CharField(max_length=50) # 'country_clash', 'distance', etc.
|
|
message = models.TextField()
|
|
severity = models.IntegerField(default=1) # 1=Warning, 2=Error
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
ordering = ['-created_at']
|
|
indexes = [
|
|
models.Index(fields=['node', 'scenario'], name='qnodeviolation_node_scen_idx'),
|
|
]
|
|
```
|
|
|
|
## Migration erstellen
|
|
|
|
```bash
|
|
conda activate planner
|
|
python manage.py makemigrations qualifiers
|
|
python manage.py migrate qualifiers
|
|
```
|
|
|
|
## Referenz-Dateien
|
|
|
|
- [qualifiers/models.py](qualifiers/models.py) - Alle Models
|
|
- [docs/qualifiers/models/](docs/qualifiers/models/) - Dokumentation
|