feat(pets): adds register view and validations wip
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
from flask import request, render_template
|
from flask import request, render_template
|
||||||
from app.pets import bp
|
from app.pets import bp
|
||||||
from app.services.pet_service import PetService
|
from app.services.pet_service import PetService
|
||||||
|
from app.utils.helpers import login_required
|
||||||
|
|
||||||
@bp.route('/')
|
@bp.route('/')
|
||||||
def index():
|
def index():
|
||||||
@@ -8,4 +9,11 @@ def index():
|
|||||||
"pets/index.html",
|
"pets/index.html",
|
||||||
options=PetService.get_options(request),
|
options=PetService.get_options(request),
|
||||||
pagination_result=PetService.get_pets(request)
|
pagination_result=PetService.get_pets(request)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@bp.route('register', methods=["GET", "POST"])
|
||||||
|
def register():
|
||||||
|
if request.method == 'POST':
|
||||||
|
PetService.register_pet(request)
|
||||||
|
types = PetService.get_pets_kind()
|
||||||
|
return render_template("pets/register.html", types=types)
|
||||||
@@ -1,22 +1,34 @@
|
|||||||
from flask import Request
|
from flask import flash, Request
|
||||||
from app.extensions import db
|
from app.extensions import db
|
||||||
from app.models.pet import Pet
|
from app.models.pet import Pet
|
||||||
from app.models.pet_kind import PetKind
|
from app.models.pet_kind import PetKind
|
||||||
|
from app.utils.alert_type import AlertType
|
||||||
|
from app.utils.errors.pets.pet_register_errors import PetRegisterError
|
||||||
|
from app.utils.flash_message import FlashMessage
|
||||||
|
from app.utils.helpers import pet_sex_id_to_str
|
||||||
|
from app.utils.validators.pet_validators import PetValidators
|
||||||
class PetService:
|
class PetService:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_pets(request: Request,results_per_page=8):
|
def register_pet(request: Request):
|
||||||
def sex_n_to_str(sex: str):
|
"""Validations"""
|
||||||
if sex == '1':
|
try:
|
||||||
return 'F'
|
name = PetValidators.is_valid_name(request.form.get('name'))
|
||||||
elif sex == '2':
|
age = PetValidators.is_valid_age(request.form.get('age'))
|
||||||
return 'M'
|
sex = PetValidators.is_valid_sex(request.form.get('sex'))
|
||||||
else:
|
heigth = PetValidators.is_valid_height(request.form.get('height'))
|
||||||
return None
|
weight = PetValidators.is_valid_weight(request.form.get('weight'))
|
||||||
|
kind = PetValidators.is_valid_type(request.form.get('type'))
|
||||||
|
location = PetValidators.is_valid_location(request.form.get('location'))
|
||||||
|
"""uploading image to cloudinary"""
|
||||||
|
"""saving the data in the db"""
|
||||||
|
except PetRegisterError as e:
|
||||||
|
flash(FlashMessage(e.message, AlertType.DANGER.value ))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_pets(request: Request,results_per_page=8):
|
||||||
type = request.args.get('type')
|
type = request.args.get('type')
|
||||||
sex = sex_n_to_str(request.args.get('sex'))
|
sex = pet_sex_id_to_str(request.args.get('sex'))
|
||||||
age_from = request.args.get('age-from')
|
age_from = request.args.get('age-from')
|
||||||
age_to = request.args.get('age-to')
|
age_to = request.args.get('age-to')
|
||||||
query = db.select(Pet)
|
query = db.select(Pet)
|
||||||
@@ -61,6 +73,10 @@ class PetService:
|
|||||||
options["age_from"] = PetService.get_age_from_options(age_from)
|
options["age_from"] = PetService.get_age_from_options(age_from)
|
||||||
return options
|
return options
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_pets_kind():
|
||||||
|
return db.session.execute(db.select(PetKind)).scalars()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_pets_kind_options(selected_kind=None):
|
def get_pets_kind_options(selected_kind=None):
|
||||||
pet_kinds = db.session.execute(db.select(PetKind)).scalars()
|
pet_kinds = db.session.execute(db.select(PetKind)).scalars()
|
||||||
|
|||||||
12
app/static/css/pets-register.css
Normal file
12
app/static/css/pets-register.css
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
.pets-register {
|
||||||
|
max-width: 480px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pets-register__form-img-container {
|
||||||
|
max-height: 150px;
|
||||||
|
max-width: 150px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.pets-register__form-img:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
30
app/static/js/pet-form-img-updater.js
Normal file
30
app/static/js/pet-form-img-updater.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
(() => {
|
||||||
|
'use strict'
|
||||||
|
function readImage(file) {
|
||||||
|
if (!file.type || !file.type.match('image\/(png|jpeg|jpg|webp)')) {
|
||||||
|
alert("Invalid image format!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(file.size > 2097152) {
|
||||||
|
alert("File is too big!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const img = document.querySelector('#img-card');
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.addEventListener('load', (event) => {
|
||||||
|
const imgObj = new Image();
|
||||||
|
imgObj.src = event.target.result;
|
||||||
|
imgObj.onload = function(ev) {
|
||||||
|
if(ev.target.naturalWidth > 512 || ev.target.naturalHeight > 512) {
|
||||||
|
alert("Image max dimesions allowed is 512x512");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
img.src = event.target.result;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
document.querySelector('#img-selector')
|
||||||
|
.addEventListener('change', function(ev){readImage(ev.target.files[0])})
|
||||||
|
})()
|
||||||
152
app/templates/pets/register.html
Normal file
152
app/templates/pets/register.html
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
{% extends "layout/layout.html" %}
|
||||||
|
{% from "forms/submit-btn.html" import form_submit_button %}
|
||||||
|
{% from "layout/inner_header.html" import inner_header%}
|
||||||
|
{% block title %} Home {% endblock %}
|
||||||
|
{% block head %}
|
||||||
|
{{ super() }}
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/pets-register.css') }}">
|
||||||
|
{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="pets-register mx-auto my-auto">
|
||||||
|
<div class="my-3">
|
||||||
|
{{ inner_header("Pet Details") }}
|
||||||
|
</div>
|
||||||
|
<div class="row px-2">
|
||||||
|
{% include 'message.html' %}
|
||||||
|
</div>
|
||||||
|
<div class="row mt-2">
|
||||||
|
<form class="needs-validation" enctype="multipart/form-data" method="post" novalidate>
|
||||||
|
<div class="row">
|
||||||
|
<input
|
||||||
|
accept="image/png, image/jpeg, image/webp"
|
||||||
|
class="d-none"
|
||||||
|
id="img-selector"
|
||||||
|
name="img"
|
||||||
|
type="file"
|
||||||
|
/>
|
||||||
|
<div class="col-12 col-sm-4 mb-3 mb-sm-0">
|
||||||
|
<div class="card pets-register__form-img-container">
|
||||||
|
<label for="img-selector">
|
||||||
|
<img
|
||||||
|
alt="pet image"
|
||||||
|
class="d-block pets-register__form-img"
|
||||||
|
height="150"
|
||||||
|
id="img-card"
|
||||||
|
src="{{ url_for('static', filename='img/pet-placeholder.webp') }}"
|
||||||
|
title="Max file size 2MB, max dimensions 512x512"
|
||||||
|
width="150"
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-8">
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="input-group flex-nowrap">
|
||||||
|
<span class="input-group-text" id="pet-name-wrapping">Name</span>
|
||||||
|
<input
|
||||||
|
aria-describedby="pet-name-wrapping"
|
||||||
|
aria-label="Pet's name"
|
||||||
|
class="form-control"
|
||||||
|
name="name"
|
||||||
|
pattern="[A-Za-z ]{2,255}"
|
||||||
|
placeholder="Pet's name"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="input-group flex-nowrap">
|
||||||
|
<span class="input-group-text" id="pet-age-wrapping">Age</span>
|
||||||
|
<input
|
||||||
|
aria-describedby="pet-age-wrapping"
|
||||||
|
aria-label="Pet's age"
|
||||||
|
class="form-control"
|
||||||
|
name="age"
|
||||||
|
pattern="^[0-9]+$"
|
||||||
|
placeholder="yrs"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="input-group flex-nowrap">
|
||||||
|
<span class="input-group-text" id="pet-sex-wrapping">Sex</span>
|
||||||
|
<select aria-label="type select field for pet's sex" class="form-select" name="sex">
|
||||||
|
<option value="1">F</option>
|
||||||
|
<option value="2">M</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="input-group flex-nowrap">
|
||||||
|
<span class="input-group-text" id="pet-height-wrapping">height</span>
|
||||||
|
<input
|
||||||
|
aria-describedby="pet-height-wrapping"
|
||||||
|
aria-label="Pet's height"
|
||||||
|
class="form-control"
|
||||||
|
name="height"
|
||||||
|
pattern="^\d+[\.]?[\d]*$"
|
||||||
|
placeholder="ft"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="input-group flex-nowrap">
|
||||||
|
<span class="input-group-text" id="pet-weight-wrapping">Weight</span>
|
||||||
|
<input
|
||||||
|
aria-describedby="pet-weight-wrapping"
|
||||||
|
aria-label="Pet's weight"
|
||||||
|
class="form-control"
|
||||||
|
name="weight"
|
||||||
|
pattern="^\d+[\.]?[\d]*$"
|
||||||
|
placeholder="lbs"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-3 ps-sm-0">
|
||||||
|
<div class="col-5">
|
||||||
|
<div class="input-group flex-nowrap">
|
||||||
|
<span class="input-group-text" id="pet-type-wrapping">Type</span>
|
||||||
|
<select aria-label="type select field" class="form-select" name="type">
|
||||||
|
{% for type in types %}
|
||||||
|
<option value="{{type.id}}">{{type.name}}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-7">
|
||||||
|
<div class="input-group flex-nowrap">
|
||||||
|
<span class="input-group-text" id="pet-location-wrapping">Location</span>
|
||||||
|
<input
|
||||||
|
aria-describedby="pet-location-wrapping"
|
||||||
|
aria-label="Pet's location"
|
||||||
|
class="form-control"
|
||||||
|
name="location"
|
||||||
|
pattern="[A-Za-z ]{2,255}"
|
||||||
|
placeholder="Pet's location"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-12 d-flex justify-content-end">
|
||||||
|
{{ form_submit_button("Submit") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="{{ url_for('static', filename='js/validate-form.js') }}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/pet-form-img-updater.js') }}"></script>
|
||||||
|
{% endblock %}
|
||||||
4
app/utils/errors/pets/pet_register_errors.py
Normal file
4
app/utils/errors/pets/pet_register_errors.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
class PetRegisterError(Exception):
|
||||||
|
def __init__(self, message):
|
||||||
|
self.message = message
|
||||||
|
super().__init__(self.message)
|
||||||
@@ -15,3 +15,11 @@ def only_guests(html: str):
|
|||||||
if not session["id"]:
|
if not session["id"]:
|
||||||
return html
|
return html
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def pet_sex_id_to_str(sex: str):
|
||||||
|
if sex == '1':
|
||||||
|
return 'F'
|
||||||
|
elif sex == '2':
|
||||||
|
return 'M'
|
||||||
|
else:
|
||||||
|
return None
|
||||||
66
app/utils/validators/pet_validators.py
Normal file
66
app/utils/validators/pet_validators.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
from app.extensions import db
|
||||||
|
from app.models.pet_kind import PetKind
|
||||||
|
from app.utils.errors.pets.pet_register_errors import PetRegisterError
|
||||||
|
from app.utils.helpers import pet_sex_id_to_str
|
||||||
|
from app.utils.validators.validators import Validators
|
||||||
|
|
||||||
|
class PetValidators:
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_valid_name(name: str|None):
|
||||||
|
if Validators.is_valid_str_and_pattern(name,'^[A-Za-z ]{2,255}$'):
|
||||||
|
return name
|
||||||
|
raise PetRegisterError("Invalid pet name")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_valid_age(age: str|None):
|
||||||
|
try:
|
||||||
|
if int(age) >= 0:
|
||||||
|
return age
|
||||||
|
except:
|
||||||
|
raise PetRegisterError("Invalid age")
|
||||||
|
raise PetRegisterError("Invalid age")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_valid_weight(w: str|None):
|
||||||
|
if w is None:
|
||||||
|
return None
|
||||||
|
if Validators.is_valid_decimal(w) and float(w) >= 0:
|
||||||
|
return w
|
||||||
|
raise PetRegisterError("Invalid pet weight")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_valid_height(h: str|None):
|
||||||
|
if h is None:
|
||||||
|
return None
|
||||||
|
if Validators.is_valid_decimal(h) and float(h) >= 0:
|
||||||
|
return h
|
||||||
|
raise PetRegisterError("Invalid pet height")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_valid_sex(sex: str|None):
|
||||||
|
pet_sex = pet_sex_id_to_str(sex)
|
||||||
|
if pet_sex is None:
|
||||||
|
raise PetRegisterError("Invalid pet sex")
|
||||||
|
return pet_sex
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_valid_type(pet_type: str|None):
|
||||||
|
INVALID_MSG = "Invalid pet type"
|
||||||
|
if pet_type is None:
|
||||||
|
raise PetRegisterError(INVALID_MSG)
|
||||||
|
types = db.session.execute(db.select(PetKind)).scalars()
|
||||||
|
try:
|
||||||
|
for type in types:
|
||||||
|
if int(pet_type) == type.id:
|
||||||
|
return pet_type
|
||||||
|
except:
|
||||||
|
raise PetRegisterError(INVALID_MSG)
|
||||||
|
raise PetRegisterError(INVALID_MSG)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_valid_location(location: str|None):
|
||||||
|
if location is not None:
|
||||||
|
if Validators.is_valid_str_and_pattern(location,'^[A-Za-z ]{2,255}$'):
|
||||||
|
return location
|
||||||
|
raise PetRegisterError("Invalid location")
|
||||||
Reference in New Issue
Block a user