From ce44f884eb4cc7f61c2259c47beec16005b8995c Mon Sep 17 00:00:00 2001 From: TheoLechemia Date: Mon, 5 Aug 2024 17:49:06 +0200 Subject: [PATCH] change model -> drop v_regne + v_group2inpn + tests --- apptax/admin/admin_view.py | 53 ++++++++--------------------- apptax/admin/mixins.py | 50 +++++++++++++++++++++++++++ apptax/taxonomie/models.py | 18 +++++----- apptax/taxonomie/routesbiblistes.py | 12 +++---- apptax/taxonomie/routestaxref.py | 29 ++++++++-------- apptax/taxonomie/schemas.py | 16 --------- apptax/tests/fixtures.py | 8 ++--- apptax/tests/test_admin.py | 16 +++++---- apptax/tests/test_taxref.py | 4 +-- 9 files changed, 108 insertions(+), 98 deletions(-) create mode 100644 apptax/admin/mixins.py diff --git a/apptax/admin/admin_view.py b/apptax/admin/admin_view.py index 88bed887d..765737efe 100644 --- a/apptax/admin/admin_view.py +++ b/apptax/admin/admin_view.py @@ -13,6 +13,8 @@ from flask_admin.model.form import InlineFormAdmin from flask_admin.model.ajax import AjaxModelLoader, DEFAULT_PAGE_SIZE from flask_admin.contrib.sqla.filters import FilterEqual +from flask_admin.form.widgets import Select2Widget +from flask_admin.contrib.sqla.fields import QuerySelectField from flask_admin.base import expose @@ -23,7 +25,7 @@ from flask_admin.model.template import EndpointLinkRowAction, TemplateLinkRowAction from flask_admin.model.template import EndpointLinkRowAction, TemplateLinkRowAction -from sqlalchemy import or_, inspect, select, func, exists +from sqlalchemy import or_, inspect, select, exists from sqlalchemy.orm import undefer @@ -39,7 +41,6 @@ CorTaxonAttribut, TMedias, BibListes, - VMRegne, cor_nom_liste, ) from apptax.admin.utils import taxref_media_file_name, get_user_permission @@ -55,6 +56,7 @@ FilterMedia, FilterAttributes, ) +from apptax.admin.mixins import RegneAndGroupFormMixin log = logging.getLogger(__name__) @@ -156,7 +158,7 @@ def can_delete(self): form_excluded_columns = ["attributs"] -class BibListesView(FlaskAdminProtectedMixin, ModelView): +class BibListesView(FlaskAdminProtectedMixin, RegneAndGroupFormMixin, ModelView): @property def can_create(self): return self._can_action(6) @@ -186,21 +188,9 @@ def can_delete(self): TemplateLinkRowAction("custom_row_actions.truncate_bib_liste", "Effacer cd_nom liste"), ] - form_args = { - "regne": { - "query_factory": lambda: db.session.scalars( - select(VMRegne).where(VMRegne.regne.isnot(None)) - ) - } - } - create_template = "admin/edit_bib_list.html" + form_columns = ["code_liste", "nom_liste", "desc_liste", "regne", "group2_inpn"] - def on_model_change(self, form, model, is_created): - """ - Force None on empty string regne - """ - if model.regne and not model.regne.regne: - model.regne = None + create_template = "admin/edit_bib_list.html" def render(self, template, **kwargs): self.extra_js = [ @@ -331,6 +321,9 @@ def _apply_search(self, query, count_query, joins, count_joins, search): column_searchable_list = ["nom_complet", "cd_nom"] + # ATTENTION : les tests se basent sur les indices + # de ce tableau. Rajouter des filtres à la fin, ou changer les + # indice des filtres dans les tests (fonction `get_list`) column_filters = [ FilterEqual(Taxref.cd_nom, name="cd_nom"), FilterEqual(Taxref.cd_ref, name="cd_ref"), @@ -368,11 +361,11 @@ def _apply_search(self, query, count_query, joins, count_joins, search): def _get_theme_attributes(self, taxon): return ( db.session.query(BibThemes) - .filter(or_(BibAttributs.v_regne == taxon.regne, BibAttributs.v_regne == None)) + .filter(or_(BibAttributs.regne == taxon.regne, BibAttributs.regne == None)) .filter( or_( - BibAttributs.v_group2_inpn == taxon.group2_inpn, - BibAttributs.v_group2_inpn == None, + BibAttributs.group2_inpn == taxon.group2_inpn, + BibAttributs.group2_inpn == None, ) ) .order_by(BibAttributs.ordre) @@ -558,7 +551,7 @@ def get_list(self, query, offset=0, limit=DEFAULT_PAGE_SIZE): return Taxref.query.with_entities(Taxref.regne).distinct().all() -class BibAttributsView(FlaskAdminProtectedMixin, ModelView): +class BibAttributsView(FlaskAdminProtectedMixin, RegneAndGroupFormMixin, ModelView): form_base_class = TAdditionalAttributForm @@ -590,8 +583,6 @@ def can_delete(self): "group2_inpn", ) - create_template = "admin/edit_attr.html" - column_labels = { "desc_attribut": "Description", "regne": "Règne", @@ -609,6 +600,7 @@ def can_delete(self): json.loads(m.liste_valeur_attribut)["values"] ), } + create_template = "admin/edit_attr.html" def render(self, template, **kwargs): self.extra_js = [ @@ -617,13 +609,6 @@ def render(self, template, **kwargs): return super(BibAttributsView, self).render(template, **kwargs) - def on_model_change(self, form, model, is_created): - """ - Force None on empty string regne - """ - if model.regne and not model.regne.regne: - model.regne = None - form_choices = { "type_attribut": [ ("int", "int"), @@ -639,11 +624,3 @@ def on_model_change(self, form, model, is_created): ("text", "text"), ], } - - form_args = { - "regne": { - "query_factory": lambda: db.session.scalars( - select(VMRegne).where(VMRegne.regne.isnot(None)) - ) - } - } diff --git a/apptax/admin/mixins.py b/apptax/admin/mixins.py new file mode 100644 index 000000000..9e34c0c02 --- /dev/null +++ b/apptax/admin/mixins.py @@ -0,0 +1,50 @@ +from flask_admin.contrib.sqla.fields import QuerySelectField +from flask_admin.form.fields import Select2Field +from sqlalchemy import select + +from apptax.database import db +from apptax.taxonomie.models import VMRegne, VMGroup2Inpn + +from flask_admin.contrib.sqla.fields import QuerySelectField +from flask_admin.form.fields import Select2Field +from sqlalchemy import select + +from apptax.database import db +from apptax.taxonomie.models import VMRegne, VMGroup2Inpn + + +class RegneAndGroupFormMixin: + form_overrides = {"regne": QuerySelectField, "group2_inpn": QuerySelectField} + + form_args = { + "regne": { + "query_factory": lambda: db.session.scalars( + select(VMRegne).where(VMRegne.regne.isnot(None)) + ).all(), + "allow_blank": True, + }, + "group2_inpn": { + "query_factory": lambda: db.session.scalars( + select(VMGroup2Inpn).where(VMGroup2Inpn.group2_inpn.isnot(None)) + ), + "allow_blank": True, + }, + } + + def on_model_change(self, form, model, is_created): + """ + Force None on empty string regne + and put transform orm object in str + """ + # HACK otherwise QuerySelectField insert the VRegne object .. + # Select2Fields with choices does not work because choices list + # is load when app is loaded (its a probleme for migrations) + if model.regne: + model.regne = model.regne.regne + if model.regne == "": + model.regne = None + + if model.group2_inpn: + model.group2_inpn = model.group2_inpn.group2_inpn + if model.group2_inpn == "": + model.group2_inpn = None diff --git a/apptax/taxonomie/models.py b/apptax/taxonomie/models.py index 3756acffa..ced838c8c 100644 --- a/apptax/taxonomie/models.py +++ b/apptax/taxonomie/models.py @@ -21,6 +21,9 @@ class VMRegne(db.Model): def __repr__(self): return self.regne + def __str__(self): + return self.regne + @serializable class VMGroup2Inpn(db.Model): @@ -31,6 +34,9 @@ class VMGroup2Inpn(db.Model): def __repr__(self): return self.group2_inpn + def __str__(self): + return self.group2_inpn + @serializable class CorTaxonAttribut(db.Model): @@ -84,14 +90,14 @@ class BibAttributs(db.Model): desc_attribut = db.Column(db.Text) type_attribut = db.Column(db.Unicode) type_widget = db.Column(db.Unicode, nullable=False) - v_regne = db.Column( + regne = db.Column( db.Unicode, ForeignKey(VMRegne.regne), name="regne", nullable=True, primary_key=False, ) - v_group2_inpn = db.Column( + group2_inpn = db.Column( db.Unicode, ForeignKey(VMGroup2Inpn.group2_inpn), name="group2_inpn", @@ -106,8 +112,6 @@ class BibAttributs(db.Model): ) ordre = db.Column(db.Integer) theme = db.relationship(BibThemes) - regne = db.relationship(VMRegne) - group2_inpn = db.relationship(VMGroup2Inpn) def __repr__(self): return self.nom_attribut @@ -228,14 +232,14 @@ class BibListes(db.Model): code_liste = db.Column(db.Unicode) nom_liste = db.Column(db.Unicode) desc_liste = db.Column(db.Text) - v_regne = db.Column( + regne = db.Column( db.Unicode, ForeignKey(VMRegne.regne), name="regne", nullable=True, primary_key=False, ) - v_group2_inpn = db.Column( + group2_inpn = db.Column( db.Unicode, ForeignKey(VMGroup2Inpn.group2_inpn), name="group2_inpn", @@ -244,8 +248,6 @@ class BibListes(db.Model): ) noms = db.relationship("Taxref", secondary=cor_nom_liste, back_populates="listes") - regne = db.relationship("VMRegne") - group2_inpn = db.relationship("VMGroup2Inpn") @hybrid_property def nb_taxons(self): diff --git a/apptax/taxonomie/routesbiblistes.py b/apptax/taxonomie/routesbiblistes.py index 21b95f6f8..f70bd0d1d 100644 --- a/apptax/taxonomie/routesbiblistes.py +++ b/apptax/taxonomie/routesbiblistes.py @@ -30,9 +30,7 @@ def get_biblistes(id=None): et le nombre d'enregistrements dans "count" """ data = db.session.query(BibListes).all() - biblistes_schema = BibListesSchema( - exclude=("v_regne", "v_group2_inpn"), only=("+regne", "+group2_inpn") - ) + biblistes_schema = BibListesSchema() maliste = {"data": [], "count": 0} maliste["count"] = len(data) maliste["data"] = biblistes_schema.dump(data, many=True) @@ -44,13 +42,11 @@ def get_biblistes(id=None): def get_biblistesbyTaxref(regne, group2_inpn): q = db.session.query(BibListes) if regne: - q = q.where(BibListes.v_regne == regne) + q = q.where(BibListes.regne == regne) if group2_inpn: - q = q.where(BibListes.v_group2_inpn == group2_inpn) + q = q.where(BibListes.group2_inpn == group2_inpn) results = q.all() - return BibListesSchema( - exclude=("v_regne", "v_group2_inpn"), only=("+regne", "+group2_inpn") - ).dump(results, many=True) + return BibListesSchema().dump(results, many=True) @adresses.route("/cor_nom_liste", methods=["GET"]) diff --git a/apptax/taxonomie/routestaxref.py b/apptax/taxonomie/routestaxref.py index 0ee9770d4..912a5f251 100644 --- a/apptax/taxonomie/routestaxref.py +++ b/apptax/taxonomie/routestaxref.py @@ -1,7 +1,7 @@ from warnings import warn from flask import abort, jsonify, Blueprint, request -from sqlalchemy import distinct, desc, func, and_ +from sqlalchemy import distinct, desc, func, and_, select from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.orm import raiseload, joinedload, aliased @@ -312,22 +312,24 @@ def get_AllTaxrefNameByListe(id_liste): if id_liste == -1: id_liste = None - q = db.session.query(VMTaxrefListForautocomplete) + q = select(VMTaxrefListForautocomplete) + requested_id_list = None if id_liste: + requested_id_list = id_liste + elif request.args.get("code_liste"): + requested_id_list = ( + db.session.execute( + select(BibListes.id_liste).filter_by(code_liste=request.args.get("code_liste")) + ) + ).scalar() + if requested_id_list: q = q.join( BibListes, and_( BibListes.noms.any(cd_nom=VMTaxrefListForautocomplete.cd_nom), - BibListes.id_liste == id_liste, + BibListes.id_liste == requested_id_list, ), ) - elif request.args.get("code_liste"): - q = ( - db.session.query(BibListes.id_liste).filter( - BibListes.code_liste == request.args.get("code_liste") - ) - ).one() - id_liste = q[0] search_name = request.args.get("search_name") if search_name: @@ -368,12 +370,9 @@ def get_AllTaxrefNameByListe(id_liste): "offset is deprecated, please use page for pagination (start at 1)", DeprecationWarning ) page = (int(request.args["offset"]) / limit) + 1 - data = q.paginate(page=page, per_page=limit, error_out=False) + data = db.paginate(q, page=page, per_page=limit, error_out=False) - if search_name: - return [d[0].as_dict(exclude=["unaccent_search_name"]) for d in data.items] - else: - return [d.as_dict(exclude=["unaccent_search_name"]) for d in data.items] + return [d.as_dict(exclude=["unaccent_search_name"]) for d in data.items] @adresses.route("/bib_habitats", methods=["GET"]) diff --git a/apptax/taxonomie/schemas.py b/apptax/taxonomie/schemas.py index ba2a42a30..fcafe7014 100644 --- a/apptax/taxonomie/schemas.py +++ b/apptax/taxonomie/schemas.py @@ -8,8 +8,6 @@ from apptax.taxonomie.models import ( BibListes, - VMRegne, - VMGroup2Inpn, TMedias, BibTypesMedia, Taxref, @@ -34,25 +32,11 @@ class Meta: types = fields.Nested(BibTypesMediaSchema()) -class VMRegneSchema(SmartRelationshipsMixin, ma.SQLAlchemyAutoSchema): - class Meta: - model = VMRegne - include_fk = False - - -class VMGroup2Inpn(SmartRelationshipsMixin, ma.SQLAlchemyAutoSchema): - class Meta: - model = VMGroup2Inpn - include_fk = False - - class BibListesSchema(SmartRelationshipsMixin, ma.SQLAlchemyAutoSchema): class Meta: model = BibListes include_fk = True - regne = fields.Pluck(VMRegneSchema, "regne", many=False) - group2_inpn = fields.Pluck(VMGroup2Inpn, "group2_inpn", many=False) nb_taxons = fields.Integer() diff --git a/apptax/tests/fixtures.py b/apptax/tests/fixtures.py index 90b2ad13d..4cc72e506 100644 --- a/apptax/tests/fixtures.py +++ b/apptax/tests/fixtures.py @@ -49,8 +49,8 @@ def attribut_example(): type_attribut="varchar(50)", type_widget="select", id_theme=theme.id_theme, - # regne="Animalia", - # group2_inpn="Oiseaux", + regne="Animalia", + group2_inpn="Oiseaux", ordre=1, ) db.session.add(attribut) @@ -86,13 +86,13 @@ def liste(): "code_liste": "TEST_LIST_Animalia", "nom_liste": "Liste test Animalia", "desc_liste": "Liste description", - "v_regne": "Animalia", + "regne": "Animalia", }, { "code_liste": "TEST_LIST_Plantae", "nom_liste": "Liste test Plantae", "desc_liste": "Liste description", - "v_regne": "Plantea", + "regne": "Plantea", }, ] diff --git a/apptax/tests/test_admin.py b/apptax/tests/test_admin.py index 938052e16..0afacd895 100644 --- a/apptax/tests/test_admin.py +++ b/apptax/tests/test_admin.py @@ -23,12 +23,12 @@ "label_attribut": "Attribut test", "desc_attribut": "Description attribut test", "type_attribut": "varchar(250)", - "liste_valeur_attribut": "{'values':['val1','val2','val3']}", + "liste_valeur_attribut": '{"values":["val1","val2","val3"]}', "type_widget": "select", "ordre": 1, "theme": 1, - "regne": None, - "group2_inpn": None, + "regne": "", + "group2_inpn": "", } @@ -151,7 +151,9 @@ def test_filter_has_attr(self, noms_example): sort_column=None, sort_desc=None, search=None, - filters=[(7, "Attributs", "1")], + filters=[ + (9, "Attributs", "1") + ], # WARNING : le premier element du tuple est l'indice du tableau `column_filters` de la class Admin -> volatile ! ) nom_with_attr = set([tax.cd_nom for tax in noms_example if tax.attributs]) set_results = set([tax.cd_nom for tax in results]) @@ -179,7 +181,9 @@ def test_filter_list(self, noms_example, liste): sort_column=None, sort_desc=None, search=None, - filters=[(3, "Est dans la liste", str(liste.id_liste))], + filters=[ + (5, "Est dans la liste", str(liste.id_liste)) + ], # WARNING : le premier element du tuple est l'indice du tableau `column_filters` de la class Admin -> volatile ! ) cd_nom_in_list = set([tax.cd_nom for tax in noms_example]) cd_nom_results = set([tax.cd_nom for tax in results]) @@ -194,7 +198,7 @@ def test_filter_animalia(self): sort_column=None, sort_desc=None, search=None, - filters=[(0, "Règne", "Animalia")], + filters=[(2, "Règne", "Animalia")], ) for tax in results: assert tax.regne == "Animalia" diff --git a/apptax/tests/test_taxref.py b/apptax/tests/test_taxref.py index da28e035b..584d98cf9 100644 --- a/apptax/tests/test_taxref.py +++ b/apptax/tests/test_taxref.py @@ -90,10 +90,8 @@ class TestAPITaxref: ) def test_get_allnamebyListe_routes(self, liste): - query_string = {"limit": 10} response = self.client.get( - url_for("taxref.get_AllTaxrefNameByListe", code_liste=liste.code_liste), - query_string=query_string, + url_for("taxref.get_AllTaxrefNameByListe", code_liste=liste.code_liste, limit=10), ) assert response.status_code == 200 data = response.json