Update app.py
Browse files
app.py
CHANGED
@@ -7,61 +7,12 @@ import datetime
|
|
7 |
DATA_DIR = "data"
|
8 |
os.makedirs(DATA_DIR, exist_ok=True)
|
9 |
|
10 |
-
# Paths
|
11 |
DB_PATH = os.path.join(DATA_DIR, "teamup.db")
|
12 |
-
|
13 |
-
# Initialize SQLite DB
|
14 |
-
def init_db():
|
15 |
-
with sqlite3.connect(DB_PATH) as conn:
|
16 |
-
conn.execute("""
|
17 |
-
CREATE TABLE IF NOT EXISTS teamup (
|
18 |
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
19 |
-
Name TEXT,
|
20 |
-
Discord TEXT UNIQUE,
|
21 |
-
City TEXT,
|
22 |
-
Country TEXT,
|
23 |
-
Address TEXT,
|
24 |
-
Looking TEXT,
|
25 |
-
Onlinecheck TEXT,
|
26 |
-
Languages TEXT,
|
27 |
-
Laptop TEXT,
|
28 |
-
Robot TEXT,
|
29 |
-
Skills TEXT,
|
30 |
-
Describe3 TEXT,
|
31 |
-
Experience TEXT,
|
32 |
-
Idea TEXT
|
33 |
-
);
|
34 |
-
""")
|
35 |
-
print("β
SQLite database initialized.")
|
36 |
-
init_db()
|
37 |
-
|
38 |
-
# Initialize database
|
39 |
-
conn = sqlite3.connect(DB_PATH)
|
40 |
-
c = conn.cursor()
|
41 |
-
c.execute('''
|
42 |
-
CREATE TABLE IF NOT EXISTS teamup (
|
43 |
-
discord TEXT PRIMARY KEY,
|
44 |
-
name TEXT,
|
45 |
-
city TEXT,
|
46 |
-
country TEXT,
|
47 |
-
address TEXT,
|
48 |
-
looking TEXT,
|
49 |
-
onlinecheck TEXT,
|
50 |
-
languages TEXT,
|
51 |
-
laptop TEXT,
|
52 |
-
robot TEXT,
|
53 |
-
skills TEXT,
|
54 |
-
describe3 TEXT,
|
55 |
-
experience TEXT,
|
56 |
-
idea TEXT
|
57 |
-
)
|
58 |
-
''')
|
59 |
-
conn.commit()
|
60 |
-
conn.close()
|
61 |
-
|
62 |
ADMIN_CODE = os.getenv("ADMIN_CODE", "")
|
|
|
63 |
|
64 |
-
|
|
|
65 |
"United States of America", "United Kingdom", "India", "France", "Germany", "Canada", "Australia", "Japan", "Brazil", "Mexico",
|
66 |
"Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Argentina", "Armenia", "Austria", "Azerbaijan",
|
67 |
"Bangladesh", "Belgium", "Bhutan", "Bolivia", "Bosnia and Herzegovina", "Botswana", "Bulgaria", "Cambodia",
|
@@ -72,63 +23,89 @@ ALL_COUNTRIES = sorted(list(set([
|
|
72 |
"Qatar", "Romania", "Russia", "Saudi Arabia", "Serbia", "Singapore", "Slovakia", "Slovenia", "South Africa", "South Korea",
|
73 |
"Spain", "Sri Lanka", "Sweden", "Switzerland", "Thailand", "Tunisia", "Turkey", "Ukraine", "United Arab Emirates", "Vietnam",
|
74 |
"Zambia", "Zimbabwe"
|
75 |
-
]))
|
76 |
|
77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
78 |
|
79 |
-
# Insert or update profile
|
80 |
def submit_profile(name, discord, city, country, address, looking, onlinecheck, languages, laptop, robot, skills, describe3, experience, idea):
|
81 |
if not discord or not city or not country or not laptop or not robot:
|
82 |
return "β Please fill in all required fields."
|
83 |
|
84 |
lang_str = ", ".join(languages) if isinstance(languages, list) else languages
|
85 |
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
""", (discord, name, city.title(), country.title(), address, looking, onlinecheck, lang_str.lower(), laptop, robot, skills, describe3, experience, idea))
|
106 |
-
conn.commit()
|
107 |
-
conn.close()
|
108 |
-
return "β
Profile saved!"
|
109 |
|
110 |
-
|
111 |
-
def filter_by_fields(selected_country, selected_city, selected_language):
|
112 |
-
conn = sqlite3.connect(DB_PATH)
|
113 |
-
df = pd.read_sql_query("SELECT * FROM teamup", conn)
|
114 |
-
conn.close()
|
115 |
|
116 |
-
|
117 |
-
|
118 |
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
|
123 |
if selected_country != "All":
|
124 |
-
|
|
|
125 |
if selected_city != "All":
|
126 |
-
|
|
|
127 |
if selected_language != "All":
|
128 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
129 |
|
130 |
if df.empty:
|
131 |
-
return "<p>No participants match your filters.</p>"
|
132 |
|
133 |
html = "<table style='width:100%; border-collapse: collapse;'><tr>"
|
134 |
headers = ["Discord", "Name", "City", "Country", "Looking", "Onlinecheck", "Languages", "Laptop", "Robot", "Skills", "Describe3", "Experience", "Idea"]
|
@@ -138,7 +115,7 @@ def filter_by_fields(selected_country, selected_city, selected_language):
|
|
138 |
|
139 |
for _, row in df.iterrows():
|
140 |
html += "<tr>"
|
141 |
-
for col in ["discord", "name", "
|
142 |
val = row[col]
|
143 |
if col == "discord":
|
144 |
val = f"<a href='https://discord.com/users/{val}' target='_blank'>{val}</a>"
|
@@ -146,72 +123,94 @@ def filter_by_fields(selected_country, selected_city, selected_language):
|
|
146 |
html += "</tr>"
|
147 |
|
148 |
html += "</table>"
|
149 |
-
return html
|
150 |
|
151 |
-
# Update city dropdown
|
152 |
def update_city_filter(country):
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
cities = [r[0].title() for r in c.fetchall() if r[0]]
|
160 |
-
conn.close()
|
161 |
return gr.update(choices=["All"] + sorted(cities), value="All")
|
162 |
|
163 |
-
# Delete
|
164 |
def delete_by_discord(discord, code):
|
165 |
if code != ADMIN_CODE:
|
166 |
return "β Invalid admin code."
|
167 |
-
|
168 |
-
|
169 |
-
c.execute("DELETE FROM teamup WHERE lower(discord) = ?", (discord.lower(),))
|
170 |
-
conn.commit()
|
171 |
-
conn.close()
|
172 |
return f"ποΈ Deleted user with Discord: {discord}"
|
173 |
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
with
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
216 |
demo.launch()
|
217 |
-
|
|
|
7 |
DATA_DIR = "data"
|
8 |
os.makedirs(DATA_DIR, exist_ok=True)
|
9 |
|
|
|
10 |
DB_PATH = os.path.join(DATA_DIR, "teamup.db")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
ADMIN_CODE = os.getenv("ADMIN_CODE", "")
|
12 |
+
PAGE_SIZE = 20
|
13 |
|
14 |
+
LANGUAGES = ["English", "French", "Spanish", "German", "Portuguese", "Chinese", "Arabic", "Hindi"]
|
15 |
+
ALL_COUNTRIES = sorted(set([
|
16 |
"United States of America", "United Kingdom", "India", "France", "Germany", "Canada", "Australia", "Japan", "Brazil", "Mexico",
|
17 |
"Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Argentina", "Armenia", "Austria", "Azerbaijan",
|
18 |
"Bangladesh", "Belgium", "Bhutan", "Bolivia", "Bosnia and Herzegovina", "Botswana", "Bulgaria", "Cambodia",
|
|
|
23 |
"Qatar", "Romania", "Russia", "Saudi Arabia", "Serbia", "Singapore", "Slovakia", "Slovenia", "South Africa", "South Korea",
|
24 |
"Spain", "Sri Lanka", "Sweden", "Switzerland", "Thailand", "Tunisia", "Turkey", "Ukraine", "United Arab Emirates", "Vietnam",
|
25 |
"Zambia", "Zimbabwe"
|
26 |
+
]))
|
27 |
|
28 |
+
def init_db():
|
29 |
+
with sqlite3.connect(DB_PATH) as conn:
|
30 |
+
conn.execute("""
|
31 |
+
CREATE TABLE IF NOT EXISTS teamup (
|
32 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
33 |
+
name TEXT,
|
34 |
+
discord TEXT UNIQUE,
|
35 |
+
city TEXT,
|
36 |
+
country TEXT,
|
37 |
+
address TEXT,
|
38 |
+
looking TEXT,
|
39 |
+
onlinecheck TEXT,
|
40 |
+
languages TEXT,
|
41 |
+
laptop TEXT,
|
42 |
+
robot TEXT,
|
43 |
+
skills TEXT,
|
44 |
+
describe3 TEXT,
|
45 |
+
experience TEXT,
|
46 |
+
idea TEXT,
|
47 |
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
48 |
+
);
|
49 |
+
""")
|
50 |
+
init_db()
|
51 |
|
|
|
52 |
def submit_profile(name, discord, city, country, address, looking, onlinecheck, languages, laptop, robot, skills, describe3, experience, idea):
|
53 |
if not discord or not city or not country or not laptop or not robot:
|
54 |
return "β Please fill in all required fields."
|
55 |
|
56 |
lang_str = ", ".join(languages) if isinstance(languages, list) else languages
|
57 |
|
58 |
+
with sqlite3.connect(DB_PATH) as conn:
|
59 |
+
conn.execute("""
|
60 |
+
INSERT INTO teamup (discord, name, city, country, address, looking, onlinecheck, languages, laptop, robot, skills, describe3, experience, idea)
|
61 |
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
62 |
+
ON CONFLICT(discord) DO UPDATE SET
|
63 |
+
name=excluded.name,
|
64 |
+
city=excluded.city,
|
65 |
+
country=excluded.country,
|
66 |
+
address=excluded.address,
|
67 |
+
looking=excluded.looking,
|
68 |
+
onlinecheck=excluded.onlinecheck,
|
69 |
+
languages=excluded.languages,
|
70 |
+
laptop=excluded.laptop,
|
71 |
+
robot=excluded.robot,
|
72 |
+
skills=excluded.skills,
|
73 |
+
describe3=excluded.describe3,
|
74 |
+
experience=excluded.experience,
|
75 |
+
idea=excluded.idea
|
76 |
+
""", (discord, name, city.title(), country.title(), address, looking, onlinecheck, lang_str.lower(), laptop, robot, skills, describe3, experience, idea))
|
|
|
|
|
|
|
|
|
77 |
|
78 |
+
return "β
Profile saved!"
|
|
|
|
|
|
|
|
|
79 |
|
80 |
+
def filter_by_fields(selected_country, selected_city, selected_language, page):
|
81 |
+
offset = (page - 1) * PAGE_SIZE
|
82 |
|
83 |
+
query = "SELECT * FROM teamup"
|
84 |
+
filters = []
|
85 |
+
params = []
|
86 |
|
87 |
if selected_country != "All":
|
88 |
+
filters.append("country = ?")
|
89 |
+
params.append(selected_country.title())
|
90 |
if selected_city != "All":
|
91 |
+
filters.append("city = ?")
|
92 |
+
params.append(selected_city.title())
|
93 |
if selected_language != "All":
|
94 |
+
filters.append("languages LIKE ?")
|
95 |
+
params.append(f"%{selected_language.lower()}%")
|
96 |
+
|
97 |
+
if filters:
|
98 |
+
query += " WHERE " + " AND ".join(filters)
|
99 |
+
query += " ORDER BY created_at DESC LIMIT ? OFFSET ?"
|
100 |
+
params.extend([PAGE_SIZE, offset])
|
101 |
+
|
102 |
+
with sqlite3.connect(DB_PATH) as conn:
|
103 |
+
df = pd.read_sql_query(query, conn, params=params)
|
104 |
+
total_query = "SELECT COUNT(*) FROM teamup" + (" WHERE " + " AND ".join(filters) if filters else "")
|
105 |
+
total_count = pd.read_sql_query(total_query, conn, params=params[:-2] if filters else []).iloc[0, 0]
|
106 |
|
107 |
if df.empty:
|
108 |
+
return "<p>No participants match your filters.</p>", page, total_count
|
109 |
|
110 |
html = "<table style='width:100%; border-collapse: collapse;'><tr>"
|
111 |
headers = ["Discord", "Name", "City", "Country", "Looking", "Onlinecheck", "Languages", "Laptop", "Robot", "Skills", "Describe3", "Experience", "Idea"]
|
|
|
115 |
|
116 |
for _, row in df.iterrows():
|
117 |
html += "<tr>"
|
118 |
+
for col in ["discord", "name", "city", "country", "looking", "onlinecheck", "languages", "laptop", "robot", "skills", "describe3", "experience", "idea"]:
|
119 |
val = row[col]
|
120 |
if col == "discord":
|
121 |
val = f"<a href='https://discord.com/users/{val}' target='_blank'>{val}</a>"
|
|
|
123 |
html += "</tr>"
|
124 |
|
125 |
html += "</table>"
|
126 |
+
return html, page, total_count
|
127 |
|
|
|
128 |
def update_city_filter(country):
|
129 |
+
with sqlite3.connect(DB_PATH) as conn:
|
130 |
+
if country == "All":
|
131 |
+
result = conn.execute("SELECT DISTINCT city FROM teamup").fetchall()
|
132 |
+
else:
|
133 |
+
result = conn.execute("SELECT DISTINCT city FROM teamup WHERE country = ?", (country.title(),)).fetchall()
|
134 |
+
cities = [r[0].title() for r in result if r[0]]
|
|
|
|
|
135 |
return gr.update(choices=["All"] + sorted(cities), value="All")
|
136 |
|
|
|
137 |
def delete_by_discord(discord, code):
|
138 |
if code != ADMIN_CODE:
|
139 |
return "β Invalid admin code."
|
140 |
+
with sqlite3.connect(DB_PATH) as conn:
|
141 |
+
conn.execute("DELETE FROM teamup WHERE lower(discord) = ?", (discord.lower(),))
|
|
|
|
|
|
|
142 |
return f"ποΈ Deleted user with Discord: {discord}"
|
143 |
|
144 |
+
def download_csv(code):
|
145 |
+
if code != ADMIN_CODE:
|
146 |
+
raise gr.Error("β Invalid admin code.")
|
147 |
+
with sqlite3.connect(DB_PATH) as conn:
|
148 |
+
df = pd.read_sql_query("SELECT * FROM teamup", conn)
|
149 |
+
csv_path = os.path.join(DATA_DIR, "teamup_export.csv")
|
150 |
+
df.to_csv(csv_path, index=False)
|
151 |
+
return csv_path
|
152 |
+
|
153 |
+
def interface():
|
154 |
+
with gr.Blocks(css=".gr-dropdown { max-height: 100px; overflow-y: auto; font-size: 12px; }") as demo:
|
155 |
+
gr.Markdown("# π LeRobot Worldwide Hackathon - Team-Up Dashboard")
|
156 |
+
|
157 |
+
with gr.Row():
|
158 |
+
with gr.Column():
|
159 |
+
name = gr.Text(label="Name")
|
160 |
+
discord = gr.Text(label="π€ Discord Username *")
|
161 |
+
city = gr.Text(label="π City *")
|
162 |
+
country = gr.Dropdown(label="π Country *", choices=ALL_COUNTRIES, value="France")
|
163 |
+
address = gr.Text(label="Address (optional)")
|
164 |
+
looking = gr.Radio(["Yes", "No"], label="π Looking for a team?")
|
165 |
+
onlinecheck = gr.Radio(["Participate Online", "Join a Local Hackathon"], label="π I will...")
|
166 |
+
languages = gr.CheckboxGroup(choices=LANGUAGES, label="Languages Spoken *")
|
167 |
+
laptop = gr.Text(label="π» Laptop Setup *")
|
168 |
+
robot = gr.Text(label="π€ Robot Setup *")
|
169 |
+
skills = gr.Text(label="π§ Skills (comma separated)")
|
170 |
+
describe3 = gr.Text(label="3 Words That Describe You")
|
171 |
+
experience = gr.Dropdown(choices=["Beginner", "Intermediate", "Advanced"], label="Experience Level", value="Beginner")
|
172 |
+
idea = gr.Textbox(label="Project Idea (optional)")
|
173 |
+
submit_btn = gr.Button("Submit or Update β
")
|
174 |
+
status = gr.Textbox(label="", interactive=False)
|
175 |
+
|
176 |
+
with gr.Column():
|
177 |
+
country_filter = gr.Dropdown(label="Filter by Country", choices=["All"] + ALL_COUNTRIES, value="All")
|
178 |
+
city_filter = gr.Dropdown(label="Filter by City", choices=["All"], value="All")
|
179 |
+
language_filter = gr.Dropdown(label="Filter by Language", choices=["All"] + LANGUAGES, value="All")
|
180 |
+
page_state = gr.Number(value=1, visible=False)
|
181 |
+
table_html = gr.HTML(label="Matching Participants")
|
182 |
+
prev_btn = gr.Button("β¬
οΈ Previous Page")
|
183 |
+
next_btn = gr.Button("β‘οΈ Next Page")
|
184 |
+
|
185 |
+
submit_btn.click(submit_profile, inputs=[name, discord, city, country, address, looking, onlinecheck, languages, laptop, robot, skills, describe3, experience, idea], outputs=[status])
|
186 |
+
submit_btn.click(filter_by_fields, inputs=[country_filter, city_filter, language_filter, page_state], outputs=[table_html, page_state, gr.Number(visible=False)])
|
187 |
+
|
188 |
+
country_filter.change(update_city_filter, inputs=[country_filter], outputs=[city_filter])
|
189 |
+
|
190 |
+
for dropdown in [country_filter, city_filter, language_filter]:
|
191 |
+
dropdown.change(lambda c, ci, l: (1,), inputs=[country_filter, city_filter, language_filter], outputs=[page_state])
|
192 |
+
dropdown.change(filter_by_fields, inputs=[country_filter, city_filter, language_filter, page_state], outputs=[table_html, page_state, gr.Number(visible=False)])
|
193 |
+
|
194 |
+
prev_btn.click(lambda p: max(p - 1, 1), inputs=[page_state], outputs=[page_state])
|
195 |
+
prev_btn.click(filter_by_fields, inputs=[country_filter, city_filter, language_filter, page_state], outputs=[table_html, page_state, gr.Number(visible=False)])
|
196 |
+
|
197 |
+
next_btn.click(lambda p: p + 1, inputs=[page_state], outputs=[page_state])
|
198 |
+
next_btn.click(filter_by_fields, inputs=[country_filter, city_filter, language_filter, page_state], outputs=[table_html, page_state, gr.Number(visible=False)])
|
199 |
+
|
200 |
+
gr.Markdown("---\n### π‘οΈ Admin Panel")
|
201 |
+
admin_discord = gr.Text(label="Discord Username")
|
202 |
+
admin_code = gr.Text(label="Admin Code", type="password")
|
203 |
+
del_btn = gr.Button("Delete Profile")
|
204 |
+
del_status = gr.Textbox(label="Status", interactive=False)
|
205 |
+
del_btn.click(delete_by_discord, inputs=[admin_discord, admin_code], outputs=[del_status])
|
206 |
+
|
207 |
+
gr.Markdown("---\n### π₯ Admin Export CSV")
|
208 |
+
export_code = gr.Text(label="Admin Code", type="password")
|
209 |
+
download_btn = gr.Button("Generate and Download CSV")
|
210 |
+
download_file = gr.File(label="CSV Export", interactive=False)
|
211 |
+
download_btn.click(download_csv, inputs=[export_code], outputs=[download_file])
|
212 |
+
|
213 |
+
return demo
|
214 |
+
|
215 |
+
demo = interface()
|
216 |
demo.launch()
|
|