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_sqlalchemy import SQLAlchemy
|
||||
from sqlalchemy import text
|
||||
from datetime import date
|
||||
import gnupg
|
||||
import secrets
|
||||
import os
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
# defines where the upload folder is and creates it
|
||||
UPLOAD_FOLDER = "static/uploads"
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
UPLOAD_FOLDER = os.path.join(BASE_DIR, "static", "uploads")
|
||||
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
||||
|
||||
# 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))
|
||||
|
||||
# saves files to the upload folder and returns their URL
|
||||
|
||||
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)
|
||||
os.makedirs(user_folder, exist_ok=True)
|
||||
|
||||
# prevents unsafe characters to be used in the filename
|
||||
profile_filename = secure_filename(profile_file.filename)
|
||||
profile_path = os.path.join(user_folder, profile_filename)
|
||||
|
||||
# saves the profile picture to the 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 = []
|
||||
for pic in pictures_files:
|
||||
if pic.filename:
|
||||
filename = secure_filename(pic.filename)
|
||||
path = os.path.join(user_folder, filename)
|
||||
pic.save(path)
|
||||
pictures_urls.append(f"/{path.replace(os.sep, '/')}")
|
||||
pictures_urls.append(f"/static/uploads/{username}/{filename}")
|
||||
|
||||
return profile_url, pictures_urls
|
||||
|
||||
|
|
@ -119,8 +117,58 @@ def pgp_encrypt_and_import(pgp_key: str, message: str):
|
|||
|
||||
@app.route("/")
|
||||
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"])
|
||||
def register():
|
||||
|
|
|
|||
|
|
@ -1,5 +1,47 @@
|
|||
{% extends "page.html" %}
|
||||
|
||||
{% 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 %}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,7 @@
|
|||
</nav>
|
||||
|
||||
<body>
|
||||
<section>
|
||||
{% block content %}{% endblock %}
|
||||
</section>
|
||||
<footer>Dating Website</footer>
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -1,37 +1,97 @@
|
|||
{% extends "page.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<section>
|
||||
<h2>{{ user.firstname }} {{ user.lastname }}</h2>
|
||||
<img src="{{ user.profile_picture }}" alt="Profile Picture" width="150"><br>
|
||||
|
||||
<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 %}
|
||||
<h3>Pictures</h3>
|
||||
<section>
|
||||
<h3>Gallery</h3>
|
||||
{% for pic in user.pictures %}
|
||||
<img src="{{ pic }}" width="100">
|
||||
<img src="{{ pic }}" style="max-width:120px; margin:5px; border-radius:6px;">
|
||||
{% endfor %}
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<section>
|
||||
<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>
|
||||
<p><strong>Date of Birth:</strong> {{ user.date_of_birth }}</p>
|
||||
<p><strong>Race:</strong> {{ user.race or 'Not specified' }}</p>
|
||||
</section>
|
||||
|
||||
|
||||
<section>
|
||||
<h3>Location</h3>
|
||||
<p>Country: {{ user.country }}</p>
|
||||
<p>City: {{ user.city or 'Not specified' }}</p>
|
||||
<p><strong>Country:</strong> {{ user.country }}</p>
|
||||
<p><strong>City:</strong> {{ user.city or 'Not specified' }}</p>
|
||||
</section>
|
||||
|
||||
|
||||
<section>
|
||||
<h3>Physical Attributes</h3>
|
||||
<p>Height: {{ user.height or 'Not specified' }} m</p>
|
||||
<p>Weight: {{ user.weight or 'Not specified' }} kg</p>
|
||||
<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>Preferred Age Range: {{ user.prefered_age_range or 'Not specified' }}</p>
|
||||
<p>Likes: {{ user.likes | join(', ') if user.likes else 'Not specified' }}</p>
|
||||
<p>Dislikes: {{ user.dislikes | join(', ') if user.dislikes else 'Not specified' }}</p>
|
||||
|
||||
<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>XMPP: {{ user.xmpp }}</p>
|
||||
<p>Email: {{ user.email or 'Not specified' }}</p>
|
||||
<p>Phone: {{ user.phone or 'Not specified' }}</p>
|
||||
<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>
|
||||
|
||||
{% 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 |