change(fastapi): simplify model imports across code-base

Closes: #133

Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
Kevin Morris 2021-10-16 19:25:25 -07:00
parent bfdc85d7d6
commit 28c4e9697b
No known key found for this signature in database
GPG key ID: F7E46DED420788F3
12 changed files with 341 additions and 343 deletions

View file

@ -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.

View file

@ -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

View file

@ -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():

View file

@ -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

View file

@ -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

View file

@ -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.") % (
"<strong>", username, "</strong>")
]
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.") % (
"<strong>", fingerprint, "</strong>")
@ -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,7 +404,7 @@ 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,
user.ssh_pub_key = models.SSHPubKey(UserID=user.ID,
PubKey=pubkey,
Fingerprint=fingerprint)
@ -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,7 +535,7 @@ 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,
user.ssh_pub_key = models.SSHPubKey(UserID=user.ID,
PubKey=pubkey,
Fingerprint=fingerprint)
elif 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)

View file

@ -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()

View file

@ -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.

View file

@ -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}",

View file

@ -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()

View file

@ -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 <script> and <style> tags.
agenda = re.sub(r'<[/]?script.*>', '', agenda)
agenda = re.sub(r'<[/]?style.*>', '', agenda)
# Create a new TUVoteInfo (proposal)!
with db.begin():
voteinfo = db.create(TUVoteInfo,
voteinfo = db.create(models.TUVoteInfo,
User=user,
Agenda=agenda,
Submitted=timestamp, End=timestamp + duration,

View file

@ -139,7 +139,7 @@ def to_qs(query: Dict[str, Any]) -> str:
def get_vote(voteinfo, request: fastapi.Request):
from aurweb.models.tu_vote import TUVote
from aurweb.models import TUVote
return voteinfo.tu_votes.filter(TUVote.User == request.user).first()