momente şi schiţe de informatică şi matematică
To attain knowledge, write. To attain wisdom, rewrite.

Cam din amintiri, o nouă aplicație Django

Django | R
2026 may

În ultimii vreo șase ani n-am mai lucrat nimic în Django; dar după Supported Versions, se cuvine de-acum să adoptăm ultima versiune (care este "+2" față de instalarea proprie, încât o simplă "updatare", având în vedere existența unor aplicații vechi, devine complicată). Deocamdată să ne amintim, folosind versiunea curentă, cam cum producem o aplicație Django; cu siguranță, am uitat multe lucruri, dar nu ne propunem o aplicație așa de sofisticată cum aveam în [1], ci doar una "cuminte" — și tot pe seama unei colecții proprii de partide, rezultate jucând de câțiva ani pe Lichess.

Este drept că Lichess oferă o interfață de programare (v. [3]) prin care putem vizualiza partide (poate și colecții de partide), dar noi vom proceda "didactic" (cam ca în [1]): descărcăm fișierul PGN conținând partidele proprii, constituim folosind R o bază de date corespunzătoare și apoi, înființăm o aplicație Django prin care să putem selecta (după an și lună) o partidă, vizualizând apoi desfășurarea acesteia. Iar pentru "vizualizare" vom folosi aplicația javaScript produsă în [2] (care îmbunătățește "pgnbrw.js" pe care bazasem aplicația Django slightchess elaborată în [1]).

Prototipul PGN, pe Lichess

Fișierul PGN obținut de pe Lichess înregistrează cele vreo 2200 de partide de șah jucate de 'vlbz', începând de prin anul 2000. Pentru fiecare partidă, avem o secțiune informativă (taguri PGN) care în general arată cam așa:

[Event "rated rapid game"]                 [WhiteElo "2228"]
[Site "https://lichess.org/a7Ty85GF"]      [BlackElo "2279"]
[Date "2021.10.12"]                        [WhiteRatingDiff "-11"]
[Round "-"]                                [BlackRatingDiff "+5"]
[White "SicilianKan"]                      [Variant "Standard"]
[Black "vlbz"]                             [TimeControl "900+10"]
[Result "0-1"]                             [ECO "A34"]
[GameId "a7Ty85GF"]                        [Termination "Normal"]
[UTCDate "2021.10.12"]                     [Annotator "lichess.org"]
[UTCTime "14:44:04"]                       

Nu vom păstra toate câmpurile, în baza de date pe care o vom asocia. Toate partidele respective țin de Variant "Standard" și "rated rapid game" cu timpul de control de 15 minute (=900 secunde); iar UTCDate coincide cu Date. fiindcă n-a fost cazul conectării din alte zone geografice.

Valoarea indicată în tagul GameId va servi ca "primary key"; adăugarea ei în bara de adresă, ca în tagul Site, conduce la vizualizarea prin Lichess a partidei respective.

Aflăm din cele de mai sus (și merită atenție, dacă vrei să crești) că inițial, coeficienții ELO erau 2228 și 2279, iar după încheierea partidei (cu Result "0-1"), ELO a scăzut la alb cu 11 puncte și a crescut la negru cu 5 puncte.

După secțiunea de taguri, urmează mutările partidei; pentru formularea fișierului PGN respectiv, am cerut ca mutările să fie și adnotate de către Lichess:

1. Nf3 { [%eval 0.22] } 1... c5 { [%eval 0.13] } 2. c4 { [%eval 0.21] } 2... Nf6 { [%eval 0.32] } 3. Nc3 { [%eval 0.1] } { A34 English Opening: Symmetrical Variation, Three Knights Variation } 3... d5 { [%eval 0.25] } 4. cxd5 { [%eval 0.25] } 4... Nxd5 { [%eval 0.3] } 5. e3 { [%eval 0.08] } 5... Nf6 { [%eval 0.29] } 6. Qb3 { [%eval 0.0] } 6... Nc6 { [%eval 0.34] } 7. Bc4?! { (0.34 → -0.46) Inaccuracy. Bb5 was best. } { [%eval -0.46] } (7. Bb5 Qc7) 7... e6 { [%eval -0.19] } 8. e4?! { (-0.19 → -0.86) Inaccuracy. Bb5 was best. } { [%eval -0.86] } (8. Bb5 Bd7 9. d4 cxd4 10. exd4 Be7 11. O-O O-O 12. Bd3 h6 13. Qxb7 Rb8 14. Qa6 Nb4) 8... a6 { [%eval -1.02] } 9. a4 { [%eval -1.05] } 9... Be7 { [%eval -0.84] } 10. O-O { [%eval -0.88] } 10... O-O { [%eval -0.72] } 11. e5?? { (-0.72 → -2.53) Blunder. d3 was best. } { [%eval -2.53] } (11. d3 b6 12. h3 Bb7 13. Bf4 Na5 14. Qc2 Nh5 15. Bd2 h6 16. Rfe1 Qc7 17. Ne2 Nc6) 11... Ng4 { [%eval -2.73] } 12. Re1 { [%eval -2.59] } 12... Qc7 { [%eval -2.86] } { White resigns. } 0-1

La majoritatea pardidelor, Lichess a inserat la fiecare mutare câte o evaluare a poziției curente; pentru analiza pozițiilor se folosește Stockfish iar evaluările rezultate sunt foarte importante pentru înțelegerea jocului și pentru sesizarea inexactităților și greșelilor. De exemplu, 1. Nf3 {[%eval 0.22]} ne spune cam așa: față de poziția inițială a pieselor, poziția rezultată după mutarea de către alb a calului pe câmpul f3 devine ușor favorabilă albului (acesta a dezvoltat o piesă, în timp ce negrul este încă în poziția inițială); "avantajul" respectiv, "0.22" este măsurat față de valoarea unui pion.

Însă nu-i cazul să păstrăm evaluările chiar pentru fiecare mutare; de exemplu, pentru mutările redate mai sus am păstra numai pe cele de la mutările 3 (unde se indică numele standard al deschiderii), 7 (care indică o inexactitate instructivă, în urma căreia albul își strică poziția), 8, și 11.

Transformarea fișierului PGN în obiect de date (data.frame)

Prin următorul program constituim (prin readLines()) un vector conținând liniile fișierului PGN și inițializăm un obiect data.frame "pgn_df" în care, pentru fiecare partidă înscriem pe coloane valorile tagurilor acesteia precum și lista mutărilor ei:

con <- file("lichess_vlbz_2026-04-20.pgn")
Lines <- readLines(con, encoding="UTF-8")
close(con)

pgn_df <- as.data.frame(matrix(ncol=20, nrow=0))
game <- list()
for(line in Lines) {
    if(! grepl("^\\s*$", line, perl=TRUE)) {
      if(grepl("^\\[", line, perl=TRUE)) {
        field <- sub("^\\[([a-zA-Z]+).*\\]$", "\\1", line)
        if(! grepl("Title", field, perl=TRUE) & 
           ! grepl("FideId", field, perl=TRUE) &
           ! grepl("Berserk", field, perl=TRUE))
            game[[field]] <- sub('\\[[a-zA-Z]+\\s+"(.+)"\\]', "\\1", line)
      } else {
        game[["Moves"]] <- gsub("\\{ \\[\\%eval [^\\{]*\\] \\}", "", line)
        pgn_df <- rbind(pgn_df, game)
        game <- list()
      }
    }
}
pgn_df[, c(1,2,4,9,15,16,18,19)] <- NULL  # elimină 'Event', 'Site', etc.

Am neglijat taguri ca WhiteTitle (fiind foarte puține, partidele în care am avut ca partener un jucător titrat, de exemplu cu titlul de maestru). Am eliminat din secțiunea mutărilor, evaluările scurte.
În final, am eliminat coloanele corespunzătoare tagurilor 'Event', 'Site', etc.
Acum, pgn_df are următoarea structură:

> str(pgn_df)
'data.frame':	2240 obs. of  12 variables:
 $ Date           : chr  "2026.04.06" "2026.04.06" "2026.04.01" "2026.03.23" ...
 $ White          : chr  "vlbz" "vlbz" "vlbz" "vlbz" ...
 $ Black          : chr  "shico" "chronischlafloser" "Wellanotherone" "Kloppo09" ...
 $ Result         : chr  "0-1" "1-0" "1/2-1/2" "1-0" ...
 $ GameId         : chr  "HoFLO49m" "tafDnPzV" "1lAWZrdx" "3j73UGFl" ...
 $ UTCTime        : chr  "15:17:51" "14:54:33" "17:45:28" "18:10:13" ...
 $ WhiteElo       : chr  "2158" "2151" "2137" "2138" ...
 $ BlackElo       : chr  "2086" "2194" "2025" "1981" ...
 $ WhiteRatingDiff: chr  "-8" "+7" "-2" "+3" ...
 $ BlackRatingDiff: chr  "+8" "-19" "+5" "-3" ...
 $ ECO            : chr  "E54" "D52" "E41" "E48" ...
 $ Moves          : chr  "1. d4  1... Nf6  2. c4  2... e6  3. Nc3 | __truncated__" 

Deocamdată am păstrat ca nume de coloane, numele de taguri.
Bineînțeles că, înainte de a salva pgn_df, se cuvine să transformăm în integer tipul de valori din cele 4 coloane referitoare la ELO.

Observăm că eliminând înscrierile scurte "{[% eval ]}", mutările negrului au rămas sub forma "1... Nf6"; eliminăm "..." din coloana 12 ('Moves') prin:

for(i in 1:nrow(pgn_df))
    pgn_df[i, 12] <- gsub("[^)}] \\d+\\.{3}", "", pgn_df[i, 12])

și salvăm datele respective în "lichess.RDS".

Tabelarea PostgreSQL a datelor din "lichess.RDS"

Prin write.csv(), putem transforma "lichess.RDS" în format CSV (și ar fi suficient, pentru a putea reda apoi, prin pgnbrw.js, partidele respective). Dar aici, în scopuri didactice, preferăm să angajăm, ca de obicei în Django, o bază de date.

Ar fi totuși ridicol, să împărțim datele între mai multe tabele (unul pentru "An", unul pentru "Luni", etc.), de legat apoi între ele prin anumite chei; vom depune datele din "lichess.RDS" într-un singur tabel, pe care-l putem adăuga în baza de date existentă "docer" (nu constituim o nouă bază de date, pentru acest singur tabel).

Dar în PostgreSQL, numele de coloane sunt de regulă constituite din litere mici (altfel, pentru a referi coloanele de date ar trebui să încadrăm numele în ghilimele). Deci în prealabil, intervenim în "lichess.RDS" pentru a simplifica numele de coloană și eventual a ordona coloanele, încât după modificarea respectivă să avem:

L <- readRDS("lichess.RDS")
print(colnames(L))
 [1] "gameid"  "date"    "times"   "white"   "black"   "result"  "welo"   
 [8] "belo"    "welodif" "belodif" "eco"     "moves"  

Folosim acum pachetul "RPostgreSQL" pentru a obține o conexiune la baza de date 'docer' și a-i adăuga tabelul numit "rlichs", conținând coloanele de date din lichess.RDS:

library(RPostgreSQL)
L <- readRDS("lichess.RDS")
drv <- dbDriver("PostgreSQL")
conn <- dbConnect(drv, 
     dbname = "docer", host = "localhost", port = "",
     user = "vbdocer", 
     password = "mhikysOD7PFGwou0rGK57P8xG"  # sau așa ceva...
)
dbWriteTable(conn, 'rlichs', L)
dbDisconnect(conn)

Dacă ne uităm în tabelul respectiv (folosind psql), constatăm că RPostgreSQL a adăugat un al 13-lea câmp, numit "row.names", îngrijindu-se astfel de existența unui câmp ale cărui valori să identifice unic înregistrările existente; dar de fapt, avem deja drept "primary key", câmpul 'gameid'.
În terminalul instanțiat de psql, tastăm comenzile necesare pentru a constitui un nou tabel, "lichs", în care inserăm în ordinea dorită coloanele din "rlichs" (pentru eventualitatea că în lichess.RDS nu le aveam în această ordine), dar fără coloana "row.names" și apoi, ștergem tabelul "rlichs":

docer=> insert into lichs select gameid, date, times, white, black, result, welo, belo, welodif, belodif, eco, moves from rlichs;
docer=> drop table rlichs;
docer=> update lichs set date=replace(date, '.', '-');

Iar prin ultima comandă de mai sus, aducem valorile din coloana "date" la formatul standard pentru Django (cu "-", nu ".", drept separator de an/lună/zi).

Un model Django pentru datele din tabelul "lichs"

Nu-i cazul să înființăm un nou proiect Django — avem deja, încă de pe vremea versiunii 1.8, proiectul "docer" (în care am inclus pe parcurs aplicațiile "bacmat", "blog" și "games"); folosind utilitarul manage.py al acestui proiect, înființăm acum aplicația numită "lichess" (și o adăugăm în fișierul de configurare al proiectului, "settings.py", în lista "INSTALLED_APPS").

N-ar fi cazul să completăm manual fișierul "models.py" (asociat aplicației tocmai înființate); lansând (din directorul proiectului) manage.py inspectdb lichs, modelul Python class Lichs() corespunzător tabelului PostgreSQL "lichs" este creat automat și ne rămâne doar să-l modificăm cum ne convine:

## lichess/models.py
from django.db import models
from django.urls import reverse

class LichsManager(models.Manager):
    def by_ym(self, ym):
        return super(LichsManager, self).get_queryset().filter(date__startswith = ym)

class Lichs(models.Model):
    gameid = models.CharField(max_length = 255, primary_key=True)
    date = models.CharField(max_length = 16)
    times = models.CharField(max_length = 16)
    white = models.CharField(max_length = 255)
    black = models.CharField(max_length = 255)
    result = models.CharField(max_length = 16)
    welo = models.IntegerField(blank=True, null=True)
    belo = models.IntegerField(blank=True, null=True)
    welodif = models.IntegerField(blank=True, null=True)
    belodif = models.IntegerField(blank=True, null=True)
    eco = models.CharField(max_length = 16)
    moves = models.TextField()

    objects = models.Manager()
    byal = LichsManager()

    class Meta:
        db_table = 'lichs'

    def get_absolute_url(self):
        return reverse('lichs', args=(self.gameid, ))

    def __str__(self):
        return "%s:%s:%s:%s" % (self.white, self.black, self.eco, self.result)

def ymdates():
    return sorted(list(set([game.date[:7] for game in Lichs.objects.all()])))

Am adăugat modelului respectiv un "manager", prin care vom putea obține lista partidelor care în câmpul 'date' înregistrează anul și luna indicate; de exemplu, lucrând în consola interactivă instanțiată prin manage.py shell:

>>> from lichess.models import Lichs
>>> Lichs.byal.by_ym('2026-04')
<QuerySet [<Lichs: vlbz:shico:E54:0-1>, <Lichs: vlbz:chronischlafloser:D52:1-0>, <Lichs: vlbz:Wellanotherone:E41:1/2-1/2>]>
>>> 

ne-a rezultat lista obiectelor Python asociate partidelor din 2026, luna 04.

Deasemenea am adăugat funcția ymdates(), prin care vom obține lista ordonată a valorilor (unice, fiindcă am folosit set()) pentru "an" și "lună" din câmpul 'date'.

Adăugirile menționate facilitează următoarea schemă de funcționare a aplicației: se afișează lista returnată de ymdates(), din care se va opta pentru un an și o lună; apoi se afișează partidele returnate prin LichsManager pentru "an-luna" respectiv, dintre care se va putea alege aceea de vizualizat.

Funcții necesare operării aplicației; șabloane de comunicare

Pentru a asigura interacțiunile necesare conform schemei de funcționare a aplicației conturate mai sus, avem de constituit anumite funcții în fișierul lichess/views.py și anumite "șabloane de comunicare" în directorul lichess/templates/.
De exemplu, prin funcția by_ym() trimitem utilizatorului (în browserul acestuia) o pagină HTML conținând pe de o parte, lista valorilor "An-Lună" (obținute apelând ymdates() din "models.py") și pe de altă parte, un "handler de click" în javascript (cu jQuery) care, sesizând alegerea făcută pentru "An-Lună", să o posteze înapoi către o anumită (altă) funcție din vievs.py:

## lichess/views.py
from .models import Lichs, ymdates
from django.shortcuts import render

def by_ym(request):
    return render(request, template_name="vlbz.html",
                  context = {'ymdates': ymdates()})

Șablonul HTML templates/vlbz.html asociat acestei funcții poate arăta astfel:

{% extends 'base.html' %}
{%block title%}vlbz Games on Lichess{%endblock%}
{%block dometas%}<meta name="description" content="Partide de șah (rapid), organizate după an și lună, jucate de 'vlbz' pe Lichess" />{%endblock%}

{% block content %}
<p id="varlichess" style="text-align:center;font-size:1.1em">
<select>{% for ym in ymdates %}
<option value="{{ym}}">{{ym}}</option>
{% endfor %}</select>&nbsp;&nbsp;<button>ask Games</button></p>

<div id="partide"></div>

<script>
$(function($) {
    $('#varlichess button').click(function(event) {
        var query = $(this).parent();
        var ym = query.find('select option:selected').text();
        $.post("{% url 'ask_games' %}", 
               {'csrfmiddlewaretoken': '{{ csrf_token }}',
                'ym': ym}, 
               function(response) {$('#partide').html(response);});
        event.preventDefault();
    });
});
</script>
{%endblock content %}

Practic, când lucrurile se vor petrece, în browserul utilizatorului va apărea un element de selectare și un buton de cerere:

Handlerul de click asociat în finalul șablonului HTML redat mai sus, butonului "ask Game" va identifica opțiunea de "An-Lună" selectată și o va trimite prin metoda post din jQuery la "adresa" indicată prin "{% url 'ask_games' %}" — interpretată de Django (pe baza fișierului de adrese urls.py, pe care îl vom completa mai încolo) ca adresând funcția "ask_games()" din fișierul "views.py"… Iar răspunsul așteptat de la funcția ask_games() va fi inserat în diviziunea marcată cu 'id="partide"', aflată dedesubtul paragrafului care conține cele două elemente redate mai sus.

Adăugăm în "views.py" funcția ask_game():

def ask_games(request):
    if request.headers.get('x-requested-with') == 'XMLHttpRequest':
        ym = request.POST.get('ym')
        games = Lichs.byal.by_ym(ym).defer('moves') \
                .order_by('date', 'times')
        context = {'games': games, 'ym': ym}
        return render(request, template_name="vlbz1.html", 
                      context=context)

Se determină partidele corespunzătoare cererii de "An-Lună" primite de la handlerul de click din "vlbz.html" (dar excluzând câmpul "moves") și se încorporează lista 'games' a acestora, împreună cu "ym", în șablonul "vlbz1.html" (de înscris și acesta, în templates/), care poate arăta cam așa:

<h2>{{ym}}</h2>
<table>
        {% for game in games %}
    <tr><td><em>{{game.white}}</em> (<code>{{game.welo}}</code>)</td><td>-<td> 
        <td><em>{{game.black}}</em> (<code>{{game.belo}}</code>)</td> 
        <td>{{game.eco}}</td>
        <td><form method="post" action="get_pgn_brw/">{%csrf_token%}
                <input type="hidden" name="gameky" value="{{game.gameid}}">
                <button type="submit" name="pgn_brw">
                    {{game.result}}
                </button>
            </form></td>
    <tr>{% endfor %}
</table>

După completarea cu datele 'games' și 'ym' a șablonului "vlbz1.html", pagina moștenită de la "vlbz.html" (cu diviziunea 'id="partide"', în care s-a înscris acum rezultatul returnat de ask_games()) va arăta cam așa:

Pe elementul <td> care conține rezultatul partidei (pentru fiecare partidă), am montat un formular simplu care prin 'action="get_pgn_brw/" va transmite funcției get_pgn_brw() (prin metoda POST), cheia primară 'gameid' a partidei respective (cu observația că s-ar fi cuvenit să montăm un singur formular — eventual ca și mai sus în "vlbz.html", folosind jQuery: pe baza indexului rândului <tr> care primește 'click', se determină 'gameid' de transmis funcției get_pgn_brw()).

Funcția get_pgn_brw() (de adăugat în "views.py") accesează cheia "gameky" din dicționarul POST, extrage partida respectivă din tabelul lichs și completează cu datele obținute șablonul "vlbz4.html" (redat ceva mai jos):

def get_pgn_brw(request):
    game = Lichs.objects.get(pk = request.POST.get('gameky'))
    return render(request, template_name="vlbz4.html", 
                  context = {'game': game})

Când lucrurile se vor petrece, "vlbz4.html" vizualizează în browserul utilizatorului aplicației, partida respectivă:

Utilizatorul va putea urmări desfășurarea partidei (înainte sau înapoi față de mutarea curentă, sau automat) folosind "butoanele" (de fapt, simboluri din Unicode) din bara de navigare aflată dedesubtul tablei de șah; când desfășurarea ajunge la o mutare adnotată (cum este pe imaginea de mai sus, mutarea 15.Rf4?!), atunci desfășurarea este stopată și în panoul din dreapta este evidențiată adnotarea respectivă.

Pentru această vizualizare am folosit fișierul "brw.js" și fișierul de stiluri "brw.css", constituite în [2]. Fiindcă în alte aplicații montate în proiectul Django 'docer' (de exemplu, în /slightchess/), folosim versiuni mai vechi ale acestor două fișiere, pentru a evita conflictele de nume (de clase CSS și chiar de funcții javascript) — am ales să constituim "vlbz4.html" ca pagină independentă (în loc de extindere a paginii de bază "base.html", cum este "vlbz.html"):

{% load static %}{% load cust_tgs %}<!DOCTYPE html>
<html><head>
  <meta charset="utf-8" />
  <title>Partide de şah (PGN (lichess.com)->R data.frame->PostgreSQL->Django, pgn_browser)</title>
<link rel="stylesheet" href="{% static 'CSS/brw.css' %}">
<script src="{% static 'JS/OUT/jquery-1.11.1.min.js' %}"></script>
<script src="{% static 'JS/OUT/jquery-ui.min.js' %}"></script>
<script src="{% static 'JS/brw.js' %}"></script>
    <style>
body {
    font: 16px/1.511 Verdana, sans-serif;
    background:  #e2d2b9; width: 50em;
    margin-left: auto; margin-right: auto;
}
#wrap {background: #ffffdd; padding-left: 1em;}
div.inqsor { 
    margin-top: 1em;  margin-left: 1.5em;  margin-right: 1.5em; 
    font-size: 13.5px; line-height:1.5em; padding:0.5em;
}
div.inqsor p {color: #663300;}
.inqsor p span {font-size: 1.5em;color: #333;}
button, input[type='checkbox'] {cursor:pointer;}
    </style>
</head>

    <body>
<div id="wrap" style="margin-bottom:50px;">
<div class="inqsor">
<p><em>{{game.white}}</em> (<code>{{game.welo}}</code>) - 
   <em>{{game.black}}</em> (<code>{{game.belo}}</code>) 
   <em>{{game.result}}</em> 
   [<code>{{game.welodif|pjoin}}/{{game.belodif|pjoin}}</code>]</p>

<textarea id="lich_area_pgn">{{game.moves}}</textarea>

    <script>
$(function() {
    $('#lich_area_pgn').pgnbrw({Load:false, Game:false, 
                                Moves:true, Notes:true, 
                                Men:30, Color:2}); 
});
    </script>
</div></div>
</body></html>

Mutările din partida returnată de get_pgn_brw() sunt înregistrate în elementul <textarea>, pe care în final, prin jQuery, instanțiem obiectul pgnbrw din "brw.js" — iar acesta produce tabla de șah, bara de navigare, diviziunea mutărilor și pe cea a adnotărilor (cum se vede pe imaginea redată mai sus), asigurând funcționalitățile și interacțiunile cu utilizatorul evocate mai sus.

În treacăt mai precizăm că am înființat un "custom template tag" (încărcat la început prin {% load cust_tgs %}), anume 'pjoin' — o funcție foarte simplă care la afișarea valorilor 'welodif' și 'belodif' adaugă în față un "+", dacă diferența respectivă este pozitivă (astfel, pe imaginea redată diferențele de ELO apar sub forma "+11/-9").

Cum cere browserul aplicației?

În fișierul lichess/urls.py specificăm ce adrese ar trebui furnizate (nu neapărat, prin "bara de adresă" a browserului) pentru a activa funcțiile constituite mai sus:

# lichess/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.by_ym, name="fromlichess"),  # o cale de acces... vidă!
    path('ask_games/', views.ask_games, name="ask_games"),
    path('get_pgn_brw/', views.get_pgn_brw, name="get_pgn_brw"),
]

Fiecare dintre aplicațiile existente în proiectul Django respectiv (aici, 'docer'), deține câte un fișier urls.py, iar calea vidă specificată pe primul loc în lista urlpatterns va fi înlocuită de calea pe care o specificăm (prin include) pentru aplicația respectivă în fișierul global urls.py al proiectului:

## în docer/urls.py
urlpatterns = [
# ...
    path('slightchess/', include("games.urls")),
    path('variante_bac_mate/', include("bacmat.urls")),
    path('fromlichess/', include("lichess.urls")),
    path('', include("blog.urls")),
]

În /etc/apache2/sites-enabled/ am configurat un "virtual host" cu ServerName docer.hom; dacă înscriem în bara de adresă a browserului "http://docer.hom/", atunci Django va consulta fișierul global "docer/urls.py" și găsind calea vidă (pe ultima linie din lista urlpatterns) va "include" 'blog.urls' și va activa funcția din blog/views.py înscrisă cu path('', ...) pe primul loc din lista urlpatterns a fișierului "blog/urls.py".
Dacă înscriem în bara de adresă "http://docer.hom/slightchess/", atunci pe baza specificației din "docer/urls.py", se va lansa aplicația "games"…
Iar acum (după adăugarea făcută mai sus în fișierul global docer/urls.py), "http://docer.hom/fromlichess/" va lansa funcția by_ym() din lichess/views.py (conform specificației pentru "cale vidă" din "lichess/urls.py").

Publicarea aplicației…

În cele de mai sus am avut în vedere calculatorul propriu, pe care avem prin Apache un server "local" de aplicații. Dacă neglijăm întrebarea "cui folosește?", nu ne împiedică nimeni să adăugăm (și) aplicația lichess realizată mai sus, pe "serverul public" (posibil de accesat mai demult prin "https//docerp.ro/", iar acum prin "https//vlad.bazon.net/").
În principiu, nu prea ne întrebăm "cui folosește", fiindcă nu ne ocupăm de confecționat produse "la un click distanță" (de pe telefon), ci… să învățăm și cum să învățăm (cam de aici, inventasem "docer" — v. latinescul docere). Aplicația Django principală, denumită "blog", prezintă articole și studii proprii (de regulă, originale) pe diverse teme de informatică și matematică, iar celelalte aplicații (la care adăugăm acum și "lichess") reflectă concret conținuturile unora sau altora dintre articolele respective (și sunt vreo 15 ani de când facem așa, cu speranța optimistă că vor fi și alții cărora să le folosească învățăturile noastre).

Ca să instituim pe server (la distanță) aplicația "lichess", transferăm întâi directorul local 'docer/lichess/' și în locurile cuvenite de pe server, fișierele locale "brw.js" și "brw.css" (implicate în șablonul "views4.html" și neexistente deja, pe server); apoi, prin "manage.py dumpdata lichess.Lichs" constituim un fișier (local) care conține în format JSON datele din tabelul 'lichs' — fișier pe care îl transferăm (la distanță) pe server. După aceasta, avem de lucrat pe server (într-o consolă care ne permite să accesăm, să vizualizăm și să modificăm, fișiere existente pe server): în fișierul "docer/settings.py" (de pe server) adăugăm sub INSTALLED_APPS aplicația 'lichess', iar în "docer/urls.py" includem "lichess.urls" (pentru calea 'fromlichess/'); apoi, prin manage.py migrate lichess obținem tabelul 'lichs' în baza de date existentă pe server, tabel pe care — prin "manage.py loaddata ~/lichs.json" — îl completăm cu datele din fișierul JSON adus anterior pe server.
Cu manage.py check putem verifica dacă nu ne-a scăpat ceva, iar dacă lucrurile sunt în regulă, restartăm serverul și putem ieși din consola prin care ne legasem la server. Din browserul propriu putem acum să lansăm aplicația respectivă, prin /fromlichess/.
N-am verificat, dar probabil că aplicația respectivă rămâne funcțională (poate cu vreo mică ajustare) și sub ultima versiune Django — fiindcă n-am folosit mai sus decât cele mai obișnuite elemente din știința "Django" (da!… Django și cu ce implică a ajuns o coșcogeamitea știință, acoperind și Python, javascript, PostgreSQL (iar noi am mai adăugat și R) care și acestea sunt veritabile științe).

vezi Cărţile mele (de programare)

docerpro | Prev |