form edit user
This commit is contained in:
parent
fe9e5e84b0
commit
3f90b71009
@ -8,6 +8,7 @@ from datetime import datetime, timedelta
|
||||
from hashlib import md5
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from time import time
|
||||
|
||||
from flask import current_app, url_for, g
|
||||
@ -18,6 +19,7 @@ import jwt
|
||||
|
||||
from app import db, login
|
||||
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_roles_default import SCO_ROLES_DEFAULTS
|
||||
import app.scodoc.sco_utils as scu
|
||||
@ -58,7 +60,7 @@ class User(UserMixin, db.Model):
|
||||
and self.email == current_app.config["SCODOC_ADMIN_MAIL"]
|
||||
):
|
||||
# super-admin
|
||||
admin_role = Role.query.filter_by(name="Admin").first()
|
||||
admin_role = Role.query.filter_by(name="SuperAdmin").first()
|
||||
assert admin_role
|
||||
self.add_role(admin_role, None)
|
||||
db.session.commit()
|
||||
@ -123,7 +125,7 @@ class User(UserMixin, db.Model):
|
||||
"last_seen": self.last_seen.isoformat() + "Z",
|
||||
"nom": (self.nom or "").encode("utf-8"), # sco8
|
||||
"prenom": (self.prenom or "").encode("utf-8"), # sco8
|
||||
"roles_string": self.get_roles_string(),
|
||||
"roles_string": self.get_roles_string(), # eg "Ens_RT, Ens_Info"
|
||||
"user_name": self.user_name.encode("utf-8"), # sco8
|
||||
}
|
||||
if include_email:
|
||||
@ -131,11 +133,24 @@ class User(UserMixin, db.Model):
|
||||
return data
|
||||
|
||||
def from_dict(self, data, new_user=False):
|
||||
for field in ["user_name", "non", "prenom", "dept", "status", "email"]:
|
||||
"""Set users' attributes from given dict values.
|
||||
Roles must be encodes as "roles_string", like "Ens_RT, Secr_CJ"
|
||||
"""
|
||||
for field in ["nom", "prenom", "dept", "status", "email"]:
|
||||
if field in data:
|
||||
setattr(self, field, data[field])
|
||||
if new_user and "password" in data:
|
||||
self.set_password(data["password"])
|
||||
if new_user:
|
||||
if "user_name" in data:
|
||||
# never change name of existing users
|
||||
self.user_name = data["user_name"]
|
||||
if "password" in data:
|
||||
self.set_password(data["password"])
|
||||
# Roles: roles_string is "Ens_RT, Secr_RT, ..."
|
||||
if "roles_string" in data:
|
||||
self.user_roles = []
|
||||
for r_d in data["roles_string"].split(","):
|
||||
role, dept = UserRole.role_dept_from_string(r_d)
|
||||
self.add_role(role, dept)
|
||||
|
||||
def get_token(self, expires_in=3600):
|
||||
now = datetime.utcnow()
|
||||
@ -163,7 +178,7 @@ class User(UserMixin, db.Model):
|
||||
|
||||
Args:
|
||||
perm: integer, one of the value defined in Permission class.
|
||||
dept: dept id (eg 'RT')
|
||||
dept: dept id (eg 'RT'), default to current departement.
|
||||
"""
|
||||
if not self.active:
|
||||
return False
|
||||
@ -195,20 +210,23 @@ class User(UserMixin, db.Model):
|
||||
self.add_role(role, dept)
|
||||
|
||||
def set_roles(self, roles, dept):
|
||||
"set roles in the given dept"
|
||||
self.user_roles = [UserRole(user=self, role=r, dept=dept) for r in roles]
|
||||
|
||||
def get_roles(self):
|
||||
"iterator on my roles"
|
||||
for role in self.roles:
|
||||
yield role
|
||||
|
||||
def get_roles_string(self):
|
||||
"""string repr. of user's roles (with depts)
|
||||
e.g. "EnsRT, EnsInfo, SecrCJ"
|
||||
e.g. "Ens_RT, Ens_Info, Secr_CJ"
|
||||
"""
|
||||
return ", ".join("{r.role.name}{r.dept}".format(r=r) for r in self.user_roles)
|
||||
return ",".join("{r.role.name}_{r.dept}".format(r=r) for r in self.user_roles)
|
||||
|
||||
def is_administrator(self):
|
||||
return self.active and self.has_permission(Permission.ScoSuperAdmin, None)
|
||||
"True if i'm an active SuperAdmin"
|
||||
return self.active and self.has_permission(Permission.ScoSuperAdmin, dept=None)
|
||||
|
||||
# Some useful strings:
|
||||
def get_nomplogin(self):
|
||||
@ -311,6 +329,21 @@ class UserRole(db.Model):
|
||||
def __repr__(self):
|
||||
return "<UserRole u={} r={} dept={}>".format(self.user, self.role, self.dept)
|
||||
|
||||
@staticmethod
|
||||
def role_dept_from_string(role_dept):
|
||||
"""Return tuple (role, dept) from the string
|
||||
role_dept, of the forme "Role_Dept".
|
||||
role is a Role instance, dept is a string.
|
||||
"""
|
||||
fields = role_dept.split("_", 1) # maxsplit=1, le dept peut contenir un "_"
|
||||
if len(fields) != 2:
|
||||
raise ScoValueError("Invalid role_dept")
|
||||
role_name, dept = fields
|
||||
role = Role.query.filter_by(name=role_name).first()
|
||||
if role is None:
|
||||
raise ScoValueError("role %s does not exists" % role_name)
|
||||
return (role, dept)
|
||||
|
||||
|
||||
@login.user_loader
|
||||
def load_user(id):
|
||||
|
@ -51,7 +51,7 @@ class ZRequest(object):
|
||||
self.AUTHENTICATED_USER = current_user
|
||||
self.REMOTE_ADDR = request.remote_addr
|
||||
if request.method == "POST":
|
||||
self.form = request.form # xxx encode en utf-8 !
|
||||
# self.form = request.form # xxx encode en utf-8 !
|
||||
# Encode en utf-8 pour ScoDoc8 #sco8
|
||||
self.form = {k: v.encode("utf-8") for (k, v) in request.form.items()}
|
||||
if request.files:
|
||||
@ -59,6 +59,10 @@ class ZRequest(object):
|
||||
# request.form is a werkzeug.datastructures.ImmutableMultiDict
|
||||
# self.form = self.form.copy()
|
||||
self.form.update(request.files)
|
||||
# self.cf = request.form.copy()
|
||||
for k in request.form:
|
||||
if k.endswith(":list"):
|
||||
self.form[k[:-5]] = request.form.getlist(k)
|
||||
elif request.method == "GET":
|
||||
# Encode en utf-8 pour ScoDoc8 #sco8
|
||||
self.form = {k: v.encode("utf-8") for (k, v) in request.args.items()}
|
||||
|
@ -37,6 +37,7 @@ from app.scodoc.notes_log import log
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError, ScoException
|
||||
from app.scodoc import sco_excel
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_users
|
||||
|
||||
TITLES = ("user_name", "nom", "prenom", "email", "roles", "dept")
|
||||
|
||||
@ -112,7 +113,7 @@ def import_users(U, auth_dept="", context=None):
|
||||
created = [] # liste de uid créés
|
||||
try:
|
||||
for u in U:
|
||||
ok, msg = context._check_modif_user(
|
||||
ok, msg = sco_users.check_modif_user(
|
||||
0,
|
||||
user_name=u["user_name"],
|
||||
nom=u["nom"],
|
||||
|
@ -42,6 +42,7 @@ from flask_login import current_user
|
||||
|
||||
import cracklib # pylint: disable=import-error
|
||||
|
||||
from app import db
|
||||
|
||||
from app.auth.models import Permission
|
||||
from app.auth.models import User
|
||||
@ -50,7 +51,6 @@ from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_excel
|
||||
from app.scodoc import sco_import_users
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc.notes_log import log
|
||||
@ -164,7 +164,7 @@ def list_users(
|
||||
users = get_user_list(dept=dept, with_inactives=with_inactives)
|
||||
comm = "dept. %s" % dept.encode(scu.SCO_ENCODING) # sco8
|
||||
else:
|
||||
r = get_user_list(with_inactives=with_inactives)
|
||||
users = get_user_list(with_inactives=with_inactives)
|
||||
comm = "tous"
|
||||
if with_inactives:
|
||||
comm += ", avec anciens"
|
||||
@ -410,3 +410,62 @@ def user_info_page(context, user_name=None, REQUEST=None):
|
||||
% url_for("users.index_html", scodoc_dept=g.scodoc_dept)
|
||||
)
|
||||
return scu.sco8_join(H) + F
|
||||
|
||||
|
||||
def check_modif_user(edit, user_name="", nom="", prenom="", email="", roles=[]):
|
||||
"""Vérifie que cet utilisateur peut être créé (edit=0) ou modifié (edit=1)
|
||||
Cherche homonymes.
|
||||
returns (ok, msg)
|
||||
- ok : si vrai, peut continuer avec ces parametres
|
||||
(si ok est faux, l'utilisateur peut quand même forcer la creation)
|
||||
- msg: message warning a presenter l'utilisateur
|
||||
"""
|
||||
if not user_name or not nom or not prenom:
|
||||
return False, "champ requis vide"
|
||||
if not email:
|
||||
return False, "vous devriez indiquer le mail de l'utilisateur créé !"
|
||||
# ce login existe ?
|
||||
users = _user_list(user_name)
|
||||
if edit and not users: # safety net, le user_name ne devrait pas changer
|
||||
return False, "identifiant %s inexistant" % user_name
|
||||
if not edit and users:
|
||||
return False, "identifiant %s déjà utilisé" % user_name
|
||||
|
||||
# Des noms/prénoms semblables existent ?
|
||||
nom = nom.lower().strip()
|
||||
prenom = prenom.lower().strip()
|
||||
similar_users = User.query.filter(
|
||||
User.nom.ilike(nom), User.prenom.ilike(prenom)
|
||||
).all()
|
||||
if edit:
|
||||
minmatch = 1
|
||||
else:
|
||||
minmatch = 0
|
||||
if len(similar_users) > minmatch:
|
||||
return (
|
||||
False,
|
||||
"des utilisateurs proches existent: "
|
||||
+ ", ".join(
|
||||
[
|
||||
"%s %s (pseudo=%s)" % (x.prenom, x.nom, x.user_name)
|
||||
for x in similar_users
|
||||
]
|
||||
),
|
||||
)
|
||||
# Roles ?
|
||||
if not roles:
|
||||
return False, "aucun rôle sélectionné, êtes vous sûr ?"
|
||||
# ok
|
||||
return True, ""
|
||||
|
||||
|
||||
def user_edit(user_name, vals):
|
||||
"""Edit the user specified by user_name
|
||||
(ported from Zope to SQLAlchemy, hence strange !)
|
||||
"""
|
||||
u = User.query.filter_by(user_name=user_name).first()
|
||||
if not u:
|
||||
raise ScoValueError("Invalid user_name")
|
||||
u.from_dict(vals)
|
||||
db.session.add(u)
|
||||
db.session.commit()
|
||||
|
@ -33,7 +33,7 @@ Vues s'appuyant sur auth et sco_users
|
||||
|
||||
Emmanuel Viennet, 2021
|
||||
"""
|
||||
|
||||
import re
|
||||
import jaxml
|
||||
|
||||
from flask import g
|
||||
@ -44,6 +44,8 @@ from app import db
|
||||
|
||||
from app.auth.models import Permission
|
||||
from app.auth.models import User
|
||||
from app.auth.models import Role
|
||||
from app.auth.models import UserRole
|
||||
from app.decorators import (
|
||||
scodoc7func,
|
||||
ScoDoc7Context,
|
||||
@ -57,9 +59,11 @@ from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_users
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc.notes_log import log
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||
from app.scodoc.sco_permissions_check import can_handle_passwd
|
||||
from app.scodoc.sco_exceptions import AccessDenied
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
|
||||
from app.views import users_bp as bp
|
||||
from scodoc_manager import sco_mgr
|
||||
|
||||
|
||||
context = ScoDoc7Context("users") # sco8
|
||||
@ -100,7 +104,10 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
|
||||
if edit:
|
||||
if not user_name:
|
||||
raise ValueError("missing argument: user_name")
|
||||
initvalues = sco_users._user_list(user_name)
|
||||
u = User.query.filter_by(user_name=user_name).first()
|
||||
if not u:
|
||||
raise ScoValueError("utilisateur inexistant")
|
||||
initvalues = u.to_dict()
|
||||
H.append("<h2>Modification de l'utilisateur %s</h2>" % user_name)
|
||||
else:
|
||||
H.append("<h2>Création d'un utilisateur</h2>")
|
||||
@ -110,42 +117,67 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
|
||||
H.append("""<p class="warning">Vous êtes super administrateur !</p>""")
|
||||
is_super_admin = True
|
||||
|
||||
# Noms de roles pouvant etre attribues aux utilisateurs via ce dialogue
|
||||
# si pas SuperAdmin, restreint aux rôles EnsX, SecrX, AdminX
|
||||
#
|
||||
editable_roles = Role.query.all()
|
||||
# Les rôles standards créés à l'initialisation de ScoDoc:
|
||||
standard_roles = [Role.get_named_role(r) for r in (u"Ens", u"Secr", u"Admin")]
|
||||
# Rôles pouvant etre attribués aux utilisateurs via ce dialogue:
|
||||
# si SuperAdmin, tous les rôles standards dans tous les départements
|
||||
# sinon, les départements dans lesquels l'utilisateur a le droit
|
||||
if is_super_admin:
|
||||
log("create_user_form called by %s (super admin)" % (current_user.user_name,))
|
||||
dept_ids = sco_mgr.get_dept_ids()
|
||||
else:
|
||||
editable_roles = [
|
||||
r for r in editable_roles if r.name in {u"Ens", u"Secr", u"Admin"}
|
||||
]
|
||||
# Si on n'est pas SuperAdmin, liste les départements dans lesquels on a la
|
||||
# permission ScoUsersAdmin
|
||||
dept_ids = sorted(
|
||||
set(
|
||||
[
|
||||
x.dept
|
||||
for x in UserRole.query.filter_by(user=current_user)
|
||||
if x.role.has_permission(Permission.ScoUsersAdmin) and x.dept
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
editable_roles_set = {(r, dept) for r in standard_roles for dept in dept_ids}
|
||||
#
|
||||
if not edit:
|
||||
submitlabel = "Créer utilisateur"
|
||||
orig_roles = set()
|
||||
else:
|
||||
submitlabel = "Modifier utilisateur"
|
||||
initvalues["roles"] = initvalues["roles"].split(",") or []
|
||||
orig_roles = set(initvalues["roles"])
|
||||
if initvalues["status"] == "old":
|
||||
editable_roles = set() # can't change roles of a disabled user
|
||||
if "roles_string" in initvalues:
|
||||
initvalues["roles"] = initvalues["roles_string"].split(",")
|
||||
else:
|
||||
initvalues["roles"] = []
|
||||
orig_roles = { # set des roles existants avant édition
|
||||
UserRole.role_dept_from_string(role_dept)
|
||||
for role_dept in initvalues["roles"]
|
||||
}
|
||||
if not initvalues["active"]:
|
||||
editable_roles_set = set() # can't change roles of a disabled user
|
||||
editable_roles_strings = {r.name + "_" + dept for (r, dept) in editable_roles_set}
|
||||
orig_roles_strings = {r.name + "_" + dept for (r, dept) in orig_roles}
|
||||
# add existing user roles
|
||||
displayed_roles = list(editable_roles.union(orig_roles))
|
||||
displayed_roles.sort()
|
||||
displayed_roles = list(editable_roles_set.union(orig_roles))
|
||||
displayed_roles.sort(key=lambda x: (x[1], x[0].name))
|
||||
displayed_roles_strings = [r.name + "_" + dept for (r, dept) in displayed_roles]
|
||||
displayed_roles_labels = [
|
||||
"{dept}: {r.name}".format(dept=dept, r=r) for (r, dept) in displayed_roles
|
||||
]
|
||||
disabled_roles = {} # pour desactiver les roles que l'on ne peut pas editer
|
||||
for i in range(len(displayed_roles)):
|
||||
if displayed_roles[i] not in editable_roles:
|
||||
for i in range(len(displayed_roles_strings)):
|
||||
if displayed_roles_strings[i] not in editable_roles_strings:
|
||||
disabled_roles[i] = True
|
||||
|
||||
# log('create_user_form: displayed_roles=%s' % displayed_roles)
|
||||
# stop() # XXX
|
||||
|
||||
descr = [
|
||||
("edit", {"input_type": "hidden", "default": edit}),
|
||||
("nom", {"title": "Nom", "size": 20, "allow_null": False}),
|
||||
("prenom", {"title": "Prénom", "size": 20, "allow_null": False}),
|
||||
]
|
||||
if auth_name != user_name: # no one can't change its own status
|
||||
if current_user.user_name != user_name:
|
||||
# no one can change its own status
|
||||
descr.append(
|
||||
(
|
||||
"status",
|
||||
@ -193,7 +225,7 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
|
||||
"user_name",
|
||||
{"input_type": "hidden", "default": initvalues["user_name"]},
|
||||
),
|
||||
("user_id", {"input_type": "hidden", "default": initvalues["user_id"]}),
|
||||
("user_name", {"input_type": "hidden", "default": initvalues["user_name"]}),
|
||||
]
|
||||
descr += [
|
||||
(
|
||||
@ -267,7 +299,8 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
|
||||
"title": "Rôles",
|
||||
"input_type": "checkbox",
|
||||
"vertical": True,
|
||||
"allowed_values": displayed_roles,
|
||||
"labels": displayed_roles_labels,
|
||||
"allowed_values": displayed_roles_strings,
|
||||
"disabled_items": disabled_roles,
|
||||
},
|
||||
),
|
||||
@ -282,14 +315,15 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
# stop() # XXX
|
||||
if "tf-submitted" in REQUEST.form and not "roles" in REQUEST.form:
|
||||
REQUEST.form["roles"] = []
|
||||
if "tf-submitted" in REQUEST.form:
|
||||
# Ajoute roles existants mais non modifiables (disabled dans le form)
|
||||
# orig_roles - editable_roles
|
||||
REQUEST.form["roles"] = list(
|
||||
set(REQUEST.form["roles"]).union(orig_roles - editable_roles)
|
||||
set(REQUEST.form["roles"]).union(
|
||||
orig_roles_strings - editable_roles_strings
|
||||
)
|
||||
)
|
||||
|
||||
tf = TrivialFormulator(
|
||||
@ -306,7 +340,7 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
|
||||
return REQUEST.RESPONSE.redirect(context.UsersURL())
|
||||
else:
|
||||
vals = tf[2]
|
||||
roles = set(vals["roles"]).intersection(editable_roles)
|
||||
roles = set(vals["roles"]).intersection(editable_roles_strings)
|
||||
if REQUEST.form.has_key("edit"):
|
||||
edit = int(REQUEST.form["edit"])
|
||||
else:
|
||||
@ -322,7 +356,7 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
|
||||
user_name = vals["user_name"]
|
||||
# ce login existe ?
|
||||
err = None
|
||||
users = _user_list(user_name)
|
||||
users = sco_users._user_list(user_name)
|
||||
if edit and not users: # safety net, le user_name ne devrait pas changer
|
||||
err = "identifiant %s inexistant" % user_name
|
||||
if not edit and users:
|
||||
@ -332,7 +366,8 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
|
||||
return "\n".join(H) + "\n" + tf[1] + F
|
||||
|
||||
if not force:
|
||||
ok, msg = context._check_modif_user(
|
||||
# XXX x = stop()
|
||||
ok, msg = sco_users.check_modif_user(
|
||||
edit,
|
||||
user_name=user_name,
|
||||
nom=vals["nom"],
|
||||
@ -350,7 +385,7 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
|
||||
|
||||
return "\n".join(H) + "\n" + tf[1] + F
|
||||
|
||||
if edit: # modif utilisateur (mais pas passwd)
|
||||
if edit: # modif utilisateur (mais pas passwd ni user_name !)
|
||||
if (not can_choose_dept) and vals.has_key("dept"):
|
||||
del vals["dept"]
|
||||
if vals.has_key("passwd"):
|
||||
@ -359,24 +394,24 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
|
||||
del vals["date_modif_passwd"]
|
||||
if vals.has_key("user_name"):
|
||||
del vals["user_name"]
|
||||
if (auth_name == user_name) and vals.has_key("status"):
|
||||
if (current_user.user_name == user_name) and vals.has_key("status"):
|
||||
del vals["status"] # no one can't change its own status
|
||||
|
||||
# traitement des roles: ne doit pas affecter les roles
|
||||
# que l'on en controle pas:
|
||||
for role in orig_roles:
|
||||
if role and not role in editable_roles:
|
||||
for role in orig_roles_strings: # { "Ens_RT", "Secr_CJ", ... }
|
||||
if role and not role in editable_roles_strings:
|
||||
roles.add(role)
|
||||
|
||||
vals["roles"] = ",".join(roles)
|
||||
vals["roles_string"] = ",".join(roles)
|
||||
|
||||
# ok, edit
|
||||
log("sco_users: editing %s by %s" % (user_name, auth_name))
|
||||
# log('sco_users: previous_values=%s' % initvalues)
|
||||
# log('sco_users: new_values=%s' % vals)
|
||||
context._user_edit(user_name, vals)
|
||||
log("sco_users: editing %s by %s" % (user_name, current_user.user_name))
|
||||
log("sco_users: previous_values=%s" % initvalues)
|
||||
log("sco_users: new_values=%s" % vals)
|
||||
sco_users.user_edit(user_name, vals)
|
||||
return REQUEST.RESPONSE.redirect(
|
||||
"userinfo?user_name=%s&head_message=Utilisateur %s modifié"
|
||||
"user_info_page?user_name=%s&head_message=Utilisateur %s modifié"
|
||||
% (user_name, user_name)
|
||||
)
|
||||
else: # creation utilisateur
|
||||
@ -399,8 +434,18 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
|
||||
if not can_choose_dept:
|
||||
vals["dept"] = auth_dept
|
||||
# ok, go
|
||||
log("sco_users: new_user %s by %s" % (vals["user_name"], auth_name))
|
||||
context.create_user(vals, REQUEST=REQUEST)
|
||||
log(
|
||||
"sco_users: new_user %s by %s"
|
||||
% (vals["user_name"], current_user.user_name)
|
||||
)
|
||||
u = User()
|
||||
u.from_dict(vals, new_user=True)
|
||||
db.session.add(u)
|
||||
db.session.commit()
|
||||
return REQUEST.RESPONSE.redirect(
|
||||
"user_info_page?user_name=%s&head_message=Nouvel utilisateur créé"
|
||||
% (user_name)
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/import_users_form")
|
||||
|
@ -59,8 +59,8 @@ class ScoDocManager:
|
||||
return self.dept_descriptions[dept_id].db_uri
|
||||
|
||||
def get_dept_ids(self):
|
||||
"get (unsorted) dept ids"
|
||||
return self.dept_descriptions.keys()
|
||||
"get (sorted) dept ids"
|
||||
return sorted(self.dept_descriptions.keys())
|
||||
|
||||
def get_db_uri(self):
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user