Programavimo nuotykiai: bazinių klasių perrašymas įgyvendinant Django daugiakalbius modelius

Django yra tikrai patogi žiniatinklio karkasinė sistema Python programavimo kalbai, bet kaip ir kiekviena karkasinė sistema, ši atsineša į programavimo pasaulį savų apribojimų žiniatinklio meistrui. Django turi daug gyvenimą palengvinančių savybių: autentikacijos, sesijų, švarių URL, išvaizdos šablonų, sparčiosios atmintinės valdymą, duomenų bazės programavimo interfeisą, standartinę duomenų administravimo sistemą, statiško turinio vertimų mechanizmą ir kt., tik ne mechanizmą modeliams su daugiakalbiu turiniu, ką kiekvienas meistras turi susikurti savaip.

Trumpai supažindinsiu su Django projektų (svetainių ar žiniatinklio programų) architektūra. Įprastai kiekvienas projektas turi savo kodą, išvaizdos šablonus ir mediją. Kodas susideda iš projekto nustatymų, URL valdymo taisyklių, komandinėj eilutėj vykdomo projekto valdiklio ir atskirų taikomųjų programų (application), kurios kuriamos pagal projekto reikalavimus, pavyzdžiui blogas, forumas, galerija, naujienų prenumerata, meniu sistema, reitingai ar kt. Kiekviena taikomoji programa savo ruožtu turi modelius, reliatyvių URL valdymo taisykles ir peržiūros funkcijas. Modeliai apibūdina esybes, jų savybes, standartinės administravimo sistemos nustatymus. Kiekvieną modelį atitinka atitinka viena duomenų bazės lentelė. Pavyzdžiui toliau aprašytam puslapio modeliui modeliui bus sukurta DB lentelė puslapiai_puslapis(*) su VARCHAR(200) tipo lauku pavadinimas ir TEXT tipo lauku turinys:

# puslapiai/models.py
from django.db import models

class Puslapis(models.Model):
    pavadinimas=models.CharField("Pavadinimas", maxlength=200)
    turinys=models.TextField("Turinys")
    irasyta=models.DateTimeField("Sukūrimo data", auto_now_add=True)

    class Meta:
        ordering=["pavadinimas", "-irasyta"]
        verbose_name="puslapis"
        verbose_name_plural="puslapiai"

    class Admin:
        list_display=["pavadinimas", "turinys", "irasyta"]

    def __str__(self):
        return self.pavadinimas

Šitam modeliui bus automatiškai sukuriama sritis administravimo sistemoje. Parametras list_display nusako, kad puslapių sąraše bus rodomas pavadinimas, turinys ir sukūrimo data, ordering – kad standartiškai sąrašas bus išrūšiuotas pagal pavadinimą abėcėlės tvarka ir sukūrimo datą nuo naujausio. Daugiau apie modelių kūrimą skaityk Django dokumentacijoj.

Vienas iš paprasčiausių būdų įgyvendinti modelio daugiakalbiškumą būtų tiesiog kiekvienam daugiakalbiam laukui sukurti po grupę kopijų vertimams. Vertimai būtų neprivalomi. Laukai vertimams turėtų plėtinius su dviraidžiu kalbos kodu. Standartinės kalbos laukas neturėtų plėtinio ir būtų privalomas. Jei vertimas nerastas, rodoma standartinės kalbos lauko reikšmė. Kaip tariam, taip ir padarom:

# puslapiai/models.py
from django.db import models
from django.utils import translation
from django.utils.translation import gettext_lazy as _

class Puslapis(models.Model):
    pavadinimas=models.CharField("Pavadinimas", maxlength=200)
    pavadinimas_en=models.CharField("Title", maxlength=200)
    pavadinimas_de=models.CharField("Titel", maxlength=200)
    turinys=models.TextField("Turinys")
    turinys_en=models.TextField("Content")
    turinys_de=models.TextField("Inhalt")
    irasyta=models.DateTimeField(_("Sukūrimo data"), auto_now_add=True)

    class Meta:
        ordering=["pavadinimas", "-irasyta"]
        verbose_name=_("puslapis")
        verbose_name_plural=_("puslapiai")

    class Admin:
        list_display=["pavadinimas", "turinys", "irasyta"]

    def __str__(self):
        return self.gauk_pavadinima()

    def gauk_pavadinima(self, kalbos_kodas=None):
        return getattr(self, "pavadinimas_%s" % kalbos_kodas or \
            translation.get_language()[:2], "") or self.pavadinimas

    def gauk_turini(self, kalbos_kodas=None):
        return getattr(self, "turinys_%s" % kalbos_kodas or \
            translation.get_language()[:2], "") or self.turinys

Viskas būtų kaip ir šaunu, tik va standartinėj administravimo sistemoj objektų sąraše šiuo atveju bus rodomas tik lietuviškas pavadinimas ir turinys, ir objektai bus rūšiuojami pagal lietuvišką pavadinimą, net jei administratorius bus pasirinkęs kitą kalbą administravimui. Iš pirmo žvilgsnio atrodo, kad būtų galima tiesiog Meta klasėj rūšiavimą nurodyti priklausomą nuo šiuo metu pasirinktos kalbos:

ordering = ["lt" == translation.get_language()[:2] and "pavadinimas" \
    or "pavadinimas_%s" % translation.get_language()[:2], "irasyta"]

Deja modelio sisteminio analizavimo metu dar nėra žinoma naudotojo (administratoriaus) pasirinkta kalba ir translation.get_language() grąžina standartinę — mūsų atveju lietuvių — kalbą. Naudotojo pasirinkta kalba sistemai sužinoma tik vėliau, apdorojant modelio informaciją ir atvaizduojant jo objektus administravimo sistemoj. Laimei, atviras karkasinės sistemos kodas ir objektiškai orientuota Python prigimtis leidžia pergudrauti apribojimus. Idėja tokia: perrašysiu bazinę sąrašo (list) klasę taip, kad ji grąžintų nuo naudotojo pasirinktos kalbos priklausomus rezultatus tada, kai į ją kreipiamasi. Konstruktorius paims sąrašą simbolių eilučių, kurios nurodys reikalingus laukus. Jei eilutė baigsis vienu pabraukimo simboliu "_", tuomet laukas bus laikomas daugiakalbiu ir vietoj šitos reikšmės bus grąžinamas atitinkamas laukas, priklausantis nuo naudotojo pasirinktos kalbos.

class LaukuSarasas(list):
    def __init__(self, sarasas=[]):
        self.sarasas = sarasas

    def __iter__(self):
        return iter(self._gauk_sarasa())

    def __getitem__(self, indeksas):
        return self._gauk_sarasa()[indeksas]

    def __nonzero__(self):
        return bool(self.sarasas)

    def __len__(self):
        return len(self.sarasas)

    def __str__(self):
        return str(self._gauk_sarasa())

    def __repr__(self):
        return repr(self._gauk_sarasa())

    def _gauk_sarasa(self):
        kalbos_kodas = translation.get_language()[:2]
        rezultatas = []
        for elementas in self.sarasas:
            if elementas[:1]=="-":
                rusiavimo_tvarka = "-"
                elementas = elementas[1:]
            else:
                rusiavimo_tvarka = ""
            if elementas[:2] == "__" or elementas[-1:] != "_":
                rezultatas.append(rusiavimo_tvarka + elementas)
            else:
                if kalbos_kodas == "lt":
                    rezultatas.append(rusiavimo_tvarka + elementas[:-1])
                else:
                    rezultatas.append(rusiavimo_tvarka + elementas + kalbos_kodas)
        return rezultatas

Belieka atnaujinti mūsų pavyzdinį modelį, pasinaudojant šita mandra sąrašo klase.

# puslapiai/models.py
from django.db import models
from django.utils import translation
from django.utils.translation import gettext_lazy as _

class Puslapis(models.Model):
    pavadinimas=models.CharField("Pavadinimas", maxlength=200)
    pavadinimas_en=models.CharField("Title", maxlength=200)
    pavadinimas_de=models.CharField("Titel", maxlength=200)
    turinys=models.TextField("Turinys")
    turinys_en=models.TextField("Content")
    turinys_de=models.TextField("Kontent")
    irasyta=models.DateTimeField(_("Sukūrimo data"), auto_now_add=True)

    class Meta:
        ordering=LaukuSarasas(["pavadinimas_", "-irasyta"])
        verbose_name=_("puslapis")
        verbose_name_plural=_("puslapiai")

    class Admin:
        list_display=LaukuSarasas(["pavadinimas_", "turinys_", "irasyta"])

    def __str__(self):
        return self.gauk_pavadinima()

    def gauk_pavadinima(self, kalbos_kodas=None):
        return getattr(self, "pavadinimas_%s" % kalbos_kodas or \
            translation.get_language()[:2], "") or self.pavadinimas

    def gauk_turini(self, kalbos_kodas=None):
        return getattr(self, "turinys_%s" % kalbos_kodas or \
            translation.get_language()[:2], "") or self.turinys

Tai tiek, mieli programuotojai! Štai jums mano variantas, kaip žaist su daugiakalbiškumu. Gal kas susidomėjot Python’u ir Django? Atsiprašau visų kitų, kurie nieko nesuprato. Ir einu tūsintis :cool: .

(*) Kintamųjų vardai visuomet turėtų būti angliški, kad kodą lengviau suprastų skirtingų šalių žmonės, bet dėl aiškumo (kas yra karkasinės sistemos dalis, o kas — mano kodas) tuos vardus sulietuvinau.

Tags:

If you liked this entry, you may also like:

No Responses to “Programavimo nuotykiai: bazinių klasių perrašymas įgyvendinant Django daugiakalbius modelius”

  1. Ne nu bet būtina tau tuos lietuviškus terminus naudot… Taip sunku suprast :(

    O šiaip, kažkaip man Ruby po Python pasirodė malonesnė kalba ir RoR lyg ir daugiau supporto turi. be to Ruby turi gems’us, o pythone nieko panašaus nemačiau :(

  2. dado1945 says:

    Python’as turi eggs’us ir easy_install :-)

  3. dado1945 says:

    Ech ir Django man iškart nepatiko po šito straipsnio. TurboGears su l10n daug geriau susitvarko.

  4. Archatas says:

    Statišką turinį (išvaizdos šablonus ar naudotojams matomus pavadinimus modeliuose) tai Django moka labai gražiai leist išverst — naudoja gettext toolsą. Be to jei nori nenaudoti standartinės administravimo sistemos, gali pasirašyt savo ir nereiks tokių hackų kaip čia.

  5. dado1945 says:

    Žiūriu atidžiau ir man atrodo tu kažko ne to pridarei. Logiškai mąstant turėjai susikurti kalbos lentelė ir tada surišti ją su kitomis lentelėmis. Bet čia tik mano nuomonė.

    O aš tuo tarpu užsipuoliau Django :) Gal Django viskas tvarkoje ten.

  6. Archatas says:

    Taip ir būčiau daręs, bet darbdaviui labai svarbu, kad būtų galima patogiai naudoti standartinę administravimo sistemą per daug jos nehackinant. Tai daug mano sprendimų būna nenormalizuoti.

  7. dado1945 says:

    Negi Django neturi kažko panašaus į TurboGears catwalk?

  8. Archatas says:

    Taigi turi contributed administration tam reikalui. Na AJAX ten standartiškai kaip ir nerasta, tik paprastas JavaScript keliems spec. efektams. Administracijoj galima redaguot visus modelius (kad ir kalbos lentelę, jei tokią susikuri), bet tuomet ji bus gan atskirta nuo konteksto, ir tai nepatogu naudotojui.

    Skirtingai nei koks Ruby, Django specializuojasi į serverinę dalį, nekišdami per daug nosies į javascript, atseit kūrėjas pats pasirinks javascript frameworką, su kuriuo jam dirbt.

  9. dado1945 says:

    Pažiūrėk šiuos video kai turėsi laiko, kad suprastum apie ką aš čia šneku :)
    http://showmedo.com/videos/series?name=CatWalk1ForTurboGears

  10. Archatas says:

    Tai Django contrib admin turi tas pačias savybes. Lyginant su filmukais tik pora trūkumų: negalima nustatyt laukų eilės per GUI — reikia modelio faile Admin klasėje nurodyt fiksuotai; ir kitas — kad panorėjus, SQL queriai eina ne į frameworko kokį tai GUI interfeisą, o į paprastą tekstinį failą.

Leave a Reply