djangofantalega: model Match
6 – Models: Match
Per creare il calendario di Lega, utilizziamo il model Match.
La relazione tra squadra (Team) e Match è one-to-many.
Aggiungere nel file fantalega/models.py il model Match:
... from django.utils import timezone ... class Match (models.Model): league = models.ForeignKey(League, related_name='matches') day = models.IntegerField() home_team = models.ForeignKey(Team, related_name='home_team') visit_team = models.ForeignKey(Team, related_name='visit_team') dead_line = models.DateTimeField(null=True) class Meta: verbose_name_plural = 'Matches' def __unicode__(self): return "[%s] %s - %s" % (self.day, self.home_team.name, self.visit_team.name)
Aggiornare il database inserendo la nuova tabella.
(venv) >python manage.py makemigrations Migrations for 'fantalega': fantalega\migrations\0004_match.py: - Create model Match
confermare con:
(venv) C:\tmp\djangosite>python manage.py migrate Operations to perform: Apply all migrations: admin, auth, contenttypes, fantalega, log, sessions Running migrations: Applying fantalega.0004_match... OK
Sistemare l’interfaccia di admin per il model Match:
file fantalega/admin.py:
# noinspection PyUnresolvedReferences from django.contrib import admin from .models import Season, League, LeaguesTeams, Team, Match # add Match # Register your models here. ... class MatchAdmin(admin.ModelAdmin): ordering = ('day', ) list_display = ('day', 'home_team', 'visit_team', 'dead_line') list_filter = ('day', ) list_per_page = 15 ... admin.site.register(Match, MatchAdmin)
Ora aggiungere gli urls inerenti Team nel file fantalega\urls.py:
... # matches urls url(r'^leagues/(?P<league_id>[0-9]+)/matches$', views.matches, name='matches'), url(r'^leagues/(?P<league_id>[0-9]+)/matches/(?P<day>[0-9]+)/$', views.match_details, name='match_details'), url(r'^leagues/(?P<league_id>[0-9]+)/matches/(?P<day>[0-9]+)/deadline$', views.match_deadline, name='match_deadline'), ]
Aggiungere nel file fantalega\views.py le nuove viste ‘matches’ e ‘match_details’
e ‘match_deadline’. Quest’ultima utilizzerà un form per l’inserimento del termine
ultimo della consegna della formazione. Siccome una giornata di Lega formata ad es.
da 10 squadre, è composta da 5 incontri, si dovrebbe inserire 5 volte la data, una
per ogni match della giornata X, con il form la si inserisce una volta sola:
... from .models import Season, League, Team, Match # Match added ... @login_required def matches(request, league_id): league = get_object_or_404(League, pk=int(league_id)) league_matches = league.matches days = [d['day'] for d in Match.objects.filter( league=league).values('day').distinct()] d_calendar = Match.calendar_to_dict(league) if request.GET.get('back_to_teams'): return redirect('league_details', league.id) context = {'league': league, 'd_calendar': d_calendar, 'days': days, 'matches': league_matches} return render(request, 'fantalega/matches.html', context) @login_required def match_details(request, league_id, day): dict_evaluated = {} league = get_object_or_404(League, pk=int(league_id)) fantaday = int(day) + league.offset if request.GET.get('back_to_calendar'): return redirect('matches', league.id) if request.GET.get('insert_dead_line'): return redirect('match_deadline', league.id, day) league_matches = league.matches.filter(day=int(day)) missing_lineups = [] if request.GET.get('calculate'): for match in league_matches: home_lineup = match.home_team.team_lineups.filter( day=int(day), league=league).first() visit_lineup = match.visit_team.team_lineups.filter( day=int(day), league=league).first() offset_day = int(day) + league.offset if Evaluation.objects.filter( day=offset_day, season=league.season).count() == 0: messages.error(request, 'day %s evaluations missing, import them!' % offset_day) return redirect('matches', league.id) if home_lineup and visit_lineup: h_home = LineupHandler(home_lineup, int(day), league) h_visit = LineupHandler(visit_lineup, int(day), league) home_pts = h_home.get_pts() + 2 visit_pts = h_visit.get_pts() home_goals, visit_goals = get_final(home_pts, visit_pts) data = lineups_data(home_goals, visit_goals) home_lineup.pts = home_pts home_lineup.save() visit_lineup.pts = visit_pts visit_lineup.save() if not h_home.new_list: # if all holders are evaluated dict_evaluated[match.home_team] = (h_home.holders, h_home.mod) else: dict_evaluated[match.home_team] = (h_home.new_list, h_home.mod) if not h_visit.new_list: dict_evaluated[match.visit_team] = (h_visit.holders, h_visit.mod) else: dict_evaluated[match.visit_team] = (h_visit.new_list, h_visit.mod) for lineup, prefix in [(home_lineup, 'h'), (visit_lineup, 'v')]: lineup.won = data.get("%sw" % prefix) lineup.matched = data.get("%sm" % prefix) lineup.lost = data.get("%sl" % prefix) lineup.goals_made = data.get("%sgm" % prefix) lineup.goals_conceded = data.get("%sgc" % prefix) lineup.save() else: if not home_lineup: missing_lineups.append(match.home_team.name) if not visit_lineup: missing_lineups.append(match.visit_team.name) if not missing_lineups: messages.success(request, 'All Lineup values are upgraded!') else: if len(missing_lineups) == league.team_set.count(): message = 'No lineups uploaded for day %s yet' % day else: message = 'Some Lineups are missing: %s' % \ ', '.join(missing_lineups) messages.error(request, message) return redirect('matches', league.id) context = {'league': league, 'matches': league_matches, 'day': day, 'dict_evaluated': dict_evaluated, 'fantaday': fantaday} return render(request, 'fantalega/match.html', context) @login_required def match_deadline(request, league_id, day): league = get_object_or_404(League, pk=int(league_id)) days = enumerate([d['day'] for d in Match.objects.filter( league=league).values('day').distinct()], 1) if request.GET.get('match_details'): return redirect('match_details', league.id, day) if request.method == "POST": form = MatchDeadLineForm(request.POST, initial={'day': day, 'days': days}) if form.is_valid(): f_day = form.cleaned_data['day'] f_dead_line = form.cleaned_data['dead_line'] for match in Match.objects.filter(league=league, day=f_day).all(): match.dead_line = f_dead_line match.save() messages.success(request, "Dead line for day %s stored!" % f_day) else: form = MatchDeadLineForm(initial={'day': day, 'days': days}) previous_dead_line = Match.get_dead_line(league, day) if previous_dead_line: messages.warning(request, "Dead line already set!") form.fields['dead_line'].initial = previous_dead_line else: messages.info(request, "Dead line doesn't exist!") return render(request, 'fantalega/deadline.html', {'form': form, 'league': league, 'day': day})
il form (fantalega/forms.py) sarà:
... class MatchDeadLineForm(forms.Form): def __init__(self, *args, **kwargs): self.dict_values = kwargs.pop('initial') super(MatchDeadLineForm, self).__init__(*args, **kwargs) m_day = self.dict_values.get('day') days = self.dict_values.get('days') self.fields['day'] = forms.ChoiceField(initial=m_day, label=u'day', choices=days, widget=forms.Select(), required=False) self.fields['dead_line'] = forms.DateTimeField(required=True)
Nota:
Per poter assegnare dinamicamente i valori alla choicefield dalla view,
è necessario utilizzare l’__init__ in modo da inizializzare i field con
i valori passati all’oggetto form, nel momento in cui esso viene creato.
# views.match_deadline ... if request.method == "POST": form = MatchDeadLineForm(request.POST, initial={'day': day, 'days': days})
Si estraggono cioè i valori da passare alla choicefield, nell’__init__,
utilizzando il metodo pop() sul dizionario kwargs, dopodichè
si invoca super() per inizializzare la classe padre del form.
class MatchDeadLineForm(forms.Form): def __init__(self, *args, **kwargs): self.dict_values = kwargs.pop('initial') super(MatchDeadLineForm, self).__init__(*args, **kwargs) ...
Nota:
In molte guide di django i form vengono
creati senza bisogno dell’__init__, ma creando i field direttamente con
attributi di classe, poichè i field sono intesi come “costanti” del form.
Il form deve essere importato nelle views ovviamente:
... from .forms import MatchDeadLineForm ...
Ora le template legate alle views suddette.
templates/matches.html
{% extends 'fantalega/base.html' %} {% load bootstrap3 %} {% load app_filters %} {% block content %} <h1>List of all matches for <font color="green">{{ league.name }}</font> </h1> <form action="#" method="get"> <input type="submit" class="btn" value="back to {{ league.name }} teams" name="back_to_teams"> </form> <table class="table table-striped" width="100%"> {% for day in days %} <tr> <th colspan="2"> <a href="{% url 'match_details' league.id day %}"> giornata {{ day }}</a> {% if league|need_calc:day %} {% if not league|has_pts:day %} <font color="green"> ....click to calculate</font> {% endif %} {% else %} <font color="purple">....lineups missing</font> {% endif %} </th> </tr> {% get_matches matches day as f_matches %} {% for match in f_matches %} <tr> <td align="left">{{ match.home_team.name }}</td> <td align="left">{{ match.visit_team.name }}</td> <td>{{ match.home_team|get_goals:day }}</td> <td>{{ match.visit_team|get_goals:day }}</td> </tr> {% endfor %} {% endfor %} </table> {% endblock %}
templates/match.html
{% extends 'fantalega/base.html' %} {% load bootstrap3 %} {% load app_filters %} {% block content %} <b><font color="green">{{ league.name }}</font>: <font color="purple">giornata {{ day }}</font></b><br> <br> <form action="#" method="get"> <input type="submit" class="btn" value="back to calendar" name="back_to_calendar"> </form> {% if user.is_staff %} <form> <input type="submit" class="btn" value="calculate" name="calculate"> <input type="submit" class="btn" value="insert deadline" name="insert_dead_line"> </form> {% endif %} <div id="container" style="width:70%;"> <table class="table table-striped" width="100%"> {% if matches %} {% for match in matches %} <tr> <td><a href="{% url 'team_details' league.id match.home_team.id %}"> {{ match.home_team }}</a></td> <td><a href="{% url 'team_details' league.id match.visit_team.id %}"> {{ match.visit_team }}</a></td> <td>{{ match.home_team|get_goals:day }}</td> <td>{{ match.visit_team|get_goals:day }}</td> </tr> {% endfor %} </table> </div> <div id="left" style="float:left; width:30%;"> {% for match in matches %} <font color="green"><b>{{match.home_team}}</b></font><br> {% for player in dict_evaluated|get_evaluated:match.home_team %} {{player.name|is_defender}}: {{player|get_fvote:fantaday}}<br> {% endfor %} <b>mod:{{dict_evaluated|get_defense_mod:match.home_team}}</b><br> <b>total: {{match.home_team|get_total:day}}</b><br><br> {% endfor %} </div> <div id="right" style="float:left; width:30%;"> {% for match in matches %} <font color="green"><b>{{match.visit_team}}</b></font><br> {% for player in dict_evaluated|get_evaluated:match.visit_team %} {{player.name|is_defender}}: {{player|get_fvote:fantaday}}<br> {% endfor %} <b>mod:{{dict_evaluated|get_defense_mod:match.visit_team}}</b><br> <b>total: {{match.visit_team|get_total:day}}</b><br><br> {% endfor %} </div> {% else %} <p>No match found.</p> {% endif %} {% endblock %}
templates/deadline.html
{% extends 'fantalega/base.html' %} {% load bootstrap3 %} {% block content %} <form action="#" method="get"> <input type="submit" class="btn" value="back to match day {{ day }}" name="match_details"> </form> <form method="POST" class="form"> {% csrf_token %} {% bootstrap_field form.day %} {% bootstrap_field form.dead_line %} {% buttons %} <button type="submit" class="btn btn-primary"> {% bootstrap_icon "save" %}Save dead line</button> {% endbuttons %} </form> {% endblock %}
Avviare il server e recarsi nella pagina relativa alle leghe:
http://127.0.0.1:8000/fantalega/leagues/
Cliccando su una delle due leghe, si vedranno una serie di pulsanti, tra
cui ‘create calendar’. E’ necessario associare a questo pulsante,
della template fantalega/league.html,
<input type="submit" class="btn" value="Create Calendar" name="calendar">
il codice necessario alla creazione del calendario.
Aggiungere nella view ‘league_details’ quanto segue:
if request.GET.get('calendar'): return redirect('calendar', league.id)
in modo che la view diventi:
def league_details(request, league_id): league = get_object_or_404(League, pk=int(league_id)) league_teams = league.team_set.all() if request.GET.get('calendar'): return redirect('calendar', league.id) context = {'league': league, 'teams': league_teams, 'user': request.user} return render(request, 'fantalega/league.html', context)
premendo il pulsante, viene generato un oggetto dizionario QueryDict:
<QueryDict: {u'calendar': [u'Create Calendar']}>
se premessimo il pulsante ‘Start Auction’:
<QueryDict: {u'auction': [u'Start Auction']}>
Utilizzando il metodo get() con chiave ‘calendar’, si ottiene, nel caso
venga trovata nel dizionario, una lista con i parametri del bottone della template.
Non interessano affatto i paramteri del bottone, ma solo che sia presente la
chiave stessa, in caso negativo get() non ritornerà nulla.
Con un semplice if il gioco è fatto e la palla sarà passata alla view ‘calendar’,
grazie a
return redirect('calendar', league.id)
‘calendar’ è il reverse name utilizzato da redirect per trovare la url necessaria.
Aggiungere quindi nel file fantalega/urls.py la url necessaria
... # calendar url url(r'^leagues/(?P<league_id>[0-9]+)/calendar$', views.calendar, name='calendar'), ]
la view ‘calendar’ sarò così composta:
... @login_required def calendar(request, league_id): league = get_object_or_404(League, pk=int(league_id)) if request.GET.get('back_to_teams'): return redirect('league_details', league.id) league_matches = league.matches.order_by('day') days = [d['day'] for d in Match.objects.filter( league=league).values('day').distinct()] if league_matches: messages.warning(request, 'Calendar already exists!!') else: league_teams = [t for t in league.team_set.all()] cal = create_season(teams=league_teams, num=league.rounds) for record in cal: day, home_team, visit_team = record Match.objects.create(league=league, day=day, home_team=home_team, visit_team=visit_team) messages.success(request, 'Calendar done!') league_matches = league.matches.order_by('day') context = {'matches': league_matches, 'league': league, 'days': days} return render(request, 'fantalega/calendar.html', context)
nella view, si fa riferimento ad una funzione create_season
cal = create_season(teams=league_teams, num=league.rounds)
Creare sotto fantalega una sottodirectory scripts con all’interno un modulo con il
codice necessario alla creazione e gestione del calendario.
fantalega/scripts/calendar.py:
from copy import copy from random import shuffle def first_step_season(teams): """ first_step_season(teams) -> list create a matrix to fill with teams""" matrix = [] counter = 0 while counter < len(teams): matrix.append([None] * len(teams)) counter += 1 matrix[0] = teams # header # reversed header without th last team row2 = copy(teams) row2.pop() row2.reverse() matrix[1][0:(len(teams) - 1)] = row2[0:(len(teams) - 1)] # Table composition: first step i = 1 while i < len(teams): k = 1 for item in matrix[i]: try: matrix[i + 1][k] = item matrix[i + 1][0] = matrix[i + 1][(len(teams) - 1)] matrix[i + 1][(len(teams) - 1)] = None k += 1 except IndexError: break i += 1 # Table composition: second step row_m = 1 while row_m < len(teams): for item_a in matrix[0]: for item_b in matrix[row_m]: if matrix[0].index(item_a) == matrix[row_m].index(item_b): if item_a == item_b: matrix[row_m][matrix[row_m].index(item_b)] = \ teams[-1] matrix[row_m][(len(teams) - 1)] = item_b row_m += 1 cal = [] day = 1 while day < len(teams): first_round = [] for team1 in matrix[0]: for team2 in matrix[day]: if matrix[0].index(team1) == matrix[day].index(team2): if team2 not in first_round or team1 not in first_round: if team1.home is True: first_round.append(team1) first_round.append(team2) cal.append((day, team1, team2)) team1.set_visit() team2.set_home() else: first_round.append(team2) first_round.append(team1) cal.append((day, team2, team1)) team1.set_home() team2.set_visit() day += 1 return cal def second_step_season(cal, teams): """ second_step_season(cal, teams) -> list Create the second round for season """ scond_round_cal = [] for match in cal: n, team1, team2 = match scond_round_cal.append((int(n) + len(teams) - 1, team2, team1)) return scond_round_cal def create_season(teams, num): """ create_season(teams, num) -> list Iterable is the list of teams present in db num is the number of round of the tournament """ shuffle(teams) for team in teams: team.home = True # Init for home_visit team calculation cal = first_step_season(teams) rounds = 1 full_cal = cal while rounds < num: cal = second_step_season(cal, teams) full_cal += cal rounds += 1 return full_cal
Ora basta importare tale modulo nelle views: fantalega/views.py
from fantalega.scripts.calendar import create_season ...
Per far sì che venga rispettata la regola del ‘in_casa’, ‘fuori_casa’,
nel model Team è stato aggiunto un flag ‘home’, che, durante la generazione del
calendario verrà settato alternativamente a True e False.
Nel model Team, quindi aggiungere:
class Team(models.Model): ... def set_home(self): self.home = True def set_visit(self): self.home = False
Manca la template calendar.html che avrà questo codice:
{% extends 'fantalega/base.html' %} {% load bootstrap3 %} {% load app_filters %} {% block content %} <b>List of all matches for <font color="green"> {{ league.name }}</font></b><br><br> <form action="#" method="get"> <input type="submit" class="btn" value="back to {{ league.name }} teams" name="back_to_teams"> </form> {% if matches %} <table class="table table-striped" width="100%"> {% for day in days %} <tr> <th colspan="2"><font color="purple">giornata {{ day }}</font></th> </tr> {% get_matches matches day as f_matches %} {% for match in f_matches %} <tr> <td align="left">{{ match.home_team.name }}</td> <td align="left">{{ match.visit_team.name }}</td> </tr> {% endfor %} {% endfor %} </table> {% else %} <font color="red"><b>No calendar/matches found.</b></font> {% endif %} {% endblock %}
Per come è strutturata la template e per come lo è il model Match,
si ha la necessità di filtrare i match in base alla giornata (day)
E’ necessario creare quindi un custom_tag che viene richiamato in
questa riga
{% get_matches matches day as f_matches %}
Aprire quindi il file fantalega/templatetags/app_filters.py e aggiungere:
@register.assignment_tag def get_matches(matches, day): return matches.filter(day=day)
La fase di creazione calendario è sistemata.
Nella template league.html è stato inserito anche un pulsante ‘view calendar’,
quindi sistemiamo la view league_details in modo che, premendo tale pulsante
si possa accedere alla pagina desiderata.
la view league_details diventerà:
@login_required def league_details(request, league_id): league = get_object_or_404(League, pk=int(league_id)) league_teams = league.team_set.all() if request.GET.get('calendar'): return redirect('calendar', league.id) if request.GET.get('matches'): # add for view calendar return redirect('matches', league.id) # add for view calendar context = {'league': league, 'teams': league_teams, 'user': request.user} return render(request, 'fantalega/league.html', context)
il redirect richiamerà la url con nome ‘matches’ che richiamerà la view ‘matches’, ecc.
Per comodità di gestione dei dati, sono stati aggiunti alcuni metodi al model Match:
class Match (models.Model): ... @staticmethod def calendar_to_dict(league): matches = league.matches.all() d = {match.day: [] for match in matches} for match in matches: day = match.day home = match.home_team visit = match.visit_team values = d[day] values.append(home) values.append(visit) return d def is_played(self): home_lineup = self.home_team.team_lineups.filter(day=self.day).first() visit_lineup = self.visit_team.team_lineups.filter(day=self.day).first() if not home_lineup or not visit_lineup: return False home_players = home_lineup.players.count() visit_players = visit_lineup.players.count() evaluations = Evaluation.objects.filter( day=self.day + self.league.offset).count() if home_players and visit_players and evaluations: return True else: return False @staticmethod def get_dead_line(league, day): match_query_set = Match.objects.filter(league=league, day=day) for match in match_query_set.all(): if not match.dead_line: return None return match_query_set.first().dead_line
il primo metodo ‘calendar_to_dict’ ritorna un comodo dizionario rappresentante il
calendario, con le giornate come chiave.
Il secondo metodo ‘is_played’ ritorna True se la partita è stata disputata
L’ultimo ‘get_dead_line’ restituisce il termine massimo di consegna formazione.
Nelle template matches.html e match.html vengono utilizzati alcuni custom_filter, da
inserire nel file fantalega/templatetags/app_filters.py:
... @register.filter(name='need_calc') def need_calc(league, day): try: for team in league.team_set.all(): queryset_lineup = team.team_lineups.filter(day=day) if not queryset_lineup.first(): return False return True except AttributeError: return False @register.filter(name='has_pts') def has_pts(league, day): lineups = [Lineup.objects.filter(team=team, league=league, day=day).first() for team in league.team_set.all() if Lineup.objects.filter(team=team, league=league, day=day).first()] calculated_lineups = [l.pts for l in lineups if l.pts > 0] return len(calculated_lineups) == len(league.team_set.all()) @register.filter(name='get_goals') def get_goals(team, day): try: lineup = team.team_lineups.filter(day=int(day)).first() if lineup: return lineup.goals_made else: return "ND" except AttributeError: return "NL" @register.filter(name='get_evaluated') def get_evaluated(dict_evaluated, key): data = dict_evaluated.get(key) if data: return data[0] else: return [] @register.filter(name='is_defender') def is_defender(player): obj_player = Player.objects.filter(name=player).first() if 200 < obj_player.code < 500: return mark_safe('<font color="#cc66ff">%s</font>' % player) else: return player @register.filter(name='get_fvote') def get_fvote(player, day): evaluation = player.player_votes.filter(day=int(day)).first() if evaluation: return '%s' % float(evaluation.fanta_value) else: return '0.0' @register.filter(name='get_defense_mod') def get_defense_mod(dict_evaluated, key): data = dict_evaluated.get(key) if data: return data[1] else: return 0.0 @register.filter(name='get_total') def get_total(team, day): lineup = team.team_lineups.filter(day=int(day)).first() return lineup.pts if lineup else '0.0: Lineup missing'
Salvare ora gli avanzamenti su github:
git add --all
git commit -m "Match added"
git push -u origin master
articoli precedenti
0 – indice
1 – Virtualenv e Git
2 – Models: Season
3 – Admin: Login e Logout
4 – Models: League
5 – Models: Team
articoli successivi
7 – Models: Player
8 – Asta
9 – Models: Lineup
10 – Models: Trade
11 – Asta di riparazione
12 – Classifica
Commenti recenti