diff --git a/app/auth/models.py b/app/auth/models.py index 0d59736d..c38590ea 100644 --- a/app/auth/models.py +++ b/app/auth/models.py @@ -58,7 +58,7 @@ def invalid_user_name(user_name: str) -> bool: ) -class User(UserMixin, db.Model, ScoDocModel): +class User(UserMixin, ScoDocModel): """ScoDoc users, handled by Flask / SQLAlchemy""" id = db.Column(db.Integer, primary_key=True) diff --git a/app/models/__init__.py b/app/models/__init__.py index ed0b3bac..d5b88d12 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -23,8 +23,17 @@ convention = { metadata_obj = sqlalchemy.MetaData(naming_convention=convention) -class ScoDocModel: - "Mixin class for our models. Add somme useful methods for editing, cloning, etc." +class ScoDocModel(db.Model): + """Superclass for our models. Add some useful methods for editing, cloning, etc. + - clone() : clone object and add copy to session, do not commit. + - create_from_dict() : create instance from given dict, applying conversions. + - convert_dict_fields() : convert dict values, called before instance creation. + By default, do nothing. + - from_dict() : update object using data from dict. data is first converted. + - edit() : update from wtf form. + """ + + __abstract__ = True # declare an abstract class for SQLAlchemy def clone(self, not_copying=()): """Clone, not copying the given attrs @@ -40,13 +49,19 @@ class ScoDocModel: return copy @classmethod - def create_from_dict(cls, data: dict): + def create_from_dict(cls, data: dict) -> "ScoDocModel": """Create a new instance of the model with attributes given in dict. The instance is added to the session (but not flushed nor committed). Use only relevant arributes for the given model and ignore others. """ - args = cls.convert_dict_fields(cls.filter_model_attributes(data)) - obj = cls(**args) + if data: + args = cls.convert_dict_fields(cls.filter_model_attributes(data)) + if args: + obj = cls(**args) + else: + obj = cls() + else: + obj = cls() db.session.add(obj) return obj @@ -79,15 +94,27 @@ class ScoDocModel: # virtual, by default, do nothing return args - def from_dict(self, args: dict, excluded: set[str] | None = None): - "Update object's fields given in dict. Add to session but don't commit." + def from_dict(self, args: dict, excluded: set[str] | None = None) -> bool: + """Update object's fields given in dict. Add to session but don't commit. + True if modification. + """ args_dict = self.convert_dict_fields( self.filter_model_attributes(args, excluded=excluded) ) + modified = False for key, value in args_dict.items(): - if hasattr(self, key): + if hasattr(self, key) and value != getattr(self, key): setattr(self, key, value) + modified = True db.session.add(self) + return modified + + def edit_from_form(self, form) -> bool: + """Generic edit method for updating model instance. + True if modification. + """ + args = {field.name: field.data for field in form} + return self.from_dict(args) from app.models.absences import Absence, AbsenceNotification, BilletAbsence diff --git a/app/models/etudiants.py b/app/models/etudiants.py index 4340b85b..df2235a6 100644 --- a/app/models/etudiants.py +++ b/app/models/etudiants.py @@ -23,7 +23,7 @@ from app.scodoc.sco_exceptions import ScoInvalidParamError, ScoValueError import app.scodoc.sco_utils as scu -class Identite(db.Model, models.ScoDocModel): +class Identite(models.ScoDocModel): """étudiant""" __tablename__ = "identite" @@ -798,7 +798,7 @@ def pivot_year(y) -> int: return y -class Adresse(db.Model, models.ScoDocModel): +class Adresse(models.ScoDocModel): """Adresse d'un étudiant (le modèle permet plusieurs adresses, mais l'UI n'en gère qu'une seule) """ @@ -834,7 +834,7 @@ class Adresse(db.Model, models.ScoDocModel): return e -class Admission(db.Model, models.ScoDocModel): +class Admission(models.ScoDocModel): """Informations liées à l'admission d'un étudiant""" __tablename__ = "admissions" diff --git a/app/models/groups.py b/app/models/groups.py index dd89109b..4df0b5d8 100644 --- a/app/models/groups.py +++ b/app/models/groups.py @@ -19,7 +19,7 @@ from app.scodoc import sco_utils as scu from app.scodoc.sco_exceptions import AccessDenied, ScoValueError -class Partition(db.Model, ScoDocModel): +class Partition(ScoDocModel): """Partition: découpage d'une promotion en groupes""" __table_args__ = (db.UniqueConstraint("formsemestre_id", "partition_name"),) @@ -205,7 +205,7 @@ class Partition(db.Model, ScoDocModel): return group -class GroupDescr(db.Model, ScoDocModel): +class GroupDescr(ScoDocModel): """Description d'un groupe d'une partition""" __tablename__ = "group_descr"