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

djangofantalega: model Player e model Evaluation

14 novembre 2016 Commenti chiusi

7 – Models: Player ed Evaluation

Creare i modelli Player ed Evaluation.

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

add_player

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

...
from .models import Player, Evaluation


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

    def __unicode__(self):
        return self.name

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

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


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

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

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

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

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

Aggiornare il database inserendo la nuova tabella.

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

confermare con:

(venv) C:\tmp\djangosite>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, fantalega, log, sessions
Running migrations:
  Applying fantalega.0005_auto_20161110_1407... OK

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

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


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

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


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

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

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

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


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

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

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

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

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


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


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


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


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

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

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

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

from .forms import UploadVotesForm
...

Ora le template legate alle views suddette.

templates/players.html

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

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

templates/player.html

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

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

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


{% endblock %}

templates/vote.html

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

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

templates/upload_votes.html

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

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

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

fantalega/views.py

@login_required
def league_details(request, league_id):
    league = get_object_or_404(League, pk=int(league_id))
    league_teams = league.team_set.all()
    if request.GET.get('calendar'):
        return redirect('calendar', league.id)
    if request.GET.get('matches'):
        return redirect('matches', league.id)
    if request.GET.get('upload votes'):
        return redirect('upload_votes', league.id)
    context = {'league': league, 'teams': league_teams,
               'user': request.user}
    return render(request, 'fantalega/league.html', context)

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

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

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


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

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

fantalega/templates/fantalega/base.html

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

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

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

Salvare ora gli avanzamenti su github:

git add --all
git commit -m "Player and Evaluation added"
git push -u origin master

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

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

Categorie:Django Tag:

djangofantalega: model Match

14 novembre 2016 Commenti chiusi

6 – Models: Match

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

add_match

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

...
from django.utils import timezone
...


class Match (models.Model):
    league = models.ForeignKey(League, related_name='matches')
    day = models.IntegerField()
    home_team = models.ForeignKey(Team, related_name='home_team')
    visit_team = models.ForeignKey(Team, related_name='visit_team')
    dead_line = models.DateTimeField(null=True)

    class Meta:
        verbose_name_plural = 'Matches'

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

Aggiornare il database inserendo la nuova tabella.

(venv) >python manage.py makemigrations
Migrations for 'fantalega':
  fantalega\migrations\0004_match.py:
    - Create model Match

confermare con:

(venv) C:\tmp\djangosite>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, fantalega, log, sessions
Running migrations:
  Applying fantalega.0004_match... OK

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

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


# Register your models here.
...
class MatchAdmin(admin.ModelAdmin):
    ordering = ('day', )
    list_display = ('day', 'home_team', 'visit_team', 'dead_line')
    list_filter = ('day', )
    list_per_page = 15


...
admin.site.register(Match, MatchAdmin)

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

...
    # matches urls
    url(r'^leagues/(?P<league_id>[0-9]+)/matches$', views.matches,
        name='matches'),
    url(r'^leagues/(?P<league_id>[0-9]+)/matches/(?P<day>[0-9]+)/$',
        views.match_details, name='match_details'),
    url(r'^leagues/(?P<league_id>[0-9]+)/matches/(?P<day>[0-9]+)/deadline$',
        views.match_deadline, name='match_deadline'),
]

Aggiungere nel file fantalega\views.py le nuove viste ‘matches’ e ‘match_details’
e ‘match_deadline’. Quest’ultima utilizzerà un form per l’inserimento del termine
ultimo della consegna della formazione. Siccome una giornata di Lega formata ad es.
da 10 squadre, è composta da 5 incontri, si dovrebbe inserire 5 volte la data, una
per ogni match della giornata X, con il form la si inserisce una volta sola:

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

@login_required
def matches(request, league_id):
    league = get_object_or_404(League, pk=int(league_id))
    league_matches = league.matches
    days = [d['day'] for d in Match.objects.filter(
        league=league).values('day').distinct()]
    d_calendar = Match.calendar_to_dict(league)
    if request.GET.get('back_to_teams'):
        return redirect('league_details', league.id)
    context = {'league': league, 'd_calendar': d_calendar,
               'days': days, 'matches': league_matches}
    return render(request, 'fantalega/matches.html', context)


@login_required
def match_details(request, league_id, day):
    dict_evaluated = {}
    league = get_object_or_404(League, pk=int(league_id))
    fantaday = int(day) + league.offset
    if request.GET.get('back_to_calendar'):
        return redirect('matches', league.id)
    if request.GET.get('insert_dead_line'):
        return redirect('match_deadline', league.id, day)
    league_matches = league.matches.filter(day=int(day))
    missing_lineups = []
    if request.GET.get('calculate'):
        for match in league_matches:
            home_lineup = match.home_team.team_lineups.filter(
                day=int(day), league=league).first()
            visit_lineup = match.visit_team.team_lineups.filter(
                day=int(day), league=league).first()
            offset_day = int(day) + league.offset
            if Evaluation.objects.filter(
                    day=offset_day, season=league.season).count() == 0:
                messages.error(request,
                               'day %s evaluations missing, import them!' %
                               offset_day)
                return redirect('matches', league.id)

            if home_lineup and visit_lineup:
                h_home = LineupHandler(home_lineup, int(day), league)
                h_visit = LineupHandler(visit_lineup, int(day), league)
                home_pts = h_home.get_pts() + 2
                visit_pts = h_visit.get_pts()
                home_goals, visit_goals = get_final(home_pts, visit_pts)
                data = lineups_data(home_goals, visit_goals)
                home_lineup.pts = home_pts
                home_lineup.save()
                visit_lineup.pts = visit_pts
                visit_lineup.save()
                if not h_home.new_list:  # if all holders are evaluated
                    dict_evaluated[match.home_team] = (h_home.holders,
                                                       h_home.mod)
                else:
                    dict_evaluated[match.home_team] = (h_home.new_list,
                                                       h_home.mod)
                if not h_visit.new_list:
                    dict_evaluated[match.visit_team] = (h_visit.holders,
                                                        h_visit.mod)
                else:
                    dict_evaluated[match.visit_team] = (h_visit.new_list,
                                                        h_visit.mod)
                for lineup, prefix in [(home_lineup, 'h'), (visit_lineup, 'v')]:
                    lineup.won = data.get("%sw" % prefix)
                    lineup.matched = data.get("%sm" % prefix)
                    lineup.lost = data.get("%sl" % prefix)
                    lineup.goals_made = data.get("%sgm" % prefix)
                    lineup.goals_conceded = data.get("%sgc" % prefix)
                    lineup.save()
            else:
                if not home_lineup:
                    missing_lineups.append(match.home_team.name)
                if not visit_lineup:
                    missing_lineups.append(match.visit_team.name)
        if not missing_lineups:
            messages.success(request, 'All Lineup values are upgraded!')
        else:
            if len(missing_lineups) == league.team_set.count():
                message = 'No lineups uploaded for day %s yet' % day
            else:
                message = 'Some Lineups are missing: %s' % \
                          ', '.join(missing_lineups)
            messages.error(request, message)
            return redirect('matches', league.id)
    context = {'league': league, 'matches': league_matches, 'day': day,
               'dict_evaluated': dict_evaluated, 'fantaday': fantaday}
    return render(request, 'fantalega/match.html', context)


@login_required
def match_deadline(request, league_id, day):
    league = get_object_or_404(League, pk=int(league_id))
    days = enumerate([d['day'] for d in Match.objects.filter(
            league=league).values('day').distinct()], 1)
    if request.GET.get('match_details'):
        return redirect('match_details', league.id, day)

    if request.method == "POST":
        form = MatchDeadLineForm(request.POST, initial={'day': day,
                                                        'days': days})
        if form.is_valid():
            f_day = form.cleaned_data['day']
            f_dead_line = form.cleaned_data['dead_line']
            for match in Match.objects.filter(league=league, day=f_day).all():
                match.dead_line = f_dead_line
                match.save()
            messages.success(request, "Dead line for day %s stored!" % f_day)

    else:
        form = MatchDeadLineForm(initial={'day': day, 'days': days})
        previous_dead_line = Match.get_dead_line(league, day)
        if previous_dead_line:
            messages.warning(request, "Dead line already set!")
            form.fields['dead_line'].initial = previous_dead_line
        else:
            messages.info(request, "Dead line doesn't exist!")

    return render(request, 'fantalega/deadline.html',
                  {'form': form, 'league': league, 'day': day})

il form (fantalega/forms.py) sarà:

...

class MatchDeadLineForm(forms.Form):
    def __init__(self, *args, **kwargs):
        self.dict_values = kwargs.pop('initial')
        super(MatchDeadLineForm, self).__init__(*args, **kwargs)
        m_day = self.dict_values.get('day')
        days = self.dict_values.get('days')
        self.fields['day'] = forms.ChoiceField(initial=m_day,
            label=u'day', choices=days,
            widget=forms.Select(), required=False)
        self.fields['dead_line'] = forms.DateTimeField(required=True)

Nota:
Per poter assegnare dinamicamente i valori alla choicefield dalla view,
è necessario utilizzare l’__init__ in modo da inizializzare i field con
i valori passati all’oggetto form, nel momento in cui esso viene creato.

  # views.match_deadline
...
    if request.method == "POST":
        form = MatchDeadLineForm(request.POST, initial={'day': day,
                                                        'days': days})

Si estraggono cioè i valori da passare alla choicefield, nell’__init__,
utilizzando il metodo pop() sul dizionario kwargs, dopodichè
si invoca super() per inizializzare la classe padre del form.

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

Nota:
In molte guide di django i form vengono
creati senza bisogno dell’__init__, ma creando i field direttamente con
attributi di classe, poichè i field sono intesi come “costanti” del form.

Il form deve essere importato nelle views ovviamente:

...
from .forms import MatchDeadLineForm
...

Ora le template legate alle views suddette.

templates/matches.html

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

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

    <table class="table table-striped" width="100%">
      {% for day in days %}
          <tr>
              <th colspan="2">
              <a href="{% url 'match_details' league.id day %}">
              giornata {{ day }}</a>
                  {% if league|need_calc:day %}
                    {% if not league|has_pts:day %}
                      <font color="green"> ....click to calculate</font>
                    {% endif %}
                  {% else %} <font color="purple">....lineups missing</font>
                  {% endif %}
              </th>
          </tr>
          {% get_matches matches day as f_matches %}
          {% for match in f_matches %}
          <tr>
              <td align="left">{{ match.home_team.name }}</td>
              <td align="left">{{ match.visit_team.name }}</td>
              <td>{{ match.home_team|get_goals:day }}</td>
              <td>{{ match.visit_team|get_goals:day }}</td>
          </tr>
          {% endfor %}
      {% endfor %}
    </table>
{% endblock %}

templates/match.html

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

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

    {% if user.is_staff %}
    <form>
        <input type="submit" class="btn" value="calculate" name="calculate">
        <input type="submit" class="btn" value="insert deadline"
               name="insert_dead_line">
    </form>
    {% endif %}

    <div id="container" style="width:70%;">

        <table class="table table-striped" width="100%">
        {% if matches %}
          {% for match in matches %}
            <tr>
            <td><a href="{% url 'team_details' league.id match.home_team.id %}">
            {{ match.home_team }}</a></td>
            <td><a href="{% url 'team_details' league.id match.visit_team.id %}">
            {{ match.visit_team }}</a></td>
            <td>{{ match.home_team|get_goals:day }}</td>
            <td>{{ match.visit_team|get_goals:day }}</td>
            </tr>
          {% endfor %}
        </table>
    </div>

  <div id="left" style="float:left; width:30%;">
  {% for match in matches %}
    <font color="green"><b>{{match.home_team}}</b></font><br>
    {% for player in dict_evaluated|get_evaluated:match.home_team %}
      {{player.name|is_defender}}: {{player|get_fvote:fantaday}}<br>
    {% endfor %}
    <b>mod:{{dict_evaluated|get_defense_mod:match.home_team}}</b><br>
    <b>total: {{match.home_team|get_total:day}}</b><br><br>

  {% endfor %}
  </div>

  <div id="right" style="float:left; width:30%;">
  {% for match in matches %}
    <font color="green"><b>{{match.visit_team}}</b></font><br>
    {% for player in dict_evaluated|get_evaluated:match.visit_team %}
      {{player.name|is_defender}}: {{player|get_fvote:fantaday}}<br>
    {% endfor %}
    <b>mod:{{dict_evaluated|get_defense_mod:match.visit_team}}</b><br>
    <b>total: {{match.visit_team|get_total:day}}</b><br><br>
  {% endfor %}
  </div>

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

templates/deadline.html

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

{% block content %}
    <form action="#" method="get">
    <input type="submit" class="btn"
           value="back to match day {{ day }}" name="match_details">
    </form>

    <form method="POST" class="form">
	  {% csrf_token %}
        {% bootstrap_field form.day %}
        {% bootstrap_field form.dead_line %}
        {% buttons %}
          <button type="submit" class="btn btn-primary">
          {% bootstrap_icon "save" %}Save dead line</button>
	    {% endbuttons %}
    </form>
{% endblock %}

Avviare il server e recarsi nella pagina relativa alle leghe:
http://127.0.0.1:8000/fantalega/leagues/
Cliccando su una delle due leghe, si vedranno una serie di pulsanti, tra
cui ‘create calendar’. E’ necessario associare a questo pulsante,
della template fantalega/league.html,

<input type="submit" class="btn" value="Create Calendar"
    name="calendar">

il codice necessario alla creazione del calendario.

Aggiungere nella view ‘league_details’ quanto segue:

    if request.GET.get('calendar'):
        return redirect('calendar', league.id)

in modo che la view diventi:

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

premendo il pulsante, viene generato un oggetto dizionario QueryDict:

<QueryDict: {u'calendar': [u'Create Calendar']}>

se premessimo il pulsante ‘Start Auction’:

<QueryDict: {u'auction': [u'Start Auction']}>

Utilizzando il metodo get() con chiave ‘calendar’, si ottiene, nel caso
venga trovata nel dizionario, una lista con i parametri del bottone della template.
Non interessano affatto i paramteri del bottone, ma solo che sia presente la
chiave stessa, in caso negativo get() non ritornerà nulla.
Con un semplice if il gioco è fatto e la palla sarà passata alla view ‘calendar’,
grazie a

return redirect('calendar', league.id)

‘calendar’ è il reverse name utilizzato da redirect per trovare la url necessaria.
Aggiungere quindi nel file fantalega/urls.py la url necessaria

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

la view ‘calendar’ sarò così composta:

...

@login_required
def calendar(request, league_id):
    league = get_object_or_404(League, pk=int(league_id))
    if request.GET.get('back_to_teams'):
        return redirect('league_details', league.id)
    league_matches = league.matches.order_by('day')
    days = [d['day'] for d in Match.objects.filter(
            league=league).values('day').distinct()]
    if league_matches:
        messages.warning(request, 'Calendar already exists!!')
    else:
        league_teams = [t for t in league.team_set.all()]
        cal = create_season(teams=league_teams, num=league.rounds)
        for record in cal:
            day, home_team, visit_team = record
            Match.objects.create(league=league, day=day, home_team=home_team,
                                 visit_team=visit_team)
        messages.success(request, 'Calendar done!')
        league_matches = league.matches.order_by('day')

    context = {'matches': league_matches, 'league': league, 'days': days}
    return render(request, 'fantalega/calendar.html', context)

nella view, si fa riferimento ad una funzione create_season

cal = create_season(teams=league_teams, num=league.rounds)

Creare sotto fantalega una sottodirectory scripts con all’interno un modulo con il
codice necessario alla creazione e gestione del calendario.
fantalega/scripts/calendar.py:

from copy import copy
from random import shuffle


def first_step_season(teams):
    """
    first_step_season(teams) -> list
    create a matrix to fill with teams"""
    matrix = []
    counter = 0
    while counter < len(teams):
        matrix.append([None] * len(teams))
        counter += 1
    matrix[0] = teams  # header

    # reversed header without th last team
    row2 = copy(teams)
    row2.pop()
    row2.reverse()
    matrix[1][0:(len(teams) - 1)] = row2[0:(len(teams) - 1)]

    # Table composition: first step
    i = 1
    while i < len(teams):
        k = 1
        for item in matrix[i]:
            try:
                matrix[i + 1][k] = item
                matrix[i + 1][0] = matrix[i + 1][(len(teams) - 1)]
                matrix[i + 1][(len(teams) - 1)] = None
                k += 1
            except IndexError:
                break
        i += 1

    # Table composition: second step
    row_m = 1
    while row_m < len(teams):
        for item_a in matrix[0]:
            for item_b in matrix[row_m]:
                if matrix[0].index(item_a) == matrix[row_m].index(item_b):
                    if item_a == item_b:
                        matrix[row_m][matrix[row_m].index(item_b)] = \
                            teams[-1]
                        matrix[row_m][(len(teams) - 1)] = item_b
        row_m += 1

    cal = []
    day = 1
    while day < len(teams):
        first_round = []
        for team1 in matrix[0]:
            for team2 in matrix[day]:
                if matrix[0].index(team1) == matrix[day].index(team2):
                    if team2 not in first_round or team1 not in first_round:
                        if team1.home is True:
                            first_round.append(team1)
                            first_round.append(team2)
                            cal.append((day, team1, team2))
                            team1.set_visit()
                            team2.set_home()
                        else:
                            first_round.append(team2)
                            first_round.append(team1)
                            cal.append((day, team2, team1))
                            team1.set_home()
                            team2.set_visit()
        day += 1
    return cal


def second_step_season(cal, teams):
    """
    second_step_season(cal, teams) -> list

    Create the second round for season
    """
    scond_round_cal = []
    for match in cal:
        n, team1, team2 = match
        scond_round_cal.append((int(n) + len(teams) - 1, team2, team1))
    return scond_round_cal


def create_season(teams, num):
    """
    create_season(teams, num) -> list
    
    Iterable is the list of teams present in db
    num is the number of round of the tournament
    """
    shuffle(teams)
    for team in teams:
        team.home = True  # Init for home_visit team calculation
    cal = first_step_season(teams)
    rounds = 1
    full_cal = cal
    while rounds < num:
        cal = second_step_season(cal, teams)
        full_cal += cal
        rounds += 1
    return full_cal

Ora basta importare tale modulo nelle views: fantalega/views.py

from fantalega.scripts.calendar import create_season
...

Per far sì che venga rispettata la regola del ‘in_casa’, ‘fuori_casa’,
nel model Team è stato aggiunto un flag ‘home’, che, durante la generazione del
calendario verrà settato alternativamente a True e False.

Nel model Team, quindi aggiungere:

class Team(models.Model):
    ...
    def set_home(self):
        self.home = True

    def set_visit(self):
        self.home = False

Manca la template calendar.html che avrà questo codice:

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

{% block content %}
    <b>List of all matches for <font color="green">
        {{ league.name }}</font></b><br><br>

    <form action="#" method="get">
     <input type="submit" class="btn" value="back to {{ league.name }} teams"
            name="back_to_teams">
    </form>
    {% if matches %}

    <table class="table table-striped" width="100%">
      {% for day in days %}
          <tr>
              <th colspan="2"><font color="purple">giornata {{ day }}</font></th>
          </tr>
          {% get_matches matches day as f_matches %}
          {% for match in f_matches %}
          <tr>
              <td align="left">{{ match.home_team.name }}</td>
              <td align="left">{{ match.visit_team.name }}</td>
          </tr>
          {% endfor %}
      {% endfor %}
    </table>
    {% else %}
      <font color="red"><b>No calendar/matches found.</b></font>
    {% endif %}
{% endblock %}

Per come è strutturata la template e per come lo è il model Match,
si ha la necessità di filtrare i match in base alla giornata (day)
E’ necessario creare quindi un custom_tag che viene richiamato in
questa riga

          {% get_matches matches day as f_matches %}

Aprire quindi il file fantalega/templatetags/app_filters.py e aggiungere:

@register.assignment_tag
def get_matches(matches, day):
    return matches.filter(day=day)

La fase di creazione calendario è sistemata.

Nella template league.html è stato inserito anche un pulsante ‘view calendar’,
quindi sistemiamo la view league_details in modo che, premendo tale pulsante
si possa accedere alla pagina desiderata.

la view league_details diventerà:

@login_required
def league_details(request, league_id):
    league = get_object_or_404(League, pk=int(league_id))
    league_teams = league.team_set.all()
    if request.GET.get('calendar'):
        return redirect('calendar', league.id)
    if request.GET.get('matches'):  # add for view calendar
        return redirect('matches', league.id)  # add for view calendar
    context = {'league': league, 'teams': league_teams,
               'user': request.user}
    return render(request, 'fantalega/league.html', context)

il redirect richiamerà la url con nome ‘matches’ che richiamerà la view ‘matches’, ecc.
Per comodità di gestione dei dati, sono stati aggiunti alcuni metodi al model Match:

class Match (models.Model):
    ...
    @staticmethod
    def calendar_to_dict(league):
        matches = league.matches.all()
        d = {match.day: [] for match in matches}
        for match in matches:
            day = match.day
            home = match.home_team
            visit = match.visit_team
            values = d[day]
            values.append(home)
            values.append(visit)
        return d

    def is_played(self):
        home_lineup = self.home_team.team_lineups.filter(day=self.day).first()
        visit_lineup = self.visit_team.team_lineups.filter(day=self.day).first()
        if not home_lineup or not visit_lineup:
            return False
        home_players = home_lineup.players.count()
        visit_players = visit_lineup.players.count()
        evaluations = Evaluation.objects.filter(
            day=self.day + self.league.offset).count()
        if home_players and visit_players and evaluations:
            return True
        else:
            return False

    @staticmethod
    def get_dead_line(league, day):
        match_query_set = Match.objects.filter(league=league, day=day)
        for match in match_query_set.all():
            if not match.dead_line:
                return None
        return match_query_set.first().dead_line

il primo metodo ‘calendar_to_dict’ ritorna un comodo dizionario rappresentante il
calendario, con le giornate come chiave.
Il secondo metodo ‘is_played’ ritorna True se la partita è stata disputata
L’ultimo ‘get_dead_line’ restituisce il termine massimo di consegna formazione.

Nelle template matches.html e match.html vengono utilizzati alcuni custom_filter, da
inserire nel file fantalega/templatetags/app_filters.py:

...
@register.filter(name='need_calc')
def need_calc(league, day):
    try:
        for team in league.team_set.all():
            queryset_lineup = team.team_lineups.filter(day=day)
            if not queryset_lineup.first():
                return False
	    return True
    except AttributeError:
        return False


@register.filter(name='has_pts')
def has_pts(league, day):
    lineups = [Lineup.objects.filter(team=team, league=league, day=day).first()
               for team in league.team_set.all() if
               Lineup.objects.filter(team=team, league=league, day=day).first()]
    calculated_lineups = [l.pts for l in lineups if l.pts > 0]
    return len(calculated_lineups) == len(league.team_set.all())


@register.filter(name='get_goals')
def get_goals(team, day):
    try:
        lineup = team.team_lineups.filter(day=int(day)).first()
        if lineup:
            return lineup.goals_made
        else:
            return "ND"
    except AttributeError:
        return "NL"


@register.filter(name='get_evaluated')
def get_evaluated(dict_evaluated, key):
    data = dict_evaluated.get(key)
    if data:
        return data[0]
    else:
        return []


@register.filter(name='is_defender')
def is_defender(player):
    obj_player = Player.objects.filter(name=player).first()
    if 200 < obj_player.code < 500:
        return mark_safe('<font color="#cc66ff">%s</font>' % player)
    else:
        return player


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


@register.filter(name='get_defense_mod')
def get_defense_mod(dict_evaluated, key):
    data = dict_evaluated.get(key)
    if data:
        return data[1]
    else:
        return 0.0


@register.filter(name='get_total')
def get_total(team, day):
    lineup = team.team_lineups.filter(day=int(day)).first()
    return lineup.pts if lineup else '0.0: Lineup missing'

Salvare ora gli avanzamenti su github:

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

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

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

Categorie:Django Tag:

djangofantalega: model Team

14 novembre 2016 Commenti chiusi

5 – Models: Team

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

team

La relazione ManytoMany è rappresentata da una AssociationTable
LeaguesTeams.

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

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


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

    def __unicode__(self):
        return self.name


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

    class Meta:
        verbose_name_plural = 'League-Team Associations'

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

Aggiornare il database inserendo la nuova tabella:

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

confermare con:

(venv) C:\tmp\djangosite>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, fantalega, log, sessions
Running migrations:
  Applying fantalega.0003_auto_20161109_1123... OK

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

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

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

users
singolo_utente

Creare le squadre:

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

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

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


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


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


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


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


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


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

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

admin_team

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

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

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

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


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

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

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

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

La template sarà quindi:
fantalega/team.html

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

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

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

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

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

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

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


register = template.Library()


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


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

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

{% load app_filters %}

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

{{ lineup.pts|pts_filter }}

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

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

team_details

Salvare gli avanzamenti su github:

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

articoli precedenti
0 – indice
1 – Virtualenv e Git
2 – Models: Season
3 – Admin: Login e Logout
4 – Models: League

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

Categorie:Django Tag:

djangofantalega: model League

14 novembre 2016 Commenti chiusi

4 – Models: League

Creare il secondo Model dell’applicazione: League.
Una stagione (Season) può avere più Leghe, ad es. Lega e Champions
quindi trattasi di relazione Season-League: one-to-many.

season_league

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

from django.db import models
from fantalega.validators import validate_season_name


class Season(models.Model):
    name = models.CharField(max_length=9, validators=[validate_season_name])

    def __unicode__(self):
        return self.name


class League(models.Model):
    name = models.CharField(max_length=32)
    budget = models.IntegerField()
    max_trades = models.IntegerField()
    max_goalkeepers = models.IntegerField()
    max_defenders = models.IntegerField()
    max_midfielders = models.IntegerField()
    max_forwards = models.IntegerField()
    rounds = models.IntegerField()
    offset = models.IntegerField()
    season = models.ForeignKey(Season, related_name='leagues')  # relationship

    def __unicode__(self):
        return self.name

Aggiornare il database inserendo la nuova tabella:

(venv) >python manage.py makemigrations
Migrations for 'fantalega':
  fantalega\migrations\0002_league.py:
    - Create model League

confermare con:

(venv) >python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, fantalega, log, sessions
Running migrations:
  Applying fantalega.0002_league... OK

Ora inserire qualche dato nel database:

(venv) >python manage.py shell
...
>>> from fantalega.models import Season, League
>>> s = Season.objects.get(pk=1)
>>> league = League.objects.create(name="lega 2016-2017", budget=500, max_trades=3, max_goalkeepers=3,
... max_defenders=8, max_midfielders=8, max_forwards=6, rounds=4, offset=2, season=s)
>>> cup = League.objects.create(name="Champions 2016-2017", budget=500, max_trades=3, max_goalkeepers=3,
... max_defenders=8, max_midfielders=8, max_forwards=6, rounds=2, offset=20, season=s)
>>> s.leagues.all()
<QuerySet [<League: lega 2016-2017>, <League: Champions 2016-2017>]>
>>> cup.season
<Season: 2016-2017>
>>> league.season
<Season: 2016-2017>
>>>

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

# noinspection PyUnresolvedReferences
from django.contrib import admin
from .models import Season, League
from django.utils.html import format_html


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

# Register your models here.
admin.site.register(Season)
admin.site.register(League, LeagueAdmin)

L’attributo fieldset è una lista di tuple, che permette di suddividere
visivamente, una serie di campi inerenti il model in oggetto.
Ogni tupla rappresenta una sezione ed è composta dal primo valore,
che rappresenta il nome della sezione (None, ‘season’, ‘number of players’),
da un dizionario che ha come chiave ‘fields’ che restituisce la
lista dei campi da visualizzare in tale sezione.

admin_league

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

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

Aggiungere nel file fantalega\views.py le nuove viste ‘leagues’ e ‘league_details’:

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


@login_required
def leagues(request):
    context = {'leagues': League.objects.order_by('-name')}
    return render(request, 'fantalega/leagues.html', context)


@login_required
def league_details(request, league_id):
    league = get_object_or_404(League, pk=int(league_id))
    context = {'league': league}
    return render(request, 'fantalega/league.html', context)

A questo punto mancano le template richiamate dalle viste:
fantalega/leagues.html

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

{% block content %}
        <h1><font color="green">List of LEAGUES</font></h1>
    {% if leagues %}
    <ul>
      {% for league in leagues %}
      <li><a href="{% url 'league_details' league.id %}">{{ league.name }} -</a>
       {{ league.season.name }}</li>
      {% endfor %}
    </ul>
    {% else %}
      <font color="red"><b>No league found.</b></font>
    {% endif %}
{% endblock %}

e fantalega/league.html

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

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

    <table class="table table-striped" width="100%">
      <tr>
          <th>budget</th>
          <th>max trade remaining</th>
          <th>max goalkeepers</th>
          <th>max defenders</th>
          <th>max midfielders</th>
          <th>max forwards</th>
          <th>rounds to do</th>
          <th>offset</th>
      </tr>
      <tr>
              <td>{{ league.budget }}</td>
              <td>{{ league.max_trades }}</td>
              <td>{{ league.max_goalkeepers }}</td>
              <td>{{ league.max_defenders }}</td>
              <td>{{ league.max_midfielders }}</td>
              <td>{{ league.max_forwards }}</td>
              <td>{{ league.rounds }}</td>
              <td>{{ league.offset }}</td>
      </tr>
    </table>

	<div id="container" style="width:100%;">
		<div id="left" style="float:left; width:50%;">
			<b><font color="orange">League Teams:</font></b>
			<ul>
			{% if teams %}
				{% for team in teams %}
				  <li><a href="{% url 'team_details' league.id team.id %}">
					{{ team.name }}</a></li>
				{% endfor %}

				{% if user.is_staff %}
				<form action="#" method="get">
				  <input type="submit" class="btn" value="Start Auction"
						name="auction">
				  <input type="submit" class="btn" value="Create Calendar"
						name="calendar">
				</form>
				{% endif %}
			{% else %}
			  <b><font color="red">no team created yet</font></b>
			{% endif %}
			</ul>
		</div>

		<div id="right" style="float:right; width:50%;">
			<b><font color="orange">Evaluation uploaded</font></b>
			<ul>
			  {% for day in days %}
			  <li><a href="{% url 'vote' league.id day %}">
				  Evaluation day: {{ day }}</a></li>
			  {% endfor %}
			</ul>
			{% if user.is_staff %}
			  <form action="#" method="get">
				<input type="submit" class="btn" value="Upload votes"
					  name="upload votes">
			  </form>
			{% endif %}
		  <form action="#" method="get">
			{% if league.matches %}
				<input type="submit" class="btn" value="view Calendar"
					  name="matches">
				<input type="submit" class="btn" value="view Chart"
					  name="chart">
				<input type="submit" class="btn" value="view trades"
					  name="trades">
			{% endif %}
		  </form>
		</div>
    </div>
{% endblock %}

In questa template sono contemplati degli if-tag che controllano se siano presenti o
meno degli oggetti (Team o Match) in modo da rendere visibili o meno certi pulsanti.
Nella situazione attuale, questi pulsanti non sono visibili non avendo ancora
creato il model Team.

La pagina Leagues è di uso comune quindi aggiungere un link nella template base,
nella navbar.

...
      {% if user.is_staff %}
        <div class="navbar-header">
          <a class="navbar-brand" href="{% url 'admin:index' %}">
              <font color="red">Admin</font></a></div>
      {% endif %}
      <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>
    {% endif %}
...

Salvare gli avanzamenti su github:

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

articoli precedenti
0 – indice
1 – Virtualenv e Git
2 – Models: Season
3 – Admin: Login e Logout

articoli successivi
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: Login e Logout

14 novembre 2016 Commenti chiusi

3 – Djangofantalega: Login e Logout

Questa è la sequenza di come un utente debba gestire una squadra di
fantalega:

1 – Registrazione al sito da parte del nuovo User
2 – Spedizione link di conferma registrazione al nuovo User
3 – COnferma di registrazione
4 – Login/Logout

Siccome la fase di registrazione, come pure quella di Login, richiede
un form dove immettere i propri dati, quindi richiede urls dedicate,
quindi views e di conseguenza templates, per una questione di ordine
si tegono tutte queste cose separate dalla altre (app fantalega).
Per questo è bene creare una app dedicata di nome ‘log’:

python manage.py startapp log

controllare che nel file djangosite\settings.py sia presente ‘log’:

INSTALLED_APPS = [
    'fantalega.apps.FantalegaConfig',  # fantalega
    'log.apps.LogConfig',              # log
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'bootstrap3'  # aggiungere questa riga
]
...

Si userà Gmail per le mail quindi attenzione ai valori aggiunti alla
fine del file djangosite\settings.py.

...
# Gmail email settings
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_USER = [email protected]'
EMAIL_HOST_PASSWORD = 'xxxxxxxx'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'

Aggiungere l’unico model UserProfile nel file log/models.py
Attenzione al percorso, si sta parlando della app ‘log’ e non ‘fantalega’!!

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


# Create your models here.
class UserProfile(models.Model):
    user = models.OneToOneField(User)
    activation_key = models.CharField(max_length=40)
    key_expires = models.DateTimeField()

Semplicemente è stata creata una tabella con il field user che
altro non è che una relazione one-to-one con il modello User già fornito
da django, al quale si aggungono:
– activation_key, creata in fase di registrazione;
– key_expires tempo oltre il quale la chiave di attivazione non sarà più attiva.

L’unico form non presente e che verrà creato nel file log/forms.py è:

# noinspection PyUnresolvedReferences
from django import forms
# noinspection PyUnresolvedReferences
from django.utils.safestring import mark_safe
from django.contrib.auth.models import User


class RegistrationForm(forms.Form):
    username = forms.RegexField(
        regex=r'^\w+$',
        widget=forms.TextInput(attrs=dict(required=True, max_length=30)),
        label="Username",
        error_messages={'invalid': "This value must contain only letters,"
                                   " numbers and underscores."})
    email = forms.EmailField(
        widget=forms.TextInput(
            attrs=dict(required=True, max_length=30)), label="Email address")
    password1 = forms.CharField(
        widget=forms.PasswordInput(
            attrs=dict(required=True, max_length=30, render_value=False)),
        label="Password")
    password2 = forms.CharField(
        widget=forms.PasswordInput(
            attrs=dict(required=True, max_length=30, render_value=False)),
        label="Password (again)")

    def clean_username(self):
        try:
            user = User.objects.get(
                username__iexact=self.cleaned_data['username'])
        except User.DoesNotExist:
            return self.cleaned_data['username']
        raise forms.ValidationError(
            "The username already exists. Please try another one.")

    def clean(self):
        if 'password1' in self.cleaned_data and 'password2' \
                in self.cleaned_data:
            if self.cleaned_data['password1'] != self.cleaned_data['password2']:
                raise forms.ValidationError(
                    "The two password fields did not match.")
        return self.cleaned_data

Le urls che gestiscono tutte queste fasi, risiedono nel file log/urls.py

# noinspection PyUnresolvedReferences
from django.conf.urls import url
# noinspection PyUnresolvedReferences
from django.contrib import admin
from log import views as log_views
from django.contrib.auth import views as auth_views  # auth system


urlpatterns = [
    url(r'^login/$', auth_views.login,
        {'template_name': 'registration/login.html'},
        name='django.contrib.auth.views.login'),  # auth system
    url(r'^logout/$', auth_views.logout,
        {'template_name': 'registration/logged_out.html'},
        name='logout'),  # auth system
    url(r'^registration/$', log_views.register_user, name='registration'),
    url(r'^registration/success/$', log_views.register_success,
        name='reg_success'),
    url(r'^accounts/activate/(?P<activation_key>\w+)/$', log_views.activate,
        name='activate'),
    url(r'^expired/$', log_views.activation_link_expired, name='expired'),
    ]

queste ulrs vanno ovviamente incluse con include() nel file djangosite/urls.py

# noinspection PyUnresolvedReferences
from django.conf.urls import url, include
# noinspection PyUnresolvedReferences
from django.contrib import admin


urlpatterns = [
    url(r'^fantalega/', include('fantalega.urls')),
    url(r'^auth/', include('log.urls')),
    url(r'^admin/', admin.site.urls),
]

Le views che gestiscono il tutto sono:

from log.forms import RegistrationForm
from django.contrib.auth.models import User
import random
import hashlib
from django.utils import timezone
from django.template import Context
from django.template.loader import get_template
from django.core.mail import EmailMultiAlternatives
from django.urls import reverse
from django.shortcuts import render, redirect, get_object_or_404
from .models import UserProfile
from datetime import datetime
from django.contrib import messages


# Create your views here.
def register_user(request):
    if request.method == 'POST':
        form = RegistrationForm(request.POST)
        context = {'form': form}
        if form.is_valid():
            user = User.objects.create_user(
                username=form.cleaned_data['username'],
                password=form.cleaned_data['password1'],
                email=form.cleaned_data['email'])
            user.is_active = False
            user.save()
            salt = hashlib.sha1(str(random.random())).hexdigest()[:5]
            activation_key = hashlib.sha1(salt + user.username).hexdigest()
            now = datetime.today()
            key_expires = datetime(now.year, now.month, now.day + 2)
            profile = UserProfile(user=user, activation_key=activation_key,
                                  key_expires=key_expires)
            profile.save()
            email_subject = 'Your new <bancaldo> fantalega account confirmation'
            # go to https://www.google.com/settings/security/lesssecureapps
            # click on active
            location = reverse("activate", args=(activation_key,))
            activation_link = request.build_absolute_uri(location)
            template = get_template('registration/confirm_email.html')
            context = Context({'user': user.username,
                               'activation_link': activation_link})
            email_body = template.render(context)
            email = EmailMultiAlternatives(email_subject, email_body,
                                           [email protected]>', [user.email])
            email.attach_alternative(email_body, 'text/html')
            print email_body  # debug: comment in production
            # email.send()  # decomment to send email
            messages.info(request,
                          "A confirmation mail has been sent to you.\n"
                          "You have 2 days before the link expires")
            return redirect('index')
    else:
        form = RegistrationForm()
        context = {'form': form}
    return render(request, 'registration/registration_form.html', context)


def activate(request, activation_key):
    user_profile = get_object_or_404(UserProfile, activation_key=activation_key)
    if user_profile.user.is_active:
        return render(request, 'registration/active.html',
                      {'user': request.user.username})
    if user_profile.key_expires < timezone.now():
        return render(request, 'registration/expired.html',
                      {'user': request.user.username})
    user_profile.user.is_active = True
    user_profile.user.save()
    messages.success(request, "You have confirmed with success!")
    return redirect('reg_success')


def register_success(request):
    return render(request, 'registration/success.html')


def activation_link_expired(request):
    return render(request, 'registration/expired.html')

Importantissimo!
perchè le mail funzionino con Gmail è necessario cliccare sul controllo
sicurezza di Gmail al link: https://www.google.com/settings/security/lesssecureapps

Infine, la pletora di templates che vanno posizionate in log/templates/registration
che sono:

log/templates/registration/active.html

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

{% block title %}Registration Successful{% endblock %}
{% block head %}
 already confirmed
{% endblock %}
{% block content %}
  <font color="green"><b>{{ user }}</b></font>, you have already confirmed!
  <br><br>
  <a href="{% url 'index' %}">Click here to login</a>
{% endblock %}

log/templates/registration/confirm_email.html

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
Dear {{ user }},<br>
Welcome to Bancaldo's Fantalega!<br>
To confirm your account please click on the following link:<br><br>
<a href="{{ activation_link }}">Click here to login</a>
<br><br>Sincerely,<br>
Bancaldo
<br><br>Note: replies to this email address are not monitored.
</body>
</html>

log/templates/registration/expired.html

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

{% block title %}activation key expired{% endblock %}

{% block content %}
  <br><br>
  <b>{{ user }}</b> <font color="red">Your activation key has expired</font>
  <br><br>
  <a href="{% url 'registration' %}">...try to register again</a>
{% endblock %}

log/templates/registration/logged_out.html

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

{% block content %}
  you have been logged out!
  <br><br>
  <font color="green"><a href="{% url 'index' %}">Click here to login</a></font>

{% endblock %}

log/templates/registration/login.html

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

{% block content %}

{% if form.errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}

{% if next %}
    {% if user.is_authenticated %}
    <p>Your account doesn't have access to this page. To proceed,
    please login with an account that has access.</p>
    {% else %}
    <p>Please login to see this page.</p>
    {% endif %}
{% endif %}

<form method="post" action="{% url 'django.contrib.auth.views.login' %}">
{% csrf_token %}
{% bootstrap_form form %}
  <input type="submit" class="btn" value="Login" name="login">
  <input type="hidden" name="next" value="{{ next }}" />
</form>
<br><br>
If you are not a registered user, please <a href="{% url 'registration' %}"> Sign up</a>

{% endblock %}

log/templates/registration/registration_form.html

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

{% block content %}

<form method="post" action="{% url 'registration' %}">
{% csrf_token %}
{% bootstrap_form form %}
 <input type="submit" class="btn" value="Register"
        name="get_registration">
</form>

{% endblock %}

log/templates/registration/success.html

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

{% block title %}Registration Successful{% endblock %}
{% block head %}
 Registration Completed Successfully
{% endblock %}
{% block content %}
    Thank you for registering.
  <br><br>
  <font color="green"><a href="{% url 'index' %}">Click here to login</a></font>
{% endblock %}

Prima di terminare è nessario aggiornare il database, visto che è stato
creato il model UserProfile:

python manage.py makemigrations log
python manage.py migrate

Aggiungere un link per il logout nella template fantalega/base.html

...
        <a class="navbar-brand" href="{% url 'seasons' %}">Seasons</a></div>
    {% endif %}

      <div class="navbar-collapse collapse">
        <ul class="nav navbar-nav navbar-right">
        {% if not user.is_anonymous %}
          <li><a class="navbar-brand"
             href="{% url 'logout' %}">
          <font color="orange">logout</font></a></li>
        {% endif %}
        </ul>
      </div>
    </div>
  </div>
{% endblock %}

Proteggere ora la view ‘seasons’ con il decoratore login_required

fantalega/views.py

# noinspection PyUnresolvedReferences
from django.shortcuts import render, redirect, get_object_or_404
from .models import Season
# noinspection PyUnresolvedReferences
from django.contrib import messages
from django.contrib.auth.decorators import login_required  # new import


@login_required  # new decorator
def index(request):
    return render(request, 'fantalega/index.html')


@login_required  # new decorator
def seasons(request):
    context = {'seasons': Season.objects.order_by('-name')}
    return render(request, 'fantalega/seasons.html', context)

e se non lo è già, riavviare il server recandosi all’indirizzo
http://127.0.0.1:8000/fantalega

Se lo User è rimasto connesso, a destra è possibile fare il logout.
Dopo di chè si viene reindirizzati alla pagina di logout dove è
presente un link alla pagina di login.

login

Se non si è registrati è possibile fare il signup.

registration

Terminata la registrazione di riceverà una mail con un link.
Cliccato il link si verrà attivati e sarà possibile effettuare il
login.

articoli precedenti
0 – indice
1 – Virtualenv e Git
2 – Models: Season

articoli successivi
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: model Season

14 novembre 2016 Commenti chiusi

2 – djangofantalega: model Season

Creare ‘Season’ (stagione) il primo model dell’applicazione e
madre di tutte le tabelle.

season

L’unico campo nella tabella ‘Season’, sarà ‘name’.
Creare il file fantalega/validators.py con all’interno il
validator da utilizzare:

from django.core.exceptions import ValidationError
import re


def validate_season_name(name):
    pattern = '^\d{4}-\d{4}$'
    if not re.compile(pattern).match(name):
        raise ValidationError('name %s is not correct: yyyy-yyyy is mandatory'\
            % name, params={'name': name},)

Nel file fantalega/models.py creare ora la classe Season.

from django.db import models
from fantalega.validators import validate_season_name


class Season(models.Model):
    name = models.CharField(max_length=9, validators=[validate_season_name])

    def __unicode__(self):
        return self.name

Attenzione:
questo validator vale in fase di inserimento dati da interfaccia di Admin.
Qualora i dati fossero inseriti da shell, non sarebbe attivo

Aggiornare il database con la prima migrazione, inserendo le prima tabella:

(venv) >python manage.py makemigrations fantalega
Migrations for 'fantalega':
  fantalega\migrations\0001_initial.py:
    - Create model Season

confermare con:

(venv) >python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, fantalega, sessions
Running migrations:
  Applying fantalega.0001_initial... OK

Creare la prima Season da shell:

(venv) >python manage.py shell
>>> from fantalega.models import Season
>>> Season.objects.create(name='2016-2017')
...
>>> exit()

Registrare l’amministratore del sito.

python manage.py createsuperuser
Username (leave blank to use 'xxxxxxxxxx'):
Email address: [email protected]
Password:
Password (again):
Superuser created successfully.

Attivare nel pannello di admin, il model Season appena creato

file fantalega/admin.py

# noinspection PyUnresolvedReferences
from django.contrib import admin
from .models import Season


# Register your models here.
admin.site.register(Season)

lanciare il server con il comando:

python manage.py runserver

ed andare alla pagina http://127.0.0.1:8000/admin/
dove verremo reindirizzati alla sezione di login.
Immettere le credenziali appena registrate.

admin_season_ok

Qualora si volessero aggiungere nuove Season dall’Admin,
entrerebbe in gioco il validator precedentemente creato.
Es. se si creasse una Season con nome ‘season 1’, il validator
solleverebbe un’eccezione:

season_validate_error

altrimenti, rispettando il pattern della regex, il tutto andrebbe a buon fine:

admin_season_ok

Chiaramente non tutto si svolgerà all’interno dell’interfaccia di admin,
pertanto saranno create:

    una url che visualizzerà la lista delle Season, richiamando la vista (view) di competenza;
    la view, richiamata dalla url precedente, che salverà i dati su database o li
    richiederà per poi fornirli alla template;
    la template che visualizzerà i dati

Creare la url nel file fantalega/urls.py:

# noinspection PyUnresolvedReferences
from django.conf.urls import url
# noinspection PyUnresolvedReferences
from django.contrib import admin
from . import views


urlpatterns = [
    url(r'^$', views.index, name='index'),
    # season urls
    url(r'^seasons/$', views.seasons, name='seasons'),
]

Creare la view nel file fantalega/views.py

# noinspection PyUnresolvedReferences
from django.shortcuts import render, redirect, get_object_or_404
from .models import Season
# noinspection PyUnresolvedReferences
from django.contrib import messages


def index(request):
    return render(request, 'fantalega/index.html')


def seasons(request):
    context = {'seasons': Season.objects.order_by('-name')}
    return render(request, 'fantalega/seasons.html', context)

E ora le templates.
dentro la directory fantalega, creare una sottodirectory “templates” ed al suo interno,
un’altra sottodirectory “fantalega”. ALl’interno di quest’ultima creare un file
base.html:

fantalega\templates\fantalega\base.html

{% load staticfiles %}
{# Load the tag library #}
{% load bootstrap3 %}
{# Load CSS and JavaScript #}
{% bootstrap_css %}
{% bootstrap_javascript %}
{% load app_filters %}

{% block head %}
{% endblock %}

{% block navbar %}
  <div class="navbar navbar-inverse" role="navigation">
    <div class="container">
    {% if user.is_anonymous %}
      <div class="navbar-header">
        <a class="navbar-brand">
            Bancaldo
            <font color="green">Fan</font><font color="white">tal</font><font color="red">ega</font>
            please login</a></div>
    {% else %}
      {% if user.is_staff %}
        <div class="navbar-header">
          <a class="navbar-brand" href="{% url 'admin:index' %}">
              <font color="red">Admin</font></a></div>
      {% endif %}
      <div class="navbar-header">
        <a class="navbar-brand" href="{% url 'seasons' %}">Seasons</a></div>
    {% endif %}

      <div class="navbar-collapse collapse">
        <ul class="nav navbar-nav navbar-right">
        {% if not user.is_anonymous %}
          <li><a class="navbar-brand"
             href="{% url 'logout' %}">
          <font color="orange">logout</font></a></li>
        {% endif %}
        </ul>
      </div>
    </div>
  </div>
{% endblock %}

<html>
    <head>
      <title>{% block title %}Django Fantalega{% endblock %}</title>
    </head>
    <body>
      {% if messages %}
        <ul class="list-unstyled messages">
        {% for message in messages %}
          {% get_bootstrap_alert_msg_css_name message.tags as alert_tag %}
          <li class="alert alert-{{ alert_tag }}">{{ message }}</li>
        {% endfor %}
        </ul>
      {% endif %}

      <div class="content container">
        <div class="row">
          <div class="col-md-8">
            {% block content %}
            {% endblock %}
          </div>
        </div>
      </div>
    </body>
</html>

Questa è la template base dalla quale erediteranno tutte le altre.

Creare ora la template per l’index (fantalega\templates\fantalega\index.html):

{% extends "fantalega/base.html" %}
{% block content %}
  <h1><font color="green">Fantalega</font></h1><br>
  {% if user %}
    <b><font color="blue">{{ user.username }}, </font>
       <font color="orange">you are welcome!</font>
    </b>
  {% endif %}
{% endblock %}

Poi creare il file fantalega\templates\fantalega\seasons.html

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

{% block content %}
        <h1><font color="green">List of SEASONS</font></h1>
    {% if seasons %}
    <ul>
      {% for season in seasons %}
      <li>{{ season.name }}</a></li>
      {% endfor %}
    </ul>
    {% else %}
      <font color="red"><b>No season found.</b></font>
    {% endif %}
{% endblock %}

Avviare il server qualora già non lo fosse e recarsi all’indirizzo:

python manage.py runserver

ed andare alla pagina http://127.0.0.1:8000/fantalega/seasons

Cosa è successo?
nel file urls.py l’indirizzo suddetto viene matchato dalla regex
r’^seasons/$’ (primo parametro della seconda url della lista urlpatterns), alla quale
corrisponde la view ‘seasons’ (file fantalega/views.py).
In questa views, viene creato un dizionario ‘context’ con all’interno la lista di tutte
le Season presenti nel database

context = {'seasons': Season.objects.order_by('-name')}

il dizionario viene passato alla funzione render() che lo fornisce alla template
‘fantalega/seasons.html’ sotto forma di tag ‘{{ seasons }}’.
In questo caso, il tag seasons non è diretto, ma viene raggiunto tramite ciclo for
‘{% for season in seasons %}’…

Nota:
Come si nota, nella template ‘base.html’, dalla quale ereditiamo, nelle sezioni
navbar, tramite il tag {{ url }} si richiama il nome ‘seasons’, che è il terzo fondamentale
paramtero usato nella costruzione delle urls.

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

le seasons sono quindi raggiungibili anche dalla barra superiore (navbar che viene
ereditata da base.html), inoltre se l’utente con il quale ci si logga, è superuser (is_staff),
può amministrare cliccando su Admin.

seasons

Salvare gli avanzamenti su github:

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

articoli precedenti
0 – indice
1 – Virtualenv e Git

articoli successivi
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: virtualenv e git

14 novembre 2016 Commenti chiusi

1- Djangofantalega: virtualenv e git

Settare virtaulenv all’interno della directory che ospiterà il progetto:

virtualenv venv

abilitare l’ambiente virtuale:
windows

venv\Scripts\activate

linux

source venv/bin/activate

installare l’ultima versione di django (attualmente la 1.10)

pip install django

installare anche bootstrap (per rendere meno spartane le templates):

pip install django-bootstrap3

creare il progetto con django

django-admin startproject djangosite

spostarsi all’interno del progetto appena creato:

cd djangosite

creare la prima applicazione base, in questo caso ‘fantalega’:

python manage.py startapp fantalega

configurare il file djangosite\settings.py aggiungendo nelle
applicazioni installate, ‘fantalega’ (dovrebbe essere aggiunta in automatico)
e ‘bootstrap3’:

INSTALLED_APPS = [
    'fantalega.apps.FantalegaConfig',  # dovrebbe essere di default
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'bootstrap3'  # aggiungere questa riga
]
...
LANGUAGE_CODE = 'it-IT'
...

Nota:
FantalegaConfig è una classe che eredita da AppConfig,
presente all’interno del file fantalega\apps.py, con
attributo name=’fantalega’

Creare il database nudo e crudo:

python manage.py migrate
(venv) >python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  ...
  Applying sessions.0001_initial... OK

Prima di creare i models del progetto, utilizzare git per
tenere traccia di tutte le modifiche.
Inizializzare il repository del progetto, senza cambiare directory

git init

settare i parametri utente ed email:

git config --global user.name "bancaldo"
git config --global user.email "[email protected]"

siccome non si vuole tenere traccia di alcuni file, quali il database, o il
virtual environment, compiliamo il file ‘.gitignore’, con l’elenco dei file
che non sono desiderati:

*.pyc
__pycache__
venv
db.sqlite3
/static

controllare cosa dice git sulla situazione di partenza

git status
On branch master

Initial commit

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        .gitignore
        djangosite/
        fantalega/
        manage.py

nothing added to commit but untracked files present (use "git add" to track)

come si nota, non appaiono nè il database (db.sqlite3), nè la directory venv.
A questo punto aggiungere tutti i file al nostro repo:

git add --all

è buona abitutine allegare sempre un messaggio al commit per le future ricognizioni.

git commit -m "primo commit applicazione fantalega

creare su github.com un nuovo repo e
terminata la creazione, collegare il tutto:

git remote add origin https://github.com/bancaldo/djangofantalega.git

ora “buttare” il contenuto del nostro repo LOCALE, su github:

git push -u origin master

chiaramente fornendo le credenziali di accesso a github.
Nel caso ci fosse un proxy, potrebbe verificarsi questo errore:

Failed to connect to github.com port 443: Connection refused

Configurare git per gestire il proxy:

git config --global http.proxy https://<nume-user>:<password-user>@<nome-proxy>:<porta>

qualora le impostazioni del proxy nel browser, fossero settate in automatico e quindi non visibili,
utilizzare questo comando:

reg query "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings" | find /i "proxyserver"

rilanciare il comando:

git push -u origin master

Username for 'https://github.com': .....
Password for 'https:[email protected]':
Counting objects: 16, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (13/13), done.
Writing objects: 100% (16/16), 3.41 KiB | 0 bytes/s, done.
Total 16 (delta 0), reused 0 (delta 0)
To https://github.com/bancaldo/djangofantalega.git
 * [new branch]      master -> master
Branch master set up to track remote branch master from origin.

articoli precedenti
0 – indice

articoli successivi
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: , ,

Django: bootstrap3 template messages

9 novembre 2016 Commenti chiusi

Per la gestione dei messaggi con django e bootstrap3,
aggiungere al file myapp/templatetags/app_filters.py

# noinspection PyUnresolvedReferences
from django import template


register = template.Library()

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

e nella template myapp/templates/base.html dalla quale ereditano tutte le altre:

{% load staticfiles %}
{# Load the tag library #}
{% load bootstrap3 %}
{# Load CSS and JavaScript #}
{% bootstrap_css %}
{% bootstrap_javascript %}
{% load app_filters %}
...
<html>
    <head>
      <title>{% block title %}Myapp{% endblock %}</title>
    </head>
    <body>
      {% if messages %}
        <ul class="list-unstyled messages">
        {% for message in messages %}
          {% get_bootstrap_alert_msg_css_name message.tags as alert_tag %}
          <li class="alert alert-{{ alert_tag }}">{{ message }}</li>
        {% endfor %}
        </ul>
      {% endif %}

      <div class="content container">
        <div class="row">
          <div class="col-md-8">
            {% block content %}
            {% endblock %}
          </div>
        </div>
        <br>
      </div>
    </body>
</html>

All’interno di views.py:

from django.contrib import messages

def a_view(request):
    ...
    messages.warning(request, "warning message")
    messages.info(request, "info message")
    messages.error(request, "error message")
    messages.success(request, "success message")
    ...
Categorie:Django Tag:

Django: template custom_filter

9 novembre 2016 Commenti chiusi

Per elaborare una variabile all’interno di una template di django,
è possibile utilizzare custom_filter.

Per prima cosa creare all’interno della directory myapp,
una sottodirectory ‘templatetags’, con all’interno un file vuoto __init__.py
e un file app_filters.py.

All’interno del file app_filters.py:

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


register = template.Library()


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

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

{% load app_filters %}

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

{{ value|pts_filter }}

il valore ‘value’ viene passato come argomento alla funzione pts_filter()
che ritorna lo stesso valore dopo averlo modificato.
E’ assolutamente necessario ritornare il valore con mark_safe.

Qualora dovessimo anche passare un argomento al filter si userà la sintassi
tag|filter:arg ad esempio:

<b>player avg</b>: {{ player|get_avg:player.code}}

dove il filter sarà:

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

Le template di django non sono completamente duttili ma è possibile creare dei tag specifici
che compiano operazioni e ritornino determinate cose.
Ad esempio filtrare queryset secondo determinati argomenti:

@register.assignment_tag
def filter_iterable(iterable, value):
    return iterable.filter(arg=value)

nella template sarà richiamato così:

{% filter_iterable iterable day as f_iterable %}
  {% for item in f_iterable %}
    {{ item.arg1 }}
    {{ item.arg2 }}
Categorie:Django Tag:

Django: Model.field custom validator

8 novembre 2016 Commenti chiusi

Ho un model Season, con un field ‘name’.
Voglio che siano accettati solo nomi con il formato
‘yyyy-yyyy’ es. ‘2016-2017’.
Per essere sicuro che, in sede di creazione da interfaccia
di Admin, venga rispettato questo criterio, posso creare un
validator.

Nella stessa directory dove risiedono i models, creo un file
validators.py con all’interno il mio custom validator.

from django.core.exceptions import ValidationError
import re


def validate_season_name(name):
    pattern = '^\d{4}-\d{4}$'
    if not re.compile(pattern).match(name):
        raise ValidationError('name %s is not correct: yyyy-yyyy is mandatory'\
            % name, params={'name': name},)

Nel file models.py userò il validator direttamente
nella definizione del field ‘name’:

# noinspection PyUnresolvedReferences
from django.db import models
from myapp.validators import validate_season_name


class Season(models.Model):
    name = models.CharField(max_length=9, validators=[validate_season_name])

    def __unicode__(self):
        return self.name

Ovviamente, essendo l’argomento validators una lista, posso utilizzare più validators.

Quando andrò a creare la Season ed inserirò
un nome non desiderat, verrà sollevata l’eccezione.
season_validate_error

Altrimenti tutto sarà ok.
admin_season_ok

Categorie:Django Tag: