Archivio

Archivio autore

Django: in_memory_zip di più in_memory_files

22 novembre 2016 Commenti chiusi

Il concetto base è di non creare file su disco,
ma utilizzare file in_memory.
Dovranno essere “volanti” quindi, sia i file
da zippare, sia il file zip stesso, che verrà
in seguito servito da django.

Semplifichiamo il tutto:

ho una funzione che genera lo zip:

from zipfile import ZipFile
from StringIO import StringIO


def zip_files():
    zip_sio = StringIO()
    zip_file = ZipFile(zip_sio, 'w')

    for line in ['a', 'b', 'c', 'd']:
        name = 'file_{}.txt'.format(line)
        outfile = StringIO()
        outfile.write(line)
        zip_file.writestr(name, outfile.getvalue())
        outfile.close()

    zip_file.close()
    zip_sio_data = zip_sio.getvalue()
    zip_sio.close()
    return zip_file, zip_sio_data

chiaramente potrebbe essere più complesso, ad esempio una
funzione che prenda in ingresso un file, lo splitti in
più parti le quali vengano restituite sotto forma di zip…

from zipfile import ZipFile
from StringIO import StringIO


def zip_files(file_in):
    for line in file_in.readlines():
        ...

In pratica utilizziamo degli oggetti StringIO, ovvero oggetti File-like,
all’interno dei quali possiamo scrivere delle stringhe, con il metodo ‘write’,
proprio come gli oggetti file.
Con il metodo ‘getvalue’, otteniamo tutto il contenuto dell’oggetto, che una
volta chiuso con ‘close’, non è più accessibile. Per questo motivo lo inseriamo
all’interno del file-like object zip prima di chiuderlo.
Gli stessi dati del file zip, li memorizziamo prima di chiudere lo stesso flie-like
zip, in modo da poterli avere disponibili ad esempio nella view di django
prima di costruire il ‘response’…

comunque nel nostro esempio avremo:

>>> z, data = zip_files()
>>> [o.filename for o in z.filelist]
['file_a.txt', 'file_b.txt', 'file_c.txt', 'file_d.txt']
>>> len(data)
410

Come già detto, ‘data’ tornerà utile quando con django andremo a servire il file zip
per il download, ad es.:

...
def a_view(request):
    if request.method == "POST":
        form = a_form(request.POST, request.FILES)
        if form.is_valid():
            file_in = request.FILES['a_form_file_field']
            zip_file, data = zip_file()  # or zip_file(file_in) if you process a file
            response = HttpResponse(data,
                                    content_type="application/x-zip-compressed")
            response['Content-Disposition'] = 'attachment; filename=a_file_zip.zip'
            return response
    else:
        form = a_form()
    return render(request, 'a_template.html', {'form': form})

Con questo sistema, non scriveremo nessun file sul server.

Categorie:Django, python Tag: ,

djangofantalega 1.0

15 novembre 2016 Commenti chiusi

Djangofantalega

Djangofantalega è un progetto realizzato con
python e django.

Si tratta di un gestore di fantalega, dalla A alla Z.
Si creano: stagione, lega, squadre.
Si effettua l’asta sui giocatori e composte le squadre,
si crea il calendario e si dà inizio al campionato.
Ad ogni giornata si consegnano le formazioni e, dopo
la pubblicazione dei voti ufficiali, si procede
con i conteggi.

INDICE

1 – Virtualenv e Git
2 – Models: Season
3 – Admin: Login e Logout
4 – Models: League
5 – Models: Team
6 – Models: Match
7 – Models: Player
8 – Asta
9 – Models: Lineup
10 – Models: Trade
11 – Asta di riparazione
12 – Classifica

Categorie:Django Tag:

djangofantalega: Classifica

15 novembre 2016 Commenti chiusi

12 – Classifica

Per visualizzare la classifica delle squadre della lega,
Colleghiamo il pulsante già presente nella template league.html
alla view league_details:

...
@login_required
def league_details(request, league_id):
    ...
    if request.GET.get('chart'):
        return redirect('chart', league.id)
    context = {'league': league, 'teams': league_teams,
               'days': days, 'user': request.user}
    return render(request, 'fantalega/league.html', context)
...

inserire la url:

...
    # chart url
    url(r'^leagues/(?P<league_id>[0-9]+)/chart$',
        views.chart, name='chart'),
]

Aggiungere nel file fantalega\views.py la nuove vista:

...

@login_required
def chart(request, league_id):
    league = get_object_or_404(League, pk=int(league_id))
    league_teams = league.team_set.all()
    if request.GET.get('back_to_teams'):
        return redirect('league_details', league.id)
    lineups_values = []
    for team in league_teams:
        lineups = [lineup for lineup in
                   Lineup.objects.filter(league=league, team=team).all()
                   if lineup.pts]
        won = sum([lineup.won for lineup in lineups])
        matched = sum([lineup.matched for lineup in lineups])
        lost = sum([lineup.lost for lineup in lineups])
        gm = sum([lineup.goals_made for lineup in lineups])
        gc = sum([lineup.goals_conceded for lineup in lineups])
        tot_pts = sum([lineup.pts for lineup in lineups])
        tot = won * 3 + matched
        lineups_values.append((team, tot, won, matched, lost, gm, gc, tot_pts))
    lineups_values.sort(key=lambda x: (x[1], x[6]), reverse=True)
    context = {'league': league, 'lineups_values': lineups_values}
    return render(request, 'fantalega/chart.html', context)

la template
fantalega/templates/fantalega/chart.html

{% extends 'fantalega/base.html' %}
{% load bootstrap3 %}

{% block content %}
	<h1><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%">
      <tr>
          <th>team</th>
          <th>pts</th>
          <th>won</th>
          <th>matched</th>
          <th>lost</th>
          <th>goals made</th>
          <th>goals conceded</th>
          <th>total pts</th>
      </tr>
      {% for values in lineups_values %}
        <tr>
              <td>{{ values.0.name }}</td>
              <td>{{ values.1 }}</td>
              <td>{{ values.2 }}</td>
              <td>{{ values.3 }}</td>
              <td>{{ values.4 }}</td>
              <td>{{ values.5 }}</td>
              <td>{{ values.6 }}</td>
              <td>{{ values.7 }}</td>
      </tr>
      {% endfor %}
    </table>

{% endblock %}

Per visualizzare la classifica basterà premere il pulsante ‘view chart’

Salvare su github:

git add --all
git commit -m "Chart 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
6 – Models: Match
7 – Models: Player
8 – Asta
9 – Models: Lineup
10 – Models: Trade
11 – Asta di riparazione

Categorie:Django Tag:

djangofantalega: Asta di riparazione

15 novembre 2016 Commenti chiusi

11 – Asta di riparazione

Il 31 Gennaio scade il vero mercato di riparazione. Da lì in avanti
si organizza l’asta di riparazione.
Una squadra deve avere la possibilità di vendere un giocatore ed incrementare
il suo budget.

Nella template di ogni singola squadra è presente un pulsante ‘sell players for repair session’
Colleghiamo il pulsante alla view team_details:

...
@login_required
def team_details(request, league_id, team_id):
    ...
    if request.GET.get('sale'):
        return redirect('sale', league.id, team.id)
    return render(request, 'fantalega/team.html', context)

inseriamo la url:

...
    # player sale url
    url(r'^leagues/(?P<league_id>[0-9]+)/teams/'
        r'(?P<team_id>[0-9]+)/player_sale$', views.sale, name='sale'),
]

Aggiungere nel file fantalega\views.py la nuove vista:

...
@login_required
def sale(request, league_id, team_id):
    league = get_object_or_404(League, pk=int(league_id))
    team = get_object_or_404(Team, pk=int(team_id))
    team_players = [(p.code, "%s [%s]" % (p.name, p.role))
                    for p in team.player_set.all()]
    form = TeamSellPlayersForm(request.POST,
                               initial={'team_players': team_players,
                                        'team': team, 'league': league})
    if request.GET.get('back_to_team_details'):
        return redirect('team_details', league.id, team.id)
    if request.method == "POST":
        if form.is_valid():
            players_to_sell = [Player.get_by_code(int(code),
                                                  season=league.season)
                               for code in form.cleaned_data['team_players']]

            gain = 0
            for player in players_to_sell:
                team.player_set.remove(player)
                gain += player.cost
                team.budget += player.cost
                team.save()
            messages.success(request, "Players sold correctly! You gain: %s" %
                             gain)
            return redirect('team_details', league.id, team.id)
    return render(request, 'fantalega/sell.html',
                  {'form': form, 'team_players': team_players, 'team': team,
                   'league': league})

il form che permetterà la vendita di un giocatore sarà:

...
class TeamSellPlayersForm(forms.Form):
    def __init__(self, *args, **kwargs):
        self.dict_values = kwargs.pop('initial')
        super(TeamSellPlayersForm, self).__init__(*args, **kwargs)
        self.fields['team_players'] = forms.MultipleChoiceField(
            choices=self.dict_values['team_players'],
            widget=forms.CheckboxSelectMultiple())

e andrà importato nel file fantalega/views.py:

from .forms import TeamSellPlayersForm
...

Ora la template:

templates/sell.html

{% extends 'fantalega/base.html' %}
{% load bootstrap3 %}

{% block content %}
    <b>Upload lineup for <font color="green">{{ team.name }}</font></b>
    <br><br>
    <form action="#" method="get">
    <input type="submit" class="btn"
           value="back to {{ team.name }} team" name="back_to_team_details">
    </form>

    <form method="POST" class="form">
      {% csrf_token %}
      {% bootstrap_field form.team_players %}
        {% buttons %}
            <button type="submit" class="btn btn-primary">
                {% bootstrap_icon "save" %}Sell players</button>
        {% endbuttons %}
    </form>
{% endblock %}

Per riacquistare un giocatore è possibile utilizzare il form utilizzato
per la prima Asta.

Salvare ora gli avanzamenti su github:

git add --all
git commit -m "Repair Auction 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
6 – Models: Match
7 – Models: Player
8 – Asta
9 – Models: Lineup
10 – Models: Trade

articoli successivi
12 – Classifica

Categorie:Django Tag:

djangofantalega: model Trade

15 novembre 2016 Commenti chiusi

10 – Models: Trade

Le squadre possono scambiarsi giocatori tra loro.
Il tetto delle operazioni è dato dal parametro max_trades del modello League.
Tale parametro viene mutuato da Team e va a scalare ad ogni operazione effettuata.
Per tenere traccia delle operazioni di mercato, aggiungeremo un modello
a quelli già esistenti.

add_trade

fantalega/models.py

...
class Trade(models.Model):
    league = models.ForeignKey(League, related_name='trades')
    player = models.ForeignKey(Player)
    team = models.ForeignKey(Team)
    direction = models.CharField(max_length=3)  # IN or OUT value

    def __unicode__(self):
        return "[%s] %s: %s" % (self.direction, self.team.name,
                                self.player.name)

Trade è legato a League, Player e Team da relazioni many-to-one.
Il fatto che un giocatore sia in uscita o in entrata, è definito
dal campo ‘direction’.

Aggiornare il database inserendo le nuove tabelle.

(venv) >python manage.py makemigrations
Migrations for 'fantalega':
  fantalega\migrations\0007_trade.py:
    - Create model Trade

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.0007_trade... OK

Aggiungere per prima cosa le urls relative ai mercati (Trade) nel file fantalega\urls.py:

...
    # trade urls
    url(r'^leagues/(?P<league_id>[0-9]+)/trades/$',
        views.trades, name='trades'),
    url(r'^leagues/(?P<league_id>[0-9]+)/teams/(?P<team_id>[0-9]+)/trade$',
        views.trade, name='trade'),
]

Abilitare le interfacce di admin in fantalega\admin.py:

...
from .models import Trade
from django.utils.html import format_html

...
class TradeAdmin(admin.ModelAdmin):
    list_display = ('direction', 'colored_player', 'team')
    list_filter = ('direction', 'team')
    list_per_page = 20

    @staticmethod
    def colored_player(obj):
        colour = '013ADF' if obj.direction == 'IN' else 'FF0080'
        return format_html('<span style="color: #{};">{}</span>',
                           colour, obj.player.name)
...
admin.site.register(TradeAdmin)

Aggiungere nel file fantalega\views.py le nuove viste:

...
from .models import Trade
...
@login_required
def trades(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)
    context = {'trades': Trade.objects.all(), 'league': league}
    return render(request, 'fantalega/trades.html', context)


@login_required
def trade(request, league_id, team_id):
    league = get_object_or_404(League, pk=int(league_id))
    team = get_object_or_404(Team, pk=int(team_id))
    if request.GET.get('back_to_team_details'):
        return redirect('team_details', league.id, team.id)
    team_players = [(p.code, "%s (%s)" % (p.name, p.role))
                    for p in team.player_set.all()]
    others = [(p.code, "%s (%s) --- %s" % (p.name, p.role, p.team.name))
              for p in Player.objects.order_by('name') if p.team and p not
              in team.player_set.all()]
    if request.method == "POST":
        form = TradeForm(request.POST, initial={'players': team_players,
                                                'others': others,
                                                'league': league})
        if form.is_valid():
            player_out_code = form.cleaned_data['player_out']
            player_out = Player.get_by_code(int(player_out_code),
                                            season=league.season)
            player_in_code = form.cleaned_data['player_in']
            player_in = Player.get_by_code(int(player_in_code),
                                           season=league.season)
            team.max_trades -= 1
            other_team = player_in.team
            other_team.max_trades -= 1
            if team.max_trades >= 0 and other_team.max_trades >= 0:
                player_in.team = team
                player_out.team = other_team
                if player_in.role == player_out.role:
                    player_in.save()
                    player_out.save()
                    team.save()
                    other_team.save()
                    Trade.objects.create(player=player_out, team=team,
                                         direction="OUT", league=league)
                    Trade.objects.create(player=player_in, team=team,
                                         direction="IN", league=league)
                    Trade.objects.create(player=player_out, team=other_team,
                                         direction="IN", league=league)
                    Trade.objects.create(player=player_in, team=other_team,
                                         direction="OUT", league=league)
                    messages.success(request,
                                     'Trade operation %s: --> '
                                     '[OUT] %s [IN] %s stored!' %
                                     (team, player_out.name, player_in.name))
                    messages.success(request,
                                     'Trade operation %s: --> '
                                     '[OUT] %s [IN] %s stored!' %
                                     (other_team, player_in.name,
                                      player_out.name))
                else:
                    messages.error(request,
                                   'Players MUST have the same role, aborted!')

            else:
                messages.error(request,
                               "Not enough trade operations: "
                               "%s [%s] and %s [%s]" %
                               (team.name, team.max_trades, other_team.name,
                                other_team.max_trades))
            return redirect('team_details', league.id, team.id)
    else:
        form = TradeForm(initial={'players': team_players, 'others': others,
                                  'league': league})
    return render(request, 'fantalega/trade.html',
                  {'form': form, 'players': team_players, 'others': others,
                   'team': team, 'league': league})

il form che permetterà di effettuare un’operazione di mercato sarà:

...
class TradeForm(forms.Form):
    def __init__(self, *args, **kwargs):
        self.dict_values = kwargs.pop('initial')
        super(TradeForm, self).__init__(*args, **kwargs)
        self.fields['player_out'] = forms.ChoiceField(
            label=u'OUT', choices=self.dict_values['players'],
            widget=forms.Select(), required=False)
        self.fields['player_in'] = forms.ChoiceField(
            label=u'IN', choices=self.dict_values['others'],
            widget=forms.Select(), required=False)

e andrà importato nel file fantalega/views.py:

from .forms import TradeForm
...

Il bottone ‘new trade’ che appare nella template team.html, va
abilitato nella view team_details:

...
@login_required
def team_details(request, league_id, team_id):
    ...
    if request.GET.get('new trade'):
        return redirect('trade', league.id, team.id)
    return render(request, 'fantalega/team.html', context)

mentre il bottone ‘view trades’ che appare nella template league.html, va
abilitato nella view league_details:

...
@login_required
def league_details(request, league_id):
    ...
    if request.GET.get('trades'):
        return redirect('trades', league.id)
    ...
    return render(request, 'fantalega/league.html', context)

Le templates saranno invece:
fantalega/templates/fantalega/trades.html

{% extends 'fantalega/base.html' %}

{% block content %}
    <b>List of all trade operations for <font color="green">
        {{ league.name }}</font></b>
    <br><br>
    <form action="#" method="get">
    <input type="submit" class="btn"
           value="back to {{ league.name }} details" name="back_to_teams">
    </form>

    {% if trades %}
        <table class="table table-striped" width="100%">
          <tr>
              <th>in/out</th>
              <th>name</th>
              <th>team</th>
          </tr>
          <tr>
          {% for trade in trades %}
              <td>{{ trade.direction }}</td>
              <td><a href="{% url 'team_details' league.id trade.team.id %}">
                  {{ trade.team.name }}</a></td>
              <td><a href="{% url 'player_details' trade.player.id %}">
                  {{ trade.player.name }}</a></td>
          </tr>
          {% endfor %}
        </table>
    {% else %}
        <font color="red"><b>No trade operations found.</b></font>
    {% endif %}
{% endblock %}

fantalega/templates/fantalega/trade.html

{% extends 'fantalega/base.html' %}
{% load bootstrap3 %}

{% block content %}
    <b>New Trade Operation for team: <font color="green">{{ team.name }}</font></b>
    <br><br>
    <form action="#" method="get">
    <input type="submit" class="btn"
           value="back to {{ team.name }} team" name="back_to_team_details">
    </form>

    <form method="POST" class="form">
		{% csrf_token %}
        {% bootstrap_form form %}
            {% buttons %}
                <button type="submit" class="btn btn-primary">
                {% bootstrap_icon "save" %} Submit</button>
			{% endbuttons %}
    </form>
{% endblock %}

Prima che venga salvata l’operazione di scambio, verranno controllati:
– i ruoli dei giocatori (devono essere gli stessi)
– il numero di operazioni rimaste.

Salvare gli avanzamenti su github:

git add --all
git commit -m "Trade 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
6 – Models: Match
7 – Models: Player
8 – Asta
9 – Models: Lineup

articoli successivi
11 – Asta di riparazione
12 – Classifica

Categorie:Django Tag:

djangofantalega: model Lineup

14 novembre 2016 Commenti chiusi

9 – Models: Lineup

Creare il Model Lineup.
Lineup (formazione) è in relazione many-to-one con Team,
ma è anche in relazione con Player.
Una formazione (Lineup) contiene molti giocatori (Player), ma un
giocatore, può far parte di più formazioni;
la relazione Player-Lineup è quindi di tipo many-to-many.

add_lineup

Come già fatto in precedenza, si utilizzerà una association-table per la relazione ManytoMany
in modo da poter sfruttare l’attributo ‘position’, poichè la posizione di un giocatore
in panchina, è determinante quando si usa il cambio modulo.

Aggiungere quindi nel file fantalega/models.py i models necessari:

...
class Lineup (models.Model):
    league = models.ForeignKey(League, related_name='league_lineups')
    team = models.ForeignKey(Team, related_name='team_lineups')
    players = models.ManyToManyField(Player, through='LineupsPlayers',
                                     related_name='player_lineups')
    timestamp = models.DateTimeField()
    day = models.IntegerField()
    pts = models.FloatField(null=True)
    won = models.IntegerField(null=True)
    matched = models.IntegerField(null=True)
    lost = models.IntegerField(null=True)
    goals_made = models.IntegerField(null=True)
    goals_conceded = models.IntegerField(null=True)

    def get_players_by_position(self):
        lineup_players = LineupsPlayers.query.filter(lineup=self.id).order_by(
            LineupsPlayers.position).all()
        if lineup_players:
            return [rec.player for rec in lineup_players]

    def __unicode__(self):
        return "[%s] %s" % (self.day, self.team.name)


# M2M secondary Association object
class LineupsPlayers(models.Model):
    player = models.ForeignKey(Player, on_delete=models.CASCADE)
    lineup = models.ForeignKey(Lineup, on_delete=models.CASCADE)
    position = models.IntegerField()

    class Meta:
        verbose_name_plural = 'Lineup-Player Associations'

    @staticmethod
    def get_sorted_lineup(lineup):
        return LineupsPlayers.objects.filter(
            lineup=lineup).order_by('position').all()

Aggiornare il database inserendo le nuove tabelle.

(venv) >python manage.py makemigrations
Migrations for 'fantalega':
  fantalega\migrations\0006_auto_20161111_1103.py:
    - Create model Lineup
    - Create model LineupsPlayers
    - Add field players to lineup
    - Add field team to lineup

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.0006_auto_20161111_1103... OK

Sistemare l’interfaccia di admin per i nuovi modelli:
file fantalega/admin.py:

# noinspection PyUnresolvedReferences
...
from .models import Lineup, LineupsPlayers


# Register your models here.
class LineupPlayersInline(admin.TabularInline):
    model = Lineup.players.through
    extra = 0
    classes = ('collapse',)
	

class LineupAdmin(admin.ModelAdmin):
    inlines = [LineupPlayersInline, ]
    ordering = ('day', )
    list_display = ('day', 'team', 'pts')
    list_filter = ('day', 'team')
    list_per_page = 20

	
class LineupsPlayersAdmin(admin.ModelAdmin):
    ordering = ('position', )
    list_display = ('position', 'colored_player', 'get_team_name', )
    list_filter = ('lineup__day', 'lineup__team__name')
    list_per_page = 21

    @staticmethod
    def get_team_name(obj):
        return obj.lineup.team.name

    @staticmethod
    def colored_player(obj):
        colour = '013ADF' if obj.position <= 11 else 'FF0080'
        return format_html('<span style="color: #{};">{}</span>',
                           colour, obj.player.name)


...
admin.site.register(Lineup, LineupAdmin)
admin.site.register(LineupsPlayers, LineupsPlayersAdmin)

Ora aggiungere gli urls inerenti Lineup nel file fantalega\urls.py:

...
    # lineup urls
    url(r'^leagues/(?P<league_id>[0-9]+)/teams/'
        r'(?P<team_id>[0-9]+)/lineup/upload$', views.upload_lineup,
        name='upload_lineup'),
    url(r'^leagues/(?P<league_id>[0-9]+)/teams/(?P<team_id>[0-9]+)'
        r'/lineup/(?P<day>[0-9]+)/$',
        views.lineup_details, name='lineup_details'),
    url(r'^leagues/(?P<league_id>[0-9]+)/teams/(?P<team_id>[0-9]+)'
        r'/lineup/(?P<day>[0-9]+)/edit$',
        views.lineup_edit, name='lineup_edit'),
]

Aggiungere nel file fantalega\views.py le nuove viste ‘upload_lineup’,
‘lineup_details’ e ‘lineup_edit’:

...
from .models import Lineup, LineupsPlayers
from django.utils import timezone  # this is important!
...
@login_required
def upload_lineup(request, league_id, team_id, day=None):
    league = get_object_or_404(League, pk=int(league_id))
    modules = [(1, '343'), (2, '352'), (3, '442'), (4, '433'), (5, '451'),
               (6, '532'), (7, '541')]
    team = get_object_or_404(Team, pk=int(team_id))
    if request.GET.get('back_to_team_details'):
        return redirect('team_details', league.id, team.id)
    team_players = [(p.code, "%s [%s]" % (p.name, p.role))
                    for p in team.player_set.all()]
    if request.method == "POST":
        form = UploadLineupForm(request.POST,
                                initial={'players': team_players, 'team': team,
                                         'modules': modules, 'day': day,
                                         'league': league})
        if form.is_valid():
            day = form.cleaned_data['day']
            lineup = Lineup.objects.filter(team=team, day=day,
                                           league=league).first()
            if lineup:
                messages.error(request, 'Lineup already exists!')
                return redirect('team_details', league_id, team_id)

            holders = [Player.get_by_code(int(code), season=league.season)
                       for code in form.cleaned_data['holders']]
            substitutes = [Player.get_by_code(int(code), season=league.season)
                           for code in [form.cleaned_data['substitute_%s' % n]
                           for n in range(1, 11)]]
            error = form.check_holders()
            if error:
                messages.error(request, error)
            else:
                dead_line = Match.objects.filter(
                    league=league, day=day).first().dead_line
                now = timezone.now()
                if now > dead_line:
                    messages.error(request, "You are out of time!")
                    messages.info(request, "Getting the previous Lineup")
                    lineup = Lineup.objects.filter(
                        team=team, day=(day - 1), league=league).first()
                    holders = [p for p in lineup.players.all()[:11]]
                    substitutes = [p for p in lineup.players.all()[11:]]
                lineup = Lineup.objects.create(team=team, day=day,
                                               league=league, timestamp=now)
                for pos, player in enumerate((holders + substitutes), 1):
                    LineupsPlayers.objects.create(position=pos, lineup=lineup,
                                                  player=player)
                messages.success(request, 'Lineup uploaded!')
                return redirect('team_details', league_id, team_id)
    else:
        form = UploadLineupForm(initial={'players': team_players, 'team': team,
                                         'modules': modules, 'day': day,
                                         'league': league})
    return render(request, 'fantalega/upload_lineup.html',
                  {'form': form, 'players': team_players, 'team': team,
                   'league': league})


@login_required
def lineup_edit(request, league_id, team_id, day):
    league = get_object_or_404(League, pk=int(league_id))
    modules = [(1, '343'), (2, '352'), (3, '442'), (4, '433'), (5, '451'),
               (6, '532'), (7, '541')]
    team = get_object_or_404(Team, pk=int(team_id))
    if request.GET.get('back_to_team_details'):
        return redirect('team_details', league.id, team.id)
    lineup = Lineup.objects.filter(team=team, league=league, day=day).first()
    team_players = [(p.code, "%s [%s]" % (p.name, p.role))
                    for p in team.player_set.all()]
    if request.method == "POST":
        form = UploadLineupForm(request.POST,
                                initial={'players': team_players, 'team': team,
                                         'modules': modules, 'day': day,
                                         'league': league})
        if form.is_valid():
            day = form.cleaned_data['day']
#            module_id = form.cleaned_data['module']
#            module = dict(form.fields['module'].choices)[int(module_id)]
            holders = [Player.get_by_code(int(code), season=league.season)
                       for code in form.cleaned_data['holders']]
            substitutes = [Player.get_by_code(int(code), season=league.season)
                           for code in [form.cleaned_data['substitute_%s' % n]
                           for n in range(1, 11)]]
            error = form.check_holders()
            if error:
                messages.error(request, error)
            else:
                now = timezone.now()
                dead_line = Match.objects.filter(
                    league=league, day=day).first().dead_line
                if now > dead_line:
                    messages.error(request, "You are out of time!")
                    messages.info(request, "No change saved")
                else:
                    messages.success(request, "Lineup correct!")
                    lineup.timestamp = now
                    lineup.save()
                    for pos, player in enumerate((holders + substitutes), 1):
                        lu = LineupsPlayers.objects.filter(
                            lineup=lineup, position=int(pos)).first()
                        lu.player = player
                        lu.save()
                        print "[INFO] Lineup %s (%s) -> Player %s " \
                              "pos %s upgraded!" % (team.name, day,
                                                    player.name, pos)
                    messages.success(request, 'Lineup upgraded!')
                return redirect('team_details', league_id, team_id)
    else:
        form = UploadLineupForm(initial={'players': team_players, 'team': team,
                                         'modules': modules, 'day': day,
                                         'league': league})
        for n, player in enumerate(lineup.players.all()[11:], 1):
            form.fields['substitute_%s' % n].initial = player.code
        form.fields['holders'].initial = [p.code for p in
                                          lineup.players.all()[:11]]
    return render(request, 'fantalega/upload_lineup.html',
                  {'form': form, 'players': team_players, 'team': team,
                   'league': league})

@login_required
def lineup_details(request, league_id, team_id, day):
    league = get_object_or_404(League, pk=int(league_id))
    total = 0.0
    mod = 0.0
    team = get_object_or_404(Team, pk=int(team_id))
    if request.GET.get('back_to_team_details'):
        return redirect('team_details', league.id, team.id)
    offset = team.leagues.all()[0].offset
    fantaday = int(day) + int(offset)
    lineup = team.team_lineups.filter(day=int(day)).first()
    lineup_players = LineupsPlayers.get_sorted_lineup(lineup)
    holders = [l_p.player for l_p in lineup_players[:11]]
    substitutes = [s_p.player for s_p in lineup_players[11:]]
    d_votes = {code: (fv, v) for code, fv, v in
               [Evaluation.get_evaluations(day=(int(day) + offset),
                                           code=p.code, season=league.season)
                for p in holders]}
    if request.GET.get('modify lineup'):
        return redirect('lineup_edit', league.id, team.id, day)
    if request.GET.get('calculate'):
        try:
            handler = LineupHandler(lineup, int(day), league)
            total = handler.get_pts()
            mod = handler.mod
        except AttributeError:
            messages.error(request, 'No pts available: '
                                    'lineups or evaluations are missing, '
                                    'check in calendar...')
            total = ''

    context = {'team': team, 'holders': holders, 'substitutes': substitutes,
               'lineup': lineup, 'day': day, 'd_votes': d_votes, 'mod': mod,
               'fantaday': fantaday, 'total': total, 'league': league}
    return render(request, 'fantalega/lineup.html', context)

Sia la vista ‘upload_lineup’ che lineup_edit, fanno utilizzo dello stesso form:

fantalega/forms.py

...
from .models import Player
...
class UploadLineupForm(forms.Form):
    def __init__(self, *args, **kwargs):
        self.dict_values = kwargs.pop('initial')
        super(UploadLineupForm, self).__init__(*args, **kwargs)
        self.fields['module'] = forms.ChoiceField(
            label=u'module', choices=self.dict_values['modules'],
            widget=forms.Select())
        self.fields['day'] = forms.IntegerField(initial=self.dict_values['day'])
        self.fields['holders'] = forms.MultipleChoiceField(
            choices=self.dict_values['players'],
            widget=forms.CheckboxSelectMultiple())
        for n in range(1, 11):
            self.fields['substitute_%s' % n] = forms.ChoiceField(
                label=u'substitute %s' % n, choices=self.dict_values['players'],
                widget=forms.Select(), required=False)

    def check_holders(self):
            error = ''
            data = self.cleaned_data['holders']
            substitutes = [self.cleaned_data.get('substitute_%s' % n)
                           for n in range(1, 11)]
            if len(data) != 11:
                return "holder players number is wrong!"
            module = dict(self.fields['module'].choices)[
                int(self.cleaned_data['module'])]
            mod_defs, mod_mids, mod_forws = module
            goalkeepers = len()
            defenders = len()
            midfielders = len()
            forwarders = len()
            if goalkeepers > 1:
                return "To many goalkeepers!"
            if defenders != int(mod_defs):
                return "number of defenders doesn't match module!"
            if midfielders != int(mod_mids):
                return "number of midfielders doesn't match module!"
            if forwarders != int(mod_forws):
                return "number of forwarders doesn't match module!"
            for code in substitutes:
                player = Player.get_by_code(int(code),
                                            self.dict_values['league'].season)
                if code in data:
                    return "substitute %s is in holders!" % player.name
                if substitutes.count(code) > 1:
                    return "Duplicate substitute %s in list!" % player.name
            return error

Nota:
I campi (field) substitute, essendo 10, sono stati creati dinamicamente con un ciclo for.

che inseriremo negli import delle views
fantalega/views.py

...
from .forms import UploadLineupForm
...

Le templates coinvolte nell'operazione di salvataggio delle formazioni saranno:

fantalega/upload_lineup.html

{% extends 'fantalega/base.html' %}
{% load bootstrap3 %}

{% block content %}
    <b>Upload lineup for <font color="green">{{ team.name }}</font></b>
    <br><br>
    <form action="#" method="get">
    <input type="submit" class="btn"
           value="back to {{ team.name }} team" name="back_to_team_details">
    </form>

    <form method="POST" class="form">
		{% csrf_token %}
        <div id="container" style="width:100%;">
            <div id="left" style="float:left; width:50%;">
              {% bootstrap_field form.module %}
              {% bootstrap_field form.holders %}
            </div>
            <div id="right" style="float:right; width:50%;">
              {% bootstrap_field form.day %}
              {% bootstrap_field form.substitute_1 %}
              {% bootstrap_field form.substitute_2 %}
              {% bootstrap_field form.substitute_3 %}
              {% bootstrap_field form.substitute_4 %}
              {% bootstrap_field form.substitute_5 %}
              {% bootstrap_field form.substitute_6 %}
              {% bootstrap_field form.substitute_7 %}
              {% bootstrap_field form.substitute_8 %}
              {% bootstrap_field form.substitute_9 %}
              {% bootstrap_field form.substitute_10 %}
            </div>
        </div>

        <div>
            {% buttons %}
                <button type="submit" class="btn btn-primary">
                    {% bootstrap_icon "save" %} upload</button>
			{% endbuttons %}
        </div>
    </form>
{% endblock %}

Nota:
siccome il form per l'upload e la modifica della formazione è lo stesso,
unica sarà anche la template. Per visualizzare i dettagli della formazione invece:

fantalega/lineup.html

{% extends 'fantalega/base.html' %}
{% load app_filters %}

{% block content %}
	<h1>Lineup <font color="green">{{ team.name }}</font>
        day: <font color="red">{{ day }}</font><br></h1>
        <div id="container" style="width:100%;">
            <div id="left" style="float:left; width:30%;">
                <b><font color="orange">holders:</font></b><br>

                <table class="table table-striped">
                  <tr>
                      <th>code</th>
                      <th>player name</th>
                      <th>real team</th>
                      <th>role</th>
                      <th>fv</th>
                      <th>v</th>
                  </tr>
                      {% for player in holders %}
                  <tr>
                          <td>{{ player.code }}</td>
                          <td><a href="{% url 'player_details' player.id %}">
                              {{ player.name }}</a></td>
                          <td>{{ player.real_team }}</td>
                          <td>{{ player.role }}</td>
                          <td>{{ player|get_fvote:fantaday }}</td>
                          <td>{{ player|get_vote:fantaday }}</td>
                  </tr>
                      {% endfor %}
                </table>
                <form action="#" method="get">
                 <input type="submit" class="btn" value="modify lineup"
                        name="modify lineup">
                </form>
                <form action="#" method="get">
                 <input type="submit" class="btn" value="calculate"
                        name="calculate">
                </form>

                <b><font color="orange">pts:</font></b>
                    {{ total|pts_filter }}
                <br>
                <b><font color="orange">defense mod:</font></b>
                    {{ mod }}
                <br>
                <br>
                <form action="#" method="get">
                <input type="submit" class="btn"
                       value="back to {{ team.name }} team"
                       name="back_to_team_details">
                </form>
            </div>
            <div id="right" style="float:right; width:30%;">
                <b><font color="orange">substitutes:</font></b><br>

                <table class="table table-striped">
                  <tr>
                      <th>code</th>
                      <th>player name</th>
                      <th>real team</th>
                      <th>role</th>
                      <th>fv</th>
                      <th>v</th>
                  </tr>
                      {% for player in substitutes %}
                  <tr>
                          <td>{{ player.code }}</td>
                          <td><a href="{% url 'player_details' player.id %}">
                              {{ player.name }}</a></td>
                          <td>{{ player.real_team }}</td>
                          <td>{{ player.role }}</td>
                          <td>{{ player|get_fvote:fantaday }}</td>
                          <td>{{ player|get_vote:fantaday }}</td>
                  </tr>
                      {% endfor %}
                </table>
            </div>


        </div>

{% endblock %}

Per inserire una formazione, ogni User dovrà recarsi all'interno
della lega e cliccare sulla propria squadra.
Nella pagina relativa ai dettagli di squadra c'è un pulsante 'new lineup', che
va come sempre, abilitato nella view di competenza: 'team_details'.
Se vogliamo che le formazioni già inserite vengano visualizzate,
dobbiamo passare alla template, tramite il dizionario 'context', la lista
delle formazioni.

...
@login_required
def team_details(request, league_id, team_id):
    league = get_object_or_404(League, pk=int(league_id))
    team = get_object_or_404(Team, pk=int(team_id))
    lineups = Lineup.objects.filter(team=team, league=league).order_by('day')
    context = {'team': team, 'user': request.user, 'league': league,
	           'lineups': lineups}
    if request.GET.get('back_to_teams'):
        return redirect('league_details', league.id)
    if request.GET.get('new lineup'):
        return redirect('upload_lineup', league.id, team.id)
    return render(request, 'fantalega/team.html', context)

...

Ora cliccando sul bottone 'New Lineup', si verrà redirezionati al form
di inserimento. Scegliere il modulo, i titolari le riserve e salvare.
I dati inseriti verranno controllati:
- correttezza del modulo

module_error

- giocatori non duplicati

player_error

- deadline rispettata

E' possibile cliccare sulla singola formazione elencata per entrare nel
dettagli della stessa. Utilizzando un custom_filter, aggiungerlo al
file fantalega/templatetags/app_filters.py

...
@register.filter(name='get_vote')
def get_vote(player, day):
    evaluation = player.player_votes.filter(day=int(day)).first()
    if evaluation:
        return '%s' % float(evaluation.net_value)
    else:
        return '0.0'

All'interno della pagina formazione, ci sono due pulsanti oltre quello
di ritorno ai dettagli squadra: 'modify lineup' e 'calculate'.
Con il primo è possibile modificare la formazione, con il secondo,
si richiama uno script che esegue i calcoli.

Attenzione!!
Ricordarsi che la giornata da calcolare tiene presente dell'offset di lega.
Se si vogliono calcolare i punteggi della giornata di fantalega 1 ma l'offset di
lega è 2, vuol dire che nella realtà si è alla giornata 3, quindi è necessario
prima importare la valutazioni della giornata reale 3, poi effettuare i calcoli.

Siccome i voti già inseriti dovrebbero apparire nella template dei dettagli
di lega, aggiungiamo al dizionario passato alla template, anche le giornate
inserite:

def league_details(request, league_id):
    league = get_object_or_404(League, pk=int(league_id))
    league_teams = league.team_set.all()
    days = [d['day'] for d in
            Evaluation.objects.filter(
                season=league.season).order_by('day').values('day').distinct()]
    ...
    context = {'league': league, 'teams': league_teams,
               'days': days, 'user': request.user}
    ...

Il tasto 'calculate' è legato alla view 'lineup_details':

...
    if request.GET.get('calculate'):
        try:
            handler = LineupHandler(lineup, int(day), league)
            total = handler.get_pts()
            mod = handler.mod
        except AttributeError:
            messages.error(request, 'No pts available: '
                                    'lineups or evaluations are missing, '
                                    'check in calendar...')
            total = ''
...

LineupHandler viene importato nel file views.py

...
from fantalega.scripts.calc import LineupHandler, get_final, lineups_data
...

il file fantalega/scripts/calc.py conterrà quindi:

from fantalega.models import Evaluation


class BadInputError(Exception):
    pass


def convert_pts_to_goals(pts):
    """
    convert_pts_to_goals(pts) -> int

    Calculate number of goals by total pts, e.g.:
    convert_pts_to_goals(60) -> 0
    convert_pts_to_goals(66) -> 1
    convert_pts_to_goals(72) -> 2
    """
    if isinstance(pts, float):
        if pts < 60.0:
            return 0
        return (float(pts) - 60) // 6
    else:
        raise BadInputError("Need a Float as input, %s in input" % pts)


def get_final(pts_a, pts_b):
    """
    get_final(pts_a, pts_b) -> tuple

    Convert pts to goals between 2 teams, e.g.:
    get_final(66, 62) -> (1, 0)
    """
    if isinstance(pts_a, float) and isinstance(pts_b, float):
        goal_a = convert_pts_to_goals(pts_a)
        goal_b = convert_pts_to_goals(pts_b)
        if goal_a == goal_b:
            if pts_a - pts_b >= 4:  # +4 in the same level results in +1 goal
                goal_a += 1
            elif pts_b - pts_a >= 4:  # +4 in the same level for team_b
                goal_b += 1
        if pts_a < 60:  # og for team_a
            goal_b += 1
        if pts_b < 60:  # og for team_b
            goal_a += 1
        if pts_a - pts_b > 9.5:  # +10 in the same level results in +1 goal
            goal_a += 1
        elif pts_b - pts_a > 9.5:  # +10 for team_b
            goal_b += 1
        return int(goal_a), int(goal_b)
    else:
        raise BadInputError("Need a Float as input, %s and %s in input" %\
                            (pts_a, pts_b))


def lineups_data(goals_a, goals_b):
    """
    lineups_data(goals_a, goals_b) -> dict

    Convert to goals team data as: won, lost, matched, goals_made etc, i.e.:
    lineups_data(3, 2) -> {'hw': 1, 'vw': 0, 'hl': 0, ...}
    hw: home_won               vw: visit_won
    hm: home_matched           vm: visit_matched
    hl: home_lost              ...and so on
    hgm: home goals made
    hgc: home goals conceded

    :param goals_a: int goals of team A
    :param goals_b: int goals of team B
    """
    data = {'hw': 0, 'hm': 0, 'hl': 0, 'hgm': goals_a, 'hgc': goals_b,
            'vw': 0, 'vm': 0, 'vl': 0, 'vgm': goals_b, 'vgc': goals_a}
    if goals_a > goals_b:
        data['hw'] = 1
        data['hl'] = 0
        data['hm'] = 0
        data['vw'] = 0
        data['vl'] = 1
        data['vm'] = 0
    elif goals_a < goals_b:
        data['hw'] = 0
        data['hl'] = 1
        data['hm'] = 0
        data['vw'] = 1
        data['vl'] = 0
        data['vm'] = 0
    else:
        data['hw'] = 0
        data['hl'] = 0
        data['hm'] = 1
        data['vw'] = 0
        data['vl'] = 0
        data['vm'] = 1
    return data


class LineupHandler(object):
    def __init__(self, lineup, day, league):
        self.league = league
        self.day = day + league.offset
        self.mod = 0.0
        self.lineup = lineup
        self.holders = [p for p in self.lineup.players.all()[:11]]
        self.substitutes = [p for p in self.lineup.players.all()[11:]]
        self.not_evaluated = []
        self.available_by_role = []
        self.added_player = []
        self.new_list = []
        self.substitutions = 0
        self.candidate = None
        self.available = [p for p in self.substitutes if
                          Evaluation.objects.filter(player=p, day=self.day,
                                                    season=league.season
                                                    ).first().fanta_value > 0.0
                          and p.role != 'goalkeeper']

    def get_goalkeeper_substitute(self):
        gks = [p for p in self.substitutes if Evaluation.objects.filter(
               player=p, day=self.day, season=self.league.season
               ).first().fanta_value > 0.0
               and p.role == 'goalkeeper']
        if gks:
            return gks[0]
        else:
            return None

    def get_evaluation(self, player):
        evaluation = Evaluation.objects.filter(player=player,
                                               season=self.league.season,
                                               day=self.day).first()
        return evaluation.fanta_value

    def need_substitutions(self):
        evaluated = [p for p in self.holders if self.get_evaluation(p) > 0.0]
        return len(evaluated) != 11

    def get_same_role_substitute(self, player):
        self.available_by_role = [p for p in self.substitutes
                                  if p.role == player.role and
                                  Evaluation.objects.filter(
                                      season=self.league.season,
                                      player=p, day=self.day
                                  ).first().fanta_value > 0.0]
        self.candidate = self.available_by_role[0]
        return self.candidate

    def get_substitute(self, player):
        if player.role == 'goalkeeper':
            self.candidate = self.get_goalkeeper_substitute()
        else:
            # print self.available
            if self.available:
                return self.available[0]

    def is_same_role_available(self, player):
        self.available_by_role = [p for p in self.substitutes if
                                  p.role == player.role and
                                  Evaluation.objects.filter(
                                      season=self.league.season,
                                      player=p, day=self.day
                                  ).first().fanta_value > 0.0]
        return len(self.available_by_role) > 0

    def is_substitute_available(self):
        self.available = [p for p in self.substitutes if
                          Evaluation.objects.filter(
                              season=self.league.season,
                              player=p, day=self.day
                          ).first().fanta_value > 0.0 and
                          p.role != 'goalkeeper']
        return len(self.available) > 0

    def get_pts(self):
        if not self.need_substitutions():
            return self.def_mod(self.holders)
        else:
            self.not_evaluated = \
                [p for p in self.holders if Evaluation.objects.filter(
                    player=p, day=self.day, season=self.league.season
                ).first().fanta_value == 0.0]
        for player in self.not_evaluated:
            if self.is_same_role_available(player):
                self.candidate = self.get_same_role_substitute(player)
                self.substitutes.pop(self.substitutes.index(self.candidate))
            elif self.is_substitute_available():
                self.candidate = self.get_substitute(player)
                # check module
                if self.is_module_accepted(player):
                    self.substitutes.pop(self.substitutes.index(self.candidate))
                else:
                    print "\n[WARNING] module doesn't exist!"
                    self.candidate = None
            else:
                self.candidate = None

            if self.candidate and self.substitutions < 3:
                self.added_player.append(self.candidate)
                self.substitutions += 1

        self.new_list = [p for p in self.holders if Evaluation.objects.filter(
            player=p, day=self.day, season=self.league.season
            ).first().fanta_value > 0.0] +\
            self.added_player
        return self.def_mod(self.new_list)

    def is_module_accepted(self, player):
        d, m, f = 0, 0, 0
        lineup = self.holders[:]
        lineup.pop(lineup.index(player))
        lineup.append(self.candidate)
        for role in [p.role for p in lineup]:
            if role == 'defender':
                d += 1
            elif role == 'midfielder':
                m += 1
            elif role == 'forward':
                f += 1
            else:
                pass
        module = '%s%s%s' % (d, m, f)
        print "\n[INFO] module changes in %s" % module
        return module in ('343', '352', '442', '433', '451', '532', '541')

    def def_mod(self, player_list):
        total = sum([Evaluation.objects.filter(
            player=p, day=self.day, season=self.league.season
            ).first().fanta_value for p in player_list])
        defenders = [p for p in player_list if p.role == 'defender']
        goalkeepers = [p for p in player_list if p.role == 'goalkeeper']
        gk = goalkeepers[0] if goalkeepers else None
        vgk = Evaluation.objects.filter(player=gk, day=self.day,
                                        season=self.league.season
                                        ).first().net_value
        def_votes = [Evaluation.objects.filter(season=self.league.season,
            player=d, day=self.day).first().net_value for d in defenders]
        if len(defenders) >= 4 and gk:
            if vgk == 0.0:
                vgk = 6.0
            for v in def_votes:
                if v == 0.0:
                    def_votes[def_votes.index(v)] = 6.0
            values = sorted(def_votes, reverse=True)[:3] + [vgk]
            avg_def = sum(values)/4.0
            if avg_def == 6:
                self.mod = 1
            elif 6 < avg_def <= 6.25:
                self.mod = 2
            elif 6.25 < avg_def <= 6.5:
                self.mod = 3
            elif 6.5 < avg_def <= 6.75:
                self.mod = 4
            elif 6.75 < avg_def <= 7:
                self.mod = 5
            elif avg_def > 7:
                self.mod = 6
            return total + self.mod
        else:
            return total

Salvare ora gli avanzamenti su github:

git add --all
git commit -m "Lineup 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
6 - Models: Match
7 - Models: Player
8 - Asta

articoli successivi
10 - Models: Trade
11 - Asta di riparazione
12 - Classifica

Categorie:Django Tag:

djangofantalega: Asta

14 novembre 2016 Commenti chiusi

8 – Asta

Una fantalega che si rispetti inizia sempre con una Asta:
ogni giocatore (Player) viene associato ad una squadra (Team).
Nella template della Lega, è stato previsto un bottone

<input type="submit" class="btn" value="Start Auction" name="auction">

questo bottone va collegato alle urls, alle views e alle templates necessarie
allo scopo.

Aggiungere per prima cosa le urls relative all’asta (auction) nel file fantalega\urls.py:

...
    # auction urls
    url(r'^leagues/(?P<league_id>[0-9]+)/auction$', views.auction,
        name='auction'),
    url(r'^leagues/(?P<league_id>[0-9]+)/auction/summary$',
        views.auction_summary, name='auction_summary'),
]

Aggiungere nel file fantalega\views.py le nuove viste:

...
@login_required
def auction(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)
    if request.GET.get('auction_summary'):
        return redirect('auction_summary', league.id)
    free_players = [(p.code, p.name) for p in
                    Player.objects.filter(season=league.season).all()
                    if not p.team]
    league_teams = [(team.id, team.name) for team in league.team_set.all()]
    if request.method == "POST":
        form = AuctionPlayer(request.POST, initial={'players': free_players,
                                                    'teams': league_teams,
                                                    'league': league})
        if form.is_valid():
            player_code = form.cleaned_data['player']
            player = Player.get_by_code(int(player_code), season=league.season)
            auction_value = form.cleaned_data['auction_value']
            team_id = form.cleaned_data['team']
            team = Team.objects.get(pk=int(team_id))
            if team.player_set.count() == league.max_players():
                messages.warning(request,
                                 '%s has reached max player number'
                                 ' the operation is not valid' % team.name)
                return redirect('auction', league.id)
            remaining_players = league.max_players() - team.player_set.count()
            budget_remaining = int(team.budget) - int(auction_value)
            if budget_remaining >= 0 and budget_remaining >= \
                    (remaining_players - 1):
                player.team = team
                player.auction_value = auction_value
                player.save()
                team.budget -= auction_value
                team.save()
                messages.success(request,
                                 'Auction operation [ %s - %s ] stored!' %
                                 (player.name, team.name))
            else:
                messages.error(request,
                               "Not enough budget: budget: %s, "
                               "auction price %s, remaining players %s" %
                               (team.budget, auction_value, remaining_players))
            return redirect('auction', league.id)
    else:
        form = AuctionPlayer(initial={'players': free_players,
                                      'teams': league_teams, 'league': league})
    return render(request, 'fantalega/auction.html',
                  {'form': form, 'players': free_players,
                   'teams': league_teams, 'league': league}, )


@login_required
def auction_summary(request, league_id):
    league = get_object_or_404(League, pk=int(league_id))
    league_teams = league.team_set.all()
    if request.GET.get('back_to_auction'):
        return redirect('auction', league.id)
    context = {'league': league, 'teams': league_teams}
    return render(request, 'fantalega/auction_summary.html', context)

il form che permetterà l’associazione Player-Team (fantalega/forms.py) sarà:

...
class AuctionPlayer(forms.Form):
    def __init__(self, *args, **kwargs):
        self.dict_values = kwargs.pop('initial')
        super(AuctionPlayer, self).__init__(*args, **kwargs)

        self.fields['player'] = forms.ChoiceField(
            label=u'player', choices=self.dict_values['players'],
            widget=forms.Select(), required=False)
        self.fields['auction_value'] = forms.IntegerField()
        self.fields['team'] = forms.ChoiceField(
            label=u'team', choices=self.dict_values['teams'],
            widget=forms.Select(), required=False)

e andrà importato nel file fantalega/views.py:

from .forms import AuctionPlayer
...

La logica è semplice:
si chiama a turno un giocatore, si fa un’asta poi nel form, si immetteranno
il nome della squadra che si è aggiudicata il giocatore e il valore d’asta,
che verrà scalato dal budget totale di squadra.
Viene anche effettuato un controllo sul valore d’asta, per assicurarsi che
ogni squadra abbia la disponibilità sufficiente per rilanciare.
E’ prevista anche una view/template per il riassunto dell’asta (auction_summary).
Ora le template legate alle views suddette.

templates/auction.html

{% extends 'fantalega/base.html' %}
{% load bootstrap3 %}

{% block content %}
    <form action="#" method="get">
     <input type="submit" class="btn" value="back to {{ league.name }} teams"
            name="back_to_teams">
     <input type="submit" class="btn" value="view auction summary"
            name="auction_summary">
    </form>

    <b>New Auction Operation for lega
        <font color="green">{{ league.name }}</font></b><br><br>
    <form method="POST" class="form">
		{% csrf_token %}
        {% bootstrap_form form %}
            {% buttons %}
                <button type="submit" class="btn btn-primary">
                {% bootstrap_icon "save" %} Submit</button>
			{% endbuttons %}
    </form>
{% endblock %}

il pulsante che rimanderà al sommario è appunto:

<input type="submit" class="btn" value="view auction summary"
       name="auction_summary">

templates/auction_summary.html

{% extends 'fantalega/base.html' %}
{% load bootstrap3 %}
{% load app_filters %}

{% block content %}
    <b><font color="green">{{ league.name }}</font>:
        <font color="purple">Auction summary</font></b><br>
    <br>
    <form action="#" method="get">
      <input type="submit" class="btn" value="back to auction"
             name="back_to_auction">
    </form>

  {% if league.team_set.all %}
      {% for team in league.team_set.all %}
        <font color="brown"><b>{{team.name}}</b></font>
        <font color="darkgrey"> {{team.user.username}}</font><br>
        {% for player in team.player_set.all %}
          {{player|color_by_role}}:
          <font color="darkgrey"> {{player.role}}</font><br>
        {% endfor %}
        <font color="red">budget remain: <b>{{team.budget}}</b><br></font>
        total goalkeepers to buy: {{team|gk_to_buy:league}}<br>
        total defenders to buy: {{team|def_to_buy:league}}<br>
        total players to buy: {{team|mid_to_buy:league}}<br>
        total players to buy: {{team|forw_to_buy:league}}<br>
        total players to buy: {{team|total_to_buy:league}}
      <br>
      <br>
      {% endfor %}

  {% else %}
    <p>No team found.</p>
  {% endif %}
{% endblock %}

Collegare il pulsante ‘auction’ alla view ‘league_details’,
file fantalega.views.py:

@login_required
def league_details(request, league_id):
    ...
    if request.GET.get('auction'):
        return redirect('auction', league.id)
    context = {'league': league, 'teams': league_teams,
               'days': days, 'user': request.user}
    ...

Nella template auction_summary.html vengono utilizzati alcuni custom_filters,
che vanno inseriti nel file templatetags/app_filters.py

from django.utils.safestring import mark_safe
...
@register.filter(name='color_by_role')
def color_by_role(player):
    if player.role.lower() == 'goalkeeper':
        color = 'blue'
    elif player.role.lower() == 'defender':
        color = 'orange'
    elif player.role.lower() == 'midfielder':
        color = 'green'
    else:
        color = 'purple'
    new_string = '<font color="%s">%s</font>' % (color, player.name.lower())
    return mark_safe(new_string)


@register.filter(name='gk_to_buy')
def gk_to_buy(team, league):
    gk_bought = team.player_set.filter(role='goalkeeper').count()
    remain = league.max_goalkeepers - gk_bought
    color = "green" if not remain else "red"
    return mark_safe('<b><font color="%s">%s</font></b>' % (color, remain))


@register.filter(name='def_to_buy')
def def_to_buy(team, league):
    def_bought = team.player_set.filter(role='defender').count()
    remain = league.max_defenders - def_bought
    color = "green" if not remain else "red"
    return mark_safe('<b><font color="%s">%s</font></b>' % (color, remain))


@register.filter(name='mid_to_buy')
def mid_to_buy(team, league):
    mid_bought = team.player_set.filter(role='midfielder').count()
    remain = league.max_midfielders - mid_bought
    color = "green" if not remain else "red"
    return mark_safe('<b><font color="%s">%s</font></b>' % (color, remain))


@register.filter(name='forw_to_buy')
def forw_to_buy(team, league):
    forw_bought = team.player_set.filter(role='forward').count()
    remain = league.max_forwards - forw_bought
    color = "green" if not remain else "red"
    return mark_safe('<b><font color="%s">%s</font></b>' % (color, remain))


@register.filter(name='total_to_buy')
def total_to_buy(team, league):
    player_bought = team.player_set.count()
    remain = league.max_players() - player_bought
    color = "green" if not remain else "red"
    return mark_safe('<b><font color="%s">%s</font></b>' % (color, remain))

Per comodità aggiungere il metodo max_players al model League,
metodo che torna utile durante l’asta per il controllo del tetto massimo
di giocatori.

fantalega/models.py

class League(models.Model):
    ...
    def max_players(self):
        return self.max_goalkeepers + self.max_defenders + \
            self.max_midfielders + self.max_forwards

Avviare il server e recarsi nella pagina relativa alla lega principale,
premere sul pulsante ‘Start Auction’ ed effettuare un acquisto, ricordandosi
il valore d’asta.

auction

Ad operazione ultimata recarsi alla pagina
‘view auction summary’ ed appariranno tutti i resoconti di ogni squadra.

E’ probabile che l’asta venga effettuata con l’ausilio di altri strumenti,
forniamo quindi il model League, di un metodo che permetta di importare
il risultato di un’asta direttamente da file csv:

fantalega/models.py

class League(models.Model):
   ... 
   @staticmethod
    def auction_upload(path):
        """This method import Auction directly from a csv file
           with this format:
           'name;auction_value;team_name
           'team_name' is the name of the object Team
        """
        with open(path) as data:
            for record in data:
                name, auction_value, team_name = record.strip().split(";")
                player = Player.objects.filter(name=name.upper()).first()
                if player:
                    player.auction_value = int(auction_value)
                    team = Team.objects.filter(name=team_name.strip()).first()
                    if team:
                        if player not in team.player_set.all():
                            player.team = team
                            team.budget -= player.auction_value
                            player.save()
                            team.save()
                    else:
                        print "[ERROR] Team %s not found" % team_name
                else:
                    print "[ERROR] Player %s not found" % name
            print "[INFO] Auction upload done!"

Ora è suffieciente aprire la shell

python manage.py shell
>>> League.auction_upload('fantalega/static/teams.csv')
>>> from fantalega.models import League
...

Salvare gli avanzamenti su github:

git add --all
git commit -m "Auction added"
git push -u origin master

La prossima sezione sarà piuttosto complicata, riguardando le formazioni (Lineup).

articoli precedenti
0 – indice
1 – Virtualenv e Git
2 – Models: Season
3 – Admin: Login e Logout
4 – Models: League
5 – Models: Team
6 – Models: Match
7 – Models: Player

articoli successivi
9 – Models: Lineup
10 – Models: Trade
11 – Asta di riparazione
12 – Classifica

Categorie:Django Tag:

djangofantalega: model Player e model Evaluation

14 novembre 2016 Commenti chiusi

7 – Models: Player ed Evaluation

Creare i modelli Player ed Evaluation.

Per Evaluation si intendono i voti che settimanalmente vengono
pubblicati e con i quali si effettuano i conteggi delle squadre.
La fantalega in oggetto si basa sulla creazione di squadre tramite Asta
(Auction) pertanto un giocatore, potrà appartenere solo ad una squadra.
Per questo la relazione Team-Player sarà di tipo one-to-many.
Lo stesso Player avrà una relazione one-to-many con Evaluation.

add_player

Aggiungere nel file fantalega/models.py i models Player e Evaluation:

...
from .models import Player, Evaluation


...
class Player(models.Model):
    code = models.IntegerField()
    name = models.CharField(max_length=32)
    real_team = models.CharField(max_length=3)
    cost = models.IntegerField()
    auction_value = models.IntegerField()
    role = models.CharField(max_length=16)
    team = models.ForeignKey(Team, null=True)
    season = models.ForeignKey(Season, related_name='players')

    def __unicode__(self):
        return self.name

    @staticmethod
    def get_by_code(code, season):
        return Player.objects.filter(code=int(code), season=season).first()

    @staticmethod
    def code_to_role(code):
        if int(code) < 200:
            return 'goalkeeper'
        elif 200 < int(code) < 500:
            return 'defender'
        elif 500 < int(code) < 800:
            return 'midfielder'
        elif int(code) > 800:
            return 'forward'
        else:
            return 'unknown'


class Evaluation(models.Model):
    season = models.ForeignKey(Season, related_name='evaluations')
    player = models.ForeignKey(Player, related_name='player_votes')
    day = models.IntegerField()
    net_value = models.FloatField()
    fanta_value = models.FloatField()
    cost = models.IntegerField()

    def __unicode__(self):
        return "[%s] %s" % (self.day, self.player.name)

    @staticmethod
    def get_evaluations(day, code, season):
        """get_evaluations(self, day, code, season) -> fanta_value, net_value
           code: Player.code
           day: lineup.day + league.offset
           season: season object
        """
        player = Player.objects.filter(code=int(code), season=season).first()
        evaluation = Evaluation.objects.filter(day=day, player=player).first()
        if evaluation and player:
            return code, evaluation.fanta_value, evaluation.net_value
        else:
            return code, None, None

    @staticmethod
    def upload(path, day, season):
        # with open(path) as data:  # for shell string-file-path upload
        with path as data:  # for InMemoryUploadedFile object upload
            for record in data:  # nnn|PLAYER_NAME|REAL_TEAM|x|y|n
                code, name, real_team, fv, v, cost = record.strip().split("|")
                player = Player.get_by_code(code, season)
                role = Player.code_to_role(code.strip())
                if not player:
                    player = Player(name=name, code=code, role=role,
                                    real_team=real_team, cost=cost,
                                    auction_value=0, season=season)
                    print "[INFO] Creating %s %s" % (code, name)
                else:
                    player.cost = cost
                    player.real_team = real_team
                    print "[INFO] Upgrading %s %s" % (code, name)
                player.save()
                # storing evaluation
                evaluation = Evaluation.objects.filter(day=day, season=season,
                                                       player=player).first()
                if evaluation:
                    evaluation.net_value = v
                    evaluation.fanta_value = fv
                    evaluation.cost = cost
                    evaluation.save()
                    print "[INFO] Upgrading values day: %s player %s [%s]" % (
                        day, player.name, season.name)
                else:
                    Evaluation.objects.create(day=day, player=player, cost=cost,
                                              net_value=v, fanta_value=fv,
                                              season=season)
                    print "[INFO] Creating values day: %s player %s [%s]" % (
                        day, player.name, season.name)
        print "[INFO] Evaluation uploading done!"

Tenendo traccia anche di stagioni precedenti, Evaluation avrà una relazione
many-to-one con Season. Stesso discorso per Player. Ogni stagione (Season)
avrà i suoi giocatori (Player) di riferimento quindi la relazione
Season-Player sarà di tipo one-to-many.

Aggiornare il database inserendo la nuova tabella.

(venv) >python manage.py makemigrations
Migrations for 'fantalega':
  fantalega\migrations\0005_auto_20161110_1407.py:
    - Create model Evaluation
    - Create model Player
    - Add field player to evaluation
    - Add field season to evaluation

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.0005_auto_20161110_1407... OK

Sistemare l’interfaccia di admin per i model Player ed Evaluation:
file fantalega/admin.py:

# noinspection PyUnresolvedReferences
...
from .models import Player, Evaluation


# Register your models here.
...
class PlayersInline(admin.TabularInline):
    model = Player
    extra = 0
    classes = ('collapse',)

...
class EvaluationAdmin(admin.ModelAdmin):
    list_display = ('day', 'player', 'fanta_value', 'net_value', 'cost')
    list_filter = ('day', 'player', 'season')
    list_per_page = 50


class PlayerAdmin(admin.ModelAdmin):
    ordering = ('code', )
    list_display = ('code', 'name', 'real_team', 'cost')
    list_filter = ('season', 'role', 'team')
    list_per_page = 20

...
admin.site.register(Player, PlayerAdmin)
admin.site.register(Evaluation, EvaluationAdmin)

La classe PlayersInline la facciamo apparire nell’interfaccia di
admin di Team, poichè è fondamentale poter vedere da quali giocatori
è composta la rosa della squadra, quindi, sempre in admin:

...
class PlayersInline(admin.TabularInline):
    model = Player
    extra = 0
    classes = ('collapse',)


class TeamAdmin(admin.ModelAdmin):
    inlines = [LeaguesInline, PlayersInline]  # TabularInLine added
    list_display = ('name', 'budget', 'max_trades')
    ordering = ('-budget', )

Ora aggiungere gli urls inerenti Player ed Evaluation nel file fantalega\urls.py:

...
    # players urls
    url(r'^players/$', views.players, name='players'),
    url(r'^players/(?P<player_id>[0-9]+)/$', views.player_details,
        name='player_details'),
    # votes urls
    url(r'^leagues/(?P<league_id>[0-9]+)/votes/(?P<day>[0-9]+)/$',
        views.vote, name='vote'),
    url(r'^leagues/(?P<league_id>[0-9]+)/upload$', views.upload_votes,
        name='upload_votes'),
]

Aggiungere nel file fantalega\views.py le nuove viste:

...
from .models import Player, Evaluation
...
@login_required
def players(request):
    sorted_players = Player.objects.order_by('code')
    context = {'players': sorted_players}
    return render(request, 'fantalega/players.html', context)


@login_required
def player_details(request, player_id):
    player = Player.objects.get(id=int(player_id))
    votes = player.player_votes.all()
    context = {'player': player, 'votes': votes}
    return render(request, 'fantalega/player.html', context)


@login_required
def vote(request, league_id, day):
    league = get_object_or_404(League, pk=int(league_id))
    if request.GET.get('back_to_teams'):
        return redirect('league_details', league.id)
    votes = Evaluation.objects.filter(season=league.season, day=day).all()
    context = {'votes': votes, 'day': day, 'league': league}
    return render(request, 'fantalega/vote.html', context)


@login_required
def upload_votes(request, league_id):
    league = get_object_or_404(League, pk=int(league_id))
    seasons = enumerate([season.name for season in Season.objects.all()])


    if request.GET.get('back_to_teams'):
        return redirect('league_details', league.id)
    if request.method == "POST":
        form = UploadVotesForm(request.POST, request.FILES,
                               initial={'seasons': seasons})
        if form.is_valid():
            day = form.cleaned_data['day']
            dict_season = dict(form.fields['season'].choices)
            season = dict_season[int(form.cleaned_data['season'])]
            obj_season = get_object_or_404(Season, name=season)
            file_in = request.FILES['file_in']
            Evaluation.upload(path=file_in, day=day, season=obj_season)
            messages.success(request, 'votes uploaded!')
            return redirect('league_details', league.id)
    else:
        form = UploadVotesForm(initial={'seasons': seasons})
    return render(request, 'fantalega/upload_votes.html',
                  {'form': form, 'league': league})

il form che permetterà l’upload dei voti e dei giocatori (fantalega/forms.py) sarà:

...
class UploadVotesForm(forms.Form):
    def __init__(self, *args, **kwargs):
        self.dict_values = kwargs.pop('initial')
        super(UploadVotesForm, self).__init__(*args, **kwargs)
        self.fields['day'] = forms.IntegerField()
        self.fields['season'] = forms.ChoiceField(label=u'season',
                                   choices=self.dict_values['seasons'],
                                   widget=forms.Select(),)
        self.fields['file_in'] = forms.FileField()

e andrà importato nel file fantalega/views.py:

from .forms import UploadVotesForm
...

Ora le template legate alle views suddette.

templates/players.html

{% extends 'fantalega/base.html' %}

{% block content %}
        <font color="green"><b>List of all players</b></font><br><br>
    {% if players %}
        <table class="table table-striped" width="100%">
          <tr>
              <th>code</th>
              <th>name</th>
              <th>team</th>
              <th>fanta team</th>
              <th>season</th>
          </tr>
          <tr>
              {% for player in players %}
                  <td>{{ player.code }}</td>
                  <td><a href="{% url 'player_details' player.id %}">
                      {{ player.name }}</a></td>
                  <td>{{ player.real_team }}</td>
                  <td>{{ player.team.name }}</td>
                  <td>{{ player.season }}</td>
          </tr>
              {% endfor %}
        </table>
    {% else %}
      <font color="red"><b>No players found.</b></font>
    {% endif %}
{% endblock %}

templates/player.html

{% extends 'fantalega/base.html' %}
{% load app_filters %}

{% block content %}
		<h1><font color="green">{{ player.name }}</font></h1>

	<table class="table table-striped" width="100%">
	  <tr>
		  <th>code</th>
		  <th>real team</th>
		  <th>role</th>
		  <th>cost</th>
		  <th>auction value</th>
		  <th>season</th>
	  </tr>
	  <tr>
		  <td>{{ player.code }}</td>
		  <td>{{ player.real_team }}</td>
		  <td>{{ player.role }}</td>
		  <td>{{ player.cost }}</td>
		  <td>{{ player.auction_value }}</td>
		  <td>{{ player.season }}</td>
	  </tr>
	</table>
    <br>
    <b><font color="orange">votes</font></b>
    <br>
	<table class="table table-striped" width="100%">
	  <tr>
		  <th>day</th>
		  <th>fanta value</th>
		  <th>value</th>
		  <th>cost</th>
	  </tr>
        {% for vote in votes %}
          <tr>
			  <td>{{ vote.day }}</td>
			  <td>{{ vote.fanta_value }}</td>
			  <td>{{ vote.net_value }}</td>
			  <td>{{ vote.cost }}</td>
    	  </tr>
	   {% endfor %}
	</table>
    <br>
    <b><font color="orange">presenze</font></b>: {{player|get_played:player.code}}
    <br>
    <b><font color="orange">fv avg</font></b>: {{player|get_avg:player.code}}


{% endblock %}

templates/vote.html

{% extends 'fantalega/base.html' %}

{% block content %}
      <font color="green"><b>{{ league.name }}</b></font>: list of evaluations for day
      <font color="purple"><b>{{ day }}</b></font><br><br>
    <form action="#" method="get">
    <input type="submit" class="btn"
           value="back to {{ league.name }} teams" name="back_to_teams">
    </form>
    <br>
    <table class="table table-striped" width="100%">
      <tr>
          <th>code</th>
          <th>name</th>
          <th>team</th>
          <th>fanta value</th>
          <th>value</th>
          <th>cost</th>
      </tr>
      <tr>
          {% for vote in votes %}
              <td>{{ vote.player.code }}</td>
              <td><a href="{% url 'player_details' vote.player.id %}">
                  {{ vote.player.name }}</a></td>
              <td>{{ vote.player.real_team }}</td>
              <td>{{ vote.fanta_value }}</td>
              <td>{{ vote.net_value }}  </td>
              <td>{{ vote.cost }}</td></tr>
          {% endfor %}
    </table>
{% endblock %}

templates/upload_votes.html

{% extends 'fantalega/base.html' %}
{% load bootstrap3 %}

{% block content %}
    <h1>Upload votes for <font color="green">{{ league.name }}</font></h1><br>
    <form action="#" method="get">
    <input type="submit" class="btn"
           value="back to {{ league.name }} teams" name="back_to_teams">
    </form>
    <form enctype="multipart/form-data" method="POST" class="form">
		{% csrf_token %}
        {% bootstrap_form form %}
            {% buttons %}
                <button type="submit" class="btn btn-primary">
                {% bootstrap_icon "upload" %} Upload</button>
			{% endbuttons %}
    </form>
{% endblock %}

Avviare il server e recarsi nella pagina relativa ai giocatori:
http://127.0.0.1:8000/fantalega/players/
Come si nota non ci sono ancora giocatori, pertanto vanno caricati.
Abilitare il pulsante ‘upload votes’ nella view league_details:

fantalega/views.py

@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'):
        return redirect('matches', league.id)
    if request.GET.get('upload votes'):
        return redirect('upload_votes', league.id)
    context = {'league': league, 'teams': league_teams,
               'user': request.user}
    return render(request, 'fantalega/league.html', context)

I file dei voti sono i soliti MCCxx.txt e si dovranno trovare
nella directory fantalega/static

Nella template player.html si fa uso di alcuni custom filter, pertanto
aggiungerli al file fantalega/templatetags/app_filters.py

...
from fantalega.models import Player, Evaluation
...
@register.filter(name='get_played')
def get_played(player, code):
    obj_player = Player.objects.filter(name=player, code=code).first()
    played = [e for e in Evaluation.objects.filter(player=obj_player).all()
              if e.fanta_value > 0.0 ]
    return len(played)


@register.filter(name='get_avg')
def get_avg(player, code):
    obj_player = Player.objects.filter(name=player, code=code).first()
    fanta_values = [e.fanta_value for e
                    in Evaluation.objects.filter(player=obj_player).all()
                    if e.fanta_value > 0.0 ]
    try:
        return sum(fanta_values)/len(fanta_values)
    except ZeroDivisionError:
        return 0.0

Siccome la pagina dei giocatori deve essere facilmente consultabile da ogni dove,
sarebbe bene aggiungerla nella template base.html (come navbar) in modo che venga
ereditata da tutte.

fantalega/templates/fantalega/base.html

...
      <div class="navbar-header">
        <a class="navbar-brand" href="{% url 'seasons' %}">Seasons</a></div>
      <div class="navbar-header">
        <a class="navbar-brand" href="{% url 'leagues' %}">Leagues</a></div>
      <div class="navbar-header">
        <a class="navbar-brand" href="{% url 'players' %}">Players</a></div>
...

Il server è già avviato, recarsi all’interno della lega e
premere sul pulsante ‘upload votes’. Una volta terminato
l’upload dei dati, recarsi nella pagina http://127.0.0.1:8000/fantalega/players/
ed appariranno tutti i giocatori.
Su ognuno sarà possibile cliccare entrando nella pagina relativa al singolo
giocatore con tutti i dettagli.

Le Squadre e i giocatori ci sono, il prossimo step è quello di definire l’asta (Auction)

Salvare ora gli avanzamenti su github:

git add --all
git commit -m "Player and Evaluation 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
6 – Models: Match

articoli successivi
8 – Asta
9 – Models: Lineup
10 – Models: Trade
11 – Asta di riparazione
12 – Classifica

Categorie:Django Tag:

djangofantalega: model Match

14 novembre 2016 Commenti chiusi

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:

djangofantalega: model Team

14 novembre 2016 Commenti chiusi

5 – Models: Team

Creare il terzo Model dell’ applicazione: Team
Una Lega (League) avrà più squadre (Team), ma una squadra potrà
prendere parte a più leghe (es. Lega e Champions), pertanto
trattasi di relazione many-to-many tra League e Team.

team

La relazione ManytoMany è rappresentata da una AssociationTable
LeaguesTeams.

Aggiungere nel file fantalega/models.py il model Team:

...
from django.contrib.auth.models import User
...


class Team(models.Model):
    name = models.CharField(max_length=32)
    budget = models.IntegerField()
    max_trades = models.IntegerField()
    user = models.OneToOneField(User, null=True, related_name='team')
    leagues = models.ManyToManyField(League, through='LeaguesTeams')

    def __unicode__(self):
        return self.name


# M2M secondary Association object
class LeaguesTeams(models.Model):
    league = models.ForeignKey(League, on_delete=models.CASCADE)
    team = models.ForeignKey(Team, on_delete=models.CASCADE)

    class Meta:
        verbose_name_plural = 'League-Team Associations'

Nota:
la relazione m2m si appoggerà ad una Association Table
dove sarà possibile in futuro, aggiungere comodi attributi.
Molto importante è anche la relazione One-to-One con User.
Un utente registrato avrà a disposizione SOLO una squadra.

Aggiornare il database inserendo la nuova tabella:

(venv) >python manage.py makemigrations
Migrations for 'fantalega':
  fantalega\migrations\0003_auto_20161109_1123.py:
    - Create model LeaguesTeams
    - Create model Team
    - Add field team to leaguesteams

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.0003_auto_20161109_1123... OK

Ora inserire qualche dato nel database con questo criterio:
prima creiamo gli users, al quale successivamente daremo i privilegi
di modifica della squadra e niente più.

(venv) >python manage.py shell
>>> from django.contrib.auth.models import User, Permission
>>> permission = Permission.objects.filter(name__icontains='change team').first()
>>> permission
<Permission: fantalega | team | Can change team>
>>> for name in ('pippo', 'pluto', 'paperino', 'topolino', 'minnie', 'etabeta', 'clarabella'):
...     u = User.objects.create_user(username=name, password=name)
...     u.user_permissions.add(permission)
...     u.save()
>>> for user in User.objects.all():
...     print "{}, staff: {}, active: {}".format(user.username, user.is_staff, user.is_active)
...
bancaldo, staff: True, active: True
pippo, staff: False, active: True
pluto, staff: False, active: True
paperino, staff: False, active: True
topolino, staff: False, active: True
minnie, staff: False, active: True
etabeta, staff: False, active: True
clarabella, staff: False, active: True
>>> permission = Permission.objects.filter(name__icontains='can change team').first()
>>> permission.user_set.all()
<QuerySet [<User: pippo>, <User: pluto>, <User: paperino>, <User: topolino>, <User: minnie>, <User: etabeta>, <User: clarabella>]>
>>> bancaldo = User.objects.get(username__iexact='bancaldo')
>>> bancaldo
<User: bancaldo>
>>> bancaldo.is_staff
True
>>> bancaldo.has_perm(permission)
True
>>> pippo = User.objects.get(username__iexact='pippo')
>>> pippo.user_permissions.all()
<QuerySet [<Permission: fantalega | team | Can change team>]>
>>> pippo.has_perm("fantalega.change_team")
True

Come si nota, ‘bancaldo’ (superuser) non appare nella lista degli users che hanno il permesso
‘Can change team’. In effetti bancaldo non ha nessun specifico permesso abilitato, poichè il parametro
is_staff settato a True durante la creazione dello superuser, li ha abilitati tutti di default.
Queste cose sono tutte consultabili da Admin, alla voce Utenti.

users
singolo_utente

Creare le squadre:

(venv) >python manage.py shell
>>> from django.contrib.auth.models import User, Permission
>>> from fantalega.models import League, Team
>>> league = League.objects.filter(name__icontains='lega').first()
>>> cup = League.objects.filter(name__icontains='champ').first()
>>> for user in User.objects.all():
...     t = Team.objects.create(name='%s_team' % user.username, budget=league.budget, max_trades=league.max_trades,
...     user=user)
...     LeaguesTeams.objects.create(team=t, league=league)
...     LeaguesTeams.objects.create(team=t, league=cup)
...
>>> league.team_set.all()
<QuerySet [<Team: bancaldo_team>, <Team: pippo_team>, <Team: pluto_team>, ...
>>> cup.team_set.all()
<QuerySet [<Team: bancaldo_team>, <Team: pippo_team>, <Team: pluto_team>, ...
>>> t.leagues.all()
<QuerySet [<League: lega 2016-2017>, <League: Champions 2016-2017>]>

Sistemare l’interfaccia di admin per il model Team:
file fantalega/admin.py:

# noinspection PyUnresolvedReferences
from django.contrib import admin
from .models import Season, League, LeaguesTeams, Team


# Register your models here.
class LeaguesInline(admin.TabularInline):
    model = Team.leagues.through
    extra = 0
    classes = ('collapse',)
    verbose_name = 'associated league'
    verbose_name_plural = 'associated leagues'


class TeamsInline(admin.TabularInline):
    model = Team.leagues.through
    extra = 0
    classes = ('collapse',)
    verbose_name = 'associated teams'
    verbose_name_plural = 'associated teams'


class LeagueAdmin(admin.ModelAdmin):
    inlines = [TeamsInline, ]
    fieldsets = [(None, {'fields': ['name', 'budget', 'max_trades',
                                    'rounds', 'offset']}),
                 ('season', {'fields': ['season']}),
                 ('number of Players', {'fields': ['max_goalkeepers',
                                                   'max_defenders',
                                                   'max_midfielders',
                                                   'max_forwards']}),
                 ]


class TeamAdmin(admin.ModelAdmin):
    inlines = [LeaguesInline]
    list_display = ('name', 'budget', 'max_trades')
    ordering = ('-budget', )


class LeaguesTeamsAdmin(admin.ModelAdmin):
    list_display = ('league', 'team')
    list_filter = ('league', 'team')
    list_per_page = 20


admin.site.register(Season)
admin.site.register(League, LeagueAdmin)
admin.site.register(LeaguesTeams, LeaguesTeamsAdmin)
admin.site.register(Team, TeamAdmin)

Qui viene introdotta la classe admin.TabularInline, che permette
di visualizzare in maniera tabulare determinati valori.
la tupla ‘classes’ permette di aggiungere il collapse di una
determinata sezione. Model è il modello di riferimento.
L’aspetto sarà il seguente:

admin_team

Ora aggiungere gli urls inerenti Team nel file fantalega\urls.py:

...
    # team urls
    url(r'^leagues/(?P<league_id>[0-9]+)/teams/(?P<team_id>[0-9]+)/$',
        views.team_details, name='team_details'),
]

Aggiungere nel file fantalega\views.py le nuove viste ‘teams’ e ‘team_details’:

...
from .models import Season, League, Team  # Team added
...


@login_required
def team_details(request, league_id, team_id):
    league = get_object_or_404(League, pk=int(league_id))
    team = get_object_or_404(Team, pk=int(team_id))
    context = {'team': team, 'user': request.user, 'league': league}
    if request.GET.get('back_to_teams'):
        return redirect('league_details', league.id)
    return render(request, 'fantalega/team.html', context)

L’if presente nel model permette di essere redirezionati verso
l’url con nome ‘league_details’, quando nella template team.html
il pulsante con valore ‘back_to_teams’, viene premuto.
Ora che esistono anche le squadre (Team), facciamo in modo che siano
presenti anche nella template relativa alla stagione (League), aggiungendo
la lista delle squadre, al dizionario ‘context’ della vista ‘league_details’:

...
from .models import Season, League, Team  # Team added
...

@login_required
def league_details(request, league_id):
    league = get_object_or_404(League, pk=int(league_id))
    league_teams = league.team_set.all()  # list of teams
    context = {'league': league, 'teams': league_teams}
    return render(request, 'fantalega/league.html', context)
...

La template sarà quindi:
fantalega/team.html

{% extends 'fantalega/base.html' %}
{% load bootstrap3 %}
{% load app_filters %}

{% block content %}
    <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="40%">
      <tr>
          <th>budget</th>
          <th>trade remaining</th>
      </tr>
      <tr>
              <td>{{ team.budget }}</td>
              <td>{{ team.max_trades }}</td>
      </tr>
    </table>
        <div id="container" style="width:100%;">
            <div id="left" style="float:left; width:50%;">
                <b><font color="orange">Players:</font></b><br>
                <table class="table table-striped" width="40%">
                  <tr>
                      <th>code</th>
                      <th>player name</th>
                      <th>real team</th>
                      <th>cost</th>
                  </tr>
                      {% for player in team.player_set.all %}
                  <tr>
                          <td>{{ player.code }}</td>
                          <td><a href="{% url 'player_details' player.id %}">
                              {{ player.name }}</a></td>
                          <td>{{ player.real_team }}</td>
                          <td>{{ player.cost }}</td>
                  </tr>
                      {% endfor %}
                </table>

                <form action="#" method="get">
                  <input type="submit" class="btn" value="sell players for repair session"
                         name="sale">
                </form>

            </div>
            <div id="right" style="float:right; width:50%;">
                <b><font color="orange">Trade operations:</font></b><br>
                <table class="table table-striped" width="40%">
                  <tr>
                      <th>IN/OUT</th>
                      <th>player name</th>
                  </tr>
                      {% for trade in team.trade_set.all %}
                  <tr>
                          <td>{{ trade.direction }}</td>
                          <td><a href="{% url 'player_details' trade.player.id %}">
                              {{ trade.player.name }}</a></td>
                  </tr>
                      {% endfor %}
                </table>
                {% if user.is_authenticated and user.team == team %}
                <form action="#" method="get">
                 <input type="submit" class="btn" value="new trade"
                        name="new trade">
                </form>
                <b><font color="orange">Team lineups:</font></b><br>
                  {% for lineup in lineups %}
                    <ul>
                      <li><a href="{% url 'lineup_details' league.id team.id lineup.day %}">lineup day:
                          {{ lineup.day }}</a>: {{ lineup.pts|pts_filter }}</li>
                    </ul>
                  {% endfor %}
                <form action="#" method="get">
                 <input type="submit" class="btn" value="new lineup"
                        name="new lineup">
                </form>
                {% endif %}
            </div>
        </div>
{% endblock %}

In questa template si fa uso di un custom_filter. Nella fattispecie, quando saranno
disponibili le formazioni (Lineup), a seconda del punteggio effettuato dalla
formazione, il valore stesso sarà colorato diversamente.
Per creare un custom_filter, creare nella directory fantalega,
una sottodirectory templatetags, con all’interno un file vuoto __init__.py e
un file app_filters.py.
Il contenuto sarà:

# noinspection PyUnresolvedReferences
from django import template
from django.utils.safestring import mark_safe


register = template.Library()


@register.assignment_tag
def get_bootstrap_alert_msg_css_name(tags):
    return 'danger' if tags == 'error' else tags


@register.filter(name='pts_filter')
def pts_filter(value):
    if value:
        if float(value) <= 60:
            color = 'e60000'
        elif 60 < float(value) <= 72:
            color = 'cc66ff'
        else:
            color = '009933'
        new_string = '<b><font color="#%s">%s</font></b>' % (color, value)
        return mark_safe(new_string)
    else:
        return value

Nella template i filters vanno caricati all’inizio con la voce:

{% load app_filters %}

mentre il filtro verrà chiamato con la sintassi ‘valore|filtro:

{{ lineup.pts|pts_filter }}

il valore lineups.pts viene passato come argomento alla pts_filter()
che ritorna lo stesso valore, corredato da una colorazione specifica.
E’ assolutamente necessario ritornare il valore con mark_safe.

Andando alla pagina http://127.0.0.1:8000/fantalega/leagues/ sarà possibile
cliccare sulla lega desiderata, entrare nel dettaglio della lega stessa e, se
create, entrare nel dettaglio di una delle squadre elencate.

team_details

Salvare gli avanzamenti su github:

git add --all
git commit -m "Team 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

articoli successivi
6 – Models: Match
7 – Models: Player
8 – Asta
9 – Models: Lineup
10 – Models: Trade
11 – Asta di riparazione
12 – Classifica

Categorie:Django Tag: