Používáte SQLAlchemy nebo pandas? pymysql – Python – Fórum – Programujte.com
 x   TIP: Přetáhni ikonu na hlavní panel pro připnutí webu

Používáte SQLAlchemy nebo pandas? pymysql – Python – Fórum – Programujte.comPoužíváte SQLAlchemy nebo pandas? pymysql – Python – Fórum – Programujte.com

 

oxidián0
Grafoman
17. 4. 2024   #1
-
0
-

Ahoj. Jsem úplný začátečník s pythonem na linuxu. Uvítal bych radu expertů co pracují na vyšší úrovni programování, nebo třeba s ORM. Myslel jsem že to vyřeším pomocí pandas, ale začínám pochybovat o efektivitě toho řešení. Hned na úvod musím říct, že mám jen 4GB ram a zbývá mi už cca 320-350 mega. Ale xFce je stabilní a má minimalistické požadavky, takže v pohodě.

Chtěl jsem napsat program na vyexportování dat (počet záznamů jde do milionu nebo víc). Některé tabulky jsem vyexportoval pomocí pandas a pymysql. Nic těžkého. Nicméně chtěl jsem udělat něco složitějšího... Momentální tabulka se jmenuje routes.txt . A jsou tam některé data pro mě přebytečná jako dlouhé popisy jízd (obsahují obvykle název města, zastávky nebo obce a aut.n.). Myslel jsem si, hele to by šlo zkrátit, místo těch názvů stanic v popisu dám sid (idečko zastávky). Když nenajdu zastávku, tak ten popis prostě smažu. To by znamenalo, že před zápisem musím provést select s left joinem na tabulku. znám route_id, a v tabulce trips je route_id a sid. V další tabulce stop_names je sid a stop_name. Prakticky to znamená udělat distinct select z trips, a pak select z stop_names.

Jenže jsem narazil na zvláštní problémy. pandas.read_sql vyžaduje string či query, a zdá se, že buď neakceptuje objekt nebo mě chce donutit k tomu abych to stringifoval. Jenže si myslím, že když to stringyfuju, tak pak to sqlalchemy nemá vůbec smysl. Proč zavádět objekty když ve výsledku to celé bude stejně neefektivní a použije se textová forma SQL dotazu.

Hlavní je zpracovávat čtené data po chunkách minimálně 1000 řádků najednou.

Na to jsem chtěl zavolat tu funkci, získat ty idečka, 1000 na jednou. Tu na testu mám jen 10. 1000 in a do selectu dát WHERE route_id IN (route_ids)

in execute


chunk.to_sql(name='routes', con=engine, if_exists='append', index=False)

    raise TypeError("Query must be a string unless using sqlalchemy.")
TypeError: Query must be a string unless using sqlalchemy.

from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, DECIMAL, ForeignKey, text
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy.ext.declarative import declarative_base
import pandas as pd
import zipfile
import io
import re

Base = declarative_base()

# Definice modelu pro tabulku stops
class Stop(Base):
    __tablename__ = 'stops'

    sid = Column(Integer, primary_key=True)
    stop_lat = Column(DECIMAL(10, 8))
    stop_lon = Column(DECIMAL(11, 8))
    dopravce = Column(String(255))
    id = Column(Integer)

# Definice modelu pro tabulku stop_names
class StopName(Base):
    __tablename__ = 'stop_names'

    sid = Column(Integer, primary_key=True, autoincrement=True)
    stop_name = Column(String(255), unique=True)

# Definice modelu pro tabulku trips
class Trip(Base):
    __tablename__ = 'trips'

    route_id = Column(String(255))
    trip_id_1 = Column(Integer, primary_key=True)
    trip_id_2 = Column(Integer, primary_key=True)
    trip_id_3 = Column(Integer, primary_key=True)
    direction_id = Column(Integer)
    sid = Column(Integer, ForeignKey('stops.sid'))

    stop = relationship("Stop")

# Připojení k databázi
password=""
db="trafic"
try:
    # Vytvoření SQLAlchemy engine s použitím pymysql
    engine = create_engine(f"mysql+pymysql://admin:{password}@localhost/{db}")
    # engine = create_engine('mysql+pymysql://', creator=lambda: mydb)
    print("Engine byl úspěšně vytvořen.")
except Exception as e:
    print("Při vytváření enginu došlo k chybě:", e)


try:
    connection = engine.connect()
    print("Spojení k databázi je aktivní.")
except Exception as e:
    print("Při testování spojení došlo k chybě:", e)

# Vytvoření tabulek v databázi
Base.metadata.create_all(engine)

# Funkce pro zpracování a vložení dat do databáze
def process_and_insert_data(session, data):
    for chunk in data:
        # Nahrazení všech výskytů "-CISR-" v route_id
        chunk['route_id'] = chunk['route_id'].str.replace('-CISR-', '')
        # Získání jedinečných route_id z aktuálního chunku
        route_ids = chunk['route_id'].unique()
        
        # Dotaz pro získání sid a stop_name
        #query = session.query(Trip.sid, StopName.stop_name).join(StopName).filter(Trip.route_id.in_(route_ids)).distinct()
        session.statement = session.query(Trip.sid, StopName.stop_name).select_from(Trip).join(StopName, Trip.sid == StopName.sid).filter(Trip.route_id.in_(route_ids)).distinct()
        # Provedení dotazu a získání výsledků
        # chunk_result = query.all()
        # session.statement = returns the sql query.
        chunk_result = pd.read_sql(session.statement, session.bind)
        
        # Zpracování a vložení dat
        for row in chunk_result:
            needle = row[1]
            for index, route_long_name in chunk['route_long_name'].items():
                pattern = re.compile(r'\b' + re.escape(needle) + r'\b')
                if re.search(pattern, route_long_name):
                    chunk.at[index, 'route_long_name'] = re.sub(pattern, f"#{row[0]}", route_long_name)
                else:
                    city_name = needle.split(',')[0].strip()  
                    city_pattern = re.compile(r'\b' + re.escape(city_name) + r'\b')
                    if re.search(city_pattern, route_long_name):
                        chunk.at[index, 'route_long_name'] = re.sub(city_pattern, f"#{row[0]}", route_long_name)
        
        # Vložení dat do databáze
        # chunk.to_sql(name='routes', con=session.connection(), if_exists='append', index=False)
        chunk.to_sql(name='routes', con=engine, if_exists='append', index=False)

# Načtení dat ze souboru routes.txt z ZIP archivu po menších blocích
zip_path = "/mnt/ramdisk/JDF_merged_GTFS.zip"
chunksize = 10

with zipfile.ZipFile(zip_path, 'r') as zip_file:
    with zip_file.open('routes.txt', 'r') as routes_file:
        data = pd.read_csv(io.TextIOWrapper(routes_file, 'utf-8'), chunksize=chunksize)
        Session = sessionmaker(bind=engine)
        with Session() as session:
            process_and_insert_data(session, data)

# Uzavření spojení
engine.dispose()

PS: Nemohl by vlastník stránek zpravit písmo textu, aby nebylo bílé a bílém nejde tu nic přečíst? Používám plugin dark-reader ve FF a vše je bílé.

Nahlásit jako SPAM
IP: 94.113.182.–
gna
~ Anonymní uživatel
1859 příspěvků
17. 4. 2024   #2
-
0
-

Do nedávna byl problém s tím, že pandas nefungovalo s novou verzí sqlalchemy, tak to zkus aktualizovat. (Jako bežný uživatel, ne root, ať si nerozhasíš systém, kdyby něco)

pip install --upgrade pandas sqlalchemy

Já jsem to teď zkoušel, jen jsem v tom readu za statement přidal ".selectable" a chodí mi to.

Nahlásit jako SPAM
IP: 213.211.51.–
oxidián0
Grafoman
17. 4. 2024   #3
-
0
-

Na .venv:

#2 gna
pip install --upgrade pandas sqlalchemy
Requirement already satisfied: pandas in ./.venv/lib/python3.10/site-packages (2.2.2)
Requirement already satisfied: sqlalchemy in ./.venv/lib/python3.10/site-packages (2.0.29)
Requirement already satisfied: numpy>=1.22.4 in ./.venv/lib/python3.10/site-packages (from pandas) (1.26.4)
Requirement already satisfied: python-dateutil>=2.8.2 in ./.venv/lib/python3.10/site-packages (from pandas) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in ./.venv/lib/python3.10/site-packages (from pandas) (2024.1)
Requirement already satisfied: tzdata>=2022.7 in ./.venv/lib/python3.10/site-packages (from pandas) (2024.1)
Requirement already satisfied: typing-extensions>=4.6.0 in ./.venv/lib/python3.10/site-packages (from sqlalchemy) (4.11.0)
Requirement already satisfied: greenlet!=0.4.17 in ./.venv/lib/python3.10/site-packages (from sqlalchemy) (3.0.3)
Requirement already satisfied: six>=1.5 in ./.venv/lib/python3.10/site-packages (from python-dateutil>=2.8.2->pandas) (1.16.0)

Nahlásit jako SPAM
IP: 94.113.182.–
oxidián0
Grafoman
17. 4. 2024   #4
-
0
-

Hele tak ve Visual Studiu editor to spustit jde - při ladění.

Ale v normálním prostředí v terminálu ne. Tak nevím něco nemám nainstalované? Ale dám ten update, něco se děje,

Requirement already satisfied: six>=1.5 in /usr/lib/python3/dist-packages (from python-dateutil>=2.8.2->pandas) (1.16.0)
Using cached pandas-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (13.0 MB)
Using cached SQLAlchemy-2.0.29-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.1 MB)
Installing collected packages: sqlalchemy, pandas
  Attempting uninstall: pandas
    Found existing installation: pandas 2.2.1
    Uninstalling pandas-2.2.1:
      Successfully uninstalled pandas-2.2.1
Successfully installed pandas-2.2.2 sqlalchemy-2.0.29

Spustit to nejde, stále stejná chyba. Ale engine vytvořen a spojení je aktivní.

Kromě toho terminálu ještě prosím o radu pro ten VS code a .vent

 Zkouším rozjet ten distinct=True v selectu - toto navrhl ChatGPT a mám chybu  select is not defined

selectable_query = select([Trip.sid, StopName.stop_name], distinct=True).select_from(Trip).join(StopName, Trip.sid == StopName.sid).filter(Trip.route_id.in_(route_ids)).distinct()

Ale zkoušel jsem importovat Select a ta chyba tam je furt

from sqlalchemy import create_engine, MetaData, Table, Select, Column, join, Integer, String, DECIMAL, ForeignKey, text

Manuál o selectable píše o syntaxy select:

 sqlalchemy.sql.expression.select(columns=None, whereclause=None, from_obj=None, distinct=False, having=None, correlate=True, prefixes=None, suffixes=None, **kwargs)

ale jak to rozjet v praxi.

Já se totiž domnívám že ten ChatGPT stále nechápe, že ten distinct musí být na první select tj. FROM trips (routes_id musí být vždy jen jedno ve výběru),  ne až na ten výsledek LEFT JOIN FROM stop_names.

díky moc za tu radu, aspoň něco mi jede.

Nahlásit jako SPAM
IP: 94.113.182.–
gna
~ Anonymní uživatel
1859 příspěvků
18. 4. 2024   #5
-
0
-

Teď mám zmatek v tom, co ti kde funguje :) Můžeš třeba zkusit na začátek programu přidat výpis verzí

import sys, pandas, sqlalchemy
print(sys.executable, pandas.__version__, sqlalchemy.__version__)

Jestli se ti to v různých prostředích liší. Ale ty logy tady vypadají, že tam máš stejné verze, tak v tom asi problém nebude a nenapadá mě kam se podívat, co by mohlo být špatně.

Co se týče toho selectu, tak do importu přidej "select" s malým s.

Nahlásit jako SPAM
IP: 213.211.51.–
oxidián0
Grafoman
18. 4. 2024   #6
-
0
-

Ve  .vent (Visual Studio Code):

.venv/bin/python 2.2.2 2.0.29

No z terminálu:

/usr/bin/python3 2.2.2 1.4.31

tam to píše chybu:

in execute
    raise TypeError("Query must be a string unless using sqlalchemy.")
TypeError: Query must be a string unless using sqlalchemy.

když to spustím jako obyč. uživatel tak pip install --upgrade sqlalchemy píše:

Defaulting to user installation because normal site-packages is not writeable

Tvoje navržené řešení funguje v tom .venv pod Visual Studio Code. Ale pak bych ještě k tomu potřeboval poradit dál pár věcí.

Nahlásit jako SPAM
IP: 94.113.187.–
oxidián0
Grafoman
18. 4. 2024   #7
-
0
-

Hele riskl jsem to:

sudo pip install --upgrade sqlalchemy

a už to jede

Nahlásit jako SPAM
IP: 94.113.187.–
oxidián0
Grafoman
18. 4. 2024   #8
-
0
-

Dále prosím o radu s tím jak sestavit ten SQL dotaz v ORM. Mě to přijde, že ChatGPT stále nerozumí co po něm chci.

teď mi navrhl tento modifikovaný dotaz:

selectable_query = select([Trip.sid, StopName.stop_name], from_obj=Trip.join(StopName, Trip.sid == StopName.sid), distinct=True).where(Trip.route_id.in_(route_ids))
chunk_result = pd.read_sql(selectable_query, session.bind)

a je tam zase chyba:

AttributeError: type object 'Trip' has no attribute 'join'

A co mi tu nesedí, že on přece neví na jaký sloupec to chci omezit. Já chci aby ta shoda na routes_id byla vybrána jen jednou (mezi zdrojem route_id a trips.routes_id má být shoda a stačí mi jeden řádek pro každé routes_id) . A teprve pak aby se řešilo to ostatní jako LEFT JOIN

# Definice modelu pro tabulku trips
class Trip(Base):
    __tablename__ = 'trips'

    route_id = Column(String(255))
    trip_id_1 = Column(Integer, primary_key=True)
    trip_id_2 = Column(Integer, primary_key=True)
    trip_id_3 = Column(Integer, primary_key=True)
    direction_id = Column(Integer)
    sid = Column(Integer, ForeignKey('stops.sid'))

    stop = relationship("Stop")

Jinak ten dotaz co píšu na ChatGPT obsahuje ten popis toho co chci celkem podrobně:

Je důležité správně rozumět tomu, že se nejprve má hledat route_id v tabulce routes, přičemž pouze jeden řádek na jedno route_id se má vrátit, celkem učitý počet řádků, který bude nastaven na testovací chunksize 10 , později chunksize 1000 . Takže při testu se má vrátit 10 řádků a to 10 různých route_id, následně se má provádět left join .. select stop_name from stop_names kde je možné že výsledné názvy měst se budou opakovat. Takže budu mít 10 různých cest (routes) pro 10 různých tripů (různých autobusových linek) kde to bude název zastávky např. "Ostrava, ÚAN" (10 stejných výsledků na konečném výstupu) musí být povoleno.

Ale mě přijde, že toto by mělo jít už nadefinovat do té třídy a pak už by se mělo jen zavolat něco jednoduššího, že ten select (pokud použijeme select) by neměl být tak složitý jak to navrhuje ChatGPT

class Trip(Base):
    __tablename__ = 'trips'

    route_id = Column(String(255))
    trip_id_1 = Column(Integer, primary_key=True)
    trip_id_2 = Column(Integer, primary_key=True)
    trip_id_3 = Column(Integer, primary_key=True)
    direction_id = Column(Integer)
    sid = Column(Integer, ForeignKey('stops.sid'))

    # stop = relationship("StopName")
    stop_name = relationship("StopName", primaryjoin='Trip.sid == StopName.sid', uselist=False)


selectable_query = select([Trip.route_id, StopName.stop_name]).select_from(Trip).join(StopName, Trip.sid == StopName.sid).where(Trip.route_id.in_(route_ids)).distinct()
Nahlásit jako SPAM
IP: 94.113.187.–
oxidián0
Grafoman
18. 4. 2024   #9
-
0
-

Tak jsem to celé rozjel. Přidal jsem do programu kod na otestování výkonu. Dotaz mám proč vykonání SELECT ... LEFT JOIN a IN 10 ideček trvá tak dlouho? Co kdybych tam dal 1000 to by asi trvalo o to déle...

Taky mám nějakou hlášku že Base se už nepoužívá a mám použít declarative_base()... co to? Do třídy dávat konstruktor?

Tady se potřebuju zeptat na to, že v té smyčce je zpracování těch načtených dat po chunkách jako for chunk in data ... ale nenačetlo to doufám celý soubor 1GB dat? Nechápu jak to funguje, že může načíst celý soubor protože tam není for data in pd.read_csv ale data = pd.read_csv ... nepodělal mi to ten ChatGPT zase? To by mohla být vážná chyba. Chci to načítat po chunkách 10 řádků na test, 1000 nebo více na ostro (1000 ideček do SELECT WHERE IN (idečka) ) není to příliš?.

Dále mám obavy že tady se vytváří objekt selectable_query = select()
A myslím si, že designově by se ten select objekt měl vytvořit před smyčkou a teprve pak by se mělo doplnit to routes_ids ve smyčce už do toho existujícího designu, jinak je to výkonnostně špatně. Poradíte s těmito věcmi?

PS: ten sloupec trips.route_id není indexovaný. Chtěl jsem zatím otestovat jaký je výkon a hlavně jestli je v pořádku ten dotaz. Pak tam dám index a pojedu ten test znova. Mám ten select v pořádku?

from sqlalchemy import create_engine, MetaData, Table, Column, join, Integer, String, DECIMAL, ForeignKey, text
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql.selectable import Selectable
from sqlalchemy.sql.expression import select, func
import pandas as pd
import zipfile
import io
import re
import time

Base = declarative_base()

# Definice modelu pro tabulku stops
class Stop(Base):
    __tablename__ = 'stops'

    sid = Column(Integer, primary_key=True)
    stop_lat = Column(DECIMAL(10, 8))
    stop_lon = Column(DECIMAL(11, 8))
    dopravce = Column(String(255))
    id = Column(Integer)

# Definice modelu pro tabulku stop_names
class StopName(Base):
    __tablename__ = 'stop_names'

    sid = Column(Integer, primary_key=True, autoincrement=True)
    stop_name = Column(String(255), unique=True)

# Definice modelu pro tabulku trips
class Trip(Base):
    __tablename__ = 'trips'

    route_id = Column(String(255), ForeignKey('routes.route_id')) # Definice cizího klíče zajistí vztah ... trips.routes_id == routes.routes_id
    trip_id_1 = Column(Integer, primary_key=True)
    trip_id_2 = Column(Integer, primary_key=True)
    trip_id_3 = Column(Integer, primary_key=True)
    direction_id = Column(Integer)
    sid = Column(Integer, ForeignKey('stop_names.sid'))

    stop_name = relationship("StopName", primaryjoin='Trip.sid == StopName.sid', uselist=False)

# Definice modelu pro tabulku routes
class Route(Base):
    __tablename__ = 'routes'

    route_id = Column(String(255), primary_key=True)
    agency_id = Column(String(255))
    route_short_name = Column(String(255))
    route_long_name = Column(String(255))
    route_desc = Column(String(255))
    route_type = Column(String(255))

    # Přidání vlastnosti join
    sid = relationship("Trip", primaryjoin='Route.route_id == Trip.route_id', uselist=False)

# Připojení k databázi
password=""
db="doprava"
use_columns=range(6) # hodnota use_columns může oříznout sloupce na počet při čtení read_csv()

try:
    # Vytvoření SQLAlchemy engine s použitím pymysql
    engine = create_engine(f"mysql+pymysql://admin:{password}@localhost/{db}")
    # engine = create_engine('mysql+pymysql://', creator=lambda: mydb)
    print("Engine byl úspěšně vytvořen.")
except Exception as e:
    print("Při vytváření enginu došlo k chybě:", e)


try:
    connection = engine.connect()
    print("Spojení k databázi je aktivní.")
except Exception as e:
    print("Při testování spojení došlo k chybě:", e)

# Vytvoření tabulek v databázi
Base.metadata.create_all(engine)

# Funkce pro zpracování a vložení dat do databáze
def process_and_insert_data(session, data):
    for chunk in data:
        chunk.drop(chunk.columns[6:], axis=1, inplace=True)
        # Nahrazení všech výskytů "-CISR-" v route_id
        chunk['route_id'] = chunk['route_id'].str.replace('-CISR-', '')
        # Získání jedinečných route_id z aktuálního chunku
        route_ids = chunk['route_id'].unique()
        
        # Dotaz pro získání sid a stop_name
        #query = session.query(Trip.sid, StopName.stop_name).join(StopName).filter(Trip.route_id.in_(route_ids)).distinct()


        start_time = time.time()
        selectable_query = select(Trip.sid, StopName.stop_name).where(Trip.route_id.in_(route_ids)).distinct().join_from(Trip,StopName)
        end_time = time.time()
        execution_time = end_time - start_time
        print(f"Čas na vykonání: {execution_time} s.")
        print(selectable_query)
        print(route_ids)

        # Provedení dotazu a získání výsledků
        # chunk_result = query.all()
        # session.statement = returns the sql query.
        start_time = time.time()
        chunk_result = pd.read_sql(selectable_query, session.bind)        
        end_time = time.time()
        execution_time = end_time - start_time
        print(f"Čas na vykonání read_sql: {execution_time} s")
        # Zpracování a vložení dat
        for row in chunk_result:
            needle = row[1]
            for index, route_long_name in chunk['route_long_name'].items():
                pattern = re.compile(r'\b' + re.escape(needle) + r'\b')
                if re.search(pattern, route_long_name):
                    chunk.at[index, 'route_long_name'] = re.sub(pattern, f"#{row[0]}", route_long_name)
                else:
                    city_name = needle.split(',')[0].strip()  
                    city_pattern = re.compile(r'\b' + re.escape(city_name) + r'\b')
                    if re.search(city_pattern, route_long_name):
                        chunk.at[index, 'route_long_name'] = re.sub(city_pattern, f"#{row[0]}", route_long_name)
        
        # Vložení dat do databáze
        # chunk.to_sql(name='routes', con=session.connection(), if_exists='append', index=False)d
        try:
            start_time = time.time()
            chunk.to_sql(name='routes', con=engine, if_exists='append', index=False)
            end_time = time.time()
            execution_time = end_time - start_time
            print(f"Čas na vykonání to_sql: {execution_time} s")
        except Exception as e:
            print("Při vkládání dat do databáze došlo k chybě:", e)
        input("Wait...")

# Načtení dat ze souboru routes.txt z ZIP archivu po menších blocích
zip_path = "/mnt/ramdisk/JDF_merged_GTFS.zip"
chunksize = 10

Session = sessionmaker(bind=engine)
count_query = select(func.count()).select_from(Route)
record_count = connection.execute(count_query).scalar()
if record_count > 0:
    raise input(f"V tabulce routes už jsou záznamy, a proto nelze pokračovat.\nPočet záznamů: {record_count}")

with zipfile.ZipFile(zip_path, 'r') as zip_file:
    with zip_file.open('routes.txt', 'r') as routes_file:
        data = pd.read_csv(io.TextIOWrapper(routes_file, 'utf-8'), usecols=use_columns, chunksize=chunksize)
        with Session() as session:
            process_and_insert_data(session, data)

# Uzavření spojení
engine.dispose()

 MovedIn20Warning: The ``declarative_base()`` function is now available as sqlalchemy.orm.declarative_base(). (deprecated since: 2.0) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
  Base = declarative_base()
Engine byl úspěšně vytvořen.
Spojení k databázi je aktivní.
Čas na vykonání: 0.0010933876037597656 s.
SELECT DISTINCT trips.sid, stop_names.stop_name
FROM trips JOIN stop_names ON stop_names.sid = trips.sid
WHERE trips.route_id IN (__[POSTCOMPILE_route_id_1])

['877516-2' '877516-1' '877516-3' '841204-1' '661261-1' '100392-2'
 '100392-1' '100392-3' '802190-1' '802190-2']
Čas na vykonání read_sql: 1.4624156951904297 s
Čas na vykonání to_sql: 0.018214941024780273 s

Nahlásit jako SPAM
IP: 94.113.187.–
Zjistit počet nových příspěvků

Přidej příspěvek

×Vložení zdrojáku

×Vložení obrázku

Vložit URL obrázku Vybrat obrázek na disku
Vlož URL adresu obrázku:
Klikni a vyber obrázek z počítače:

×Vložení videa

Aktuálně jsou podporována videa ze serverů YouTube, Vimeo a Dailymotion.
×
 
Podporujeme Gravatara.
Zadej URL adresu Avatara (40 x 40 px) nebo emailovou adresu pro použití Gravatara.
Email nikam neukládáme, po získání Gravatara je zahozen.
-
Pravidla pro psaní příspěvků, používej diakritiku. ENTER pro nový odstavec, SHIFT + ENTER pro nový řádek.
Sledovat nové příspěvky (pouze pro přihlášené)
Sleduj vlákno a v případě přidání nového příspěvku o tom budeš vědět mezi prvními.
Reaguješ na příspěvek:

Uživatelé prohlížející si toto vlákno

Uživatelé on-line: 0 registrovaných, 3 hosté

 

Hostujeme u Českého hostingu       ISSN 1801-1586       ⇡ Nahoru Webtea.cz logo © 20032024 Programujte.com
Zasadilo a pěstuje Webtea.cz, šéfredaktor Lukáš Churý