added search filter and user grid, fixed an issue with files upload
68
src/main.py
|
|
@ -1,13 +1,14 @@
|
||||||
from flask import Flask, render_template, request, redirect, url_for, flash, session
|
from flask import Flask, render_template, request, redirect, url_for, flash, session
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from sqlalchemy import text
|
||||||
from datetime import date
|
from datetime import date
|
||||||
import gnupg
|
import gnupg
|
||||||
import secrets
|
import secrets
|
||||||
import os
|
import os
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
# defines where the upload folder is and creates it
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
UPLOAD_FOLDER = "static/uploads"
|
UPLOAD_FOLDER = os.path.join(BASE_DIR, "static", "uploads")
|
||||||
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
||||||
|
|
||||||
# configures the app
|
# configures the app
|
||||||
|
|
@ -77,27 +78,24 @@ def calculate_age(dob: date) -> int:
|
||||||
return today.year - dob.year - ((today.month, today.day) < (dob.month, dob.day))
|
return today.year - dob.year - ((today.month, today.day) < (dob.month, dob.day))
|
||||||
|
|
||||||
# saves files to the upload folder and returns their URL
|
# saves files to the upload folder and returns their URL
|
||||||
|
|
||||||
def save_files(username: str, profile_file, pictures_files):
|
def save_files(username: str, profile_file, pictures_files):
|
||||||
# creates a path for the user inside the upload forlder
|
|
||||||
user_folder = os.path.join(app.config['UPLOAD_FOLDER'], username)
|
user_folder = os.path.join(app.config['UPLOAD_FOLDER'], username)
|
||||||
os.makedirs(user_folder, exist_ok=True)
|
os.makedirs(user_folder, exist_ok=True)
|
||||||
|
|
||||||
# prevents unsafe characters to be used in the filename
|
|
||||||
profile_filename = secure_filename(profile_file.filename)
|
profile_filename = secure_filename(profile_file.filename)
|
||||||
profile_path = os.path.join(user_folder, profile_filename)
|
profile_path = os.path.join(user_folder, profile_filename)
|
||||||
|
|
||||||
# saves the profile picture to the path
|
|
||||||
profile_file.save(profile_path)
|
profile_file.save(profile_path)
|
||||||
profile_url = f"/{profile_path.replace(os.sep, '/')}"
|
|
||||||
|
|
||||||
# saves all of the other pictures
|
profile_url = f"/static/uploads/{username}/{profile_filename}"
|
||||||
|
|
||||||
pictures_urls = []
|
pictures_urls = []
|
||||||
for pic in pictures_files:
|
for pic in pictures_files:
|
||||||
if pic.filename:
|
if pic.filename:
|
||||||
filename = secure_filename(pic.filename)
|
filename = secure_filename(pic.filename)
|
||||||
path = os.path.join(user_folder, filename)
|
path = os.path.join(user_folder, filename)
|
||||||
pic.save(path)
|
pic.save(path)
|
||||||
pictures_urls.append(f"/{path.replace(os.sep, '/')}")
|
pictures_urls.append(f"/static/uploads/{username}/{filename}")
|
||||||
|
|
||||||
return profile_url, pictures_urls
|
return profile_url, pictures_urls
|
||||||
|
|
||||||
|
|
@ -119,8 +117,58 @@ def pgp_encrypt_and_import(pgp_key: str, message: str):
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def home():
|
def home():
|
||||||
return render_template("index.html")
|
query = User.query
|
||||||
|
|
||||||
|
country = request.args.get("country")
|
||||||
|
city = request.args.get("city")
|
||||||
|
sex = request.args.get("sex")
|
||||||
|
age_min = request.args.get("age_min")
|
||||||
|
age_max = request.args.get("age_max")
|
||||||
|
race = request.args.get("race")
|
||||||
|
likes = request.args.get("likes")
|
||||||
|
dislikes = request.args.get("dislikes")
|
||||||
|
|
||||||
|
if country:
|
||||||
|
query = query.filter(User.country.ilike(f"%{country}%"))
|
||||||
|
if city:
|
||||||
|
query = query.filter(User.city.ilike(f"%{city}%"))
|
||||||
|
if sex:
|
||||||
|
query = query.filter(User.sex==sex)
|
||||||
|
if race:
|
||||||
|
query = query.filter(User.race.ilike(f"%{race}%"))
|
||||||
|
|
||||||
|
today = date.today()
|
||||||
|
if age_min:
|
||||||
|
try:
|
||||||
|
min_age = int(age_min)
|
||||||
|
dob_max = date(today.year - min_age, today.month, today.day)
|
||||||
|
query = query.filter(User.date_of_birth <= dob_max)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
if age_max:
|
||||||
|
try:
|
||||||
|
max_age = int(age_max)
|
||||||
|
dob_min = date(today.year - max_age - 1, today.month, today.day)
|
||||||
|
query = query.filter(User.date_of_birth >= dob_min)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if likes:
|
||||||
|
likes_list = [x.strip().lower() for x in likes.split(",") if x.strip()]
|
||||||
|
for like in likes_list:
|
||||||
|
query = query.filter(
|
||||||
|
text(f"JSON_CONTAINS(likes, '\"{like}\"')")
|
||||||
|
)
|
||||||
|
|
||||||
|
if dislikes:
|
||||||
|
dislikes_list = [x.strip().lower() for x in dislikes.split(",") if x.strip()]
|
||||||
|
for dislike in dislikes_list:
|
||||||
|
query = query.filter(
|
||||||
|
text(f"JSON_CONTAINS(dislikes, '\"{dislike}\"')")
|
||||||
|
)
|
||||||
|
|
||||||
|
users = query.all()
|
||||||
|
return render_template("index.html", users=users, date=date)
|
||||||
|
|
||||||
@app.route("/register", methods=["GET", "POST"])
|
@app.route("/register", methods=["GET", "POST"])
|
||||||
def register():
|
def register():
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,47 @@
|
||||||
{% extends "page.html" %}
|
{% extends "page.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Main Page</h2>
|
<h2>Discover Users</h2>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h3>Search Users</h3>
|
||||||
|
<form method="GET" action="{{ url_for('home') }}">
|
||||||
|
<input type="text" name="country" placeholder="Country" value="{{ request.args.get('country', '') }}">
|
||||||
|
<input type="text" name="city" placeholder="City" value="{{ request.args.get('city', '') }}">
|
||||||
|
|
||||||
|
<select name="sex">
|
||||||
|
<option value="">Any Sex</option>
|
||||||
|
<option value="male" {% if request.args.get('sex')=='male' %}selected{% endif %}>Male</option>
|
||||||
|
<option value="female" {% if request.args.get('sex')=='female' %}selected{% endif %}>Female</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<input type="number" name="age_min" placeholder="Min Age" min="18" value="{{ request.args.get('age_min', '') }}">
|
||||||
|
<input type="number" name="age_max" placeholder="Max Age" min="18" value="{{ request.args.get('age_max', '') }}">
|
||||||
|
|
||||||
|
<input type="text" name="race" placeholder="Race" value="{{ request.args.get('race', '') }}">
|
||||||
|
<input type="text" name="likes" placeholder="Likes" value="{{ request.args.get('likes', '') }}">
|
||||||
|
<input type="text" name="dislikes" placeholder="Dislikes" value="{{ request.args.get('dislikes', '') }}">
|
||||||
|
|
||||||
|
<button type="submit">Search</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h3>Users</h3>
|
||||||
|
{% if users %}
|
||||||
|
<div>
|
||||||
|
{% for user in users %}
|
||||||
|
<div>
|
||||||
|
<a href="{{ url_for('user_profile', username=user.username) }}">
|
||||||
|
<img src="{{ user.profile_picture }}" alt="{{ user.username }}" width="100"><br>
|
||||||
|
Age: {{ (date.today() - user.date_of_birth).days // 365 }}<br>
|
||||||
|
Country: {{ user.country }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<p>No users found.</p>
|
||||||
|
{% endif %}
|
||||||
|
</section>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,7 @@
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<section>
|
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</section>
|
|
||||||
<footer>Dating Website</footer>
|
<footer>Dating Website</footer>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,97 @@
|
||||||
{% extends "page.html" %}
|
{% extends "page.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>{{ user.firstname }} {{ user.lastname }}</h2>
|
|
||||||
<img src="{{ user.profile_picture }}" alt="Profile Picture" width="150"><br>
|
<section>
|
||||||
|
<h2>{{ user.firstname }} {{ user.lastname }}</h2>
|
||||||
|
|
||||||
|
<img src="{{ user.profile_picture }}" alt="Profile Picture" style="max-width:200px; border-radius:8px;"><br><br>
|
||||||
|
|
||||||
|
<p><strong>Age:</strong> {{ (date.today() - user.date_of_birth).days // 365 }}</p>
|
||||||
|
<p><strong>Sex:</strong> {{ user.sex|capitalize }}</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
{% if user.pictures %}
|
{% if user.pictures %}
|
||||||
<h3>Pictures</h3>
|
<section>
|
||||||
{% for pic in user.pictures %}
|
<h3>Gallery</h3>
|
||||||
<img src="{{ pic }}" width="100">
|
{% for pic in user.pictures %}
|
||||||
{% endfor %}
|
<img src="{{ pic }}" style="max-width:120px; margin:5px; border-radius:6px;">
|
||||||
|
{% endfor %}
|
||||||
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<h3>Personal Info</h3>
|
|
||||||
<p>Sex: {{ user.sex }}</p>
|
|
||||||
<p>Date of Birth: {{ user.date_of_birth }}</p>
|
|
||||||
<p>Age: {{ (date.today() - user.date_of_birth).days // 365 }}</p>
|
|
||||||
<p>Race: {{ user.race or 'Not specified' }}</p>
|
|
||||||
|
|
||||||
<h3>Location</h3>
|
<section>
|
||||||
<p>Country: {{ user.country }}</p>
|
<h3>Personal Info</h3>
|
||||||
<p>City: {{ user.city or 'Not specified' }}</p>
|
<p><strong>Date of Birth:</strong> {{ user.date_of_birth }}</p>
|
||||||
|
<p><strong>Race:</strong> {{ user.race or 'Not specified' }}</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
<h3>Physical Attributes</h3>
|
|
||||||
<p>Height: {{ user.height or 'Not specified' }} m</p>
|
|
||||||
<p>Weight: {{ user.weight or 'Not specified' }} kg</p>
|
|
||||||
|
|
||||||
<h3>Preferences</h3>
|
<section>
|
||||||
<p>Preferred Age Range: {{ user.prefered_age_range or 'Not specified' }}</p>
|
<h3>Location</h3>
|
||||||
<p>Likes: {{ user.likes | join(', ') if user.likes else 'Not specified' }}</p>
|
<p><strong>Country:</strong> {{ user.country }}</p>
|
||||||
<p>Dislikes: {{ user.dislikes | join(', ') if user.dislikes else 'Not specified' }}</p>
|
<p><strong>City:</strong> {{ user.city or 'Not specified' }}</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h3>Physical Attributes</h3>
|
||||||
|
<p><strong>Height:</strong>
|
||||||
|
{% if user.height %}
|
||||||
|
{{ user.height }} m
|
||||||
|
{% else %}
|
||||||
|
Not specified
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p><strong>Weight:</strong>
|
||||||
|
{% if user.weight %}
|
||||||
|
{{ user.weight }} kg
|
||||||
|
{% else %}
|
||||||
|
Not specified
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h3>Preferences</h3>
|
||||||
|
|
||||||
|
<p><strong>Preferred Age Range:</strong>
|
||||||
|
{{ user.prefered_age_range or 'Not specified' }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p><strong>Likes:</strong></p>
|
||||||
|
{% if user.likes %}
|
||||||
|
<ul>
|
||||||
|
{% for like in user.likes %}
|
||||||
|
<li>{{ like }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<p>Not specified</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<p><strong>Dislikes:</strong></p>
|
||||||
|
{% if user.dislikes %}
|
||||||
|
<ul>
|
||||||
|
{% for dislike in user.dislikes %}
|
||||||
|
<li>{{ dislike }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<p>Not specified</p>
|
||||||
|
{% endif %}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h3>Contacts</h3>
|
||||||
|
<p><strong>XMPP:</strong> {{ user.xmpp }}</p>
|
||||||
|
<p><strong>Email:</strong> {{ user.email or 'Not specified' }}</p>
|
||||||
|
<p><strong>Phone:</strong> {{ user.phone or 'Not specified' }}</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
<h3>Contacts</h3>
|
|
||||||
<p>XMPP: {{ user.xmpp }}</p>
|
|
||||||
<p>Email: {{ user.email or 'Not specified' }}</p>
|
|
||||||
<p>Phone: {{ user.phone or 'Not specified' }}</p>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
BIN
static/uploads/bacalhau/DSCF6163.JPG
Normal file
|
After Width: | Height: | Size: 5.1 MiB |
BIN
static/uploads/bacalhau/DSCF6169.JPG
Normal file
|
After Width: | Height: | Size: 5.1 MiB |
BIN
static/uploads/bacalhau/DSCF6170.JPG
Normal file
|
After Width: | Height: | Size: 5.2 MiB |
BIN
static/uploads/bacalhau/DSCF6171.JPG
Normal file
|
After Width: | Height: | Size: 5.3 MiB |
BIN
static/uploads/bacalhau/DSCF6236.JPG
Normal file
|
After Width: | Height: | Size: 3.4 MiB |
BIN
static/uploads/bacalhau/DSCF6238.JPG
Normal file
|
After Width: | Height: | Size: 3.2 MiB |
BIN
static/uploads/bacalhau/DSCF6242.JPG
Normal file
|
After Width: | Height: | Size: 3.2 MiB |