Vinay Jose commited on
Commit
2b88012
·
unverified ·
2 Parent(s): 337e996 8ac8b3d

Merge pull request #2 from vinay-jose/03-a-web-1.0-application

Browse files
Dockerfile ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9
2
+
3
+ RUN useradd -m -u 1000 user
4
+ USER user
5
+ ENV PATH="/home/user/.local/bin:$PATH"
6
+
7
+ WORKDIR /app
8
+
9
+ COPY --chown=user ./requirements.txt requirements.txt
10
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
11
+
12
+ COPY --chown=user . /app
13
+ CMD ["python", "app.py", "--host", "0.0.0.0", "--port", "7860"]
app.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, redirect, render_template, flash, request
2
+ from models import Contact
3
+
4
+ Contact.load_db()
5
+ app = Flask(__name__)
6
+
7
+ app.secret_key = b'just keep swimming'
8
+
9
+ @app.get("/")
10
+ def index():
11
+ return redirect("/contacts")
12
+
13
+ @app.get("/contacts")
14
+ def contacts():
15
+ search = request.args.get("q")
16
+ if search:
17
+ contacts_set = Contact.search(search)
18
+ else:
19
+ contacts_set = Contact.all()
20
+ return render_template("index.html", contacts=contacts_set)
21
+
22
+ @app.get("/contacts/<contact_id>")
23
+ def contacts_view(contact_id=0):
24
+ contact = Contact.find(contact_id)
25
+ return render_template("show.html", contact=contact)
26
+
27
+ @app.get("/contacts/new")
28
+ def contacts_new_get():
29
+ return render_template("new.html", contact=Contact())
30
+
31
+ @app.post("/contacts/new")
32
+ def contacts_new_post():
33
+ c = Contact(
34
+ None,
35
+ request.form['first'],
36
+ request.form['last'],
37
+ request.form['phone'],
38
+ request.form['email']
39
+ )
40
+ if c.save():
41
+ flash("Created New Contact!")
42
+ return redirect("/contacts")
43
+ else:
44
+ return render_template("new.html", contact=c)
45
+
46
+ @app.get("/contacts/<contact_id>/edit")
47
+ def contacts_edit_get(contact_id=0):
48
+ contact = Contact.find(contact_id)
49
+ return render_template("edit.html", contact=contact)
50
+
51
+ @app.post("/contacts/<contact_id>/edit")
52
+ def contacts_edit_post(contact_id=0):
53
+ c = Contact.find(contact_id)
54
+ c.update(
55
+ request.form['first'],
56
+ request.form['last'],
57
+ request.form['phone'],
58
+ request.form['email']
59
+ )
60
+ if c.save():
61
+ flash("Updated Contact!")
62
+ return redirect(f"/contacts/{contact_id}")
63
+ else:
64
+ render_template("edit.html", contact=c)
65
+
66
+ @app.post("/contacts/<contact_id>/delete")
67
+ def contacts_delete(contact_id=0):
68
+ contact = Contact.find(contact_id)
69
+ contact.delete()
70
+ flash("Deleted Contact!")
71
+ return redirect("/contacts")
72
+
73
+ if __name__ == "__main__":
74
+ app.run(host="0.0.0.0", port="7860", debug=True)
75
+
contacts.json ADDED
@@ -0,0 +1,402 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": 1,
4
+ "first": "Aarav",
5
+ "last": "Patel",
6
+ "phone": "987-654-3210",
7
+ "email": "[email protected]",
8
+ "errors": {}
9
+ },
10
+ {
11
+ "id": 2,
12
+ "first": "Diya",
13
+ "last": "Sharma",
14
+ "phone": "876-543-2109",
15
+ "email": "[email protected]",
16
+ "errors": {}
17
+ },
18
+ {
19
+ "id": 3,
20
+ "first": "Arjun",
21
+ "last": "Kumar",
22
+ "phone": "765-432-1098",
23
+ "email": "[email protected]",
24
+ "errors": {}
25
+ },
26
+ {
27
+ "id": 4,
28
+ "first": "Ananya",
29
+ "last": "Singh",
30
+ "phone": "654-321-0987",
31
+ "email": "[email protected]",
32
+ "errors": {}
33
+ },
34
+ {
35
+ "id": 5,
36
+ "first": "Vikram",
37
+ "last": "Gupta",
38
+ "phone": "543-210-9876",
39
+ "email": "[email protected]",
40
+ "errors": {}
41
+ },
42
+ {
43
+ "id": 6,
44
+ "first": "Neha",
45
+ "last": "Reddy",
46
+ "phone": "432-109-8765",
47
+ "email": "[email protected]",
48
+ "errors": {}
49
+ },
50
+ {
51
+ "id": 7,
52
+ "first": "Rohan",
53
+ "last": "Mehta",
54
+ "phone": "321-098-7654",
55
+ "email": "[email protected]",
56
+ "errors": {}
57
+ },
58
+ {
59
+ "id": 8,
60
+ "first": "Priya",
61
+ "last": "Desai",
62
+ "phone": "210-987-6543",
63
+ "email": "[email protected]",
64
+ "errors": {}
65
+ },
66
+ {
67
+ "id": 9,
68
+ "first": "Karthik",
69
+ "last": "Nair",
70
+ "phone": "109-876-5432",
71
+ "email": "[email protected]",
72
+ "errors": {}
73
+ },
74
+ {
75
+ "id": 10,
76
+ "first": "Kavya",
77
+ "last": "Joshi",
78
+ "phone": "098-765-4321",
79
+ "email": "[email protected]",
80
+ "errors": {}
81
+ },
82
+ {
83
+ "id": 11,
84
+ "first": "Rahul",
85
+ "last": "Malhotra",
86
+ "phone": "987-654-3211",
87
+ "email": "[email protected]",
88
+ "errors": {}
89
+ },
90
+ {
91
+ "id": 12,
92
+ "first": "Aisha",
93
+ "last": "Choudhury",
94
+ "phone": "876-543-2110",
95
+ "email": "[email protected]",
96
+ "errors": {}
97
+ },
98
+ {
99
+ "id": 13,
100
+ "first": "Vivek",
101
+ "last": "Kapoor",
102
+ "phone": "765-432-1099",
103
+ "email": "[email protected]",
104
+ "errors": {}
105
+ },
106
+ {
107
+ "id": 14,
108
+ "first": "Meera",
109
+ "last": "Iyer",
110
+ "phone": "654-321-0988",
111
+ "email": "[email protected]",
112
+ "errors": {}
113
+ },
114
+ {
115
+ "id": 15,
116
+ "first": "Siddharth",
117
+ "last": "Verma",
118
+ "phone": "543-210-9877",
119
+ "email": "[email protected]",
120
+ "errors": {}
121
+ },
122
+ {
123
+ "id": 16,
124
+ "first": "Riya",
125
+ "last": "Saxena",
126
+ "phone": "432-109-8766",
127
+ "email": "[email protected]",
128
+ "errors": {}
129
+ },
130
+ {
131
+ "id": 17,
132
+ "first": "Aditya",
133
+ "last": "Menon",
134
+ "phone": "321-098-7655",
135
+ "email": "[email protected]",
136
+ "errors": {}
137
+ },
138
+ {
139
+ "id": 18,
140
+ "first": "Anjali",
141
+ "last": "Bhatia",
142
+ "phone": "210-987-6544",
143
+ "email": "[email protected]",
144
+ "errors": {}
145
+ },
146
+ {
147
+ "id": 19,
148
+ "first": "Nikhil",
149
+ "last": "Rao",
150
+ "phone": "109-876-5433",
151
+ "email": "[email protected]",
152
+ "errors": {}
153
+ },
154
+ {
155
+ "id": 20,
156
+ "first": "Pooja",
157
+ "last": "Chopra",
158
+ "phone": "098-765-4322",
159
+ "email": "[email protected]",
160
+ "errors": {}
161
+ },
162
+ {
163
+ "id": 21,
164
+ "first": "Akash",
165
+ "last": "Mukherjee",
166
+ "phone": "987-654-3212",
167
+ "email": "[email protected]",
168
+ "errors": {}
169
+ },
170
+ {
171
+ "id": 22,
172
+ "first": "Shreya",
173
+ "last": "Sengupta",
174
+ "phone": "876-543-2111",
175
+ "email": "[email protected]",
176
+ "errors": {}
177
+ },
178
+ {
179
+ "id": 23,
180
+ "first": "Varun",
181
+ "last": "Khanna",
182
+ "phone": "765-432-1100",
183
+ "email": "[email protected]",
184
+ "errors": {}
185
+ },
186
+ {
187
+ "id": 24,
188
+ "first": "Tanvi",
189
+ "last": "Agarwal",
190
+ "phone": "654-321-0989",
191
+ "email": "[email protected]",
192
+ "errors": {}
193
+ },
194
+ {
195
+ "id": 25,
196
+ "first": "Gaurav",
197
+ "last": "Bansal",
198
+ "phone": "543-210-9878",
199
+ "email": "[email protected]",
200
+ "errors": {}
201
+ },
202
+ {
203
+ "id": 26,
204
+ "first": "Ishaan",
205
+ "last": "Chakraborty",
206
+ "phone": "432-109-8767",
207
+ "email": "[email protected]",
208
+ "errors": {}
209
+ },
210
+ {
211
+ "id": 27,
212
+ "first": "Nisha",
213
+ "last": "Sinha",
214
+ "phone": "321-098-7656",
215
+ "email": "[email protected]",
216
+ "errors": {}
217
+ },
218
+ {
219
+ "id": 28,
220
+ "first": "Dhruv",
221
+ "last": "Trivedi",
222
+ "phone": "210-987-6545",
223
+ "email": "[email protected]",
224
+ "errors": {}
225
+ },
226
+ {
227
+ "id": 29,
228
+ "first": "Aditi",
229
+ "last": "Mishra",
230
+ "phone": "109-876-5434",
231
+ "email": "[email protected]",
232
+ "errors": {}
233
+ },
234
+ {
235
+ "id": 30,
236
+ "first": "Kunal",
237
+ "last": "Rastogi",
238
+ "phone": "098-765-4323",
239
+ "email": "[email protected]",
240
+ "errors": {}
241
+ },
242
+ {
243
+ "id": 31,
244
+ "first": "Sakshi",
245
+ "last": "Malik",
246
+ "phone": "987-654-3213",
247
+ "email": "[email protected]",
248
+ "errors": {}
249
+ },
250
+ {
251
+ "id": 32,
252
+ "first": "Rohit",
253
+ "last": "Bajaj",
254
+ "phone": "876-543-2112",
255
+ "email": "[email protected]",
256
+ "errors": {}
257
+ },
258
+ {
259
+ "id": 33,
260
+ "first": "Shivani",
261
+ "last": "Bhatt",
262
+ "phone": "765-432-1101",
263
+ "email": "[email protected]",
264
+ "errors": {}
265
+ },
266
+ {
267
+ "id": 34,
268
+ "first": "Arnav",
269
+ "last": "Ahuja",
270
+ "phone": "654-321-0990",
271
+ "email": "[email protected]",
272
+ "errors": {}
273
+ },
274
+ {
275
+ "id": 35,
276
+ "first": "Swati",
277
+ "last": "Chawla",
278
+ "phone": "543-210-9879",
279
+ "email": "[email protected]",
280
+ "errors": {}
281
+ },
282
+ {
283
+ "id": 36,
284
+ "first": "Yash",
285
+ "last": "Dewan",
286
+ "phone": "432-109-8768",
287
+ "email": "[email protected]",
288
+ "errors": {}
289
+ },
290
+ {
291
+ "id": 37,
292
+ "first": "Kritika",
293
+ "last": "Arora",
294
+ "phone": "321-098-7657",
295
+ "email": "[email protected]",
296
+ "errors": {}
297
+ },
298
+ {
299
+ "id": 38,
300
+ "first": "Arun",
301
+ "last": "Narang",
302
+ "phone": "210-987-6546",
303
+ "email": "[email protected]",
304
+ "errors": {}
305
+ },
306
+ {
307
+ "id": 39,
308
+ "first": "Sana",
309
+ "last": "Qureshi",
310
+ "phone": "109-876-5435",
311
+ "email": "[email protected]",
312
+ "errors": {}
313
+ },
314
+ {
315
+ "id": 40,
316
+ "first": "Karan",
317
+ "last": "Mehra",
318
+ "phone": "098-765-4324",
319
+ "email": "[email protected]",
320
+ "errors": {}
321
+ },
322
+ {
323
+ "id": 41,
324
+ "first": "Tanya",
325
+ "last": "Basu",
326
+ "phone": "987-654-3214",
327
+ "email": "[email protected]",
328
+ "errors": {}
329
+ },
330
+ {
331
+ "id": 42,
332
+ "first": "Mihir",
333
+ "last": "Bose",
334
+ "phone": "876-543-2113",
335
+ "email": "[email protected]",
336
+ "errors": {}
337
+ },
338
+ {
339
+ "id": 43,
340
+ "first": "Kajal",
341
+ "last": "Ghosh",
342
+ "phone": "765-432-1102",
343
+ "email": "[email protected]",
344
+ "errors": {}
345
+ },
346
+ {
347
+ "id": 44,
348
+ "first": "Pranav",
349
+ "last": "Lal",
350
+ "phone": "654-321-0991",
351
+ "email": "[email protected]",
352
+ "errors": {}
353
+ },
354
+ {
355
+ "id": 45,
356
+ "first": "Simran",
357
+ "last": "Mathur",
358
+ "phone": "543-210-9880",
359
+ "email": "[email protected]",
360
+ "errors": {}
361
+ },
362
+ {
363
+ "id": 46,
364
+ "first": "Aamir",
365
+ "last": "Khan",
366
+ "phone": "432-109-8769",
367
+ "email": "[email protected]",
368
+ "errors": {}
369
+ },
370
+ {
371
+ "id": 47,
372
+ "first": "Zoya",
373
+ "last": "Suri",
374
+ "phone": "321-098-7658",
375
+ "email": "[email protected]",
376
+ "errors": {}
377
+ },
378
+ {
379
+ "id": 48,
380
+ "first": "Vedant",
381
+ "last": "Chauhan",
382
+ "phone": "210-987-6547",
383
+ "email": "[email protected]",
384
+ "errors": {}
385
+ },
386
+ {
387
+ "id": 49,
388
+ "first": "Nandini",
389
+ "last": "Venkatesh",
390
+ "phone": "109-876-5436",
391
+ "email": "[email protected]",
392
+ "errors": {}
393
+ },
394
+ {
395
+ "id": 50,
396
+ "first": "Kabir",
397
+ "last": "Dhillon",
398
+ "phone": "098-765-4325",
399
+ "email": "[email protected]",
400
+ "errors": {}
401
+ }
402
+ ]
models.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import time
3
+ # ========================================================
4
+ # Contact Model
5
+ # ========================================================
6
+ PAGE_SIZE = 100
7
+
8
+ class Contact:
9
+ # mock contacts database
10
+ db = {}
11
+ def __init__(self, id_=None, first=None, last=None, phone=None, email=None):
12
+ self.id = id_
13
+ self.first = first
14
+ self.last = last
15
+ self.phone = phone
16
+ self.email = email
17
+ self.errors = {}
18
+
19
+ def __str__(self):
20
+ return json.dumps(self.__dict__, ensure_ascii=False)
21
+
22
+ def update(self, first, last, phone, email):
23
+ self.first = first
24
+ self.last = last
25
+ self.phone = phone
26
+ self.email = email
27
+
28
+ def validate(self):
29
+ if not self.email:
30
+ self.errors['email'] = "Email Required"
31
+ existing_contact = next(filter(lambda c: c.id != self.id and c.email == self.email, Contact.db.values()), None)
32
+ if existing_contact:
33
+ self.errors['email'] = "Email Must Be Unique"
34
+ return len(self.errors) == 0
35
+
36
+ def save(self):
37
+ if not self.validate():
38
+ return False
39
+ if self.id is None:
40
+ if len(Contact.db) == 0:
41
+ max_id = 1
42
+ else:
43
+ max_id = max(contact.id for contact in Contact.db.values())
44
+ self.id = max_id + 1
45
+ Contact.db[self.id] = self
46
+ Contact.save_db()
47
+ return True
48
+
49
+ def delete(self):
50
+ del Contact.db[self.id]
51
+ Contact.save_db()
52
+
53
+ @classmethod
54
+ def count(cls):
55
+ time.sleep(2)
56
+ return len(cls.db)
57
+
58
+ @classmethod
59
+ def all(cls, page=1):
60
+ page = int(page)
61
+ start = (page - 1) * PAGE_SIZE
62
+ end = start + PAGE_SIZE
63
+ return list(cls.db.values())[start:end]
64
+
65
+ @classmethod
66
+ def search(cls, text):
67
+ result = []
68
+ for c in cls.db.values():
69
+ match_first = c.first is not None and text in c.first
70
+ match_last = c.last is not None and text in c.last
71
+ match_email = c.email is not None and text in c.email
72
+ match_phone = c.phone is not None and text in c.phone
73
+ if match_first or match_last or match_email or match_phone:
74
+ result.append(c)
75
+ return result
76
+
77
+ @classmethod
78
+ def load_db(cls):
79
+ with open('contacts.json', 'r') as contacts_file:
80
+ contacts = json.load(contacts_file)
81
+ cls.db.clear()
82
+ for c in contacts:
83
+ cls.db[c['id']] = Contact(c['id'], c['first'], c['last'], c['phone'], c['email'])
84
+
85
+ @staticmethod
86
+ def save_db():
87
+ out_arr = [c.__dict__ for c in Contact.db.values()]
88
+ with open("contacts.json", "w") as f:
89
+ json.dump(out_arr, f, indent=2)
90
+
91
+ @classmethod
92
+ def find(cls, id_):
93
+ id_ = int(id_)
94
+ c = cls.db.get(id_)
95
+ if c is not None:
96
+ c.errors = {}
97
+ return c
98
+
requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Flask
templates/edit.html ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'layout.html' %}
2
+
3
+ {% block content %}
4
+
5
+ <form action="/contacts/{{ contact.id }}/edit" method="post">
6
+ <fieldset>
7
+ <legend>Contact Info</legend>
8
+ <p>
9
+ <label for="email">Email</label>
10
+ <input name="email" id="email" type="text" placeholder="Email" value="{{ contact.email }}"/>
11
+ <span class="error">{{ contact.errors["email"] }}</span>
12
+ </p>
13
+ <p>
14
+ <label for="first">First Name</label>
15
+ <input name="first" id="first" type="text" placeholder="First Name" value="{{ contact.first }}"/>
16
+ <span class="error">{{ contact.errors["first"] }}</span>
17
+ </p>
18
+ <p>
19
+ <label for="last">Last Name</label>
20
+ <input name="last" id="last" type="text" placeholder="Last Name" value="{{ contact.last }}"/>
21
+ <span class="error">{{ contact.errors["last"] }}</span>
22
+ </p>
23
+ <p>
24
+ <label for="phone">Phone</label>
25
+ <input name="phone" id="phone" type="text" placeholder="Phone" value="{{ contact.phone }}"/>
26
+ <span class="phone">{{ contact.errors["phone"]}}</span>
27
+ </p>
28
+ <button>Save</button>
29
+ </fieldset>
30
+ </form>
31
+ <form action="/contacts/{{ contact.id }}/delete" method="post">
32
+ <button>Delete Contact</button>
33
+ </form>
34
+ <p>
35
+ <a href="/contacts">Back</a>
36
+ </p>
37
+ {% endblock %}
templates/index.html ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'layout.html' %}
2
+
3
+ {% block content %}
4
+
5
+ <form action="/contacts" method="get" class="tool-bar">
6
+ <label for="search">Search Term</label>
7
+ <input id="search" type="search" name="q"
8
+ value="{{ request.args.get('q') or '' }}"/>
9
+ <input type="submit" value="Search"/>
10
+ </form>
11
+ <table>
12
+ <thead>
13
+ <tr>
14
+ <th>First name <th>Last name <th>Phone <th>Email </th>
15
+ </tr>
16
+ </thead>
17
+ <tbody>
18
+ {% for contact in contacts %}
19
+ <tr>
20
+ <td>{{ contact.first }}</td>
21
+ <td>{{ contact.last }}</td>
22
+ <td>{{ contact.phone }}</td>
23
+ <td>{{ contact.email }}</td>
24
+ <td>
25
+ <a href="contacts/{{ contact.id }}/edit">Edit</a>
26
+ <a href="contacts/{{ contact.id }}">View</a>
27
+ </td>
28
+ </tr>
29
+ {% endfor %}
30
+ </tbody>
31
+ </table>
32
+ <p>
33
+ <a href="/contacts/new">Add New Contact</a>
34
+ </p>
35
+
36
+ {% endblock %}
templates/layout.html ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="">
3
+ <head>
4
+ <title>Contact App</title>
5
+ </head>
6
+ <body>
7
+ <main>
8
+ <header>
9
+ <h1>Contacts.app</h1>
10
+ <h3>A Demo Contacts Application</h3>
11
+ </header>
12
+ {% block content %}{% endblock %}
13
+ </main>
14
+ </body>
15
+ </html>
templates/new.html ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'layout.html' %}
2
+
3
+ {% block content %}
4
+
5
+ <form action="/contacts/new" method="post">
6
+ <fieldset>
7
+ <legend>Contact Info</legend>
8
+ <p>
9
+ <label for="email">Email</label>
10
+ <input name="email" id="email" type="text" placeholder="Email" value="{{ contact.email or '' }}"/>
11
+ <span class="error">{{ contact.errors['email'] }}</span>
12
+ </p>
13
+ <p>
14
+ <label for="first">First Name</label>
15
+ <input name="first" id="first" type="text" placeholder="First Name" value="{{ contact.first or '' }}"/>
16
+ <span class="error">{{ contact.errors['first']}}</span>
17
+ </p>
18
+ <p>
19
+ <label for="last">Last Name</label>
20
+ <input name="last" id="last" type="text" placeholder="Last Name" value="{{ contact.last or '' }}"/>
21
+ <span class="error">{{ contact.errors['last'] }}</span>
22
+ </p>
23
+ <p>
24
+ <label for="phone">Phone</label>
25
+ <input name="phone" id="phone" type="text" placeholder="Phone" value="{{ contact.phone or '' }}"/>
26
+ <span class="error">{{ contact.errors['phone'] }}</span>
27
+ </p>
28
+ <button>Save</button>
29
+ </fieldset>
30
+ </form>
31
+ <p>
32
+ <a href="/contacts">Back</a>
33
+ </p>
34
+ {% endblock %}
templates/show.html ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'layout.html' %}
2
+
3
+ {% block content %}
4
+
5
+ <h1>{{contact.first}} {{contact.last}}</h1>
6
+ <div>
7
+ <div>Phone: {{contact.phone}}</div>
8
+ <div>Email: {{contact.email}}</div>
9
+ </div>
10
+ <p>
11
+ <a href="/contacts/{{ contact.id }}/edit">Edit</a>
12
+ <a href="/contacts">Back</a>
13
+ </p>
14
+
15
+ {% endblock %}