roundabout,
created on Saturday, 14 September 2024, 08:22:36 (1726302156),
received on Saturday, 14 September 2024, 09:36:45 (1726306605)
Author identity: vlad <vlad.muntoiu@gmail.com>
c692e16ebe9602c4a11fec8ba9f74abc4a1aceff
app.py
@@ -77,6 +77,7 @@ with app.app_context():
joined_timestamp = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
galleries = db.relationship("Gallery", back_populates="owner")
galleries_joined = db.relationship("UserInGallery", back_populates="user")
ratings = db.relationship("PictureRating", back_populates="user")
def __init__(self, username, password):
self.username = username
@@ -235,6 +236,7 @@ with app.app_context():
licences = db.relationship("PictureLicence", back_populates="resource")
galleries = db.relationship("PictureInGallery", back_populates="resource")
ratings = db.relationship("PictureRating", back_populates="resource")
def __init__(self, title, author, description, origin_url, licence_ids, mime,
nature=None):
@@ -267,6 +269,17 @@ with app.app_context():
region_row = PictureRegion(region_data, self, picture_object)
db.session.add(region_row)
@property
def average_rating(self):
if not self.ratings:
return None
return db.session.query(db.func.avg(PictureRating.rating)).filter_by(resource=self).scalar()
@property
def rating_totals(self):
all_ratings = db.session.query(PictureRating.rating).filter_by(resource=self)
return {rating: all_ratings.filter_by(rating=rating).count() for rating in range(1, 6)}
class PictureInGallery(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
@@ -310,6 +323,22 @@ with app.app_context():
self.owner = owner
class PictureRating(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
resource_id = db.Column(db.Integer, db.ForeignKey("picture_resource.id"), nullable=False)
username = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
rating = db.Column(db.Integer, db.CheckConstraint("rating >= 1 AND rating <= 5"),
nullable=False)
resource = db.relationship("PictureResource", back_populates="ratings")
user = db.relationship("User", back_populates="ratings")
def __init__(self, resource, user, rating):
self.resource = resource
self.user = user
self.rating = rating
@app.route("/")
def index():
return flask.render_template("home.html", resources=PictureResource.query.order_by(
@@ -509,10 +538,14 @@ def picture(id):
current_user = db.session.get(User, flask.session.get("username"))
have_permission = current_user and (current_user == resource.author or current_user.admin)
own_rating = None
if current_user:
own_rating = PictureRating.query.filter_by(resource=resource, user=current_user).first()
return flask.render_template("picture.html", resource=resource,
file_extension=mimetypes.guess_extension(resource.file_format),
size=image.size, copies=resource.copies,
have_permission=have_permission)
have_permission=have_permission, own_rating=own_rating)
@app.route("/picture/<int:id>/annotate")
@@ -702,6 +735,42 @@ def edit_picture(id):
PictureLicence=PictureLicence)
@app.route("/picture/<int:id>/rate", methods=["POST"])
def rate_picture(id):
resource = db.session.get(PictureResource, id)
if resource is None:
flask.abort(404)
current_user = db.session.get(User, flask.session.get("username"))
if current_user is None:
flask.abort(401)
rating = int(flask.request.form.get("rating"))
if not rating:
# Delete the existing rating
if PictureRating.query.filter_by(resource=resource, user=current_user).first():
db.session.delete(PictureRating.query.filter_by(resource=resource,
user=current_user).first())
db.session.commit()
return flask.redirect("/picture/" + str(resource.id))
if not 1 <= rating <= 5:
flask.flash("Invalid rating")
return flask.redirect("/picture/" + str(resource.id))
if PictureRating.query.filter_by(resource=resource, user=current_user).first():
PictureRating.query.filter_by(resource=resource, user=current_user).first().rating = rating
else:
# Create a new rating
db.session.add(PictureRating(resource, current_user, rating))
db.session.commit()
return flask.redirect("/picture/" + str(resource.id))
@app.route("/picture/<int:id>/edit-metadata", methods=["POST"])
def edit_picture_post(id):
resource = db.session.get(PictureResource, id)
static/style.css
@@ -6,6 +6,8 @@
--text-soft: #000000C0;
--text-softer: #0000009A;
--text-faint: #00000066;
--text-softest: #00000033;
--color-star: #FFC107;
--color-shape-label: #0097A7; --color-shape-label-text: #ffffff;
--shadow-card-inset: inset 0 3px 6px -4px rgba(0, 0, 0, 0.12), inset 0 3px 6px 0 rgba(0, 0, 0, 0.24), inset 0 1px 4px 0 rgba(0, 0, 0, 0.12);
/*view-transition-name: root;*/
@@ -415,3 +417,35 @@ dd {
text-decoration: none;
color: var(--color-card-text);
}
.star-rating-container {
display: flex;
flex-direction: row-reverse;
gap: 0.5ch;
font-size: 1.25em;
align-items: center;
justify-content: flex-end; /* In this case, it means LEFT, not right, because of the row-reverse */
}
.star-rating-container > input {
width: 0 !important;
height: 0 !important;
visibility: hidden;
}
.star-rating-container > label {
cursor: pointer;
color: var(--text-softest);
transition: color 0.25s cubic-bezier(0.37, 0, 0.63, 1),
filter 0.25s cubic-bezier(0.37, 0, 0.63, 1);
}
.star-rating-container > label:hover,
.star-rating-container > label:hover ~ label {
color: var(--color-star);
filter: drop-shadow(0 0 3px #00000040);
}
.star-rating-container > input:checked ~ label {
color: var(--color-star);
}
templates/picture.html
@@ -91,6 +91,30 @@
{% set licences = resource.licences | map(attribute="licence") | list %}
{% set contains = resource.regions | map(attribute="object_id") | set | select | sort | list %}
<x-vbox>
<p>
{{ resource.rating_totals }} ratings, average {{ resource.average_rating }}
</p>
{% if current_user %}
<form id="rating-form" method="POST" action="/picture/{{ resource.id }}/rate">
<label>
<input name="rating" type="radio" value="0" {% if not own_rating.rating %}checked{% endif %}>
Clear rating
</label>
<div class="star-rating-container">
<input type="radio" id="stars-5" name="rating" value="5" title="Perfect" {% if own_rating.rating == 5 %}checked{% endif %}>
<label for="stars-5" tabindex="0"><iconify-icon icon="mdi:star" class="star">5 stars</iconify-icon></label>
<input type="radio" id="stars-4" name="rating" value="4" title="Good" {% if own_rating.rating == 4 %}checked{% endif %}>
<label for="stars-4" tabindex="0"><iconify-icon icon="mdi:star" class="star">4 stars</iconify-icon></label>
<input type="radio" id="stars-3" name="rating" value="3" title="OK" {% if own_rating.rating == 3 %}checked{% endif %}>
<label for="stars-3" tabindex="0"><iconify-icon icon="mdi:star" class="star">3 stars</iconify-icon></label>
<input type="radio" id="stars-2" name="rating" value="2" title="Poor" {% if own_rating.rating == 2 %}checked{% endif %}>
<label for="stars-2" tabindex="0"><iconify-icon icon="mdi:star" class="star">2 stars</iconify-icon></label>
<input type="radio" id="stars-1" name="rating" value="1" title="Awful" {% if own_rating.rating == 1 %}checked{% endif %}>
<label for="stars-1" tabindex="0"><iconify-icon icon="mdi:star" class="star">1 star</iconify-icon></label>
</div>
<button type="submit">Rate</button>
</form>
{% endif %}
<div class="icon-explainer">
<span>Type</span>
<span>{{ resource.nature.id }}</span>
@@ -175,6 +199,7 @@
</div>
</li>
{% endfor %}
</ul>
</x-vbox>
</x-frame>
{% endblock %}