Home > Django > djangofantalega: model Match

djangofantalega: model Match

14 Novembre 2016

6 – Models: Match

Per creare il calendario di Lega, utilizziamo il model Match.
La relazione tra squadra (Team) e Match è one-to-many.

add_match

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

Categorie:Django Tag:
I commenti sono chiusi.