Verified Commit 8350399a authored by Mattia Rizzolo's avatar Mattia Rizzolo

Merge branch 'flaking' of salsa.debian.org:lyknode-guest/debexpo into live

MR: !54Signed-off-by: Mattia Rizzolo's avatarMattia Rizzolo <mattia@debian.org>
parents 459201ba 6e7d0311
Pipeline #42429 passed with stage
in 4 minutes and 39 seconds
......@@ -24,3 +24,11 @@ unstable:
stable-bpo:
<<: *test
image: debian:stable-backports
flake8:
image: debian:unstable-slim
before_script:
- apt-get -q update
- env DEBIAN_FRONTEND=noninteractive apt-get -q -y install --no-install-recommends python-flake8
script:
- python -m flake8
......@@ -3,7 +3,8 @@
#
# debexpo-importer — executable script to import new packages
#
# This file is part of debexpo - https://salsa.debian.org/mentors.debian.net-team/debexpo
# This file is part of debexpo -
# https://salsa.debian.org/mentors.debian.net-team/debexpo
#
# Copyright © 2008 Jonny Lamb <jonny@debian.org>
#
......@@ -36,8 +37,10 @@ import sys
from optparse import OptionParser
from debexpo.importer.importer import Importer
def main():
parser = OptionParser(usage="%prog -c FILE -i FILE [--skip-email] [--skip-gpg-check]")
parser = OptionParser(
usage="%prog -c FILE -i FILE [--skip-email] [--skip-gpg-check]")
parser.add_option('-c', '--changes', dest='changes',
help='Path to changes file to import',
metavar='FILE', default=None)
......@@ -56,9 +59,11 @@ def main():
sys.exit(0)
logging.debug('Importer started with arguments: %s' % sys.argv[1:])
i = Importer(options.changes, options.ini, options.skip_email, options.skip_gpg)
i = Importer(options.changes, options.ini, options.skip_email,
options.skip_gpg)
sys.exit(i.main())
if __name__=='__main__':
if __name__ == '__main__':
main()
......@@ -3,7 +3,8 @@
#
# debexpo_worker.py — Worker task
#
# This file is part of debexpo - https://salsa.debian.org/mentors.debian.net-team/debexpo
# This file is part of debexpo -
# https://salsa.debian.org/mentors.debian.net-team/debexpo
#
# Copyright © 2011 Arno Töll <debian@toell.net>
#
......@@ -53,6 +54,7 @@ from debexpo.config.environment import load_environment
log = None
class Worker(object):
def __init__(self, pidfile, inifile, daemonize):
"""
......@@ -71,7 +73,6 @@ class Worker(object):
self.daemonize = daemonize
self.jobs = {}
def _daemonize(self):
"""
Daemonize method. Runs the actual worker thread in the background
......@@ -81,7 +82,8 @@ class Worker(object):
if pid > 0:
sys.exit(0)
except OSError as e:
log.error( "%s failed to fork: %s (err %d)" % (__name__, e.strerror, e.errno))
log.error("%s failed to fork: %s (err %d)" %
(__name__, e.strerror, e.errno))
sys.exit(1)
os.chdir("/")
......@@ -89,7 +91,7 @@ class Worker(object):
os.umask(0)
signal.signal(signal.SIGTERM, self._remove_pid)
file(self.pidfile, "w+").write( "%d\n" % os.getpid())
file(self.pidfile, "w+").write("%d\n" % os.getpid())
def _remove_pid(self, _a, _b):
"""
......@@ -97,7 +99,6 @@ class Worker(object):
"""
os.remove(self.pidfile)
def _import_plugin(self, name):
"""
Imports a module and returns it.
......@@ -119,16 +120,17 @@ class Worker(object):
if str(e).startswith('No module named'):
log.debug('Import failed - module not found: %s', e)
else:
log.warn('Import of module "%s" failed with error: %s', name, e)
log.warn('Import of module "%s" failed with error: %s',
name, e)
return None
def _load_jobs(self):
"""
Tries to load configured cronjobs. This method roughly works the same way,
as does the equivalent method in the plugins code.
Tries to load configured cronjobs. This method roughly works the same
way, as does the equivalent method in the plugins code.
"""
if not 'debexpo.cronjobs' in pylons.config:
if 'debexpo.cronjobs' not in pylons.config:
log.error("debexpo.cronjobs it not set - doing nothing?")
sys.exit(1)
......@@ -145,14 +147,16 @@ class Worker(object):
module = self._import_plugin(name)
if not module:
log.warning("Cronjob %s was configured, but not found" % (plugin))
continue
log.warning("Cronjob %s was configured, but not found" %
(plugin))
continue
if hasattr(module, 'cronjob') and hasattr(module, 'schedule'):
self.jobs[plugin] = {
'module': getattr(module, 'cronjob')(parent=self, config=pylons.config, log=log),
'schedule': getattr(module, 'schedule'),
'last_run': datetime.datetime(year=1970, month=1, day=1)
'module': getattr(module, 'cronjob')(
parent=self, config=pylons.config, log=log),
'schedule': getattr(module, 'schedule'),
'last_run': datetime.datetime(year=1970, month=1, day=1)
}
else:
log.debug("Cronjob %s seems invalid" % (plugin))
......@@ -171,7 +175,6 @@ class Worker(object):
print('Config file does not have [loggers] section')
sys.exit(1)
logging.config.fileConfig(self.inifile)
logger_name = 'debexpo.worker'
log = logging.getLogger(logger_name)
......@@ -180,7 +183,6 @@ class Worker(object):
conf = appconfig('config:' + self.inifile)
pylons.config = load_environment(conf.global_conf, conf.local_conf)
def run(self):
"""
Run the event loop.
......@@ -190,17 +192,18 @@ class Worker(object):
self._setup()
if os.path.exists(self.pidfile):
try:
read_pid = file(self.pidfile,'r')
read_pid = file(self.pidfile, 'r')
pid = read_pid.readline().strip()
read_pid.close()
except:
except Exception:
pid = None
else:
pid = None
if pid:
log.error("Refusing to start - is another instance with PID %s running?" % (pid))
sys.exit(1)
log.error("Refusing to start - is another instance with PID "
"%s running?" % (pid))
sys.exit(1)
if self.daemonize:
log.debug("Go into background now")
......@@ -211,18 +214,23 @@ class Worker(object):
while(True):
for job in self.jobs:
if (datetime.datetime.now() - self.jobs[job]['last_run']) >= self.jobs[job]['schedule']:
if ((datetime.datetime.now() - self.jobs[job]['last_run']) >=
self.jobs[job]['schedule']):
log.debug("Run job %s" % (job))
self.jobs[job]['module'].invoke()
self.jobs[job]['last_run'] = datetime.datetime.now()
log.debug("Job %s complete" % (job))
time.sleep(delay)
if __name__ == '__main__':
parser = optparse.OptionParser()
parser.add_option("-i", "--ini", dest="ini", help="path to application ini file", metavar="FILE")
parser.add_option("-p", "--pid-file", dest="pid", help="path where the PID file is stored", metavar="FILE")
parser.add_option("-d", "--daemonize", dest="daemonize", action='store_true', help="go into background")
parser.add_option("-i", "--ini", dest="ini",
help="path to application ini file", metavar="FILE")
parser.add_option("-p", "--pid-file", dest="pid",
help="path where the PID file is stored", metavar="FILE")
parser.add_option("-d", "--daemonize", dest="daemonize",
action='store_true', help="go into background")
(options, args) = parser.parse_args()
if not options.pid or not options.ini:
......
......@@ -3,7 +3,8 @@
#
# key_importer.py — Regenerate the mentors keyring from scratch
#
# This file is part of debexpo - https://salsa.debian.org/mentors.debian.net-team/debexpo
# This file is part of debexpo -
# https://salsa.debian.org/mentors.debian.net-team/debexpo
#
# Copyright © 2012 Arno Töll <debian@toell.net>
#
......@@ -38,7 +39,6 @@ __author__ = 'Arno Töll'
__copyright__ = 'Copyright © 2012 Arno Töll'
__license__ = 'MIT'
import datetime
import tempfile
import shutil
import os.path
......@@ -46,7 +46,6 @@ import sys
import optparse
import pylons
from paste.deploy import appconfig
from debexpo.config.environment import load_environment
from debexpo.model.users import User
......@@ -67,7 +66,8 @@ class UpdateKeyring(object):
def invoke(self):
"""
Loops through the debexpo.upload.incoming directory and runs the debexpo.importer for each file
Loops through the debexpo.upload.incoming directory and runs the
debexpo.importer for each file
"""
if 'debexpo.gpg_keyring' not in self.config:
......@@ -78,7 +78,8 @@ class UpdateKeyring(object):
keyring = open(self.config['debexpo.gpg_keyring'], "a+")
keyring.close()
except IOError as e:
print("Can't access file: %s: %s" % (self.config['debexpo.gpg_keyring'], str(e)))
print("Can't access file: %s: %s" %
(self.config['debexpo.gpg_keyring'], str(e)))
return
print("Regenerate keyring into %s" % (keyring.name))
......@@ -101,9 +102,10 @@ class UpdateKeyring(object):
meta.session.close()
if __name__=='__main__':
if __name__ == '__main__':
parser = optparse.OptionParser()
parser.add_option("-i", "--ini", dest="ini", help="path to application ini file", metavar="FILE")
parser.add_option("-i", "--ini", dest="ini",
help="path to application ini file", metavar="FILE")
(options, args) = parser.parse_args()
if not options.ini:
parser.print_help()
......
......@@ -2,7 +2,8 @@
#
# user_importer.py — executable script to import new users
#
# This file is part of debexpo - https://salsa.debian.org/mentors.debian.net-team/debexpo
# This file is part of debexpo -
# https://salsa.debian.org/mentors.debian.net-team/debexpo
#
# Copyright © 2011 Asheesh Laroia <paulproteus@debian.org>
#
......@@ -45,10 +46,12 @@ from debexpo.config.environment import load_environment
import pylons
def import_users(list_of_dicts):
for d in list_of_dicts:
import_one_user(d)
def import_one_user(data_dict):
'''This imports user data. It expects the following keys:
* realname
......@@ -71,12 +74,12 @@ def import_one_user(data_dict):
# First, make sure that the data_dict has everything we need
for key in transformation:
if key not in data_dict:
raise ValueError, ("Missing a key from data_dict: %s" % key)
raise (ValueError, ("Missing a key from data_dict: %s" % key))
# Then, see if the email address matches a current user
user_email = data_dict['email']
matches = meta.session.query(debexpo.model.users.User
).filter_by(email=user_email)
matches = meta.session.query(debexpo.model.users.User) \
.filter_by(email=user_email)
if matches.count():
logging.warn("A user with email address %s already exists" % (
user_email,))
......@@ -96,10 +99,12 @@ def import_one_user(data_dict):
meta.session.add(u)
meta.session.commit()
def main():
parser = OptionParser(usage="%prog -u FILE -i FILE")
parser.add_option('-u', '--user-json-path', dest='user_json_path',
help='Path to JSON file with user data to import (/dev/stdin is permissible)',
help='Path to JSON file with user data to import '
'(/dev/stdin is permissible)',
metavar='FILE', default=None)
parser.add_option('-i', '--ini', dest='ini',
help='Path to application ini file',
......@@ -115,7 +120,6 @@ def main():
conf = appconfig('config:' + os.path.abspath(options.ini))
pylons.config = load_environment(conf.global_conf, conf.local_conf)
list_of_dicts = simplejson.load(open(options.user_json_path))
import_users(list_of_dicts)
......
......@@ -68,7 +68,7 @@ def run_migrations_online():
"""
# specify here how the engine is acquired
engine = meta.engine
#raise NotImplementedError("Please specify engine connectivity here")
# raise NotImplementedError("Please specify engine connectivity here")
with engine.connect() as connection: # noqa
context.configure(
......
"""Base
Revision ID: 7cceb63f746e
Revises:
Revises:
Create Date: 2019-02-13 14:07:03.774616
This file exists to allow downgrading from the first real version
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '7cceb63f746e'
......
"""Add a field on table package_files to hold file's sha256 checksum
Revision ID: 8d6f497e959e
Revises:
Revises:
Create Date: 2019-02-13 13:33:36.249795
"""
......@@ -31,7 +31,7 @@ depends_on = None
def upgrade():
op.add_column('package_files', sa.Column('sha256sum', sa.types.String(64),
nullable=False, server_default=''))
nullable=False, server_default=''))
with op.batch_alter_table('package_files', schema=None) as batch_op:
batch_op.alter_column('sha256sum', server_default=None)
pass
......
......@@ -15,6 +15,7 @@ down_revision = '8d6f497e959e'
branch_labels = None
depends_on = None
def upgrade():
with op.batch_alter_table('package_files', schema=None) as batch_op:
batch_op.alter_column('md5sum', type_=sa.types.String(32))
......
......@@ -10,5 +10,5 @@ def easy_app_init(ini_path):
# Initialize Pylons app
conf = appconfig('config:' + ini_path)
import debexpo.config.environment
pylons.config = debexpo.config.environment.load_environment(conf.global_conf, conf.local_conf)
pylons.config = debexpo.config.environment \
.load_environment(conf.global_conf, conf.local_conf)
......@@ -2,7 +2,8 @@
#
# environment.py — Pylons environment configuration
#
# This file is part of debexpo - https://salsa.debian.org/mentors.debian.net-team/debexpo
# This file is part of debexpo
# https://salsa.debian.org/mentors.debian.net-team/debexpo
#
# Copyright © 2008 Jonny Lamb <jonny@debian.org>
# Copyright © 2010 Jan Dittberner <jandd@debian.org>
......@@ -37,7 +38,6 @@ __copyright__ = 'Copyright © 2008 Jonny Lamb, Copyright © 2010 Jan Dittberner'
__license__ = 'MIT'
import os
import sys
from mako.lookup import TemplateLookup
from pylons.configuration import PylonsConfig
......@@ -50,6 +50,7 @@ from debexpo.config.routing import make_map
from sqlalchemy import engine_from_config
from debexpo.model import init_model
def load_environment(global_conf, app_conf):
"""
Configures the Pylons environment via the ``pylons.config`` object.
......@@ -80,8 +81,8 @@ def load_environment(global_conf, app_conf):
import pylons
pylons.cache._push_object(config['pylons.app_globals'].cache)
#config['pylons.strict_c'] = False
#config['pylons.tmpl_context_attach_args'] = True
# config['pylons.strict_c'] = False
# config['pylons.tmpl_context_attach_args'] = True
# Customize templating options via this variable
config['pylons.app_globals'].mako_lookup = TemplateLookup(
......@@ -89,7 +90,8 @@ def load_environment(global_conf, app_conf):
error_handler=handle_mako_error,
module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
input_encoding='utf-8', default_filters=['escape'],
imports=['from webhelpers.html import escape', 'from debexpo.lib.filters import semitrusted'])
imports=['from webhelpers.html import escape',
'from debexpo.lib.filters import semitrusted'])
# CONFIGURATION OPTIONS HERE (note: all config options will override
# any Pylons config options)
......
......@@ -2,7 +2,8 @@
#
# middleware.py — Pylons middleware initialization
#
# This file is part of debexpo - https://salsa.debian.org/mentors.debian.net-team/debexpo
# This file is part of debexpo
# https://salsa.debian.org/mentors.debian.net-team/debexpo
#
# Copyright © 2008 Jonny Lamb <jonny@debian.org>
# Copyrithg © 2010 Jan Dittberner <jandd@debian.org>
......@@ -43,12 +44,12 @@ from paste.deploy.converters import asbool
from beaker.middleware import SessionMiddleware
from routes.middleware import RoutesMiddleware
import pylons
from pylons.middleware import ErrorHandler, StatusCodeRedirect
from pylons.wsgiapp import PylonsApp
from debexpo.config.environment import load_environment
def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
"""Creates a Pylons WSGI application and returns it.
......
......@@ -2,7 +2,8 @@
#
# routing.py — Routes configuration
#
# This file is part of debexpo - https://salsa.debian.org/mentors.debian.net-team/debexpo
# This file is part of debexpo
# https://salsa.debian.org/mentors.debian.net-team/debexpo
#
# Copyright © 2008 Jonny Lamb <jonny@debian.org>
# Copyright © 2010 Jan Dittberner <jandd@debian.org>
......@@ -41,6 +42,7 @@ __license__ = 'MIT'
from routes import Mapper
def make_map(config):
"""
Creates, configures and returns the routes Mapper.
......@@ -66,30 +68,49 @@ def make_map(config):
map.connect('intro-maintainers', '/intro-maintainers',
controller='index', action='intro_maintainers')
map.connect('sponsors', '/sponsors', controller='sponsor', action='index')
map.connect('packaging-team', '/sponsors/spackaging-team', controller='sponsor', action='packaging_team')
map.connect('guidelines', '/sponsors/guidelines', controller='sponsor', action='guidelines')
map.connect('sponsor-developer', '/sponsors/profile/{id}', controller='sponsor', action='developer')
map.connect('sponsor_tag_save', '/sponsors/save', controller='sponsor', action='save')
map.connect('sponsor_tag_clear', '/sponsors/clear', controller='sponsor', action='clear')
map.connect('intro-reviewers', '/intro-reviewers', controller='index', action='intro_reviewers')
map.connect('packaging-team', '/sponsors/spackaging-team',
controller='sponsor', action='packaging_team')
map.connect('guidelines', '/sponsors/guidelines', controller='sponsor',
action='guidelines')
map.connect('sponsor-developer', '/sponsors/profile/{id}',
controller='sponsor', action='developer')
map.connect('sponsor_tag_save', '/sponsors/save', controller='sponsor',
action='save')
map.connect('sponsor_tag_clear', '/sponsors/clear', controller='sponsor',
action='clear')
map.connect('intro-reviewers', '/intro-reviewers', controller='index',
action='intro_reviewers')
map.connect('my', '/my', controller='my', action='index')
map.connect('login', '/login', controller='login', action='index')
map.connect('logout', '/logout', controller='login', action='logout')
map.connect('news', '/news', controller='index', action='news')
map.connect('/package', controller='package', action='index')
map.connect('package', '/package/{packagename}', controller='package', action='index')
map.connect('comment', '/package/{packagename}/comment', controller='package', action='comment')
map.connect('sponsor', '/package/{packagename}/needs_sponsor/{key}', controller='package', action='sponsor')
map.connect('subscribe', '/package/{packagename}/subscribe', controller='package', action='subscribe')
map.connect('delete', '/package/{packagename}/delete/{key}', controller='package', action='delete')
map.connect('rfs-howto', '/sponsors/rfs-howto', controller='sponsor', action='rfs_howto')
map.connect('rfs', '/sponsors/rfs-howto/{packagename}', controller='sponsor', action='rfs_howto')
map.connect('packages', '/packages/{action}', controller='packages', action='index')
map.connect('all-packages', '/packages', controller='packages', action='index')
map.connect('packages-uploader', '/packages/uploader/{id}', controller='packages', action='uploader')
#map.connect('/packages/{action}/{id}', controller='packages', action='index', id=None)
map.connect('packages_filter_feed', '/packages/{filter}/{id}/feed', controller='packages', action='feed')
#map.connect('packages_feed', '/packages/feed', controller='packages', action='feed')
map.connect('package', '/package/{packagename}', controller='package',
action='index')
map.connect('comment', '/package/{packagename}/comment',
controller='package', action='comment')
map.connect('sponsor', '/package/{packagename}/needs_sponsor/{key}',
controller='package', action='sponsor')
map.connect('subscribe', '/package/{packagename}/subscribe',
controller='package', action='subscribe')
map.connect('delete', '/package/{packagename}/delete/{key}',
controller='package', action='delete')
map.connect('rfs-howto', '/sponsors/rfs-howto', controller='sponsor',
action='rfs_howto')
map.connect('rfs', '/sponsors/rfs-howto/{packagename}',
controller='sponsor', action='rfs_howto')
map.connect('packages', '/packages/{action}', controller='packages',
action='index')
map.connect('all-packages', '/packages', controller='packages',
action='index')
map.connect('packages-uploader', '/packages/uploader/{id}',
controller='packages', action='uploader')
# map.connect('/packages/{action}/{id}', controller='packages',
# action='index', id=None)
map.connect('packages_filter_feed', '/packages/{filter}/{id}/feed',
controller='packages', action='feed')
# map.connect('packages_feed', '/packages/feed', controller='packages',
# action='feed')
map.connect('qa', '/qa', controller='index', action='qa')
# LEGACY ROUTE. CAN BE REMOVED LATER
......@@ -100,6 +121,6 @@ def make_map(config):
map.connect('/{controller}/{action}')
map.connect('/{controller}/{action}/{id}')
#map.connect('/*url', controller='template', action='view')
# map.connect('/*url', controller='template', action='view')
return map
......@@ -2,7 +2,8 @@
#
# debian.py — Debian controller for accessing the repository
#
# This file is part of debexpo - https://salsa.debian.org/mentors.debian.net-team/debexpo
# This file is part of debexpo -
# https://salsa.debian.org/mentors.debian.net-team/debexpo
#
# Copyright © 2008 Jonny Lamb <jonny@debian.org>
#
......@@ -45,10 +46,13 @@ import os
import logging
import paste.fileapp
from debexpo.lib.base import *
from debexpo.lib.base import BaseController
from pylons import config, request
from pylons.controllers.util import abort
log = logging.getLogger(__name__)
class DebianController(BaseController):
"""
Class for handling the /debian/ directory.
......@@ -56,8 +60,8 @@ class DebianController(BaseController):
def index(self, filename):
"""
Entry point to the controller. Opens a file in the repository using Paste's
FileApp.
Entry point to the controller. Opens a file in the repository using
Paste's FileApp.
"""
file = os.path.join(config['debexpo.repository'], filename)
log.debug('%s requested' % filename)
......
......@@ -2,7 +2,8 @@
#
# error.py — The application's ErrorController object
#
# This file is part of debexpo - https://salsa.debian.org/mentors.debian.net-team/debexpo
# This file is part of debexpo -
# https://salsa.debian.org/mentors.debian.net-team/debexpo
#
# Copyright © 2008 Jonny Lamb <jonny@debian.org>
# Copyright © 2010 Jan Dittberner <jandd@debian.org>
......@@ -40,10 +41,13 @@ import cgi
import os.path
import paste.fileapp
from pylons.middleware import error_document_template, media_path
from pylons.middleware import media_path
from pylons import tmpl_context as c, request, response
from pylons.templating import render_mako as render
from webhelpers.html.builder import literal
from debexpo.lib.base import *
from debexpo.lib.base import BaseController
class ErrorController(BaseController):
"""
......@@ -62,7 +66,8 @@ class ErrorController(BaseController):
"""
resp = request.environ.get('pylons.original_response')
orig_request = request.environ.get('pylons.original_request')
c.message = literal(resp.body) or cgi.escape(request.GET.get('message', ''))
c.message = literal(resp.body) or cgi.escape(request.GET.get('message',
''))
c.code = cgi.escape(request.GET.get('code', str(resp.status_int)))
response.headers = resp.headers
......@@ -91,7 +96,8 @@ class ErrorController(BaseController):
def _serve_file(self, path):
"""
Calls Paste's FileApp (a WSGI application) to serve the file at the specified path.
Calls Paste's FileApp (a WSGI application) to serve the file at the
specified path.
``path``
Path of the file to serve.
......
......@@ -2,7 +2,8 @@
#
# index.py — index controller
#
# This file is part of debexpo - https://salsa.debian.org/mentors.debian.net-team/debexpo
# This file is part of debexpo -
# https://salsa.debian.org/mentors.debian.net-team/debexpo
#
# Copyright © 2008 Jonny Lamb <jonny@debian.org>
# Copyright © 2010 Jan Dittberner <jandd@debian.org>
......@@ -38,28 +39,25 @@ __license__ = 'MIT'
import logging
from debexpo.lib.base import BaseController, c, config, render, session
from debexpo.lib import constants
from debexpo.controllers.packages import PackagesController, PackageGroups
from webhelpers.html import literal
from debexpo.lib.base import BaseController, c, config, render
from debexpo.controllers.packages import PackagesController
from datetime import datetime, timedelta
from debexpo.model.package_versions import PackageVersion
from debexpo.model.packages import Package
from debexpo.model.users import User
from debexpo.model import meta
log = logging.getLogger(__name__)
class IndexController(BaseController):
def index(self):
pkg_controller = PackagesController()
c.config = config
max_time = datetime.today() - timedelta(days=30)
c.packages = pkg_controller._get_packages(
package_version_filter=(PackageVersion.uploaded >= (datetime.today() - timedelta(days=30))),
package_filter=(Package.needs_sponsor == 1)
package_version_filter=(PackageVersion.uploaded >= max_time),
package_filter=(Package.needs_sponsor == 1)
)
c.deltas = pkg_controller._get_timedeltas(c.packages)
c.deltas.pop()
......@@ -81,4 +79,3 @@ class IndexController(BaseController):
"""Return an introduction page for package maintainers"""
c.config = config
return render('/index/intro-maintainers.mako')
......@@ -2,7 +2,8 @@
#
# login.py — Login controller
#
# This file is part of debexpo - https://salsa.debian.org/mentors.debian.net-team/debexpo
# This file is part of debexpo -
# https://salsa.debian.org/mentors.debian.net-team/debexpo
#
# Copyright © 2008 Jonny Lamb <jonny@debian.org>
# Copyright © 2010 Jan Dittberner <jandd@debian.org>
......@@ -38,9 +39,9 @@ __license__ = 'MIT'
import logging
from datetime import datetime
from passlib.hash import bcrypt