Python web frameworkök
összehasonlítása


Django

Programozási paradigmák

Imperatív

Objektum orientált

Deklaratív

Clean Architecture

Előnyök

  • Egyértelműen leírja a téma nyelvtanát, 
    ún. domain-jét (beléptető rendszerek)
    minden mástól függetlenül
     
  • Üzleti logika teljesen el van szeparálva,
    könnyebb rajta módosítani
     
  • Sokkal rugalmasabb lesz a kód
     
  • UI bármikor kicserélhető
     
  • Sokkal könnyebben tesztelhető automatikus tesztekkel

Hátrányok

  • Hosszabb ideig tart megírni?
  • Több kód?

Példa Entities

class Ticket(object):
    def __init__(self, price, valid_until):
        self.price = price
        self.valid_until = valid_until

    def is_valid(self):
        return datetime.now() <= valid_until
class Zone(object):
    def __init__(self, name):
        self.name = name
        self.persons = []

    def __contains__(self, person):
        return person in self.persons

    def exit_person(self, person):
        self.persons.index(person)
        del self.persons[person_index]
class Person(object):
    def __init__(self, name, age, ticket):
        self.name = name
        self.ticket = ticket

    @property
    def shortname(self):
        return self.name[:10]

    @property
    def full_name(self):
        return self.first_name + self.middle_name + self.last_name

    def has_ticket(self):
        return self.ticket is not None

Példa Use Cases

def enter_person(zone, person):
    if person.has_ticket() and person.ticket.is_valid():
        zone.persons.append(person)
def exit_person(zone, person):
    if person in zone:
        zone.exit_person(person)
    
    if person.has_zsigmondy_kartya():
        say_something_ridiculous()
def check_entry(zone, person):
    return (person.age > 18 and 
            person.has_ticket() and 
            zone.max_persons > zone.persons)

Django

Főbb jellemzők

  • Batteries included
  • Big framework (86k LOC)
  • Erősen objektum orientált szemléletű
  • Komponensei nem szétválaszthatók
  • Óriási community, sok pénz van mögötte
  • Rock solid, stabil
  • Brutális dokumentáció, minden egy helyen
  • Rugalmatlan (pl. PostgreSQL-el érdemes használni)

Batteries

  • konfiguráció
  • URL routing
  • Templating
  • ORM
  • Form kezelés
  • admin felület
  • i18n, l10n
  • GeoDjango (PostgGIS)
  • User authentikáció, authorizáció

Konfiguráció

settings.py, globális

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'officecontrol',
        'HOST': '192.168.224.24',
        'PASSWORD': 'jelszo',
    }
}

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'anteus.ticketcontrol',
    'anteus.parkcontrol',
)

LANGUAGE_CODE = 'hu-hu'

TIME_ZONE = 'Europe/Budapest'
USE_I18N = True
USE_L10N = True
USE_TZ = True

URL routing

from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^articles/2003/$', views.special_case_2003),
    url(r'^articles/([0-9]{4})/$', views.year_archive),
    url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
    url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
]

regex --> függvény

URL routing

from django.conf.urls import url
from django.views.generic import TemplateView

urlpatterns = [
    url(r'^about/', TemplateView.as_view(template_name="about.html")),
]

regex --> Class Based View

Class Based View példa 2

class ImageDetailView(BreadCrumbMixin, generic.DetailView):
    model = WebGroup
    template_name = 'image.html'
    context_object_name = 'web_group'

    def get_context_data(self, **kwargs):
        context = super(ImageDetailView, self).get_context_data(**kwargs)
        context['image_ext'] = self.object.image.split('.')[-1].lower()
        return context

regex --> Class Based View

from django.conf.urls import url
from myapp import views

urlpatterns = [
    url(r'^termekkep/(?P<pk>.*)/(?P<slug>.*)/$', views.ImageDetailView.as_view()),
]

Templating

{% extends "base.html" %}

{% block title %}
  {{ block.super }} - Bejelentkezés
{% endblock title %}

{% block content %}
<div class="row login">

  <div class="row">
    <div class="col-md-offset-2 col-md-6">
      {% if form.errors %}
        {% for error in form.non_field_errors %}
          <div class="alert alert-danger"> {{ error }} </div>
        {% empty %}
          <div class="col-md-offset-3 alert alert-danger">Hiba!</div>
        {% endfor %}
      {% elif user.is_authenticated %}
        <div class="col-md-offset-3 alert alert-info"> Bejelentkezhet egy másik Felhasználónévvel. </div>
      {% else %}
        <div class="col-md-offset-3 alert alert-warning"> A megtekintéshez bejelentkezés szükséges! </div>
      {% endif %}
    </div>
  </div>

  <div class="row">
    <div class="col-md-6 col-md-offset-2">

      <form method="post" action="{% url 'login' %}" class="form-horizontal login" role="form">
        {% csrf_token %}

        {# Mivel az anchor nem kerül küldésre a szerver felé (RFC 1808) #}
        {# JavaScripttel kinyerem az url-ből és hozzácsatolom a beküldött GET['next'] részhez. #}
        <script type="text/javascript">
          document.write("<input type=\"hidden\" name=\"next\" value=\"{{ request.GET.next }}" + window.location.hash + "\">");
        </script>

        {% include "form_fields/_input.html" with field=form.username type="text" %}

        {% include "form_fields/_input.html" with field=form.password type="password" %}

        <div class="form-group">
          <div class="col-sm-offset-3 col-sm-5">
            <div class="checkbox">
              <label>
                <input type="checkbox" name="remember_me"> Maradjak bejelentkezve
              </label>
            </div>
          </div>
          <div class="col-sm-4">
            <button type="submit" class="btn btn-primary">Bejelentkezés</button>
          </div>
        </div>

      </form>

    </div>
  </div>

</div>

{% endblock content %}

ORM

  • Active Record pattern
  • Támogatott adatbázisok:
    MySQL, PostgreSQL, SQLite, Oracle
  • Mindenféle típust kezel
  • Leginkább egyszerű CRUD műveletekre való, 
    de az utóbbi években sokat fejlődött
  • Automatikus migrációk
  • Form generálás modellekből

ORM model példa

from django.db import models

class Menu(models.Model):
    parent = models.ForeignKey('self', blank=True, null=True, related_name='children')

    name = models.CharField(max_length=100, blank=True)
    descr = models.TextField(blank=True)
    fullname = models.CharField(max_length=100, blank=True)
    ord = models.IntegerField(blank=True, null=True)
    image = models.CharField(max_length=100, blank=True)
    ord_tree = models.CharField(max_length=100, blank=True)
    image_thumbnail = models.BinaryField(blank=True, null=True)
    meta_keyword = models.TextField(blank=True)
    meta_title = models.TextField(blank=True)
    meta_descr = models.TextField(blank=True)
    text_top = models.TextField(blank=True)
    text_bottom = models.TextField(blank=True)
    to_upload_image = models.NullBooleanField()

    objects = WebMenuManager()
    not_empty = NotEmptyWebMenuManager()

    class Meta:
        managed = False
        db_table = 'web_menu'
        verbose_name = 'Menü'
        verbose_name_plural = 'Menük'
        ordering = ['ord_tree']

Automatikus adatbázis migráció

  • Migráció is a kód része
  • teljesen automatikus
    (kitalálja a változásokat)
  • bármelyik fejlesztő egyetlen paranccsal képest létrehozni a legutolsó adatbázis állapotot

Migráció példa

from django.db import models


class Ticket(models.Model):
    name = models.CharField(max_length=255)
    price = models.DecimalField(max_digits=8, decimal_places=2)
from django.db import models


class Ticket(models.Model):
    name = models.CharField(max_length=255)
    price = models.DecimalField(max_digits=8, decimal_places=2)
    comment = models.TextField(blank=True)

Migráció demo

Modularitás:

appok

Teljesen elkülöníthető minden részük:

  • template
  • url
  • modelek
  • migrációk
  • stb.

App létrehozás példa

$ manage.py startapp ticketcontrol
$ tree ticketcontrol                                                                                                                               [10:54:41]
ticketcontrol
├── __init__.py
├── admin.py
├── migrations
│   └── __init__.py
├── models.py
├── tests.py
└── views.py

1 directory, 6 files

Admin felület

  • Modellekből automatikusan generált
  • Minden típust ismer
  • Konfigurálható, Testre szabható
  • Nem a felhasználónak való!
    (kb. Config-nak felel meg)
  • Automatikus szűrők
  • Bulk operations

Internationalization (i18n)

Preparing the software for localization. Usually done by developers.

Localization (l10n)

Writing the translations and local formats. Usually done by translators.

i18n és l10n

  • szövegek többnyelvűsítése
  • dátumok, számok formázása az adott ország konvencióinak megfelelően
  • időzónák kezelés
  • GNU gettext toolsettel (.po) 
    kód példa, template példa
  • lazy translation
    (konkrét elérés vs hívás) 

GeoDjango

geographic Web framework

User authentikáció

Global típusú, azaz fel lehet venni mindenféle jogosultságot és azt ellenőrizni

(row level szintű jogosultságkezelés nem lehetséges vele mint Pyramid-nál)

de vannak hozzá csomagok még rugalmasabb jogosultság kezelésre

Példa kód

Pyramid

Főbb jellemzők

  • Some batteries
  • Legrugalmasabb framework mind közül
  • Közepes méretű (37k LOC)
    (nem micro, de Djangonál jóval kisebb)
  • Hosszú múlt, nagyon jó minőségű kód
  • Gyors
    (bizonyos részei C-ben íródtak,
    pl. nincs paraméter átadás overhead)
  • Kicsi, de nagyon aktív és tapasztalt community

Bővíthetőség

  • Teljesen különálló részek

https://xiaonuogantan.wordpress.com/2011/12/24/pyramid-vs-django/

Hello world

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response


def hello_world(request):
    return Response('Hello %(name)s!' % request.matchdict)

if __name__ == '__main__':
    config = Configurator()
    config.add_route('hello', '/hello/{name}')
    config.add_view(hello_world, route_name='hello')
    app = config.make_wsgi_app()
    server = make_server('0.0.0.0', 8080, app)
    server.serve_forever()
   

Flask

Főbb jellemzők

  • Pluginezhető
  • Microframework (2,5k LOC)
  • Sok plugin, de ezek sokszor gyenge minőségűek,
    vagy nincsenek karban tartva

Hello world

from flask import Flask
app = Flask(__name__)


@app.route("/")
def hello():
    return "Hello World!"


if __name__ == "__main__":
    app.run()

Python web frameworkök

By Kiss György

Python web frameworkök

  • 225