Aplikace pro zamlouvání sedadel (část 1)

1. díl - Aplikace pro zamlouvání sedadel (část 1)

Tomáš Herceg       4. 2. 2011       C#, VB.NET, ASP.NET WebForms, ASP.NET/IIS, .NET       8314 zobrazení

V této části vytvoříme databázi, napíšeme základní infrastrukturu a nakonfigurujeme přihlašování uživatelů pomocí knihovny Altairis Web Security.

Nedávno na fóru zazněl poměrně zajímavý dotaz. Jeho autor potřeboval při kliknutí na buňku v tabulce vyvolat serverovou událost, ale tak, aby v té buňce nemuselo být tlačítko. V tomto článku si ukážeme, jak se to dělá – napíšeme si jednoduché zamlouvátko na sedadla v opeře (teď nemyslím prohlížeč, ale takovou tu budovu kde hraje orchestr a pobíhají kvílející zpěváci). Kromě toho si ukážeme základy práce s AJAXem v ASP.NET WebForms, což může ušetřit přenášená data a tím zrychlit načítání stránek, ale hlavně při postbacku přestanou stránky problikávat. A dále se seznámíme s technologií LINQ to SQL a ukážeme si její základy.

Jak to bude vypadat

Na naší stránce si uděláme tabulku o velikosti 8x3 políčka. Po kliknutí na políčko se do databáze zapíše údaj o tom, kdo na daném místě sedí, přičemž se stav obsazení sedadel zaktualizuje. Možností, jak tento problém řešit, je pochopitelně víc.

První možností, na kterou jsou odkázaní všichni, kdo nepoužívají ASP.NET WebForm (ale která i ve webformech jde poměrně snadno použít), je při kliknutí na sedadlo javascriptem zavolat nějakou webovou službu nebo handler, který zapíše informaci o tom, kdo si sedadlo rezervoval, do databáze, a vrátí aktuální stav (případně po odpovědi handleru lze udělat refresh stránky, ale to by bylo neobratné a stránka by problikla – to nechceme). Poněkud nepříjemné zde bude parsování odpovědi serveru a aktualizace tabulky ve stránce, i když s jQuery to dnes takový problém není. Handler nebo webová služba mohou odpověď vrátit v XML, JSONu nebo použít vlastní kódování (například hodnoty oddělené čárkou), případně může vrátit HTML celé tabulky sedadel, které se do stránky umístí místo aktuální tabulky. To je asi nejjednodušší, protože odpadá parsování odpovědi, i když je zde bezpečnostní riziko – někdo by mohl podvrhnout kus HTML s javascriptem, který by provedl něco ošklivého. Pokud je ale handler pod naší kontrolou, něco takového je velmi nepravděpodobné.

Druhou možností, kterou máme ve WebFormech, je vykašlat se na javascript i na webovou službu – kliknutí na sedadlo prostě vyvolá postback, na serveru si v našem C# kódu odchytíme událost Click na dotyčné buňce tabulky, a nějak na ni zareagujeme. Pak provedeme nový databinding a tabulku zaktualizujeme. A když použijeme AJAX (konkrétně komponentu UpdatePanel kolem tabulky), tak se zpátky nebude posílat celá nová stránka, ale jen ta tabulka. Tím se dostaneme s trochu větším pohodlím na prakticky stejné řešení, jako máme při použití prvního způsobu – javascriptem zavoláme handler (stránku) a zpátky dostaneme nový HTML obsah tabulky.

Jediný rozdíl je v tom, že handler voláme metodou POST a odesíláme ViewState, ale pokud se nad tím zamyslíme, zjistíme, že jej pro naši tabulku nepotřebujeme – stejně při každém postbacku tabulku znovu naplníme z databáze. Nepotřebujeme si pamatovat, co jsme ní tabulky vyplnili.

Začínáme

Pomocí SQL Server Management Studia vytvořte novou databázi a spusťte proti ní tento skript pro vytvoření jednoduché tabulky:

 -- uživatelé
CREATE TABLE [Users] (
[UserId] INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
[UserName] NVARCHAR(100) NOT NULL UNIQUE,
[PasswordHash] BINARY(64) NOT NULL,
[PasswordSalt] BINARY(128) NOT NULL,
[Email] NVARCHAR(100) NOT NULL,
[Comment] NVARCHAR(MAX) NULL,
[IsApproved] BIT NOT NULL,
[DateCreated] DATETIME NOT NULL,
[DateLastLogin] DATETIME NULL,
[DateLastActivity] DATETIME NULL,
[DateLastPasswordChange] DATETIME NOT NULL
)

-- sály
CREATE TABLE [Halls] (
[HallId] INT NOT NULL PRIMARY KEY IDENTITY(1,1),
[SeatRows] INT NOT NULL,
[SeatColumns] INT NOT NULL
)
INSERT INTO [Halls] ([SeatRows], [SeatColumns]) VALUES (3, 8)
DECLARE @HallId INT
SELECT
@HallId = SCOPE_IDENTITY()

-- sedadla v sálech
CREATE TABLE [Seats] (
[SeatId] INT NOT NULL PRIMARY KEY IDENTITY(1,1),
[HallId] INT NOT NULL REFERENCES [Halls]([HallId]) ON DELETE CASCADE,
[X] INT NOT NULL,
[Y] INT NOT NULL,
[UserId] INT NULL REFERENCES [Users]([UserId]) ON DELETE SET NULL
)
DECLARE @x INT, @y INT
SELECT
@x = 0, @y = 0
WHILE (@y < 3) BEGIN
WHILE (@x < 8) BEGIN
INSERT INTO [Seats] ([HallId], [X], [Y]) VALUES (@HallId, @x, @y)
SELECT @x = @x + 1
END
SELECT @y = @y + 1, @x = 0
END

Naše databáze se skládá z pouhých tří tabulek – tabulka Users reprezentuje uživatele, tabulka Halls koncertní sály a tabulka Seats sedadla v koncertních sálech. Zároveň jsme si do databáze vygenerovali 1 koncertní sál a do něj 24 sedadel. Každému sedadlu se dá přiřadit ID uživatele a je povolena hodnota NULL, která znamená, že sedadlo je volné.

Správa uživatelů

Pro správu uživatelů použijeme knihovnu Altairis Web Security od Michala Valáška, jejíž nová verze vyšla nedávno. Stáhněte její aktuální verzi a soubory Altairis.Web.Security.dll a Altairis.Web.Security.pdb nakopírujte do adresáře Bin vaší webové aplikace.

Naše aplikace bude mít dvě stránky – Default.aspx a Login.aspx. Na stránku Login.aspx umístíme i komponentu pro registraci nově příchozích uživatelů, vypadat může nějak takto:

 <%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Opera - přihlášení</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Login ID="Login1" runat="server" DestinationPageUrl="~/Default.aspx">
</asp:Login>
<p></p>

<asp:CreateUserWizard ID="CreateUserWizard1" runat="server"
ContinueDestinationPageUrl="~/Default.aspx" LoginCreatedUser="True">
<WizardSteps>
<asp:CreateUserWizardStep runat="server">
</asp:CreateUserWizardStep>
<asp:CompleteWizardStep runat="server">
</asp:CompleteWizardStep>
</WizardSteps>
</asp:CreateUserWizard>
</div>
</form>
</body>
</html>

Vzhled nijak neřešíme, jde o funkčnost – na této stránce se lze přihlásit, nebo si založit účet. Ať už tak či onak, uživatel je po této akci přesměrován na stránku Default.aspx, kde si může zamluvit sedadlo.

Aby se pro přihlašování použila knihovna Altairis Web Security a aby byl zakázán přístup na stránku Default.aspx bez přihlášení, je nutné přidat pár věcí do web.configu, pokud používáte ASP.NET 4, měl by vypadat takto:

 <?xml version="1.0"?>
<configuration>

<!-- spojení k databázi -->
<connectionStrings>
<add name="DB" providerName="System.Data.SqlClient"
connectionString="Data Source=.\SQLEXPRESS; Initial Catalog=Opera; Integrated Security=True"/>
</connectionStrings>

<system.web>
<compilation debug="false" targetFramework="4.0" />

<!-- přilinkovat CSS soubor -->
<pages theme="Default" />

<!-- použít Altairis Web Security pro přihlašování uživatelů -->
<membership defaultProvider="MyMembershipProvider">
<providers>
<clear/>
<add name="MyMembershipProvider" connectionStringName="DB"
type="Altairis.Web.Security.TableMembershipProvider, Altairis.Web.Security" />
</providers>
</membership>
<authentication mode="Forms" />

</system.web>

<!-- zakázat nepřihlášené uživatele na stránce Default.aspx -->
<location path="Default.aspx">
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</location>

</configuration>

Přidali jsme connection string pro spojení s databází v elementu connectionStrings - upravte si ho podle svého SQL Serveru a názvu databáze.

Dále jsme v elementu system.web/pages nastavili téma pro všechny stránky.

Pak jsme v elementu system.web/membership smazali výchozího providera a přidali membership providera z knihovny Altairis Web Security. Elementem system.web/authentication jsme nastavili Forms autentizaci. Standardně se totiž do ASP.NET aplikací přihlašuje pomocí Windows autentizace, tedy vaším uživatelským účtem z Windows. Forms autentizace naproti tomu používá přihlášení pomocí vyplnění formuláře na webové stránce.

A nakonec jsme v elementu location stránce Default.aspx řekli, že na ní nechceme pustit nepřihlášené uživatele.

Element location může obsahovat jakékoliv změny konfigurace oproti tomu, co je přímo v elementu configuration. Klidně bychom dovnitř mohli dát element connectionStrings a říct tím, že na stránce Default.aspx se bude používat jiný. Nebo bychom mohli jen pro stránku Default.aspx změnit téma či membership providera. Ale nám stačí zakázat přístup nepřihlášeným uživatelům.

Aby to fungovalo, musíme ještě v aplikaci vytvořit adresář App_Themes a v něm adresář Default. Do tohoto adresáře můžete umístit CSS soubory a skiny, které se automaticky použijí na všechny stránky.

Tabulka sedadel

To by byla omáčka kolem – nyní se můžeme vrhnout na jádro pudla, totiž na samotnou tabulku sedadel a klikání dovnitř. Stránka Default.aspx může vypadat nějak takto:

 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Opera - zamlouvání sedadel</title>
</head>
<body>
<form id="form1" runat="server">
<div>

<h1>Zamlouvání sedadel</h1>

<p><asp:Label ID="ErrorLabel" runat="server" ForeColor="Red" ViewStateMode="Disabled" /></p>

<asp:Table ID="SeatsTable" runat="server" CssClass="seats">
</asp:Table>

<p class="legend">
<span class="reserved"></span> - obsazené sedadlo<br />
<span class="free"></span> - volné sedadlo<br />
<span class="yours"></span> - vaše sedadlo
</p>

</div>
</form>
</body>
</html>

Aby to vypadalo nějak hezky, do složky App_Themes/Default přidejte CSS soubor s těmito definicemi:

 .legend span 
{
width: 16px;
height: 16px;
display: inline-block;
}

.reserved
{
background-color: #d0d0d0;
}
.free
{
background-color: #ff0000;
}
.yours
{
background-color: #80a0ff;
}

.seats td
{
width: 40px;
height: 40px;
text-align: center;
cursor: pointer;
}
.seats td.free:hover
{
background-color: #ffa0a0;
}

To je pro tento díl vše. Příště si napíšeme jednoduchou třídu, která bude umět vracet seznam sedadel a zarezervovat sedadlo pro konkrétního uživatele.

 

hodnocení článku

1 bodů / 1 hlasů       Hodnotit mohou jen registrované uživatelé.

 

Všechny díly tohoto seriálu

4. Jak na dlouhotrvající úlohy 26. 1. 2012
3. Aplikace pro zamlouvání sedadel (část 3) 13. 2. 2011
2. Aplikace pro zamlouvání sedadel (část 2) 8. 2. 2011
1. Aplikace pro zamlouvání sedadel (část 1) 4. 2. 2011

 

Mohlo by vás také zajímat

Autentizace uživatelů v ASP.NET aplikaci pomocí Facebooku

V poslední době stále častěji tvůrci webových aplikací opouštějí vlastní autentizaci pomocí jména a hesla a umožňují svým uživatelům přihlašovat se pomocí účtu na sociálních sítích nebo pomocí jiných služeb, například Microsoft Live ID. V tomto článku si ukážeme napojení ASP.NET aplikace na přihlašování pomocí Facebooku pomocí knihovny Microsoft Web Pages OAuth.

Co je nového ve Visual Studio 2013 Update 3

LINQ a Entity Framework - díl 3.: LINQ - Rozhraní Ienumerable, iqueryable a yield return

 

 

Nový příspěvek

 

Diskuse: Aplikace pro zamlouvání sedadel (část 1)

Nevíte někdo jak vyřešit problém s přístupem z aplikace do databáze? Prohlížeč mi píše:


[SqlException (0x80131904): Cannot open database "Opera" requested by the login. The login failed.
Login failed for user 'IIS APPPOOL\ASP.NET v4.0'.]
   System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection) +6389680
   System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning() +412
   System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) +2660
   System.Data.SqlClient.SqlInternalConnectionTds.CompleteLogin(Boolean enlistOK) +53
   System.Data.SqlClient.SqlInternalConnectionTds.LoginNoFailover(ServerInfo serverInfo, String newPassword, Boolean redirectedUserInstance, SqlConnection owningObject, SqlConnectionString connectionOptions, TimeoutTimer timeout) +6410302
   System.Data.SqlClient.SqlInternalConnectionTds.OpenLoginEnlist(SqlConnection owningObject, TimeoutTimer timeout, SqlConnectionString connectionOptions, String newPassword, Boolean redirectedUserInstance) +6410281
   System.Data.SqlClient.SqlInternalConnectionTds..ctor(DbConnectionPoolIdentity identity, SqlConnectionString connectionOptions, Object providerInfo, String newPassword, SqlConnection owningObject, Boolean redirectedUserInstance) +352
   System.Data.SqlClient.SqlConnectionFactory.CreateConnection(DbConnectionOptions options, Object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection) +831
   System.Data.ProviderBase.DbConnectionFactory.CreatePooledConnection(DbConnection owningConnection, DbConnectionPool pool, DbConnectionOptions options) +49
   System.Data.ProviderBase.DbConnectionPool.CreateObject(DbConnection owningObject) +6412086
   System.Data.ProviderBase.DbConnectionPool.UserCreateRequest(DbConnection owningObject) +78
   System.Data.ProviderBase.DbConnectionPool.GetConnection(DbConnection owningObject) +2194
   System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConnection owningConnection) +89
   System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory) +6415614
   System.Data.SqlClient.SqlConnection.Open() +300
   Altairis.Web.Security.TableMembershipProvider.GetUser(String username, Boolean userIsOnline) in C:\Users\altair\Projects\_Components\AltairisWebSecurity\trunk\Altairis.Web.Security\TableMembershipProvider.cs:450

[ProviderException: Error while performing database query.]
   Altairis.Web.Security.TableMembershipProvider.GetUser(String username, Boolean userIsOnline) in C:\Users\altair\Projects\_Components\AltairisWebSecurity\trunk\Altairis.Web.Security\TableMembershipProvider.cs:471
   Altairis.Web.Security.TableMembershipProvider.CreateUser(String username, String password, String email, String passwordQuestion, String passwordAnswer, Boolean isApproved, Object providerUserKey, MembershipCreateStatus& status) in C:\Users\altair\Projects\_Components\AltairisWebSecurity\trunk\Altairis.Web.Security\TableMembershipProvider.cs:216
   System.Web.UI.WebControls.CreateUserWizard.AttemptCreateUser() +412
   System.Web.UI.WebControls.CreateUserWizard.OnNextButtonClick(WizardNavigationEventArgs e) +226
   System.Web.UI.WebControls.Wizard.OnBubbleEvent(Object source, EventArgs e) +586
   System.Web.UI.Control.RaiseBubbleEvent(Object source, EventArgs args) +52
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +3707

Chyba doufám není v souboru web.config v connectionStringu, ten mám ukázaný na svoji databázi. Kód se mi vypíše při zmáčnutí tlačítka Create user.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Co takhle přeložit (jnebo hodit do translatoru) tu chybovou hlášku? Píše to, že uživatel nemá práva k přihlášení do té databáze.

V SQL Server Management studiu se připojte k databázi, v levém stromu rozlikněte Security / Users a přidejte uživatele "IIS APPPOOL\ASP.NET v4.0" (anebo upravte, pokud tam není).

V detailu toho uživatele v sekci User Mapping zaškrtněte v horním seznamu tu databázi, kam uživateli chcete přidělit oprávnění, a pak dole vyberte roli pro tuto databázi - db_owner je asi nejlepší.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Děkuji za odpověď. Já jsem si to přeložil, ale netušil jsem, kde se nastavují práva přístupu do databáze. Pokud bych ještě mohl otravovat... Kdybych chtěl udělat stránku "správu sálů" a při vytvoření nového sálu si tedy zapsal do databáze počet řad a sloupců, jak vygeneruji zároveň pro daný sál sedadla (přidání by probíhalo ve dvou tabulkách různých požadavků)? Ještě jednou děkuji.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Diskuse: Aplikace pro zamlouvání sedadel (část 1)

Ahoj

Super clanok.

Kde mozem stiahnut tuto aplikaciu?

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Stáhnout nejde, podle návodu si ji musíte napsat.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Diskuse: Aplikace pro zamlouvání sedadel (část 1)

Zdravím,

vím, že je to trošku mimo mísu, ale měl bych 2 dotazy. 1.) Snažil jsem se podle Vašeho článku použít dodatečné user info - Profile s použitím Altairis Simple Web Providers a narazil jsem na problém. Ve Web Application to nejde (pouze ve WebSite), podle toho co jsem zjistil, tak tam zřejmě nejsou naimportované třídy pro "Profile". Ale nějaké použitelné info jsem nevygooglil. Prosil bych o radu, jak je toto nejjednodušší zprovoznit (nejlépe s novou verzí Altairis Web Security)2.) Převedl jsem projekt z verze .NET frameworku 3.5 na 4.0 a komponenty přestaly být v češtině. Googlil jsem, jak to tedy nastavit ve web.configu a narazil jsem na toto:

<globalization
   requestEncoding="utf-8"
   responseEncoding="utf-8"
   culture="auto:cs-CZ"
   uiCulture="auto:cs-CZ"
/>

Mělo by to detekovat culture podle prohlížeče a jako výchozí by měla být nastavena čeština. Bohužel to taky nefunguje. I tady by se hodila rada.

Omlouvám se, vím, že to tu není žádná poradna, ale postupoval jsem podle Vašeho(výborného) videotutoriálu Začínáme s ASP.NET.

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Tak si nejdřív nastudujte začátky programování a pak budete vědět kam a proč se podívat a jak odstranit nedodělky a věci co nefungují.Evidentně nevíte jak profily používat a konfigurovat..

nahlásit spamnahlásit spam -3 / 3 odpovědětodpovědět

Ano, měl bych se tomu věnovat více, máte pravdu. Ale přeci jen jsem řešení našel. Sice to není zrovna elegantní (dalo by se to napsat ručně), ale dostačuje to k vyřešení absence třídy Profile, která není v typu projektu WAP automaticky generována.

(http://www.kps-fl.com/blog/?p=17)

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Diskuse: Aplikace pro zamlouvání sedadel (část 1)

Ačkoliv si velmi vážím všech co přispívají svými články a znalostmi na tento web, neodpustím si malé šťouchnutí. Stačí totiž zabrousit do historie a najdeme spoustu dobře rozjetých článků na pokračování, které mají jedno společné. Nejsou dotažené do konce. Zaseknou se tu po dvou, tu po třech - někdy i po více dílech, ale do konce nedorazí.

Je moc fajn, nakouknout do kuchyně těm, kteří umí a opravdu jim za to děkuji. Zároveň se však opravdu přimlouvám za to, aby trend nastolený výše už nepokračoval a než mnoho nedodělaných projektů, raději méně, ale úplně.

Díky a přeji pěkné dny všem

Šalom

nahlásit spamnahlásit spam 3 / 3 odpovědětodpovědět

Seriály, na které jsem neměl 2 roky čas, dokončovat nehodlám. Zaprvé bych si je musel znovu přečíst, abych věděl, co jsem v nich zmiňoval a co ne, a za druhé si myslím, že jako nakopnutí pro začátek to, co zde je, stačí a další díly potřeba nejsou (ať už mluvím o libovolném svém seriálu).

Co se týče ostatních autorů, do ničeho je tlačit nemůžu, je to jejich svobodná vůle a většinou seriály stagnují z nedostatku času, kterého nikdo nemá dost.

Tady jsem nový seriál založil, protože do Tipy a triky pro ASP.NET se nehodí (je to moc lehké) a do ASP.NET pro úplné začátečníky se taky nehodí (je to zase těžší).

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Mě je naprosto jasné, jak to vzniká. Není třeba se omlouvat. Jen je to bohužel osud víceméně všech článků na pokračování zde a ta snaha pak mnohdy směrem ven vychází vniveč. Ono totiž nemá příliš smysl vydávat články typu - začínáme s .. a po třech kapitolách se zastavit. To je prostě stracený čas jak pro autora, tak pro čtenáře, který nakonec sáhne po jiném zdroji. Prosím neberte to jako výtku. Pouze se snažím podat feedback ze strany jednoho z návštěvníků tohoto webu.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Já ten feedback oceňuji, na druhou stranu základy se dají naučit i z nedokončeného seriálu o třech dílech a nepřijde mi, že by čtení těchto seriálů bylo ztrátou času soudě dle jejich čtenosti.

nahlásit spamnahlásit spam 0 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback