Warning: Creating default object from empty value in /membri/bancaldo/wp-content/plugins/paged-comments/paged-comments.php on line 31
bancaldo™ » django

Archivio

Posts Tagged ‘django’

django: standalone application

3 aprile 2017 Commenti chiusi

Supponiamo di dover creare una piccola applicazione (‘players’)
che inserisca in un database dei dati, ad esempio i nomi
dei calciatori della serie A.
Ci piace utilizzare django per la confidenza che abbiamo con il suo ORM,
ma non abbiamo voglia di sbatterci con tutto il meraviglioso e vastissimo
mondo di django, comprensivo di pattern Model-View-Template, Admin, browser…
Vogliamo invece usare le wx, ma non SQLAlchemy, o peewee.
Non resta che strutturare la nostra mini applicazione in questo modo:

C:\TMP\STANDALONEDJANGOEXAMPLE
|   main.py
|   manage.py
|   settings.py
|   __init__.py
|   
\---players
    |   controller.py
    |   model.py
    |   models.py
    |   view.py
    |   __init__.py
    |   
    \---migrations
            __init__.py

partendo dall’alto, analizziamo i singoli file.

‘main.py’ è il cuore della nostra applicazione django STAND-ALONE e contiene
i settaggi che ci permetteranno di utilizzare solo l’ORM di django:

import os

# qui importiamo il modulo settings
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")


import sys
# qui aggiungiamo il percorso della nostra app al path
sys.path.append('/tmp/standalonedjangoexample')
# qui aggiungiamo i virtualenv site-packages al sys.path
sys.path.append('/tmp/venv/Lib/site-packages')


from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()


if __name__ == '__main__':
    from players.controller import Controller
    c = Controller()

questo è il contenuto di ‘manage.py’, necessario per le migrations:

import os
import sys

sys.dont_write_bytecode = True

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
    from django.core.management import execute_from_command_line
    execute_from_command_line(sys.argv)

poi abbiamo i ‘settings.py’:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': 'players.db',                      # Or path to database file if using sqlite3.
        'USER': '',                      # Not used with sqlite3.
        'PASSWORD': '',                  # Not used with sqlite3.
        'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
    }
}

INSTALLED_APPS = (
    'players',
    )

SECRET_KEY = 'mysecretkey'

Ora entriamo nel cuore della app, che prevede:
– i models che django utilizzerà con il suo ORM
– i file per uno spartano MVC

Questo spartano MVC prevede una file view.py che importa il modulo wx
per creare un’interfaccia di inserimento dati, un model.py per la gestione
dei dati e un controller che metta interagisca con essi tenendoli separati.

Il file models.py (django models)

from django.db import models


class Player(models.Model):
    name = models.CharField(max_length=32)

    def __unicode__(self):
        return self.name

il file view.py

import wx


class InfoMessage(wx.MessageDialog):
    """Simple message Dialog"""
    def __init__(self, parent, message):
        super(InfoMessage, self).__init__(parent, message, 'info', wx.OK |
                                          wx.ICON_EXCLAMATION)
        if self.ShowModal() == wx.ID_OK:
            self.Destroy()


class ViewNewPlayer(wx.Frame):
    def __init__(self, parent, controller, title):
        self.title = title
        super(ViewNewPlayer, self).__init__(parent=parent, title=title,)
                                         #style=STYLE)
        self.controller = controller
        self._build()
        self._bind_widgets()
        self.Centre()
        self.Show()

    def _build(self):
        self.panel = PanelNewPlayer(parent=self)
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.panel, 1, wx.EXPAND)
        self.SetSizer(sizer)
        self.SetSize((250, 100))

    def _bind_widgets(self):
        self.Bind(wx.EVT_BUTTON, self.on_quit, self.panel.btn_quit)
        self.Bind(wx.EVT_BUTTON, self.on_save, self.panel.btn_save)
        self.Bind(wx.EVT_TEXT, self.on_text_entry, self.panel.name)

    def show_message(self, message):
        InfoMessage(parent=self, message=message)

    def on_text_entry(self, event):
        entry = self.panel.name.GetValue()
        if len(entry) > 2:
            self.panel.btn_save.Enable()
        else:
            self.panel.btn_save.Disable()

    def on_save(self, event):
        name = self.panel.name.GetValue()
        self.controller.save_player(name)

    def on_quit(self, event):
        self.Destroy()


class PanelNewPlayer(wx.Panel):
    def __init__(self, parent):
        super(PanelNewPlayer, self).__init__(parent)
        # Attributes
        self.name = wx.TextCtrl(self)
        text_sizer = wx.FlexGridSizer(rows=2, cols=2, hgap=5, vgap=5)
        text_sizer.Add(wx.StaticText(self, label=''),
                       0, wx.ALIGN_CENTER_VERTICAL)
        text_sizer.AddGrowableCol(1)
        button_sizer = wx.StdDialogButtonSizer()
        self.btn_save = wx.Button(self, wx.ID_SAVE)
        self.btn_quit = wx.Button(self, wx.ID_CANCEL)
        # Buttons initialization
        self.btn_quit.SetDefault()
        self.btn_save.Disable()
        # Sizers
        button_sizer.AddButton(self.btn_save)
        button_sizer.AddButton(self.btn_quit)
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(text_sizer, 1, wx.EXPAND | wx.ALL, 5)
        sizer.Add(button_sizer, 0, wx.ALIGN_CENTER | wx.ALL, 5)
        button_sizer.Realize()
        self.SetBackgroundColour('LightGray')
        self.SetSizer(sizer)

il file model.py (MVC) che si interfaccerà a sua volta con i models di django:

from models import Player


class Model(object):

    @staticmethod
    def new_player(name):
        """new_player(name) -> player object"""
        player = Player.objects.create(name=name.upper())
        return player

    @staticmethod
    def get_player(name):
        """get_player(name) -> player object"""
        return Player.objects.filter(name=name.upper()).first()

    @staticmethod
    def get_players():
        """get_players() -> iterable"""
        return Player.objects.all()

il file controller.py

import wx
from model import Model
from view import ViewNewPlayer


class Controller(object):
    def __init__(self):
        app = wx.App()
        self.model = Model()
        self.view = ViewNewPlayer(parent=None, controller=self,
                                  title='New Player')
        app.MainLoop()

    def get_player(self, name):
        return self.model.get_player(name)

    def save_player(self, name):
        player_object = self.get_player(name)
        if player_object:
            self.view.show_message("Player already exist")
        else:
            new_player = self.model.new_player(name)
            self.view.show_message("New Player %s saved" % new_player.name)

Questo è quanto.
Ovviamente non esistono ancora le tabelle del database e provvediamo con i
comandi:

(venv) C:\tmp\standalonedjangoexample>python manage.py makemigrations
Migrations for 'players':
  players\migrations\0001_initial.py:
    - Create model Player

e successivamente:

(venv) C:\tmp\standalonedjangoexample>python manage.py migrate
Operations to perform:
  Apply all migrations: players
Running migrations:
  Applying players.0001_initial... OK

poi è possibile eseguire la nostra applicazione utilizzando il file
main.py.

Ovviamente la struttura dell’applicazione può essere fatta in mille modi
diversi, come pure il raggruppamento dei codici.
Lo stesso main.py può fungere da controller riducendo i livelli di
separazione, soprattutto qualora non si volesse utilizzare un
pattern simil-MVC.

Categorie:Django, python Tag: ,

django – RuntimeWarning: DateTimeField received a naive datetime

2 febbraio 2017 Commenti chiusi

Abbiamo un form

class ChangeForm(forms.Form):
    date = forms.DateField(label='date', initial=datetime.date.today)
    ...

e la view di competenza

def edit(request, item_id):
    myitem = get_object_or_404(AItem, pk=item_id)
    if request.method == "POST":
        form = ChangeForm(request.POST)
        if form.is_valid():
            myitem.date = form.cleaned_data['date']
            myitem.save()
            return redirect(...)
    else:
        form = ChangeForm(initial={'date': myitem.date})
    return render(request, 'atemplate.html',
                  {'form': form, 'myitem': myitem})

nel momento del salvataggio dei dati (POST) ottengo il warning in oggetto.

per risolvere il problema, tramite timezone di django, trasformiamo le date
da formato ‘naive’ a formato ‘aware’.
Nel mio caso la data è di tipo datetime.date per tento prima,
la trasformerò in formato datetime.datetime.

>>> import datetime
>>> from django.utils import timezone
>>> a_date = datetime.date.today()
>>> a_datetime = datetime.datetime.combine(a_date, datetime.time())
>>> a_datetime
datetime.datetime(2017, 2, 2, 0, 0)
>>> aware_date = timezone.make_aware(a_datetime, timezone.get_default_timezone())
>>> aware_date
datetime.datetime(2017, 2, 2, 0, 0, tzinfo=<UTC>)

oppure tramite il modulo pytz (installabile tramite pip):

>>> import pytz
>>> naive = datetime.datetime(2017, 2, 2, 12, 0, 0)
>>> aware = datetime.datetime(2017, 2, 2, 12, 0, 0, 0, pytz.UTC)
>>> aware == naive
Traceback (most recent call last):
  File "<console>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware datetimes
>>> aware == pytz.utc.localize(naive)
True

ora basta modificare la view con la gestione corretta delle date:

def edit(request, item_id):
    myitem = get_object_or_404(AItem, pk=item_id)
    if request.method == "POST":
        form = ChangeForm(request.POST)
        if form.is_valid():
            naive_date = form.cleaned_data['date']
            naive_datetime = datetime.datetime.combine(naive_date, datetime.time())
            myitem.date = timezone.make_aware(naive_datetime, 
                                              timezone.get_default_timezone())
            myitem.save()
            return redirect(...)
    else:
        form = ChangeForm(initial={'date': myitem.date})
    return render(request, 'atemplate.html',
                  {'form': form, 'myitem': myitem})
Categorie:Django, python Tag: ,

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: