() translation by (you can also view the original English article)
W poprzedniej części cyklu zaimplementowaliśmy paginację listy marzeń na stronie użytkownika. W tym odcinku wprowadzimy możliwość wstawiania obrazka odpowiadającego życzeniu, opcję oznaczenia życzenia spełnionego oraz możliwość ustawienia prywatności.
Pierwsze kroki
Zaczynamy od sklonowania poprzedniej części tutorialu z GitHub.
1 |
git clone https://github.com/jay3dec/PythonFlaskMySQLApp_Part5.git |
Gdy plik źródłowy zostanie sklonowany, przejdź do katalogu projektu i uruchom serwer.
1 |
cd PythonFlaskMySQLApp_Part5
|
2 |
python app.py |
Skieruj przeglądarkę na adres http://localhost:5002/ - aplikacja powinna zadziałać.
Zmienianie interfejsu użytkownika
Na początek zajmiemy się stroną "dodaj marzenie" i wprowadzimy na niej opcję przesyłania obrazów. Przejdź do templates/addWish.html
. Formularz przy addWish.html
wygląda bardzo skromnie, zmieńmy więc nasz kod bootstrap HTML, by formularz wyświetlany był pionowo.
Najpierw zmienimy form-horizontal
na pionową - usuń klasę form-horizontal
z formularza. Dodamy też trzy nowe kontrolery: kontroler przesyłania plików, okienko do zaznaczania, czy marzenie jest prywatne i dodatkowe do odhaczenia, gdy marzenie zostanie spełnione. Tak wygląda zmodyfikowany addWish.html
.
1 |
<!DOCTYPE html>
|
2 |
<html lang="en"> |
3 |
|
4 |
<head>
|
5 |
<title>Python Flask Bucket List App</title> |
6 |
|
7 |
|
8 |
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css"> |
9 |
|
10 |
<link href="http://getbootstrap.com/examples/jumbotron-narrow/jumbotron-narrow.css" rel="stylesheet"> |
11 |
|
12 |
<script src="../static/js/jquery-1.11.2.js"></script> |
13 |
<style>
|
14 |
.btn-file { |
15 |
position: relative; |
16 |
overflow: hidden; |
17 |
}
|
18 |
|
19 |
.btn-file input[type=file] { |
20 |
position: absolute; |
21 |
top: 0; |
22 |
right: 0; |
23 |
min-width: 100%; |
24 |
min-height: 100%; |
25 |
font-size: 100px; |
26 |
text-align: right; |
27 |
filter: alpha(opacity=0); |
28 |
opacity: 0; |
29 |
outline: none; |
30 |
background: white; |
31 |
cursor: inherit; |
32 |
display: block; |
33 |
}
|
34 |
</style>
|
35 |
|
36 |
</head>
|
37 |
|
38 |
<body>
|
39 |
|
40 |
<div class="container"> |
41 |
<div class="header"> |
42 |
<nav>
|
43 |
<ul class="nav nav-pills pull-right"> |
44 |
<li role="presentation" class="active"><a href="#">Add Item</a> |
45 |
</li>
|
46 |
<li role="presentation"><a href="/logout">Logout</a> |
47 |
</li>
|
48 |
</ul>
|
49 |
</nav>
|
50 |
<h3 class="text-muted">Python Flask App</h3> |
51 |
</div>
|
52 |
|
53 |
<form role="form" method="post" action="/addWish"> |
54 |
|
55 |
|
56 |
<!-- Form Name -->
|
57 |
<legend>Create Your Wish</legend> |
58 |
|
59 |
<!-- Text input-->
|
60 |
<div class="form-group"> |
61 |
<label for="txtTitle">Title</label> |
62 |
|
63 |
<input id="txtTitle" name="inputTitle" type="text" placeholder="placeholder" class="form-control input-md"> |
64 |
|
65 |
</div>
|
66 |
|
67 |
<!-- Textarea -->
|
68 |
<div class="form-group"> |
69 |
<label for="txtPost">Description</label> |
70 |
|
71 |
<textarea class="form-control" id="txtPost" name="inputDescription"></textarea> |
72 |
|
73 |
</div>
|
74 |
|
75 |
|
76 |
<div class="form-group"> |
77 |
<label for="txtPost">Photos</label> |
78 |
|
79 |
<div class="input-group"> |
80 |
<span class="input-group-btn"> |
81 |
<span class="btn btn-primary btn-file"> |
82 |
Browse… <input type="file" id="fileupload" name="file" multiple> |
83 |
</span>
|
84 |
</span>
|
85 |
<input type="text" class="form-control" readonly> |
86 |
</div>
|
87 |
|
88 |
</div>
|
89 |
|
90 |
<div class="form-group"> |
91 |
<label>Mark this as private and not visible to others.</label> |
92 |
<br/>
|
93 |
<input type="checkbox"> Mark as Private <span class="glyphicon glyphicon-lock" aria-hidden="true"></span> |
94 |
</div>
|
95 |
|
96 |
<div class="form-group"> |
97 |
<label>Have you already accomplished this?</label> |
98 |
<br/>
|
99 |
<input type="checkbox"> Mark as Done <span class="glyphicon glyphicon-ok" aria-hidden="true"></span> |
100 |
</div>
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
<!-- Button -->
|
106 |
<div class="form-group"> |
107 |
|
108 |
<p class="text-center"> |
109 |
<input id="singlebutton" name="singlebutton" class="btn btn-primary" type="submit" value="Publish" /> |
110 |
</p>
|
111 |
</div>
|
112 |
|
113 |
|
114 |
</form>
|
115 |
|
116 |
<footer class="footer"> |
117 |
<p>© Company 2015</p> |
118 |
</footer>
|
119 |
|
120 |
</div>
|
121 |
</body>
|
122 |
|
123 |
</html>
|
Zapisz zmianę i zrestartuj serwer. Po zalogowaniu, kliknij w link Add Wish, by zobaczyć zmienioną stronę dodawania marzenia.



Implementacja funkcji przesyłania
Do zaimplementowania funkcji przesyłania plików skorzystamy z blueimp jQuery-File-Upload. Ściągnij potrzebne pliki z GitHub. Rozpakuj pliki źródłowe i dopisz następujące odwołania do addWish.html
.
1 |
<script src="../static/js/jquery-1.11.2.js"></script> |
2 |
|
3 |
<script src="../static/js/jquery.ui.widget.js"></script> |
4 |
|
5 |
<script type="text/javascript" src="../static/js/jquery.fileupload.js"></script> |
6 |
|
7 |
<script type="text/javascript" src="../static/js/jquery.fileupload-process.js"></script> |
8 |
|
9 |
<script type="text/javascript" src="../static/js/jquery.fileupload-ui.js"></script> |
Dopisz kod inicjalizacji wtyczki do przycisku dodawania plików przy załadowaniu strony addWish.html
.
1 |
$(function() { |
2 |
$('#fileupload').fileupload({ |
3 |
url: 'upload', |
4 |
dataType: 'json', |
5 |
add: function(e, data) { |
6 |
data.submit(); |
7 |
},
|
8 |
success: function(response, status) { |
9 |
console.log(response); |
10 |
},
|
11 |
error: function(error) { |
12 |
console.log(error); |
13 |
}
|
14 |
});
|
15 |
})
|
Jak widać, przypięliśmy wtyczkę do przesyłania plików do przycisku #fileupload
. Wtyczka do przesyłania plików wysyła plik do procedury obsługi zapytań /upload
, którą zdefiniujemy w pliku Pythona. Zdefiniowaliśmy również funkcję add
do podawania danych, a także informacje zwrotne (callback) success
i failure
, które obsłużą odpowiednio udaną i nieudaną próbę przesłania pliku.
Teraz zdefiniujmy funkcję obsługi przesyłania plików upload
wewnątrz app.py
. Zdefiniiuj przkierowanie /upload
, jak w przykładzie:
1 |
@app.route('/upload', methods=['GET', 'POST']) |
2 |
def upload(): |
3 |
# file upload handler code will be here
|
Sprawdź, czy zapytanie jest typu POST - w takim przypadku odczytamy plik z zapytania.
1 |
if request.method == 'POST': |
2 |
file = request.files['file'] |
Musimy też poznać rozszerzenie pliku przesłanego obrazka, zanim go zapiszemy. Zaimportuj zatem os
, a następnie wyodrębnij rozszerzenie z nazwy pliku.
1 |
extension = os.path.splitext(file.filename)[1] |
Gdy już znamy rozszerzenie, nadajemy obrazkowi nową, unikalną nazwę za pomocą metody uuid
. Zaimportuj uuid
i wygeneruj nową nazwę pliku.
1 |
f_name = str(uuid.uuid4()) + extension |
Utwórz katalog o nazwie Uploads
w folderze static. Tu będziemy przechowywać przesłane pliki. Dopisz ścieżkę folderu Upload w ustawieniach aplikacji.
1 |
app.config['UPLOAD_FOLDER'] = 'static/Uploads' |
Teraz zapisz przesłany plik pod UPLOAD_FOLDER
i zwróć w odpowiedzi nazwę pliku.
1 |
file.save(os.path.join(app.config['UPLOAD_FOLDER'], f_name)) |
2 |
return json.dumps({'filename':f_name}) |
Zapisz wszystkie zmiany i uruchom serwer ponownie. Skieruj przeglądarkę na http://localhost:5002 i zaloguj się poprawnymi danymi. Spróbuj wczytać zdjęcie korzystając z przycisku browse, a następnie sprawdź, co dzieje się w konsoli przeglądarki. Powinna się pokazać nazwa wczytanego pliku.
W miejsce pola tekstu wejściowego tylko do odczytu wstawimy element obrazka, w którym wyświetli się wczytane zdjęcie. Zastąp więc pole tekstowe następującym kodem HTML.
1 |
<div class="pull-right"> |
2 |
<img id="imgUpload" style="width: 140px; height: 140px;" class="img-thumbnail"> |
3 |
</div>
|
W informacji zwrotnej o wczytaniu pliku zaktualizuj adres wysłanego obrazka - src
przy #imgUpload
.
1 |
$('#imgUpload').attr('src','static/Uploads/'+response.filename); |
Zapisz powyższe zmiany i uruchom ponownie serwer. Zaloguj się do aplikacji i spróbuj przesłać nowy plik obrazu. Powinieneś już zobaczyć przesłane zdjęcie.



Musimy teraz zmienić strukturę naszej tbl_wish
- dodać do niej trzy nowe pola. Wprowadź zmiany w tbl_wish
, jak pokazano poniżej:
1 |
ALTER TABLE `BucketList`.`tbl_wish` |
2 |
ADD COLUMN `wish_file_path` VARCHAR(200) NULL AFTER `wish_date`, |
3 |
ADD COLUMN `wish_accomplished` INT NULL DEFAULT 0 AFTER `wish_file_path`, |
4 |
ADD COLUMN `wish_private` INT NULL DEFAULT 0 AFTER `wish_accomplished`; |
Teraz zmodyfikujmy nasze procedury składowane sp_addWish
i sp_updateWish
, by pojawiły się w nich nowododane pola bazy.
Zmień sp_addWish
, dodając trzy nowe pola.
1 |
USE `BucketList`; |
2 |
DROP procedure IF EXISTS `sp_addWish`; |
3 |
|
4 |
DELIMITER $$ |
5 |
USE `BucketList`$$ |
6 |
CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_addWish`( |
7 |
IN p_title varchar(45), |
8 |
IN p_description varchar(1000), |
9 |
IN p_user_id bigint, |
10 |
IN p_file_path varchar(200), |
11 |
IN p_is_private int, |
12 |
IN p_is_done int |
13 |
)
|
14 |
BEGIN
|
15 |
insert into tbl_wish( |
16 |
wish_title, |
17 |
wish_description, |
18 |
wish_user_id, |
19 |
wish_date, |
20 |
wish_file_path, |
21 |
wish_private, |
22 |
wish_accomplished
|
23 |
)
|
24 |
values
|
25 |
(
|
26 |
p_title, |
27 |
p_description, |
28 |
p_user_id, |
29 |
NOW(), |
30 |
p_file_path, |
31 |
p_is_private, |
32 |
p_is_done
|
33 |
);
|
34 |
END$$ |
35 |
|
36 |
DELIMITER ; |
37 |
Zmień również sp_updateWish
, dołączając trzy nowo dodane pola.
1 |
USE `BucketList`; |
2 |
DROP procedure IF EXISTS `sp_updateWish`; |
3 |
|
4 |
DELIMITER $$ |
5 |
USE `BucketList`$$ |
6 |
CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_updateWish`( |
7 |
IN p_title varchar(45), |
8 |
IN p_description varchar(1000), |
9 |
IN p_wish_id bigint, |
10 |
In p_user_id bigint, |
11 |
IN p_file_path varchar(200), |
12 |
IN p_is_private int, |
13 |
IN p_is_done int |
14 |
)
|
15 |
BEGIN
|
16 |
update tbl_wish set |
17 |
wish_title = p_title, |
18 |
wish_description = p_description, |
19 |
wish_file_path = p_file_path, |
20 |
wish_private = p_is_private, |
21 |
wish_accomplished = p_is_done |
22 |
where wish_id = p_wish_id and wish_user_id = p_user_id; |
23 |
END$$ |
24 |
|
25 |
DELIMITER ; |
26 |
A teraz zmodyfikuj funkcję obsługi zapytań /addWish
, aby czytała nowoprzesłane pola i przekazywała je do procedury składowanej.
1 |
if request.form.get('filePath') is None: |
2 |
_filePath = '' |
3 |
else: |
4 |
_filePath = request.form.get('filePath') |
5 |
|
6 |
if request.form.get('private') is None: |
7 |
_private = 0 |
8 |
else: |
9 |
_private = 1 |
10 |
|
11 |
if request.form.get('done') is None: |
12 |
_done = 0 |
13 |
else: |
14 |
_done = 1 |
Po zczytaniu wartości, prześlemy je do odwołania do procedury składowanej MySQL.
1 |
cursor.callproc('sp_addWish',(_title,_description,_user,_filePath,_private,_done)) |
Na stronie addWish.html
musimy ustalić atrybut name
elementów, które zostaną przesłane. Dodaj zatem name
przy obu nowoutworzonych polach do zaznaczania.
1 |
<input name="private" type="checkbox"> Mark as Private <span class="glyphicon glyphicon-lock" aria-hidden="true"></span> |
2 |
|
3 |
<input name="done" type="checkbox"> Mark as Done <span class="glyphicon glyphicon-ok" aria-hidden="true"></span> |
Teraz musimy także przesłać adres do wczytanych plików. Utworzymy zatem ukryte pole wejściowe i ustalimy jego wartość w odwołaniu zwrotnym o powodzeniu przesyłania pliku.
1 |
<input type="hidden" name="filePath" id="filePath"></input> |
Ustaw zawartość pola tekstowego w odwołaniu zwrotnym.
1 |
success: function(response, status) { |
2 |
|
3 |
var filePath = 'static/Uploads/' + response.filename; |
4 |
$('#imgUpload').attr('src', filePath); |
5 |
|
6 |
$('#filePath').val(filePath); |
7 |
|
8 |
}
|
Zapisz powyższe zmiany i uruchom ponownie serwer. Zaloguj się przy użyciu prawidłowych danych i spróbuj dodać nowe marzenie wraz ze wszystkimi szczegółami.. Po wprowadzeniu wszystkich danych, powinny się one pojawić na stronie domowej użytkownika.
Zmiany w implementacji Edycji Marzenia
Najpierw musimy wstawić nieco kodu HTML dla nowych trzech pól. Otwórz więc userHome.html
i wklej poniższy kod HTML poniżej title
i description
.
1 |
<div class="form-group"> |
2 |
<label for="txtPost">Photos</label> |
3 |
|
4 |
<div class="input-group"> |
5 |
<span class="input-group-btn"> |
6 |
<span class="btn btn-primary btn-file"> |
7 |
Browse… <input type="file" id="fileupload" name="file" multiple> |
8 |
</span>
|
9 |
</span>
|
10 |
<div class="pull-right"> |
11 |
<img id="imgUpload" style="width: 140px; height: 140px;" class="img-thumbnail"> |
12 |
<input type="hidden" name="filePath" id="filePath"></input> |
13 |
</div>
|
14 |
</div>
|
15 |
|
16 |
</div>
|
17 |
|
18 |
<div class="form-group"> |
19 |
<label>Mark this as private and not visible to others.</label> |
20 |
<br/>
|
21 |
<input id="chkPrivate" name="private" type="checkbox"> Mark as Private <span class="glyphicon glyphicon-lock" aria-hidden="true"></span> |
22 |
</div>
|
23 |
|
24 |
<div class="form-group"> |
25 |
<label>Have you already accomplished this?</label> |
26 |
<br/>
|
27 |
<input id="chkDone" name="done" type="checkbox"> Mark as Done <span class="glyphicon glyphicon-ok" aria-hidden="true"></span> |
28 |
</div>
|
Musimy odebrać wymagane dane, aby wprowadzić je w do pól formularza edycji. Zmieńmy więc procedurę składowaną sp_GetWishById
, aby uwzględniała dodatkowe pola, jak pokazano:
1 |
CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_GetWishById`( |
2 |
IN p_wish_id bigint, |
3 |
In p_user_id bigint |
4 |
)
|
5 |
BEGIN
|
6 |
select wish_id,wish_title,wish_description,wish_file_path,wish_private,wish_accomplished from tbl_wish where wish_id = p_wish_id and wish_user_id = p_user_id; |
7 |
END
|
Teraz musimy zmodyfikować ciąg JSON
w przekierowaniu /getWishById
, by uwzględniał nowe pola: Zmień listę marzeń w /getWishById
, zgodnie z instrukcją:
1 |
wish.append({'Id':result[0][0],'Title':result[0][1],'Description':result[0][2],'FilePath':result[0][3],'Private':result[0][4],'Done':result[0][5]}) |
Aby wyrenderować stronę, musimy zczytać dane otrzymane w pozytywnej informacji zwrotnej z funkcji JavaScript Edit
na userHome.html
.
1 |
success: function(res) { |
2 |
|
3 |
var data = JSON.parse(res); |
4 |
|
5 |
$('#editTitle').val(data[0]['Title']); |
6 |
|
7 |
$('#editDescription').val(data[0]['Description']); |
8 |
|
9 |
$('#imgUpload').attr('src', data[0]['FilePath']); |
10 |
|
11 |
if (data[0]['Private'] == "1") { |
12 |
$('#chkPrivate').attr('checked', 'checked'); |
13 |
}
|
14 |
|
15 |
if (data[0]['Done'] == "1") { |
16 |
$('#chkDone').attr('checked', 'checked'); |
17 |
}
|
18 |
|
19 |
$('#editModal').modal(); |
20 |
|
21 |
}
|
Zapisz zmiany i uruchom serwer ponownie. Zaloguj się właściwymi danymi, a gdy znajdziesz się na stronie domowej, spróbuj edytować jedno z marzeń z listy. Dane powinny wyświetlić się w polach okienka Edit.



Teraz, w taki sam sposób, jak to zrobiliśmy na stronie dodawania marzeń, wstaw odwołanie do jQuery-File-Upload na userHome.html
.
1 |
<script src="../static/js/jquery-1.11.2.js"></script> |
2 |
|
3 |
<script src="../static/js/jquery.ui.widget.js"></script> |
4 |
|
5 |
<script type="text/javascript" src="../static/js/jquery.fileupload.js"></script> |
6 |
|
7 |
<script type="text/javascript" src="../static/js/jquery.fileupload-process.js"></script> |
8 |
|
9 |
<script type="text/javascript" src="../static/js/jquery.fileupload-ui.js"></script> |
Wstaw moduł przesyłania plików do okienka edycji korzystając z tego samego kodu, którego użyliśmy na stronie dodawania marzeń.
1 |
$(function() { |
2 |
$('#fileupload').fileupload({ |
3 |
url: 'upload', |
4 |
dataType: 'json', |
5 |
add: function(e, data) { |
6 |
data.submit(); |
7 |
},
|
8 |
success: function(response, status) { |
9 |
|
10 |
var filePath = 'static/Uploads/' + response.filename; |
11 |
$('#imgUpload').attr('src', filePath); |
12 |
$('#filePath').val(filePath); |
13 |
|
14 |
},
|
15 |
error: function(error) { |
16 |
console.log(error); |
17 |
}
|
18 |
});
|
19 |
})
|
Teraz musimy zmienić reakcję na kliknięcie przycisku Update w okienku Edit, aby uwzględnić kilka nowych pól. A więc, przy kliknięciu pod btnUpdate
, zmień parametry przesyłanych danych, dopisując trzy nowe pola, zgodnie z instrukcją:
1 |
data : {title:$('#editTitle').val(),description:$('#editDescription').val(),id:localStorage.getItem('editId'),filePath:$('#imgUpload').attr('src'),isPrivate:$('#chkPrivate').is(':checked')?1:0,isDone:$('#chkDone').is(':checked')?1:0} |
Otwórz app.py
i zmień funkcję obsługi odwołania /updateWish
tak, aby zczytywała dane z nowych pól.
1 |
_filePath = request.form['filePath'] |
2 |
_isPrivate = request.form['isPrivate'] |
3 |
_isDone = request.form['isDone'] |
Zmodyfikuj metodę wywołania procedury, wstawiając dodatkowe parametry.
1 |
cursor.callproc('sp_updateWish',(_title,_description,_wish_id,_user,_filePath,_isPrivate,_isDone)) |
Teraz otwórz sp_updateWish
i zmień ją, by uwzględnić nowe pola.
1 |
DELIMITER $$ |
2 |
|
3 |
CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_updateWish`( |
4 |
IN p_title varchar(45), |
5 |
IN p_description varchar(1000), |
6 |
IN p_wish_id bigint, |
7 |
In p_user_id bigint, |
8 |
IN p_file_path varchar(200), |
9 |
IN p_is_private int, |
10 |
IN p_is_done int |
11 |
)
|
12 |
BEGIN
|
13 |
update tbl_wish set |
14 |
wish_title = p_title, |
15 |
wish_description = p_description, |
16 |
wish_file_path = p_file_path, |
17 |
wish_private = p_is_private, |
18 |
wish_accomplished = p_is_done |
19 |
where wish_id = p_wish_id and wish_user_id = p_user_id; |
20 |
END
|
Zapisz wszystkie zmiany i ponownie uruchom serwer. Zaloguj się prawidłowymi danymi i spróbuj edytować i aktualizować wcześniejsze wpisy.
Podsumowując
W tej części tutorialu nauczyliśmy się wstawiać i wtyczkę blueimp jQuery-File-Upload i korzystać z niej do wczytywania obrazków w aplikacji Python Flask. W kolejnej części cyklu pokażemy marzenia zrealizowane przez użytkowników na głównej stronie aplikacji i dodamy funkcję. która pozwoli je polubić.
Podziel się z nami swoją opinią, uwagami i sugestiami w komentarzach poniżej. Kod źródłowy z tego tutorialu jest dostępny na GitHub.