From 28c4e9697baa1347d93f40caed569d9ff3565cc5 Mon Sep 17 00:00:00 2001 From: Kevin Morris Date: Sat, 16 Oct 2021 19:25:25 -0700 Subject: [PATCH] change(fastapi): simplify model imports across code-base Closes: #133 Signed-off-by: Kevin Morris --- aurweb/asgi.py | 3 +- aurweb/auth.py | 3 +- aurweb/captcha.py | 2 +- aurweb/packages/search.py | 91 +++++++------- aurweb/packages/util.py | 94 +++++++-------- aurweb/routers/accounts.py | 139 ++++++++++----------- aurweb/routers/auth.py | 2 +- aurweb/routers/html.py | 61 +++++----- aurweb/routers/packages.py | 212 ++++++++++++++++----------------- aurweb/routers/rss.py | 3 +- aurweb/routers/trusted_user.py | 72 ++++++----- aurweb/util.py | 2 +- 12 files changed, 341 insertions(+), 343 deletions(-) diff --git a/aurweb/asgi.py b/aurweb/asgi.py index e892eb19..9b4f6c21 100644 --- a/aurweb/asgi.py +++ b/aurweb/asgi.py @@ -16,8 +16,7 @@ import aurweb.logging from aurweb.auth import BasicAuthBackend from aurweb.db import get_engine, query -from aurweb.models.accepted_term import AcceptedTerm -from aurweb.models.term import Term +from aurweb.models import AcceptedTerm, Term from aurweb.routers import accounts, auth, errors, html, packages, rpc, rss, sso, trusted_user # Setup the FastAPI app. diff --git a/aurweb/auth.py b/aurweb/auth.py index 52a4260c..6544dce2 100644 --- a/aurweb/auth.py +++ b/aurweb/auth.py @@ -14,9 +14,8 @@ from starlette.requests import HTTPConnection import aurweb.config from aurweb import l10n, util +from aurweb.models import Session, User from aurweb.models.account_type import ACCOUNT_TYPE_ID -from aurweb.models.session import Session -from aurweb.models.user import User from aurweb.templates import make_variable_context, render_template diff --git a/aurweb/captcha.py b/aurweb/captcha.py index 9451f42c..529b09e1 100644 --- a/aurweb/captcha.py +++ b/aurweb/captcha.py @@ -4,7 +4,7 @@ import hashlib from jinja2 import pass_context from aurweb.db import query -from aurweb.models.user import User +from aurweb.models import User def get_captcha_salts(): diff --git a/aurweb/packages/search.py b/aurweb/packages/search.py index 854834ee..e4729d89 100644 --- a/aurweb/packages/search.py +++ b/aurweb/packages/search.py @@ -1,13 +1,6 @@ from sqlalchemy import and_, case, or_, orm -from aurweb import config, db -from aurweb.models.package import Package -from aurweb.models.package_base import PackageBase -from aurweb.models.package_comaintainer import PackageComaintainer -from aurweb.models.package_keyword import PackageKeyword -from aurweb.models.package_notification import PackageNotification -from aurweb.models.package_vote import PackageVote -from aurweb.models.user import User +from aurweb import config, db, models DEFAULT_MAX_RESULTS = 2500 @@ -18,22 +11,22 @@ class PackageSearch: # A constant mapping of short to full name sort orderings. FULL_SORT_ORDER = {"d": "desc", "a": "asc"} - def __init__(self, user: User): + def __init__(self, user: models.User): """ Construct an instance of PackageSearch. This constructors performs several steps during initialization: 1. Setup self.query: an ORM query of Package joined by PackageBase. """ self.user = user - self.query = db.query(Package).join(PackageBase).join( - PackageVote, - and_(PackageVote.PackageBaseID == PackageBase.ID, - PackageVote.UsersID == self.user.ID), + self.query = db.query(models.Package).join(models.PackageBase).join( + models.PackageVote, + and_(models.PackageVote.PackageBaseID == models.PackageBase.ID, + models.PackageVote.UsersID == self.user.ID), isouter=True ).join( - PackageNotification, - and_(PackageNotification.PackageBaseID == PackageBase.ID, - PackageNotification.UserID == self.user.ID), + models.PackageNotification, + and_(models.PackageNotification.PackageBaseID == models.PackageBase.ID, + models.PackageNotification.UserID == self.user.ID), isouter=True ) self.ordering = "d" @@ -65,59 +58,64 @@ class PackageSearch: def _search_by_namedesc(self, keywords: str) -> orm.Query: self.query = self.query.filter( - or_(Package.Name.like(f"%{keywords}%"), - Package.Description.like(f"%{keywords}%")) + or_(models.Package.Name.like(f"%{keywords}%"), + models.Package.Description.like(f"%{keywords}%")) ) return self def _search_by_name(self, keywords: str) -> orm.Query: - self.query = self.query.filter(Package.Name.like(f"%{keywords}%")) + self.query = self.query.filter( + models.Package.Name.like(f"%{keywords}%")) return self def _search_by_exact_name(self, keywords: str) -> orm.Query: - self.query = self.query.filter(Package.Name == keywords) + self.query = self.query.filter( + models.Package.Name == keywords) return self def _search_by_pkgbase(self, keywords: str) -> orm.Query: - self.query = self.query.filter(PackageBase.Name.like(f"%{keywords}%")) + self.query = self.query.filter( + models.PackageBase.Name.like(f"%{keywords}%")) return self def _search_by_exact_pkgbase(self, keywords: str) -> orm.Query: - self.query = self.query.filter(PackageBase.Name == keywords) + self.query = self.query.filter( + models.PackageBase.Name == keywords) return self def _search_by_keywords(self, keywords: str) -> orm.Query: - self.query = self.query.join(PackageKeyword).filter( - PackageKeyword.Keyword == keywords + self.query = self.query.join(models.PackageKeyword).filter( + models.PackageKeyword.Keyword == keywords ) return self def _search_by_maintainer(self, keywords: str) -> orm.Query: self.query = self.query.join( - User, User.ID == PackageBase.MaintainerUID - ).filter(User.Username == keywords) + models.User, models.User.ID == models.PackageBase.MaintainerUID + ).filter(models.User.Username == keywords) return self def _search_by_comaintainer(self, keywords: str) -> orm.Query: - self.query = self.query.join(PackageComaintainer).join( - User, User.ID == PackageComaintainer.UsersID - ).filter(User.Username == keywords) + self.query = self.query.join(models.PackageComaintainer).join( + models.User, models.User.ID == models.PackageComaintainer.UsersID + ).filter(models.User.Username == keywords) return self def _search_by_co_or_maintainer(self, keywords: str) -> orm.Query: self.query = self.query.join( - PackageComaintainer, + models.PackageComaintainer, isouter=True ).join( - User, or_(User.ID == PackageBase.MaintainerUID, - User.ID == PackageComaintainer.UsersID) - ).filter(User.Username == keywords) + models.User, + or_(models.User.ID == models.PackageBase.MaintainerUID, + models.User.ID == models.PackageComaintainer.UsersID) + ).filter(models.User.Username == keywords) return self def _search_by_submitter(self, keywords: str) -> orm.Query: self.query = self.query.join( - User, User.ID == PackageBase.SubmitterUID - ).filter(User.Username == keywords) + models.User, models.User.ID == models.PackageBase.SubmitterUID + ).filter(models.User.Username == keywords) return self def search_by(self, search_by: str, keywords: str) -> orm.Query: @@ -128,17 +126,17 @@ class PackageSearch: return result def _sort_by_name(self, order: str): - column = getattr(Package.Name, order) + column = getattr(models.Package.Name, order) self.query = self.query.order_by(column()) return self def _sort_by_votes(self, order: str): - column = getattr(PackageBase.NumVotes, order) + column = getattr(models.PackageBase.NumVotes, order) self.query = self.query.order_by(column()) return self def _sort_by_popularity(self, order: str): - column = getattr(PackageBase.Popularity, order) + column = getattr(models.PackageBase.Popularity, order) self.query = self.query.order_by(column()) return self @@ -147,10 +145,10 @@ class PackageSearch: # in terms of performance. We should improve this; there's no # reason it should take _longer_. column = getattr( - case([(PackageVote.UsersID == self.user.ID, 1)], else_=0), + case([(models.PackageVote.UsersID == self.user.ID, 1)], else_=0), order ) - self.query = self.query.order_by(column(), Package.Name.desc()) + self.query = self.query.order_by(column(), models.Package.Name.desc()) return self def _sort_by_notify(self, order: str): @@ -158,21 +156,24 @@ class PackageSearch: # in terms of performance. We should improve this; there's no # reason it should take _longer_. column = getattr( - case([(PackageNotification.UserID == self.user.ID, 1)], else_=0), + case([(models.PackageNotification.UserID == self.user.ID, 1)], + else_=0), order ) - self.query = self.query.order_by(column(), Package.Name.desc()) + self.query = self.query.order_by(column(), models.Package.Name.desc()) return self def _sort_by_maintainer(self, order: str): - column = getattr(User.Username, order) + column = getattr(models.User.Username, order) self.query = self.query.join( - User, User.ID == PackageBase.MaintainerUID, isouter=True + models.User, + models.User.ID == models.PackageBase.MaintainerUID, + isouter=True ).order_by(column()) return self def _sort_by_last_modified(self, order: str): - column = getattr(PackageBase.ModifiedTS, order) + column = getattr(models.PackageBase.ModifiedTS, order) self.query = self.query.order_by(column()) return self diff --git a/aurweb/packages/util.py b/aurweb/packages/util.py index 2ea1155f..be351ebe 100644 --- a/aurweb/packages/util.py +++ b/aurweb/packages/util.py @@ -7,43 +7,35 @@ import orjson from fastapi import HTTPException from sqlalchemy import and_, orm -from aurweb import db -from aurweb.models.official_provider import OFFICIAL_BASE, OfficialProvider -from aurweb.models.package import Package -from aurweb.models.package_base import PackageBase -from aurweb.models.package_comment import PackageComment -from aurweb.models.package_dependency import PackageDependency -from aurweb.models.package_notification import PackageNotification -from aurweb.models.package_relation import PackageRelation -from aurweb.models.package_vote import PackageVote -from aurweb.models.relation_type import PROVIDES_ID, RelationType -from aurweb.models.user import User +from aurweb import db, models +from aurweb.models.official_provider import OFFICIAL_BASE +from aurweb.models.relation_type import PROVIDES_ID from aurweb.redis import redis_connection from aurweb.templates import register_filter -def dep_depends_extra(dep: PackageDependency) -> str: +def dep_depends_extra(dep: models.PackageDependency) -> str: """ A function used to produce extra text for dependency display. """ return str() -def dep_makedepends_extra(dep: PackageDependency) -> str: +def dep_makedepends_extra(dep: models.PackageDependency) -> str: """ A function used to produce extra text for dependency display. """ return "(make)" -def dep_checkdepends_extra(dep: PackageDependency) -> str: +def dep_checkdepends_extra(dep: models.PackageDependency) -> str: """ A function used to produce extra text for dependency display. """ return "(check)" -def dep_optdepends_extra(dep: PackageDependency) -> str: +def dep_optdepends_extra(dep: models.PackageDependency) -> str: """ A function used to produce extra text for dependency display. """ return "(optional)" @register_filter("dep_extra") -def dep_extra(dep: PackageDependency) -> str: +def dep_extra(dep: models.PackageDependency) -> str: """ Some dependency types have extra text added to their display. This function provides that output. However, it **assumes** that the dep passed is bound to a valid one @@ -53,7 +45,7 @@ def dep_extra(dep: PackageDependency) -> str: @register_filter("dep_extra_desc") -def dep_extra_desc(dep: PackageDependency) -> str: +def dep_extra_desc(dep: models.PackageDependency) -> str: extra = dep_extra(dep) if not dep.DepDesc: return extra @@ -63,30 +55,30 @@ def dep_extra_desc(dep: PackageDependency) -> str: @register_filter("pkgname_link") def pkgname_link(pkgname: str) -> str: base = "/".join([OFFICIAL_BASE, "packages"]) - official = db.query(OfficialProvider).filter( - OfficialProvider.Name == pkgname) + official = db.query(models.OfficialProvider).filter( + models.OfficialProvider.Name == pkgname) if official.scalar(): return f"{base}/?q={pkgname}" return f"/packages/{pkgname}" @register_filter("package_link") -def package_link(package: Package) -> str: +def package_link(package: models.Package) -> str: base = "/".join([OFFICIAL_BASE, "packages"]) - official = db.query(OfficialProvider).filter( - OfficialProvider.Name == package.Name) + official = db.query(models.OfficialProvider).filter( + models.OfficialProvider.Name == package.Name) if official.scalar(): return f"{base}/?q={package.Name}" return f"/packages/{package.Name}" @register_filter("provides_list") -def provides_list(package: Package, depname: str) -> list: - providers = db.query(Package).join( - PackageRelation).join(RelationType).filter( +def provides_list(package: models.Package, depname: str) -> list: + providers = db.query(models.Package).join( + models.PackageRelation).join(models.RelationType).filter( and_( - PackageRelation.RelName == depname, - RelationType.ID == PROVIDES_ID + models.PackageRelation.RelName == depname, + models.RelationType.ID == PROVIDES_ID ) ) @@ -102,7 +94,9 @@ def provides_list(package: Package, depname: str) -> list: return string -def get_pkg_or_base(name: str, cls: Union[Package, PackageBase] = PackageBase): +def get_pkg_or_base( + name: str, + cls: Union[models.Package, models.PackageBase] = models.PackageBase): """ Get a PackageBase instance by its name or raise a 404 if it can't be found in the database. @@ -110,20 +104,21 @@ def get_pkg_or_base(name: str, cls: Union[Package, PackageBase] = PackageBase): :raises HTTPException: With status code 404 if record doesn't exist :return: {Package,PackageBase} instance """ - provider = db.query(OfficialProvider).filter( - OfficialProvider.Name == name).first() + provider = db.query(models.OfficialProvider).filter( + models.OfficialProvider.Name == name).first() if provider: raise HTTPException(status_code=HTTPStatus.NOT_FOUND) instance = db.query(cls).filter(cls.Name == name).first() - if cls == PackageBase and not instance: + if cls == models.PackageBase and not instance: raise HTTPException(status_code=HTTPStatus.NOT_FOUND) return instance -def get_pkgbase_comment(pkgbase: PackageBase, id: int) -> PackageComment: - comment = pkgbase.comments.filter(PackageComment.ID == id).first() +def get_pkgbase_comment( + pkgbase: models.PackageBase, id: int) -> models.PackageComment: + comment = pkgbase.comments.filter(models.PackageComment.ID == id).first() if not comment: raise HTTPException(status_code=HTTPStatus.NOT_FOUND) return comment @@ -131,10 +126,11 @@ def get_pkgbase_comment(pkgbase: PackageBase, id: int) -> PackageComment: @register_filter("out_of_date") def out_of_date(packages: orm.Query) -> orm.Query: - return packages.filter(PackageBase.OutOfDateTS.isnot(None)) + return packages.filter(models.PackageBase.OutOfDateTS.isnot(None)) -def updated_packages(limit: int = 0, cache_ttl: int = 600) -> List[Package]: +def updated_packages(limit: int = 0, + cache_ttl: int = 600) -> List[models.Package]: """ Return a list of valid Package objects ordered by their ModifiedTS column in descending order from cache, after setting the cache when no key yet exists. @@ -149,10 +145,10 @@ def updated_packages(limit: int = 0, cache_ttl: int = 600) -> List[Package]: # If we already have a cache, deserialize it and return. return orjson.loads(packages) - query = db.query(Package).join(PackageBase).filter( - PackageBase.PackagerUID.isnot(None) + query = db.query(models.Package).join(models.PackageBase).filter( + models.PackageBase.PackagerUID.isnot(None) ).order_by( - PackageBase.ModifiedTS.desc() + models.PackageBase.ModifiedTS.desc() ) if limit: @@ -178,7 +174,8 @@ def updated_packages(limit: int = 0, cache_ttl: int = 600) -> List[Package]: return packages -def query_voted(query: List[Package], user: User) -> Dict[int, bool]: +def query_voted(query: List[models.Package], + user: models.User) -> Dict[int, bool]: """ Produce a dictionary of package base ID keys to boolean values, which indicate whether or not the package base has a vote record related to user. @@ -189,18 +186,19 @@ def query_voted(query: List[Package], user: User) -> Dict[int, bool]: """ output = defaultdict(bool) query_set = {pkg.PackageBase.ID for pkg in query} - voted = db.query(PackageVote).join( - PackageBase, - PackageBase.ID.in_(query_set) + voted = db.query(models.PackageVote).join( + models.PackageBase, + models.PackageBase.ID.in_(query_set) ).filter( - PackageVote.UsersID == user.ID + models.PackageVote.UsersID == user.ID ) for vote in voted: output[vote.PackageBase.ID] = True return output -def query_notified(query: List[Package], user: User) -> Dict[int, bool]: +def query_notified(query: List[models.Package], + user: models.User) -> Dict[int, bool]: """ Produce a dictionary of package base ID keys to boolean values, which indicate whether or not the package base has a notification record related to user. @@ -211,11 +209,11 @@ def query_notified(query: List[Package], user: User) -> Dict[int, bool]: """ output = defaultdict(bool) query_set = {pkg.PackageBase.ID for pkg in query} - notified = db.query(PackageNotification).join( - PackageBase, - PackageBase.ID.in_(query_set) + notified = db.query(models.PackageNotification).join( + models.PackageBase, + models.PackageBase.ID.in_(query_set) ).filter( - PackageNotification.UserID == user.ID + models.PackageNotification.UserID == user.ID ) for notify in notified: output[notify.PackageBase.ID] = True diff --git a/aurweb/routers/accounts.py b/aurweb/routers/accounts.py index 030ff651..a9257d3d 100644 --- a/aurweb/routers/accounts.py +++ b/aurweb/routers/accounts.py @@ -11,17 +11,12 @@ from sqlalchemy import and_, func, or_ import aurweb.config -from aurweb import db, l10n, time, util +from aurweb import db, l10n, models, time, util from aurweb.auth import account_type_required, auth_required from aurweb.captcha import get_captcha_answer, get_captcha_salts, get_captcha_token from aurweb.l10n import get_translator_for_request -from aurweb.models.accepted_term import AcceptedTerm -from aurweb.models.account_type import (DEVELOPER, DEVELOPER_ID, TRUSTED_USER, TRUSTED_USER_AND_DEV, TRUSTED_USER_AND_DEV_ID, - TRUSTED_USER_ID, USER_ID, AccountType) -from aurweb.models.ban import Ban -from aurweb.models.ssh_pub_key import SSHPubKey, get_fingerprint -from aurweb.models.term import Term -from aurweb.models.user import User +from aurweb.models import account_type +from aurweb.models.ssh_pub_key import get_fingerprint from aurweb.scripts.notify import ResetKeyNotification, WelcomeNotification from aurweb.templates import make_context, make_variable_context, render_template @@ -46,8 +41,8 @@ async def passreset_post(request: Request, context = await make_variable_context(request, "Password Reset") # The user parameter being required, we can match against - user = db.query(User, or_(User.Username == user, - User.Email == user)).first() + user = db.query(models.User, or_(models.User.Username == user, + models.User.Email == user)).first() if not user: context["errors"] = ["Invalid e-mail."] return render_template(request, "passreset.html", context, @@ -72,13 +67,13 @@ async def passreset_post(request: Request, return render_template(request, "passreset.html", context, status_code=HTTPStatus.BAD_REQUEST) - if len(password) < User.minimum_passwd_length(): + if len(password) < models.User.minimum_passwd_length(): # Translate the error here, which simplifies error output # in the jinja2 template. _ = get_translator_for_request(request) context["errors"] = [_( "Your password must be at least %s characters.") % ( - str(User.minimum_passwd_length()))] + str(models.User.minimum_passwd_length()))] return render_template(request, "passreset.html", context, status_code=HTTPStatus.BAD_REQUEST) @@ -95,7 +90,7 @@ async def passreset_post(request: Request, status_code=HTTPStatus.SEE_OTHER) # If we got here, we continue with issuing a resetkey for the user. - resetkey = db.make_random_value(User, User.ResetKey) + resetkey = db.make_random_value(models.User, models.User.ResetKey) with db.begin(): user.ResetKey = resetkey @@ -107,7 +102,7 @@ async def passreset_post(request: Request, status_code=HTTPStatus.SEE_OTHER) -def process_account_form(request: Request, user: User, args: dict): +def process_account_form(request: Request, user: models.User, args: dict): """ Process an account form. All fields are optional and only checks requirements in the case they are present. @@ -129,11 +124,11 @@ def process_account_form(request: Request, user: User, args: dict): _ = get_translator_for_request(request) host = request.client.host - ban = db.query(Ban, Ban.IPAddress == host).first() + ban = db.query(models.Ban, models.Ban.IPAddress == host).first() if ban: return False, [ - "Account registration has been disabled for your " + - "IP address, probably due to sustained spam attacks. " + + "Account registration has been disabled for your " + "IP address, probably due to sustained spam attacks. " "Sorry for the inconvenience." ] @@ -181,12 +176,12 @@ def process_account_form(request: Request, user: User, args: dict): timezone = args.get("TZ", None) def username_exists(username): - return and_(User.ID != user.ID, - func.lower(User.Username) == username.lower()) + return and_(models.User.ID != user.ID, + func.lower(models.User.Username) == username.lower()) def email_exists(email): - return and_(User.ID != user.ID, - func.lower(User.Email) == email.lower()) + return and_(models.User.ID != user.ID, + func.lower(models.User.Email) == email.lower()) if not util.valid_email(email): return False, ["The email address is invalid."] @@ -203,13 +198,13 @@ def process_account_form(request: Request, user: User, args: dict): return False, ["Language is not currently supported."] elif timezone and timezone not in time.SUPPORTED_TIMEZONES: return False, ["Timezone is not currently supported."] - elif db.query(User, username_exists(username)).first(): + elif db.query(models.User, username_exists(username)).first(): # If the username already exists... return False, [ _("The username, %s%s%s, is already in use.") % ( "", username, "") ] - elif db.query(User, email_exists(email)).first(): + elif db.query(models.User, email_exists(email)).first(): # If the email already exists... return False, [ _("The address, %s%s%s, is already in use.") % ( @@ -217,15 +212,16 @@ def process_account_form(request: Request, user: User, args: dict): ] def ssh_fingerprint_exists(fingerprint): - return and_(SSHPubKey.UserID != user.ID, - SSHPubKey.Fingerprint == fingerprint) + return and_(models.SSHPubKey.UserID != user.ID, + models.SSHPubKey.Fingerprint == fingerprint) if ssh_pubkey: fingerprint = get_fingerprint(ssh_pubkey.strip().rstrip()) if fingerprint is None: return False, ["The SSH public key is invalid."] - if db.query(SSHPubKey, ssh_fingerprint_exists(fingerprint)).first(): + if db.query(models.SSHPubKey, + ssh_fingerprint_exists(fingerprint)).first(): return False, [ _("The SSH public key, %s%s%s, is already in use.") % ( "", fingerprint, "") @@ -246,7 +242,7 @@ def process_account_form(request: Request, user: User, args: dict): def make_account_form_context(context: dict, request: Request, - user: User, + user: models.User, args: dict): """ Modify a FastAPI context and add attributes for the account form. @@ -382,20 +378,20 @@ async def account_register_post(request: Request, # Create a user with no password with a resetkey, then send # an email off about it. - resetkey = db.make_random_value(User, User.ResetKey) + resetkey = db.make_random_value(models.User, models.User.ResetKey) # By default, we grab the User account type to associate with. - account_type = db.query(AccountType, - AccountType.AccountType == "User").first() + atype = db.query(models.AccountType, + models.AccountType.AccountType == "User").first() # Create a user given all parameters available. with db.begin(): - user = db.create(User, Username=U, + user = db.create(models.User, Username=U, Email=E, HideEmail=H, BackupEmail=BE, RealName=R, Homepage=HP, IRCNick=I, PGPKey=K, LangPreference=L, Timezone=TZ, CommentNotify=CN, UpdateNotify=UN, OwnershipNotify=ON, - ResetKey=resetkey, AccountType=account_type) + ResetKey=resetkey, AccountType=atype) # If a PK was given and either one does not exist or the given # PK mismatches the existing user's SSHPubKey.PubKey. @@ -408,9 +404,9 @@ async def account_register_post(request: Request, pubkey = parts[0] + " " + parts[1] fingerprint = get_fingerprint(pubkey) with db.begin(): - user.ssh_pub_key = SSHPubKey(UserID=user.ID, - PubKey=pubkey, - Fingerprint=fingerprint) + user.ssh_pub_key = models.SSHPubKey(UserID=user.ID, + PubKey=pubkey, + Fingerprint=fingerprint) # Send a reset key notification to the new user. executor = db.ConnectionExecutor(db.get_engine().raw_connection()) @@ -435,7 +431,7 @@ def cannot_edit(request, user): @auth_required(True, redirect="/account/{username}") async def account_edit(request: Request, username: str): - user = db.query(User, User.Username == username).first() + user = db.query(models.User, models.User.Username == username).first() response = cannot_edit(request, user) if response: return response @@ -473,7 +469,8 @@ async def account_edit_post(request: Request, passwd: str = Form(default=str())): from aurweb.db import session - user = session.query(User).filter(User.Username == username).first() + user = session.query(models.User).filter( + models.User.Username == username).first() response = cannot_edit(request, user) if response: return response @@ -538,9 +535,9 @@ async def account_edit_post(request: Request, fingerprint = get_fingerprint(pubkey) if not user.ssh_pub_key: # No public key exists, create one. - user.ssh_pub_key = SSHPubKey(UserID=user.ID, - PubKey=pubkey, - Fingerprint=fingerprint) + user.ssh_pub_key = models.SSHPubKey(UserID=user.ID, + PubKey=pubkey, + Fingerprint=fingerprint) elif user.ssh_pub_key.PubKey != pubkey: # A public key already exists, update it. user.ssh_pub_key.PubKey = pubkey @@ -584,7 +581,7 @@ async def account(request: Request, username: str): _ = l10n.get_translator_for_request(request) context = await make_variable_context(request, _("Account") + username) - user = db.query(User, User.Username == username).first() + user = db.query(models.User, models.User.Username == username).first() if not user: raise HTTPException(status_code=HTTPStatus.NOT_FOUND) @@ -595,7 +592,9 @@ async def account(request: Request, username: str): @router.get("/accounts/") @auth_required(True, redirect="/accounts/") -@account_type_required({TRUSTED_USER, DEVELOPER, TRUSTED_USER_AND_DEV}) +@account_type_required({account_type.TRUSTED_USER, + account_type.DEVELOPER, + account_type.TRUSTED_USER_AND_DEV}) async def accounts(request: Request): context = make_context(request, "Accounts") return render_template(request, "account/search.html", context) @@ -603,7 +602,9 @@ async def accounts(request: Request): @router.post("/accounts/") @auth_required(True, redirect="/accounts/") -@account_type_required({TRUSTED_USER, DEVELOPER, TRUSTED_USER_AND_DEV}) +@account_type_required({account_type.TRUSTED_USER, + account_type.DEVELOPER, + account_type.TRUSTED_USER_AND_DEV}) async def accounts_post(request: Request, O: int = Form(default=0), # Offset SB: str = Form(default=str()), # Search By @@ -626,44 +627,44 @@ async def accounts_post(request: Request, # Setup order by criteria based on SB. order_by_columns = { - "t": (AccountType.ID.asc(), User.Username.asc()), - "r": (User.RealName.asc(), AccountType.ID.asc()), - "i": (User.IRCNick.asc(), AccountType.ID.asc()), + "t": (models.AccountType.ID.asc(), models.User.Username.asc()), + "r": (models.User.RealName.asc(), models.AccountType.ID.asc()), + "i": (models.User.IRCNick.asc(), models.AccountType.ID.asc()), } - default_order = (User.Username.asc(), AccountType.ID.asc()) + default_order = (models.User.Username.asc(), models.AccountType.ID.asc()) order_by = order_by_columns.get(SB, default_order) # Convert parameter T to an AccountType ID. account_types = { - "u": USER_ID, - "t": TRUSTED_USER_ID, - "d": DEVELOPER_ID, - "td": TRUSTED_USER_AND_DEV_ID + "u": account_type.USER_ID, + "t": account_type.TRUSTED_USER_ID, + "d": account_type.DEVELOPER_ID, + "td": account_type.TRUSTED_USER_AND_DEV_ID } account_type_id = account_types.get(T, None) # Get a query handle to users, populate the total user # count into a jinja2 context variable. - query = db.query(User).join(AccountType) + query = db.query(models.User).join(models.AccountType) context["total_users"] = query.count() # Populate this list with any additional statements to # be ANDed together. statements = [] if account_type_id is not None: - statements.append(AccountType.ID == account_type_id) + statements.append(models.AccountType.ID == account_type_id) if U: - statements.append(User.Username.like(f"%{U}%")) + statements.append(models.User.Username.like(f"%{U}%")) if S: - statements.append(User.Suspended == S) + statements.append(models.User.Suspended == S) if E: - statements.append(User.Email.like(f"%{E}%")) + statements.append(models.User.Email.like(f"%{E}%")) if R: - statements.append(User.RealName.like(f"%{R}%")) + statements.append(models.User.RealName.like(f"%{R}%")) if I: - statements.append(User.IRCNick.like(f"%{I}%")) + statements.append(models.User.IRCNick.like(f"%{I}%")) if K: - statements.append(User.PGPKey.like(f"%{K}%")) + statements.append(models.User.PGPKey.like(f"%{K}%")) # Filter the query by combining all statements added above into # an AND statement, unless there's just one statement, which @@ -692,12 +693,12 @@ def render_terms_of_service(request: Request, async def terms_of_service(request: Request): # Query the database for terms that were previously accepted, # but now have a bumped Revision that needs to be accepted. - diffs = db.query(Term).join(AcceptedTerm).filter( - AcceptedTerm.Revision < Term.Revision).all() + diffs = db.query(models.Term).join(models.AcceptedTerm).filter( + models.AcceptedTerm.Revision < models.Term.Revision).all() # Query the database for any terms that have not yet been accepted. - unaccepted = db.query(Term).filter( - ~Term.ID.in_(db.query(AcceptedTerm.TermsID))).all() + unaccepted = db.query(models.Term).filter( + ~models.Term.ID.in_(db.query(models.AcceptedTerm.TermsID))).all() # Translate the 'Terms of Service' part of our page title. _ = l10n.get_translator_for_request(request) @@ -714,12 +715,12 @@ async def terms_of_service_post(request: Request, accept: bool = Form(default=False)): # Query the database for terms that were previously accepted, # but now have a bumped Revision that needs to be accepted. - diffs = db.query(Term).join(AcceptedTerm).filter( - AcceptedTerm.Revision < Term.Revision).all() + diffs = db.query(models.Term).join(models.AcceptedTerm).filter( + models.AcceptedTerm.Revision < models.Term.Revision).all() # Query the database for any terms that have not yet been accepted. - unaccepted = db.query(Term).filter( - ~Term.ID.in_(db.query(AcceptedTerm.TermsID))).all() + unaccepted = db.query(models.Term).filter( + ~models.Term.ID.in_(db.query(models.AcceptedTerm.TermsID))).all() if not accept: # Translate the 'Terms of Service' part of our page title. @@ -737,12 +738,12 @@ async def terms_of_service_post(request: Request, # and update its Revision to the term's current Revision. for term in diffs: accepted_term = request.user.accepted_terms.filter( - AcceptedTerm.TermsID == term.ID).first() + models.AcceptedTerm.TermsID == term.ID).first() accepted_term.Revision = term.Revision # For each term that was never accepted, accept it! for term in unaccepted: - db.create(AcceptedTerm, User=request.user, + db.create(models.AcceptedTerm, User=request.user, Term=term, Revision=term.Revision) return RedirectResponse("/", status_code=HTTPStatus.SEE_OTHER) diff --git a/aurweb/routers/auth.py b/aurweb/routers/auth.py index d94199b5..b3f9cc68 100644 --- a/aurweb/routers/auth.py +++ b/aurweb/routers/auth.py @@ -8,7 +8,7 @@ import aurweb.config from aurweb import util from aurweb.auth import auth_required -from aurweb.models.user import User +from aurweb.models import User from aurweb.templates import make_variable_context, render_template router = APIRouter() diff --git a/aurweb/routers/html.py b/aurweb/routers/html.py index 7bae739b..86f8d13e 100644 --- a/aurweb/routers/html.py +++ b/aurweb/routers/html.py @@ -11,14 +11,10 @@ from sqlalchemy import and_, case, or_ import aurweb.config import aurweb.models.package_request -from aurweb import db, util +from aurweb import db, models, util from aurweb.cache import db_count_cache from aurweb.models.account_type import TRUSTED_USER_AND_DEV_ID, TRUSTED_USER_ID -from aurweb.models.package import Package -from aurweb.models.package_base import PackageBase -from aurweb.models.package_comaintainer import PackageComaintainer -from aurweb.models.package_request import PENDING_ID, PackageRequest -from aurweb.models.user import User +from aurweb.models.package_request import PENDING_ID from aurweb.packages.util import query_notified, query_voted, updated_packages from aurweb.templates import make_context, render_template @@ -69,31 +65,31 @@ async def index(request: Request): context = make_context(request, "Home") context['ssh_fingerprints'] = util.get_ssh_fingerprints() - bases = db.query(PackageBase) + bases = db.query(models.PackageBase) redis = aurweb.redis.redis_connection() stats_expire = 300 # Five minutes. updates_expire = 600 # Ten minutes. # Package statistics. - query = bases.filter(PackageBase.PackagerUID.isnot(None)) + query = bases.filter(models.PackageBase.PackagerUID.isnot(None)) context["package_count"] = await db_count_cache( redis, "package_count", query, expire=stats_expire) query = bases.filter( - and_(PackageBase.MaintainerUID.is_(None), - PackageBase.PackagerUID.isnot(None)) + and_(models.PackageBase.MaintainerUID.is_(None), + models.PackageBase.PackagerUID.isnot(None)) ) context["orphan_count"] = await db_count_cache( redis, "orphan_count", query, expire=stats_expire) - query = db.query(User) + query = db.query(models.User) context["user_count"] = await db_count_cache( redis, "user_count", query, expire=stats_expire) query = query.filter( - or_(User.AccountTypeID == TRUSTED_USER_ID, - User.AccountTypeID == TRUSTED_USER_AND_DEV_ID)) + or_(models.User.AccountTypeID == TRUSTED_USER_ID, + models.User.AccountTypeID == TRUSTED_USER_AND_DEV_ID)) context["trusted_user_count"] = await db_count_cache( redis, "trusted_user_count", query, expire=stats_expire) @@ -105,29 +101,29 @@ async def index(request: Request): one_hour = 3600 updated = bases.filter( - and_(PackageBase.ModifiedTS - PackageBase.SubmittedTS >= one_hour, - PackageBase.PackagerUID.isnot(None)) + and_(models.PackageBase.ModifiedTS - models.PackageBase.SubmittedTS >= one_hour, + models.PackageBase.PackagerUID.isnot(None)) ) query = bases.filter( - and_(PackageBase.SubmittedTS >= seven_days_ago, - PackageBase.PackagerUID.isnot(None)) + and_(models.PackageBase.SubmittedTS >= seven_days_ago, + models.PackageBase.PackagerUID.isnot(None)) ) context["seven_days_old_added"] = await db_count_cache( redis, "seven_days_old_added", query, expire=stats_expire) - query = updated.filter(PackageBase.ModifiedTS >= seven_days_ago) + query = updated.filter(models.PackageBase.ModifiedTS >= seven_days_ago) context["seven_days_old_updated"] = await db_count_cache( redis, "seven_days_old_updated", query, expire=stats_expire) year = seven_days * 52 # Fifty two weeks worth: one year. year_ago = now - year - query = updated.filter(PackageBase.ModifiedTS >= year_ago) + query = updated.filter(models.PackageBase.ModifiedTS >= year_ago) context["year_old_updated"] = await db_count_cache( redis, "year_old_updated", query, expire=stats_expire) query = bases.filter( - PackageBase.ModifiedTS - PackageBase.SubmittedTS < 3600) + models.PackageBase.ModifiedTS - models.PackageBase.SubmittedTS < 3600) context["never_updated"] = await db_count_cache( redis, "never_updated", query, expire=stats_expire) @@ -137,19 +133,19 @@ async def index(request: Request): if request.user.is_authenticated(): # Authenticated users get a few extra pieces of data for # the dashboard display. - packages = db.query(Package).join(PackageBase) + packages = db.query(models.Package).join(models.PackageBase) maintained = packages.join( - User, PackageBase.MaintainerUID == User.ID + models.User, models.PackageBase.MaintainerUID == models.User.ID ).filter( - PackageBase.MaintainerUID == request.user.ID + models.PackageBase.MaintainerUID == request.user.ID ) # Packages maintained by the user that have been flagged. context["flagged_packages"] = maintained.filter( - PackageBase.OutOfDateTS.isnot(None) + models.PackageBase.OutOfDateTS.isnot(None) ).order_by( - PackageBase.ModifiedTS.desc(), Package.Name.asc() + models.PackageBase.ModifiedTS.desc(), models.Package.Name.asc() ).limit(50).all() # Flagged packages that request.user has voted for. @@ -165,17 +161,18 @@ async def index(request: Request): # Package requests created by request.user. context["package_requests"] = request.user.package_requests.filter( - PackageRequest.RequestTS >= start + models.PackageRequest.RequestTS >= start ).order_by( # Order primarily by the Status column being PENDING_ID, # and secondarily by RequestTS; both in descending order. - case([(PackageRequest.Status == PENDING_ID, 1)], else_=0).desc(), - PackageRequest.RequestTS.desc() + case([(models.PackageRequest.Status == PENDING_ID, 1)], + else_=0).desc(), + models.PackageRequest.RequestTS.desc() ).limit(50).all() # Packages that the request user maintains or comaintains. context["packages"] = maintained.order_by( - PackageBase.ModifiedTS.desc(), Package.Name.desc() + models.PackageBase.ModifiedTS.desc(), models.Package.Name.desc() ).limit(50).all() # Packages that request.user has voted for. @@ -188,11 +185,11 @@ async def index(request: Request): # Any packages that the request user comaintains. context["comaintained"] = packages.join( - PackageComaintainer + models.PackageComaintainer ).filter( - PackageComaintainer.UsersID == request.user.ID + models.PackageComaintainer.UsersID == request.user.ID ).order_by( - PackageBase.ModifiedTS.desc(), Package.Name.desc() + models.PackageBase.ModifiedTS.desc(), models.Package.Name.desc() ).limit(50).all() # Comaintained packages that request.user has voted for. diff --git a/aurweb/routers/packages.py b/aurweb/routers/packages.py index 83c796db..58c89192 100644 --- a/aurweb/routers/packages.py +++ b/aurweb/routers/packages.py @@ -7,27 +7,13 @@ from fastapi.responses import JSONResponse, RedirectResponse from sqlalchemy import and_, case import aurweb.filters -import aurweb.models.package_comment -import aurweb.models.package_keyword import aurweb.packages.util -from aurweb import db, defaults, l10n, util +from aurweb import db, defaults, l10n, models, util from aurweb.auth import auth_required -from aurweb.models.license import License -from aurweb.models.package import Package -from aurweb.models.package_base import PackageBase -from aurweb.models.package_comaintainer import PackageComaintainer -from aurweb.models.package_comment import PackageComment -from aurweb.models.package_dependency import PackageDependency -from aurweb.models.package_license import PackageLicense -from aurweb.models.package_notification import PackageNotification -from aurweb.models.package_relation import PackageRelation -from aurweb.models.package_request import ACCEPTED_ID, PENDING_ID, REJECTED_ID, PackageRequest -from aurweb.models.package_source import PackageSource -from aurweb.models.package_vote import PackageVote +from aurweb.models.package_request import ACCEPTED_ID, PENDING_ID, REJECTED_ID from aurweb.models.relation_type import CONFLICTS_ID -from aurweb.models.request_type import DELETION_ID, RequestType -from aurweb.models.user import User +from aurweb.models.request_type import DELETION_ID from aurweb.packages.search import PackageSearch from aurweb.packages.util import get_pkg_or_base, get_pkgbase_comment, query_notified, query_voted from aurweb.scripts import notify, popupdate @@ -75,9 +61,9 @@ async def packages_get(request: Request, context: Dict[str, Any]): # do **not** have OutOfDateTS. criteria = None if flagged == "on": - criteria = PackageBase.OutOfDateTS.isnot + criteria = models.PackageBase.OutOfDateTS.isnot else: - criteria = PackageBase.OutOfDateTS.is_ + criteria = models.PackageBase.OutOfDateTS.is_ # Apply the flag criteria to our PackageSearch.query. search.query = search.query.filter(criteria(None)) @@ -86,7 +72,8 @@ async def packages_get(request: Request, context: Dict[str, Any]): if submit == "Orphans": # If the user clicked the "Orphans" button, we only want # orphaned packages. - search.query = search.query.filter(PackageBase.MaintainerUID.is_(None)) + search.query = search.query.filter( + models.PackageBase.MaintainerUID.is_(None)) # Apply user-specified specified sort column and ordering. search.sort_by(sort_by, sort_order) @@ -116,19 +103,19 @@ async def packages(request: Request) -> Response: return await packages_get(request, context) -def create_request_if_missing(requests: List[PackageRequest], - reqtype: RequestType, - user: User, - package: Package): +def create_request_if_missing(requests: List[models.PackageRequest], + reqtype: models.RequestType, + user: models.User, + package: models.Package): now = int(datetime.utcnow().timestamp()) - pkgreq = db.query(PackageRequest).filter( - PackageRequest.PackageBaseName == package.PackageBase.Name + pkgreq = db.query(models.PackageRequest).filter( + models.PackageRequest.PackageBaseName == package.PackageBase.Name ).first() if not pkgreq: # No PackageRequest existed. Create one. comments = "Automatically generated by aurweb." closure_comment = "Deleted by aurweb." - pkgreq = db.create(PackageRequest, + pkgreq = db.create(models.PackageRequest, RequestType=reqtype, PackageBase=package.PackageBase, PackageBaseName=package.PackageBase.Name, @@ -141,8 +128,7 @@ def create_request_if_missing(requests: List[PackageRequest], requests.append(pkgreq) -def delete_package(deleter: User, - package: Package): +def delete_package(deleter: models.User, package: models.Package): notifications = [] requests = [] bases_to_delete = [] @@ -150,8 +136,8 @@ def delete_package(deleter: User, conn = db.ConnectionExecutor(db.get_engine().raw_connection()) # In all cases, though, just delete the Package in question. if package.PackageBase.packages.count() == 1: - reqtype = db.query(RequestType).filter( - RequestType.ID == DELETION_ID + reqtype = db.query(models.RequestType).filter( + models.RequestType.ID == DELETION_ID ).first() with db.begin(): @@ -187,7 +173,7 @@ def delete_package(deleter: User, async def make_single_context(request: Request, - pkgbase: PackageBase) -> Dict[str, Any]: + pkgbase: models.PackageBase) -> Dict[str, Any]: """ Make a basic context for package or pkgbase. :param request: FastAPI request @@ -203,11 +189,11 @@ async def make_single_context(request: Request, context["packages_count"] = pkgbase.packages.count() context["keywords"] = pkgbase.keywords context["comments"] = pkgbase.comments.order_by( - PackageComment.CommentTS.desc() + models.PackageComment.CommentTS.desc() ) context["pinned_comments"] = pkgbase.comments.filter( - PackageComment.PinnedTS != 0 - ).order_by(PackageComment.CommentTS.desc()) + models.PackageComment.PinnedTS != 0 + ).order_by(models.PackageComment.CommentTS.desc()) context["is_maintainer"] = (request.user.is_authenticated() and request.user.ID == pkgbase.MaintainerUID) @@ -216,10 +202,10 @@ async def make_single_context(request: Request, context["out_of_date"] = bool(pkgbase.OutOfDateTS) context["voted"] = request.user.package_votes.filter( - PackageVote.PackageBaseID == pkgbase.ID).scalar() + models.PackageVote.PackageBaseID == pkgbase.ID).scalar() context["requests"] = pkgbase.requests.filter( - PackageRequest.ClosedTS.is_(None) + models.PackageRequest.ClosedTS.is_(None) ).count() return context @@ -228,8 +214,8 @@ async def make_single_context(request: Request, @router.get("/packages/{name}") async def package(request: Request, name: str) -> Response: # Get the Package. - pkg = get_pkg_or_base(name, Package) - pkgbase = (get_pkg_or_base(name, PackageBase) + pkg = get_pkg_or_base(name, models.Package) + pkgbase = (get_pkg_or_base(name, models.PackageBase) if not pkg else pkg.PackageBase) # Add our base information. @@ -237,28 +223,32 @@ async def package(request: Request, name: str) -> Response: context["package"] = pkg # Package sources. - context["sources"] = db.query(PackageSource).join(Package).join( - PackageBase).filter(PackageBase.ID == pkgbase.ID) + context["sources"] = db.query(models.PackageSource).join( + models.Package).join(models.PackageBase).filter( + models.PackageBase.ID == pkgbase.ID) # Package dependencies. - dependencies = db.query(PackageDependency).join(Package).join( - PackageBase).filter(PackageBase.ID == pkgbase.ID) + dependencies = db.query(models.PackageDependency).join( + models.Package).join(models.PackageBase).filter( + models.PackageBase.ID == pkgbase.ID) context["dependencies"] = dependencies # Package requirements (other packages depend on this one). - required_by = db.query(PackageDependency).join(Package).filter( - PackageDependency.DepName == pkgbase.Name).order_by( - Package.Name.asc()) + required_by = db.query(models.PackageDependency).join( + models.Package).filter( + models.PackageDependency.DepName == pkgbase.Name).order_by( + models.Package.Name.asc()) context["required_by"] = required_by - licenses = db.query(License).join(PackageLicense).join(Package).join( - PackageBase).filter(PackageBase.ID == pkgbase.ID) + licenses = db.query(models.License).join(models.PackageLicense).join( + models.Package).join(models.PackageBase).filter( + models.PackageBase.ID == pkgbase.ID) context["licenses"] = licenses - conflicts = db.query(PackageRelation).join(Package).join( - PackageBase).filter( - and_(PackageRelation.RelTypeID == CONFLICTS_ID, - PackageBase.ID == pkgbase.ID) + conflicts = db.query(models.PackageRelation).join(models.Package).join( + models.PackageBase).filter( + and_(models.PackageRelation.RelTypeID == CONFLICTS_ID, + models.PackageBase.ID == pkgbase.ID) ) context["conflicts"] = conflicts @@ -268,7 +258,7 @@ async def package(request: Request, name: str) -> Response: @router.get("/pkgbase/{name}") async def package_base(request: Request, name: str) -> Response: # Get the PackageBase. - pkgbase = get_pkg_or_base(name, PackageBase) + pkgbase = get_pkg_or_base(name, models.PackageBase) # If this is not a split package, redirect to /packages/{name}. if pkgbase.packages.count() == 1: @@ -285,7 +275,7 @@ async def package_base(request: Request, name: str) -> Response: @router.get("/pkgbase/{name}/voters") async def package_base_voters(request: Request, name: str) -> Response: # Get the PackageBase. - pkgbase = get_pkg_or_base(name, PackageBase) + pkgbase = get_pkg_or_base(name, models.PackageBase) context = make_context(request, "Voters") context["pkgbase"] = pkgbase return render_template(request, "pkgbase/voters.html", context) @@ -298,7 +288,7 @@ async def pkgbase_comments_post( comment: str = Form(default=str()), enable_notifications: bool = Form(default=False)): """ Add a new comment. """ - pkgbase = get_pkg_or_base(name, PackageBase) + pkgbase = get_pkg_or_base(name, models.PackageBase) if not comment: raise HTTPException(status_code=HTTPStatus.BAD_REQUEST) @@ -307,13 +297,13 @@ async def pkgbase_comments_post( # update the db record. now = int(datetime.utcnow().timestamp()) with db.begin(): - comment = db.create(PackageComment, User=request.user, + comment = db.create(models.PackageComment, User=request.user, PackageBase=pkgbase, Comments=comment, RenderedComment=str(), CommentTS=now) if enable_notifications and not request.user.notified(pkgbase): - db.create(PackageNotification, + db.create(models.PackageNotification, User=request.user, PackageBase=pkgbase) update_comment_render(comment.ID) @@ -327,8 +317,8 @@ async def pkgbase_comments_post( @auth_required(True, login=False) async def pkgbase_comment_form(request: Request, name: str, id: int): """ Produce a comment form for comment {id}. """ - pkgbase = get_pkg_or_base(name, PackageBase) - comment = pkgbase.comments.filter(PackageComment.ID == id).first() + pkgbase = get_pkg_or_base(name, models.PackageBase) + comment = pkgbase.comments.filter(models.PackageComment.ID == id).first() if not comment: return JSONResponse({}, status_code=HTTPStatus.NOT_FOUND) @@ -349,7 +339,7 @@ async def pkgbase_comment_post( request: Request, name: str, id: int, comment: str = Form(default=str()), enable_notifications: bool = Form(default=False)): - pkgbase = get_pkg_or_base(name, PackageBase) + pkgbase = get_pkg_or_base(name, models.PackageBase) db_comment = get_pkgbase_comment(pkgbase, id) if not comment: @@ -365,10 +355,10 @@ async def pkgbase_comment_post( db_comment.EditedTS = now db_notif = request.user.notifications.filter( - PackageNotification.PackageBaseID == pkgbase.ID + models.PackageNotification.PackageBaseID == pkgbase.ID ).first() if enable_notifications and not db_notif: - db.create(PackageNotification, + db.create(models.PackageNotification, User=request.user, PackageBase=pkgbase) update_comment_render(db_comment.ID) @@ -381,7 +371,7 @@ async def pkgbase_comment_post( @router.post("/pkgbase/{name}/comments/{id}/delete") @auth_required(True, redirect="/pkgbase/{name}/comments/{id}/delete") async def pkgbase_comment_delete(request: Request, name: str, id: int): - pkgbase = get_pkg_or_base(name, PackageBase) + pkgbase = get_pkg_or_base(name, models.PackageBase) comment = get_pkgbase_comment(pkgbase, id) authorized = request.user.has_credential("CRED_COMMENT_DELETE", @@ -404,7 +394,7 @@ async def pkgbase_comment_delete(request: Request, name: str, id: int): @router.post("/pkgbase/{name}/comments/{id}/undelete") @auth_required(True, redirect="/pkgbase/{name}/comments/{id}/undelete") async def pkgbase_comment_undelete(request: Request, name: str, id: int): - pkgbase = get_pkg_or_base(name, PackageBase) + pkgbase = get_pkg_or_base(name, models.PackageBase) comment = get_pkgbase_comment(pkgbase, id) has_cred = request.user.has_credential("CRED_COMMENT_UNDELETE", @@ -426,7 +416,7 @@ async def pkgbase_comment_undelete(request: Request, name: str, id: int): @router.post("/pkgbase/{name}/comments/{id}/pin") @auth_required(True, redirect="/pkgbase/{name}/comments/{id}/pin") async def pkgbase_comment_pin(request: Request, name: str, id: int): - pkgbase = get_pkg_or_base(name, PackageBase) + pkgbase = get_pkg_or_base(name, models.PackageBase) comment = get_pkgbase_comment(pkgbase, id) has_cred = request.user.has_credential("CRED_COMMENT_PIN", @@ -448,7 +438,7 @@ async def pkgbase_comment_pin(request: Request, name: str, id: int): @router.post("/pkgbase/{name}/comments/{id}/unpin") @auth_required(True, redirect="/pkgbase/{name}/comments/{id}/unpin") async def pkgbase_comment_unpin(request: Request, name: str, id: int): - pkgbase = get_pkg_or_base(name, PackageBase) + pkgbase = get_pkg_or_base(name, models.PackageBase) comment = get_pkgbase_comment(pkgbase, id) has_cred = request.user.has_credential("CRED_COMMENT_PIN", @@ -470,7 +460,7 @@ async def pkgbase_comment_unpin(request: Request, name: str, id: int): @auth_required(True, redirect="/pkgbase/{name}/comaintainers") async def package_base_comaintainers(request: Request, name: str) -> Response: # Get the PackageBase. - pkgbase = get_pkg_or_base(name, PackageBase) + pkgbase = get_pkg_or_base(name, models.PackageBase) # Unauthorized users (Non-TU/Dev and not the pkgbase maintainer) # get redirected to the package base's page. @@ -498,8 +488,8 @@ def remove_users(pkgbase, usernames): for username in usernames: # We know that the users we passed here are in the DB. # No need to check for their existence. - comaintainer = pkgbase.comaintainers.join(User).filter( - User.Username == username + comaintainer = pkgbase.comaintainers.join(models.User).filter( + models.User.Username == username ).first() notifications.append( notify.ComaintainerRemoveNotification( @@ -519,7 +509,7 @@ async def package_base_comaintainers_post( request: Request, name: str, users: str = Form(default=str())) -> Response: # Get the PackageBase. - pkgbase = get_pkg_or_base(name, PackageBase) + pkgbase = get_pkg_or_base(name, models.PackageBase) # Unauthorized users (Non-TU/Dev and not the pkgbase maintainer) # get redirected to the package base's page. @@ -540,7 +530,7 @@ async def package_base_comaintainers_post( # Get the highest priority in the comaintainer set. last_priority = pkgbase.comaintainers.order_by( - PackageComaintainer.Priority.desc() + models.PackageComaintainer.Priority.desc() ).limit(1).first() # If that record exists, we use a priority which is 1 higher. @@ -562,7 +552,8 @@ async def package_base_comaintainers_post( _ = l10n.get_translator_for_request(request) memo = {} for username in usernames: - user = db.query(User).filter(User.Username == username).first() + user = db.query(models.User).filter( + models.User.Username == username).first() if not user: return _("Invalid user name: %s") % username memo[username] = user @@ -579,7 +570,7 @@ async def package_base_comaintainers_post( # If we get here, our user model object is in the memo. comaintainer = db.create( - PackageComaintainer, + models.PackageComaintainer, PackageBase=pkgbase, User=user, Priority=priority) @@ -620,21 +611,21 @@ async def requests(request: Request, context["PP"] = PP # A PackageRequest query, with left inner joined User and RequestType. - query = db.query(PackageRequest).join( - User, PackageRequest.UsersID == User.ID - ).join(RequestType) + query = db.query(models.PackageRequest).join( + models.User, models.PackageRequest.UsersID == models.User.ID + ).join(models.RequestType) # If the request user is not elevated (TU or Dev), then # filter PackageRequests which are owned by the request user. if not request.user.is_elevated(): - query = query.filter(PackageRequest.UsersID == request.user.ID) + query = query.filter(models.PackageRequest.UsersID == request.user.ID) context["total"] = query.count() context["results"] = query.order_by( # Order primarily by the Status column being PENDING_ID, # and secondarily by RequestTS; both in descending order. - case([(PackageRequest.Status == PENDING_ID, 1)], else_=0).desc(), - PackageRequest.RequestTS.desc() + case([(models.PackageRequest.Status == PENDING_ID, 1)], else_=0).desc(), + models.PackageRequest.RequestTS.desc() ).limit(PP).offset(O).all() return render_template(request, "requests.html", context) @@ -645,7 +636,8 @@ async def requests(request: Request, async def package_request(request: Request, name: str): context = make_context(request, "Submit Request") - pkgbase = db.query(PackageBase).filter(PackageBase.Name == name).first() + pkgbase = db.query(models.PackageBase).filter( + models.PackageBase.Name == name).first() if not pkgbase: raise HTTPException(status_code=HTTPStatus.NOT_FOUND) @@ -660,7 +652,7 @@ async def pkgbase_request_post(request: Request, name: str, type: str = Form(...), merge_into: str = Form(default=None), comments: str = Form(default=str())): - pkgbase = get_pkg_or_base(name, PackageBase) + pkgbase = get_pkg_or_base(name, models.PackageBase) # Create our render context. context = make_context(request, "Submit Request") @@ -682,8 +674,8 @@ async def pkgbase_request_post(request: Request, name: str, context["errors"] = ['The "Merge into" field must not be empty.'] return render_template(request, "pkgbase/request.html", context) - target = db.query(PackageBase).filter( - PackageBase.Name == merge_into + target = db.query(models.PackageBase).filter( + models.PackageBase.Name == merge_into ).first() if not target: # TODO: This error needs to be translated. @@ -701,12 +693,14 @@ async def pkgbase_request_post(request: Request, name: str, # All good. Create a new PackageRequest based on the given type. now = int(datetime.utcnow().timestamp()) - reqtype = db.query(RequestType, RequestType.Name == type).first() + reqtype = db.query(models.RequestType).filter( + models.RequestType.Name == type).first() conn = db.ConnectionExecutor(db.get_engine().raw_connection()) notify_ = None with db.begin(): - pkgreq = db.create(PackageRequest, RequestType=reqtype, RequestTS=now, - PackageBase=pkgbase, PackageBaseName=pkgbase.Name, + pkgreq = db.create(models.PackageRequest, RequestType=reqtype, + RequestTS=now, PackageBase=pkgbase, + PackageBaseName=pkgbase.Name, MergeBaseName=merge_into, User=request.user, Comments=comments, ClosureComment=str()) @@ -726,7 +720,8 @@ async def pkgbase_request_post(request: Request, name: str, @router.get("/requests/{id}/close") @auth_required(True, redirect="/requests/{id}/close") async def requests_close(request: Request, id: int): - pkgreq = db.query(PackageRequest).filter(PackageRequest.ID == id).first() + pkgreq = db.query(models.PackageRequest).filter( + models.PackageRequest.ID == id).first() if not request.user.is_elevated() and request.user != pkgreq.User: # Request user doesn't have permission here: redirect to '/'. return RedirectResponse("/", status_code=HTTPStatus.SEE_OTHER) @@ -741,7 +736,8 @@ async def requests_close(request: Request, id: int): async def requests_close_post(request: Request, id: int, reason: int = Form(default=0), comments: str = Form(default=str())): - pkgreq = db.query(PackageRequest).filter(PackageRequest.ID == id).first() + pkgreq = db.query(models.PackageRequest).filter( + models.PackageRequest.ID == id).first() if not request.user.is_elevated() and request.user != pkgreq.User: # Request user doesn't have permission here: redirect to '/'. return RedirectResponse("/", status_code=HTTPStatus.SEE_OTHER) @@ -776,7 +772,7 @@ async def requests_close_post(request: Request, id: int, @router.get("/pkgbase/{name}/flag") @auth_required(True, redirect="/pkgbase/{name}") async def pkgbase_flag_get(request: Request, name: str): - pkgbase = get_pkg_or_base(name, PackageBase) + pkgbase = get_pkg_or_base(name, models.PackageBase) has_cred = request.user.has_credential("CRED_PKGBASE_FLAG") if not has_cred or pkgbase.Flagger is not None: @@ -792,7 +788,7 @@ async def pkgbase_flag_get(request: Request, name: str): @auth_required(True, redirect="/pkgbase/{name}") async def pkgbase_flag_post(request: Request, name: str, comments: str = Form(default=str())): - pkgbase = get_pkg_or_base(name, PackageBase) + pkgbase = get_pkg_or_base(name, models.PackageBase) if not comments: context = make_context(request, "Flag Package Out-Of-Date") @@ -817,7 +813,7 @@ async def pkgbase_flag_post(request: Request, name: str, @router.post("/pkgbase/{name}/unflag") @auth_required(True, redirect="/pkgbase/{name}") async def pkgbase_unflag(request: Request, name: str): - pkgbase = get_pkg_or_base(name, PackageBase) + pkgbase = get_pkg_or_base(name, models.PackageBase) has_cred = request.user.has_credential( "CRED_PKGBASE_UNFLAG", approved=[pkgbase.Flagger, pkgbase.Maintainer]) @@ -834,15 +830,15 @@ async def pkgbase_unflag(request: Request, name: str): @router.post("/pkgbase/{name}/notify") @auth_required(True, redirect="/pkgbase/{name}") async def pkgbase_notify(request: Request, name: str): - pkgbase = get_pkg_or_base(name, PackageBase) + pkgbase = get_pkg_or_base(name, models.PackageBase) notif = db.query(pkgbase.notifications.filter( - PackageNotification.UserID == request.user.ID + models.PackageNotification.UserID == request.user.ID ).exists()).scalar() has_cred = request.user.has_credential("CRED_PKGBASE_NOTIFY") if has_cred and not notif: with db.begin(): - db.create(PackageNotification, + db.create(models.PackageNotification, PackageBase=pkgbase, User=request.user) @@ -853,10 +849,10 @@ async def pkgbase_notify(request: Request, name: str): @router.post("/pkgbase/{name}/unnotify") @auth_required(True, redirect="/pkgbase/{name}") async def pkgbase_unnotify(request: Request, name: str): - pkgbase = get_pkg_or_base(name, PackageBase) + pkgbase = get_pkg_or_base(name, models.PackageBase) notif = pkgbase.notifications.filter( - PackageNotification.UserID == request.user.ID + models.PackageNotification.UserID == request.user.ID ).first() has_cred = request.user.has_credential("CRED_PKGBASE_NOTIFY") if has_cred and notif: @@ -870,16 +866,16 @@ async def pkgbase_unnotify(request: Request, name: str): @router.post("/pkgbase/{name}/vote") @auth_required(True, redirect="/pkgbase/{name}") async def pkgbase_vote(request: Request, name: str): - pkgbase = get_pkg_or_base(name, PackageBase) + pkgbase = get_pkg_or_base(name, models.PackageBase) vote = pkgbase.package_votes.filter( - PackageVote.UsersID == request.user.ID + models.PackageVote.UsersID == request.user.ID ).first() has_cred = request.user.has_credential("CRED_PKGBASE_VOTE") if has_cred and not vote: now = int(datetime.utcnow().timestamp()) with db.begin(): - db.create(PackageVote, + db.create(models.PackageVote, User=request.user, PackageBase=pkgbase, VoteTS=now) @@ -895,10 +891,10 @@ async def pkgbase_vote(request: Request, name: str): @router.post("/pkgbase/{name}/unvote") @auth_required(True, redirect="/pkgbase/{name}") async def pkgbase_unvote(request: Request, name: str): - pkgbase = get_pkg_or_base(name, PackageBase) + pkgbase = get_pkg_or_base(name, models.PackageBase) vote = pkgbase.package_votes.filter( - PackageVote.UsersID == request.user.ID + models.PackageVote.UsersID == request.user.ID ).first() has_cred = request.user.has_credential("CRED_PKGBASE_VOTE") if has_cred and vote: @@ -913,7 +909,7 @@ async def pkgbase_unvote(request: Request, name: str): status_code=HTTPStatus.SEE_OTHER) -def disown_pkgbase(pkgbase: PackageBase, disowner: User): +def disown_pkgbase(pkgbase: models.PackageBase, disowner: models.User): conn = db.ConnectionExecutor(db.get_engine().raw_connection()) notif = notify.DisownNotification(conn, disowner.ID, pkgbase.ID) @@ -922,7 +918,7 @@ def disown_pkgbase(pkgbase: PackageBase, disowner: User): pkgbase.Maintainer = None else: co = pkgbase.comaintainers.order_by( - PackageComaintainer.Priority.asc() + models.PackageComaintainer.Priority.asc() ).limit(1).first() if co: @@ -938,7 +934,7 @@ def disown_pkgbase(pkgbase: PackageBase, disowner: User): @router.get("/pkgbase/{name}/disown") @auth_required(True, redirect="/pkgbase/{name}") async def pkgbase_disown_get(request: Request, name: str): - pkgbase = get_pkg_or_base(name, PackageBase) + pkgbase = get_pkg_or_base(name, models.PackageBase) has_cred = request.user.has_credential("CRED_PKGBASE_DISOWN", approved=[pkgbase.Maintainer]) @@ -955,7 +951,7 @@ async def pkgbase_disown_get(request: Request, name: str): @auth_required(True, redirect="/pkgbase/{name}") async def pkgbase_disown_post(request: Request, name: str, confirm: bool = Form(default=False)): - pkgbase = get_pkg_or_base(name, PackageBase) + pkgbase = get_pkg_or_base(name, models.PackageBase) has_cred = request.user.has_credential("CRED_PKGBASE_DISOWN", approved=[pkgbase.Maintainer]) @@ -979,7 +975,7 @@ async def pkgbase_disown_post(request: Request, name: str, @router.post("/pkgbase/{name}/adopt") @auth_required(True) async def pkgbase_adopt_post(request: Request, name: str): - pkgbase = get_pkg_or_base(name, PackageBase) + pkgbase = get_pkg_or_base(name, models.PackageBase) has_cred = request.user.has_credential("CRED_PKGBASE_ADOPT") if has_cred or not pkgbase.Maintainer: @@ -1001,7 +997,7 @@ async def pkgbase_delete_get(request: Request, name: str): status_code=HTTPStatus.SEE_OTHER) context = make_context(request, "Package Deletion") - context["pkgbase"] = get_pkg_or_base(name, PackageBase) + context["pkgbase"] = get_pkg_or_base(name, models.PackageBase) return render_template(request, "packages/delete.html", context) @@ -1009,7 +1005,7 @@ async def pkgbase_delete_get(request: Request, name: str): @auth_required(True) async def pkgbase_delete_post(request: Request, name: str, confirm: bool = Form(default=False)): - pkgbase = get_pkg_or_base(name, PackageBase) + pkgbase = get_pkg_or_base(name, models.PackageBase) if not request.user.has_credential("CRED_PKGBASE_DELETE"): return RedirectResponse(f"/pkgbase/{name}", diff --git a/aurweb/routers/rss.py b/aurweb/routers/rss.py index 50127dd2..672a47d6 100644 --- a/aurweb/routers/rss.py +++ b/aurweb/routers/rss.py @@ -5,8 +5,7 @@ from fastapi.responses import Response from feedgen.feed import FeedGenerator from aurweb import db, util -from aurweb.models.package import Package -from aurweb.models.package_base import PackageBase +from aurweb.models import Package, PackageBase router = APIRouter() diff --git a/aurweb/routers/trusted_user.py b/aurweb/routers/trusted_user.py index fdde059a..5b2a56d3 100644 --- a/aurweb/routers/trusted_user.py +++ b/aurweb/routers/trusted_user.py @@ -10,12 +10,9 @@ from fastapi import APIRouter, Form, HTTPException, Request from fastapi.responses import RedirectResponse, Response from sqlalchemy import and_, or_ -from aurweb import db, l10n +from aurweb import db, l10n, models from aurweb.auth import account_type_required, auth_required from aurweb.models.account_type import DEVELOPER, TRUSTED_USER, TRUSTED_USER_AND_DEV -from aurweb.models.tu_vote import TUVote -from aurweb.models.tu_voteinfo import TUVoteInfo -from aurweb.models.user import User from aurweb.templates import make_context, make_variable_context, render_template router = APIRouter() @@ -72,16 +69,18 @@ async def trusted_user(request: Request, past_by = "desc" context["past_by"] = past_by - current_votes = db.query(TUVoteInfo, TUVoteInfo.End > ts).order_by( - TUVoteInfo.Submitted.desc()) + current_votes = db.query(models.TUVoteInfo).filter( + models.TUVoteInfo.End > ts).order_by( + models.TUVoteInfo.Submitted.desc()) context["current_votes_count"] = current_votes.count() current_votes = current_votes.limit(pp).offset(current_off) context["current_votes"] = reversed(current_votes.all()) \ if current_by == "asc" else current_votes.all() context["current_off"] = current_off - past_votes = db.query(TUVoteInfo, TUVoteInfo.End <= ts).order_by( - TUVoteInfo.Submitted.desc()) + past_votes = db.query(models.TUVoteInfo).filter( + models.TUVoteInfo.End <= ts).order_by( + models.TUVoteInfo.Submitted.desc()) context["past_votes_count"] = past_votes.count() past_votes = past_votes.limit(pp).offset(past_off) context["past_votes"] = reversed(past_votes.all()) \ @@ -92,14 +91,14 @@ async def trusted_user(request: Request, # We order last votes by TUVote.VoteID and User.Username. # This is really bad. We should add a Created column to # TUVote of type Timestamp and order by that instead. - last_votes_by_tu = db.query(TUVote).filter( - and_(TUVote.VoteID == TUVoteInfo.ID, - TUVoteInfo.End <= ts, - TUVote.UserID == User.ID, - or_(User.AccountTypeID == 2, - User.AccountTypeID == 4)) - ).group_by(User.ID).order_by( - TUVote.VoteID.desc(), User.Username.asc()) + last_votes_by_tu = db.query(models.TUVote).filter( + and_(models.TUVote.VoteID == models.TUVoteInfo.ID, + models.TUVoteInfo.End <= ts, + models.TUVote.UserID == models.User.ID, + or_(models.User.AccountTypeID == 2, + models.User.AccountTypeID == 4)) + ).group_by(models.User.ID).order_by( + models.TUVote.VoteID.desc(), models.User.Username.asc()) context["last_votes_by_tu"] = last_votes_by_tu.all() context["current_by_next"] = "asc" if current_by == "desc" else "desc" @@ -118,9 +117,9 @@ async def trusted_user(request: Request, def render_proposal(request: Request, context: dict, proposal: int, - voteinfo: TUVoteInfo, - voters: typing.Iterable[User], - vote: TUVote, + voteinfo: models.TUVoteInfo, + voters: typing.Iterable[models.User], + vote: models.TUVote, status_code: HTTPStatus = HTTPStatus.OK): """ Render a single TU proposal. """ context["proposal"] = proposal @@ -135,7 +134,7 @@ def render_proposal(request: Request, (participation > voteinfo.Quorum and voteinfo.Yes > voteinfo.No) context["accepted"] = accepted - can_vote = voters.filter(TUVote.User == request.user).first() is None + can_vote = voters.filter(models.TUVote.User == request.user).first() is None context["can_vote"] = can_vote if not voteinfo.is_running(): @@ -155,13 +154,16 @@ async def trusted_user_proposal(request: Request, proposal: int): context = await make_variable_context(request, "Trusted User") proposal = int(proposal) - voteinfo = db.query(TUVoteInfo, TUVoteInfo.ID == proposal).first() + voteinfo = db.query(models.TUVoteInfo).filter( + models.TUVoteInfo.ID == proposal).first() if not voteinfo: raise HTTPException(status_code=HTTPStatus.NOT_FOUND) - voters = db.query(User).join(TUVote).filter(TUVote.VoteID == voteinfo.ID) - vote = db.query(TUVote, and_(TUVote.UserID == request.user.ID, - TUVote.VoteID == voteinfo.ID)).first() + voters = db.query(models.User).join(models.TUVote).filter( + models.TUVote.VoteID == voteinfo.ID) + vote = db.query(models.TUVote).filter( + and_(models.TUVote.UserID == request.user.ID, + models.TUVote.VoteID == voteinfo.ID)).first() if not request.user.is_trusted_user(): context["error"] = "Only Trusted Users are allowed to vote." @@ -183,13 +185,16 @@ async def trusted_user_proposal_post(request: Request, context = await make_variable_context(request, "Trusted User") proposal = int(proposal) # Make sure it's an int. - voteinfo = db.query(TUVoteInfo, TUVoteInfo.ID == proposal).first() + voteinfo = db.query(models.TUVoteInfo).filter( + models.TUVoteInfo.ID == proposal).first() if not voteinfo: raise HTTPException(status_code=HTTPStatus.NOT_FOUND) - voters = db.query(User).join(TUVote).filter(TUVote.VoteID == voteinfo.ID) - vote = db.query(TUVote, and_(TUVote.UserID == request.user.ID, - TUVote.VoteID == voteinfo.ID)).first() + voters = db.query(models.User).join(models.TUVote).filter( + models.TUVote.VoteID == voteinfo.ID) + vote = db.query(models.TUVote).filter( + and_(models.TUVote.UserID == request.user.ID, + models.TUVote.VoteID == voteinfo.ID)).first() status_code = HTTPStatus.OK if not request.user.is_trusted_user(): @@ -215,7 +220,7 @@ async def trusted_user_proposal_post(request: Request, status_code=HTTPStatus.BAD_REQUEST) with db.begin(): - vote = db.create(TUVote, User=request.user, VoteInfo=voteinfo) + vote = db.create(models.TUVote, User=request.user, VoteInfo=voteinfo) voteinfo.ActiveTUs += 1 context["error"] = "You've already voted for this proposal." @@ -262,12 +267,14 @@ async def trusted_user_addvote_post(request: Request, # Alright, get some database records, if we can. if type != "bylaws": - user_record = db.query(User, User.Username == user).first() + user_record = db.query(models.User).filter( + models.User.Username == user).first() if user_record is None: context["error"] = "Username does not exist." return render_addvote(context, HTTPStatus.NOT_FOUND) - voteinfo = db.query(TUVoteInfo, TUVoteInfo.User == user).count() + voteinfo = db.query(models.TUVoteInfo).filter( + models.TUVoteInfo.User == user).count() if voteinfo: _ = l10n.get_translator_for_request(request) context["error"] = _( @@ -288,13 +295,14 @@ async def trusted_user_addvote_post(request: Request, duration, quorum = ADDVOTE_SPECIFICS.get(type) timestamp = int(datetime.utcnow().timestamp()) + # TODO: Review this. Is this even necessary? # Remove