Commit b8056faf authored by Arno Töll's avatar Arno Töll
Browse files

fix merge conflict

parents 0fdb672f 4f6d0f0d
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
*.mo *.mo
docs/.build/ docs/.build/
debexpo.db* debexpo.db*
data/ /data/
*~ *~
*.kpf *.kpf
build build
......
...@@ -89,7 +89,7 @@ def load_environment(global_conf, app_conf): ...@@ -89,7 +89,7 @@ def load_environment(global_conf, app_conf):
error_handler=handle_mako_error, error_handler=handle_mako_error,
module_directory=os.path.join(app_conf['cache_dir'], 'templates'), module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
input_encoding='utf-8', default_filters=['escape'], input_encoding='utf-8', default_filters=['escape'],
imports=['from webhelpers.html import escape']) imports=['from webhelpers.html import escape', 'from debexpo.lib.filters import semitrusted'])
# CONFIGURATION OPTIONS HERE (note: all config options will override # CONFIGURATION OPTIONS HERE (note: all config options will override
# any Pylons config options) # any Pylons config options)
......
...@@ -63,7 +63,9 @@ def make_map(config): ...@@ -63,7 +63,9 @@ def make_map(config):
map.connect('contact', '/contact', controller='index', action='contact') map.connect('contact', '/contact', controller='index', action='contact')
map.connect('intro-maintainers', '/intro-maintainers', map.connect('intro-maintainers', '/intro-maintainers',
controller='index', action='intro_maintainers') controller='index', action='intro_maintainers')
map.connect('intro-sponsors', '/intro-sponsors', controller='index', action='intro_sponsors') map.connect('sponsors', '/sponsors', controller='sponsor', action='index')
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('intro-reviewers', '/intro-reviewers', controller='index', action='intro_reviewers')
map.connect('my', '/my', controller='my', action='index') map.connect('my', '/my', controller='my', action='index')
map.connect('login', '/login', controller='login', action='index') map.connect('login', '/login', controller='login', action='index')
......
...@@ -38,12 +38,16 @@ __license__ = 'MIT' ...@@ -38,12 +38,16 @@ __license__ = 'MIT'
import logging import logging
from debexpo.lib.base import BaseController, c, config, render from debexpo.lib.base import BaseController, c, config, render, session
from debexpo.lib import constants
from debexpo.controllers.packages import PackagesController, PackageGroups from debexpo.controllers.packages import PackagesController, PackageGroups
from webhelpers.html import literal from webhelpers.html import literal
from datetime import datetime, timedelta from datetime import datetime, timedelta
from debexpo.model.package_versions import PackageVersion from debexpo.model.package_versions import PackageVersion
from debexpo.model.packages import Package from debexpo.model.packages import Package
from debexpo.model.users import User
from debexpo.model import meta
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -89,16 +93,14 @@ class IndexController(BaseController): ...@@ -89,16 +93,14 @@ class IndexController(BaseController):
else: else:
c.custom_html = '' c.custom_html = ''
return render('/index/intro-maintainers.mako') # The template will need to look at the user details.
if 'user_id' in session:
log.debug('Getting user object for user_id = "%s"' % session['user_id'])
def intro_sponsors(self): self.user = meta.session.query(User).get(session['user_id'])
"""Return an introduction page for sponsors""" c.user = self.user
if 'debexpo.html.sponsors_intro' in config: c.logged_in = True
f = open(config['debexpo.html.sponsors_intro'])
c.custom_html = literal(f.read())
f.close()
else: else:
c.custom_html = '' c.logged_in = False
return render('/index/intro-maintainers.mako')
return render('/index/intro-sponsors.mako')
...@@ -40,12 +40,15 @@ import logging ...@@ -40,12 +40,15 @@ import logging
from debexpo.lib.base import * from debexpo.lib.base import *
from debexpo.lib import constants, form from debexpo.lib import constants, form
from debexpo.lib.schemas import DetailsForm, GpgForm, PasswordForm, OtherDetailsForm from debexpo.lib.schemas import DetailsForm, GpgForm, PasswordForm, OtherDetailsForm, MetricsForm
from debexpo.lib.gnupg import GnuPG from debexpo.lib.gnupg import GnuPG
from debexpo.model import meta from debexpo.model import meta
from debexpo.model.users import User from debexpo.model.users import User
from debexpo.model.user_countries import UserCountry from debexpo.model.user_countries import UserCountry
from debexpo.model.sponsor_metrics import SponsorMetrics, SponsorMetricsTags, SponsorTags
from sqlalchemy.orm import joinedload
import debexpo.lib.utils import debexpo.lib.utils
...@@ -152,6 +155,45 @@ class MyController(BaseController): ...@@ -152,6 +155,45 @@ class MyController(BaseController):
redirect(url('my')) redirect(url('my'))
@validate(schema=MetricsForm(), form='index')
def _metrics(self):
"""
Handles a user submitting the metrics form.
"""
log.debug('Metrics form validated successfully')
if 'user_id' not in session:
log.debug('Requires authentication')
session['path_before_login'] = request.path_info
session.save()
redirect(url('login'))
sm = SponsorMetrics(user_id=session['user_id'])
sm.contact = int(self.form_result['preferred_contact_method'])
#XXX TODO: WTF?! Find out why on earth package_types is no string
sm.types = str(self.form_result['package_types'])
sm.guidelines_text = self.form_result['packaging_guideline_text']
sm.social_requirements = self.form_result['social_requirements']
sm.availability = self.form_result['availability']
if self.form_result['packaging_guidelines'] == constants.SPONSOR_GUIDELINES_TYPE_URL:
sm.guidelines = constants.SPONSOR_GUIDELINES_TYPE_URL
elif self.form_result['packaging_guidelines'] == constants.SPONSOR_GUIDELINES_TYPE_TEXT:
sm.guidelines = constants.SPONSOR_GUIDELINES_TYPE_TEXT
else:
sm.guidelines = constants.SPONSOR_GUIDELINES_TYPE_NONE
for tag in meta.session.query(SponsorTags).all():
if tag.tag in self.form_result:
log.debug("Weighten tag %s to %s" % (tag.tag, self.form_result[tag.tag]))
metrics = SponsorMetricsTags(tag=tag.tag, user_id=session['user_id'], weight=self.form_result[tag.tag])
sm.tags.append(metrics)
meta.session.merge(sm)
meta.session.commit()
redirect(url('my'))
def index(self, get=False): def index(self, get=False):
""" """
Controller entry point. Displays forms to change user details. Controller entry point. Displays forms to change user details.
...@@ -178,7 +220,8 @@ class MyController(BaseController): ...@@ -178,7 +220,8 @@ class MyController(BaseController):
return { 'details' : self._details, return { 'details' : self._details,
'gpg' : self._gpg, 'gpg' : self._gpg,
'password' : self._password, 'password' : self._password,
'other_details' : self._other_details 'other_details' : self._other_details,
'metrics' : self._metrics,
}[request.params['form']]() }[request.params['form']]()
except KeyError: except KeyError:
log.error('Could not find form name; defaulting to main page') log.error('Could not find form name; defaulting to main page')
...@@ -219,5 +262,30 @@ class MyController(BaseController): ...@@ -219,5 +262,30 @@ class MyController(BaseController):
else: else:
c.currentgpg = None c.currentgpg = None
if self.user.status == constants.USER_STATUS_DEVELOPER:
# Fill in various sponsor metrics
c.constants = constants
c.contact_methods = [
(constants.SPONSOR_CONTACT_METHOD_NONE, _('None')),
(constants.SPONSOR_CONTACT_METHOD_EMAIL, _('Email')),
(constants.SPONSOR_CONTACT_METHOD_IRC, _('IRC')),
(constants.SPONSOR_CONTACT_METHOD_JABBER, _('Jabber')),
]
c.metrics = meta.session.query(SponsorMetrics)\
.options(joinedload(SponsorMetrics.user))\
.options(joinedload(SponsorMetrics.tags))\
.filter_by(user_id = session['user_id'])\
.first()
c.technical_tags = meta.session.query(SponsorTags).filter_by(tag_type=constants.SPONSOR_METRICS_TYPE_TECHNICAL).all()
c.social_tags = meta.session.query(SponsorTags).filter_by(tag_type=constants.SPONSOR_METRICS_TYPE_SOCIAL).all()
if not c.metrics:
# Set some sane defaults
log.debug("Generating new defaults for sponsor metrics")
c.metrics = SponsorMetrics()
c.metrics.availability = constants.SPONSOR_METRICS_PRIVATE
c.metrics.guidelines = constants.SPONSOR_GUIDELINES_TYPE_NONE
log.debug('Rendering page') log.debug('Rendering page')
return render('/my/index.mako') return render('/my/index.mako')
# -*- coding: utf-8 -*-
#
# index.py — index controller
#
# This file is part of debexpo - http://debexpo.workaround.org
#
# Copyright © 2011 Arno Töll <debian@toell.net>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
"""
Holds the IndexController.
"""
__author__ = 'Arno Töll'
__copyright__ = 'Copyright © 2011 Arno Töll'
__license__ = 'MIT'
import logging
import random
import struct
import socket
from debexpo.lib.base import BaseController, c, config, render, session, \
redirect, url, abort, request
from debexpo.lib import constants
from debexpo.model.sponsor_metrics import SponsorMetrics, SponsorTags, SponsorMetricsTags
from debexpo.model.users import User
from sqlalchemy.orm import joinedload, contains_eager
from debexpo.model import meta
log = logging.getLogger(__name__)
class SponsorController(BaseController):
def _validate_tags(self, tags, existing_tags = None):
"""
Validates a list of tags with actual existing ones
```tags```
A list of tags which need to be verified
```existing_tags````
The list of existing tags. Might be None to let this method fetch the tag list
"""
if not existing_tags:
existing_tags = [tag.tag for tag in meta.session.query(SponsorTags).all()]
else:
existing_tags = [tag.tag for tag in existing_tags]
for tag in tags:
if not tag in existing_tags:
return False
return True
def clear(self):
"""
Clear applied filters in the session.
This method can be safely called, even if no filter was registered
"""
if 'sponsor_filters' in session:
log.debug("Clear sponsor filter")
del(session['sponsor_filters'])
session.save()
redirect(url('sponsors'))
def save(self):
"""
Toggle a filter within the session.
This method prepares a list of filters to limit results in the sponsor list
```tag``` the sponsor tag to be filtered. If the tag is already in the filter
list remove it, add it otherwise.
"""
tags = request.params.getall('t')
if not self._validate_tags(tags):
abort(404)
if 'sponsor_filters' not in session:
session['sponsor_filters'] = []
session['sponsor_filters'] = tags
session.save()
redirect(url('sponsors'))
def index(self):
"""
Return an introduction page for sponsors
This page honors filters configured in the user session
"""
if not config['debexpo.enable_experimental_code'].lower() == 'true':
return render('/sponsor/index-old.mako')
if 'debexpo.html.sponsors_intro' in config:
f = open(config['debexpo.html.sponsors_intro'])
c.custom_html = literal(f.read())
f.close()
else:
c.custom_html = ''
c.constants = constants
c.sponsors = meta.session.query(SponsorMetrics)\
.options(joinedload(SponsorMetrics.user))\
.options(joinedload(SponsorMetrics.tags))\
.filter(SponsorMetrics.availability >= constants.SPONSOR_METRICS_RESTRICTED)\
.all()
def hash_ip():
"""
This is a simple hashing algorithm not supposed to be called from anywhere
but for internal use only.
It reads the client IP address and returns a float between 0.01 and 0.91 which is
used as input for random.shuffle
"""
ip = request.environ['REMOTE_ADDR']
try:
ip = struct.unpack( "!L", socket.inet_pton( socket.AF_INET, ip ))
ip = ip[0]
except socket.error:
ip = struct.unpack( "!QQ", socket.inet_pton( socket.AF_INET6, ip ))
ip = ip[1]
ip = (float(ip % 10) + 0.01) / 10
return ip
random.shuffle(c.sponsors, hash_ip)
# The select above works fine, except that it sucks.
# It suffers from a poor performance and it could be significantly improved by querying the tag
# labels and descriptions (i.e. the SponsorTags table by joining them with SponsorMetricsTags.
# However the resulting result set does not quite look like what I imagine. Feel free to replace it
# by something which actually works.
#c.sponsors = meta.session.query(SponsorMetrics, SponsorMetricsTags, SponsorTags, User)\
# .join(User)\
# .join(SponsorMetricsTags)\
# .join(SponsorTags)\
# .filter(SponsorMetrics.availability >= constants.SPONSOR_METRICS_RESTRICTED)\
if 'sponsor_filters' in session:
log.debug("Applying tag filter")
c.sponsor_filter = session['sponsor_filters']
else:
c.sponsor_filter = []
if request.params.getall('t'):
c.sponsor_filter = request.params.getall('t')
c.technical_tags = meta.session.query(SponsorTags).filter_by(tag_type=constants.SPONSOR_METRICS_TYPE_TECHNICAL).all()
c.social_tags = meta.session.query(SponsorTags).filter_by(tag_type=constants.SPONSOR_METRICS_TYPE_SOCIAL).all()
if not self._validate_tags(c.sponsor_filter, c.technical_tags + c.social_tags):
abort(404)
return render('/sponsor/index.mako')
...@@ -39,7 +39,6 @@ __license__ = 'MIT' ...@@ -39,7 +39,6 @@ __license__ = 'MIT'
import os import os
import logging import logging
import subprocess import subprocess
import md5
import base64 import base64
try: try:
...@@ -48,7 +47,7 @@ except ImportError: # for sqlalchemy 0.7.1 and above ...@@ -48,7 +47,7 @@ except ImportError: # for sqlalchemy 0.7.1 and above
from sqlalchemy.exc import InvalidRequestError from sqlalchemy.exc import InvalidRequestError
from debexpo.lib.base import * from debexpo.lib.base import *
from debexpo.lib.utils import allowed_upload from debexpo.lib.filesystem import CheckFiles
from debexpo.model import meta from debexpo.model import meta
from debexpo.model.user_upload_key import UserUploadKey from debexpo.model.user_upload_key import UserUploadKey
...@@ -105,7 +104,7 @@ class UploadController(BaseController): ...@@ -105,7 +104,7 @@ class UploadController(BaseController):
# Check whether the file extension is supported by debexpo # Check whether the file extension is supported by debexpo
if not allowed_upload(filename): if not CheckFiles().allowed_upload(filename):
log.error('File type not supported: %s' % filename) log.error('File type not supported: %s' % filename)
abort(403, 'The uploaded file type is not supported') abort(403, 'The uploaded file type is not supported')
......
...@@ -35,6 +35,12 @@ __author__ = 'Jonny Lamb' ...@@ -35,6 +35,12 @@ __author__ = 'Jonny Lamb'
__copyright__ = 'Copyright © 2008 Jonny Lamb' __copyright__ = 'Copyright © 2008 Jonny Lamb'
__license__ = 'MIT' __license__ = 'MIT'
# Importer related constants
ORIG_TARBALL_LOCATION_NOT_FOUND = 0
ORIG_TARBALL_LOCATION_LOCAL = 1
ORIG_TARBALL_LOCATION_REPOSITORY = 2
# User constants # User constants
USER_TYPE_NORMAL = 1 USER_TYPE_NORMAL = 1
USER_TYPE_ADMIN = 2 USER_TYPE_ADMIN = 2
...@@ -76,3 +82,21 @@ PACKAGE_COMMENT_STATUS_UPLOADED = 2 ...@@ -76,3 +82,21 @@ PACKAGE_COMMENT_STATUS_UPLOADED = 2
# Package subscriptions # Package subscriptions
SUBSCRIPTION_LEVEL_UPLOADS = 1 SUBSCRIPTION_LEVEL_UPLOADS = 1
SUBSCRIPTION_LEVEL_COMMENTS = 2 SUBSCRIPTION_LEVEL_COMMENTS = 2
#Sponsor metrics
SPONSOR_METRICS_PRIVATE = 0
SPONSOR_METRICS_RESTRICTED = 1
SPONSOR_METRICS_PUBLIC = 2
SPONSOR_METRICS_TYPE_TECHNICAL = 1
SPONSOR_METRICS_TYPE_SOCIAL = 2
SPONSOR_CONTACT_METHOD_NONE = 0
SPONSOR_CONTACT_METHOD_EMAIL = 1
SPONSOR_CONTACT_METHOD_IRC = 2
SPONSOR_CONTACT_METHOD_JABBER = 3
SPONSOR_GUIDELINES_TYPE_NONE = 0
SPONSOR_GUIDELINES_TYPE_URL = 1
SPONSOR_GUIDELINES_TYPE_TEXT = 2
...@@ -106,8 +106,8 @@ class CheckFiles(object): ...@@ -106,8 +106,8 @@ class CheckFiles(object):
def find_orig_tarball(self, changes_file): def find_orig_tarball(self, changes_file):
""" """
Look to see whether there is an orig tarball present, if the dsc refers to one. Look to see whether there is an orig tarball present, if the dsc refers to one.
If it is present or not necessary, this returns True. Otherwise, it returns the This method returns a triple (filename, file_found, location_hint), returning the (expected)
name of the file required. name of the original tarball, and whether it was found in the local repository
```changes_file``` ```changes_file```
The changes file to parse for the orig.tar (note the dsc file referenced must exist) The changes file to parse for the orig.tar (note the dsc file referenced must exist)
...@@ -119,12 +119,22 @@ class CheckFiles(object): ...@@ -119,12 +119,22 @@ class CheckFiles(object):
if (file['name'].endswith('orig.tar.gz') or if (file['name'].endswith('orig.tar.gz') or
file['name'].endswith('orig.tar.bz2') or file['name'].endswith('orig.tar.bz2') or
file['name'].endswith('orig.tar.xz')): file['name'].endswith('orig.tar.xz')):
full_filename = os.path.join(pylons.config['debexpo.repository'], changes_file.get_pool_path(), file['name'])
# tar.gz was found in the local directory
if os.path.isfile(file['name']): if os.path.isfile(file['name']):
return (file['name'], True) sum = md5sum(file['name'])
if sum == file['md5sum']:
return (file['name'], constants.ORIG_TARBALL_LOCATION_LOCAL)
# tar.gz was found, but does not seem to be the same file
break
# tar.gz was found in the repository
elif os.path.isfile(full_filename):
return (file['name'], constants.ORIG_TARBALL_LOCATION_REPOSITORY)
# tar.gz was expected but not found at all
else: else:
return (file['name'], False) return (file['name'], constants.ORIG_TARBALL_LOCATION_NOT_FOUND)
return (None, False) return (None, constants.ORIG_TARBALL_LOCATION_NOT_FOUND)
def find_files_for_package(self, package, absolute_path=False): def find_files_for_package(self, package, absolute_path=False):
...@@ -161,6 +171,26 @@ class CheckFiles(object): ...@@ -161,6 +171,26 @@ class CheckFiles(object):
if os.path.exists(file): if os.path.exists(file):
log.debug("Removing file '%s'" % (file)) log.debug("Removing file '%s'" % (file))
os.unlink(file) os.unlink(file)
if os.path.isdir(path): if os.path.isdir(path) and os.listdir(path) == []:
log.debug("Remove empty package repository '%s'" % (path)) log.debug("Remove empty package repository '%s'" % (path))
os.rmdir(path) os.rmdir(path)
def allowed_upload(self, filename):
"""
Looks at a filename's extension and decides whether to accept it.
We only want package files to be uploaded, after all.
It returns a boolean of whether to accept the file or not.
``filename``
File to test.
"""
for suffix in ['.changes', '.dsc', '.tar.gz', '.diff.gz', '.deb', '.udeb', '.tar.bz2', ".tar.xz"]:
if filename.endswith(suffix):
return True
return False
# -*- coding: utf-8 -*-
#
# filter.py — Output filters for the template engine
#
# This file is part of debexpo - http://debexpo.workaround.org
#
# Copyright © 2011 Arno Töll <debian@toell.net>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
"""
Holds various output filters which can be applied to templates
"""
__author__ = 'Arno Töll'
__copyright__ = 'Copyright © 2011 Arno Töll'
__license__ = 'MIT'
import cgi
def semitrusted(input_filter):
"""
This filter filters all input, but keeps formatting, e.g. newlines
``input_filter`` The data to be filtered
"""
escaped_filter = cgi.escape(input_filter, True)
escaped_filter = escaped_filter.replace('\n', '<br />')
return escaped_filter
...@@ -141,8 +141,11 @@ class Plugins(object): ...@@ -141,8 +141,11 @@ class Plugins(object):
for item in dsc['Files']: for item in dsc['Files']:
if item['name'] not in self.changes.get_files(): if item['name'] not in self.changes.get_files():
src_file = os.path.join(self.config['debexpo.upload.incoming'], item['name']) src_file = os.path.join(self.config['debexpo.upload.incoming'], item['name'])
repository_src_file = os.path.join(self.config['debexpo.repository'], self.changes.get_pool_path(), item['name'])
if os.path.exists(src_file): if os.path.exists(src_file):
shutil.copy(src_file, self.tempdir) shutil.copy(src_file, self.tempdir)
elif os.path.exists(repository_src_file):
shutil.copy(repository_src_file, self.tempdir)
else: else:
log.critical("Trying to copy non-existing file %s" % (src_file)) log.critical("Trying to copy non-existing file %s" % (src_file))
......
...@@ -39,10 +39,14 @@ __license__ = 'MIT' ...@@ -39,10 +39,14 @@ __license__ = 'MIT'
import formencode import formencode
from pylons import config from pylons import config
from debexpo.lib.base import meta
from debexpo.lib import constants from debexpo.lib import constants
from debexpo.lib.validators import NewEmailToSystem, GpgKey, \ from debexpo.lib.validators import NewEmailToSystem, GpgKey, \
CurrentPassword, CheckBox, NewNameToSystem, ValidateSponsorEmail CurrentPassword, CheckBox, NewNameToSystem, ValidateSponsorEmail, \
ValidatePackagingGuidelines, DummyValidator
from debexpo.model.sponsor_metrics import SponsorTags
class LoginForm(formencode.Schema): class LoginForm(formencode.Schema):
""" """
...@@ -97,6 +101,44 @@ class OtherDetailsForm(MyForm): ...@@ -97,6 +101,44 @@ class OtherDetailsForm(MyForm):
jabber = formencode.validators.String() jabber = formencode.validators.String()
status = CheckBox() status = CheckBox()
class MetricsForm(MyForm):
"""
Schema for updating the metrics in the controller
"""
def __init__(self, *args, **kwargs):
for tag in meta.session.query(SponsorTags).all():
kwargs[tag.tag] = formencode.validators.Number(min=-10, max=10, not_empty=True)
MyForm.__init__(self, *args, **kwargs)
preferred_contact_method = formencode.compound.All(
formencode.validators.OneOf([
constants.SPONSOR_CONTACT_METHOD_NONE,
constants.SPONSOR_CONTACT_METHOD_EMAIL,
constants.SPONSOR_CONTACT_METHOD_IRC,
constants.SPONSOR_CONTACT_METHOD_JABBER
]), formencode.validators.Int(not_empty=True))
package_types = formencode.validators.String()
packaging_guidelines = formencode.compound.All(
formencode.validators.OneOf([
constants.SPONSOR_GUIDELINES_TYPE_NONE,
constants.SPONSOR_GUIDELINES_TYPE_URL,
constants.SPONSOR_GUIDELINES_TYPE_TEXT]), formencode.validators.Int(not_empty=True))
availability = formencode.compound.All(
formencode.validators.OneOf([
constants.SPONSOR_METRICS_PRIVATE,
constants.SPONSOR_METRICS_RESTRICTED,
constants.SPONSOR_METRICS_PUBLIC]), formencode.validators.Int(not_empty=True))
social_requirements = formencode.validators.String()
# Postpone validation of packaging_guideline_text, as its validation
# depends on the value of packaging_guidelines
packaging_guideline_text = DummyValidator
chained_validators = [
formencode.schema.SimpleFormValidator(ValidatePackagingGuidelines)
]
class RegisterForm(formencode.Schema): class RegisterForm(formencode.Schema):
""" """
Schema for the general fields in the register controller. The maintainer Schema for the general fields in the register controller. The maintainer
......
...@@ -37,28 +37,12 @@ __license__ = 'MIT' ...@@ -37,28 +37,12 @@ __license__ = 'MIT'
import logging import logging
import hashlib import hashlib
import md5
import os import os
from pylons import config from pylons import config
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def allowed_upload(filename):
"""
Looks at a filename's extension and decides whether to accept it.
We only want package files to be uploaded, after all.
It returns a boolean of whether to accept the file or not.
``filename``
File to test.
"""
for suffix in ['.changes', '.dsc', '.tar.gz', '.diff.gz', '.deb', '.udeb', '.tar.bz2', ".tar.xz"]:
if filename.endswith(suffix):
return True
return False
def parse_section(section): def parse_section(section):
""" """
Works out the component and section from the "Section" field. Works out the component and section from the "Section" field.
...@@ -101,7 +85,7 @@ def md5sum(filename): ...@@ -101,7 +85,7 @@ def md5sum(filename):
except: except:
raise AttributeError('Failed to open file %s.' % filename) raise AttributeError('Failed to open file %s.' % filename)
sum = md5.new() sum = hashlib.md5()
while True: while True:
chunk = f.read(10240) chunk = f.read(10240)
if not chunk: if not chunk:
......
...@@ -43,6 +43,7 @@ from debexpo.lib.gnupg import GnuPG ...@@ -43,6 +43,7 @@ from debexpo.lib.gnupg import GnuPG
from debexpo.model import meta from debexpo.model import meta
from debexpo.model.users import User from debexpo.model.users import User
from debexpo.lib import constants
import debexpo.lib.utils import debexpo.lib.utils
...@@ -173,3 +174,17 @@ def ValidateSponsorEmail(values, state, validator): ...@@ -173,3 +174,17 @@ def ValidateSponsorEmail(values, state, validator):
if values['sponsor'] == '1' and not values['email'].endswith('@debian.org'): if values['sponsor'] == '1' and not values['email'].endswith('@debian.org'):
return {'sponsor': 'A sponsor account must be registered with your @debian.org address' } return {'sponsor': 'A sponsor account must be registered with your @debian.org address' }
class DummyValidator(formencode.FancyValidator):
pass
def ValidatePackagingGuidelines(values, state, validator):
try:
if values['packaging_guidelines'] == constants.SPONSOR_GUIDELINES_TYPE_TEXT:
formencode.validators.String(min=1).to_python(values['packaging_guideline_text'])
elif values['packaging_guidelines'] == constants.SPONSOR_GUIDELINES_TYPE_URL:
formencode.validators.URL(add_http=True).to_python(values['packaging_guideline_text'])
else:
formencode.validators.Empty().to_python(values['packaging_guideline_text'])
except Exception as e:
return {'packaging_guideline_text': e}
return None
...@@ -61,7 +61,7 @@ def import_all_models(): ...@@ -61,7 +61,7 @@ def import_all_models():
from debexpo.model import binary_packages, package_files, packages, source_packages, \ from debexpo.model import binary_packages, package_files, packages, source_packages, \
user_metrics, package_comments, package_info, package_versions, user_countries, \ user_metrics, package_comments, package_info, package_versions, user_countries, \
users, package_subscriptions, user_upload_key, password_reset users, package_subscriptions, user_upload_key, password_reset, sponsor_metrics
class OrmObject(object): class OrmObject(object):
""" """
...@@ -85,9 +85,8 @@ class OrmObject(object): ...@@ -85,9 +85,8 @@ class OrmObject(object):
'not column in mapped table: %s' % (key,)) 'not column in mapped table: %s' % (key,))
def __repr__(self): def __repr__(self):
atts = [] attrs = []
for key in dir(self): for key in self.__dict__:
if not key.startswith('_') and key is not "foreign" and key not in self.foreign: if not key.startswith('_'):
atts.append((key, getattr(self, key))) attrs.append((key, getattr(self, key)))
return self.__class__.__name__ + '(' + ', '.join(x[0] + '=' + repr(x[1]) for x in attrs) + ')'
return self.__class__.__name__ + '(' + ', '.join(x[0] + '=' + repr(x[1]) for x in atts) + ')'
import debexpo.lib.constants
TAGS = {
debexpo.lib.constants.SPONSOR_METRICS_TYPE_TECHNICAL: [
('CDBS','cdbs', 'Your package makes use of the <a href="http://build-common.alioth.debian.org/">The Common Debian Build System</a>'),
('(Plain) debhelper','debhelper', 'Your package makes use of the <a href="http://kitenet.net/~joey/code/debhelper/">debhelper</a> build system'),
('(Short) dh-style debhelper', 'dh', 'Your package makes use of short <tt>dh(1)</tt> style build system'),
('No build helper / home brewed debian/rules','yada', 'Your package is using a completely customized, yet <a href="http://www.debian.org/doc/debian-policy/ch-source.html#s-debianrules">policy compliant</a> <tt>debian/rules</tt> file, which does not make use of either debhelper or CDBS.'),
('NMUs','nmu', 'Your package is a <a href="http://www.debian.org/doc/manuals/developers-reference/pkgs.html#nmu">NMU</a>'),
('QA uploads','qa', 'Your package is a <a href="http://www.debian.org/doc/manuals/developers-reference/pkgs.html#nmu-qa-upload">QA upload</a>'),
('Backports','backports', 'Your package is a <a href="http://backports-master.debian.org/">backported package</a>'),
('Modified tarballs (but good reasons)','modified-tarballs', 'Your package modified the original source tarball somehow. It does not match the original checksum anymore but you have a <a href="http://www.debian.org/doc/manuals/developers-reference/best-pkging-practices.html#bpp-origtargz">good reason</a> to do so'),
('Library package', 'library-package', 'You are packaging a <a href="http://www.debian.org/doc/debian-policy/ch-sharedlibs.html">policy compliant library</a>'),
('VCS snapshot tarballs','vcs-tarball', 'Your package is not based on a original source tarball at all but is based on a VCS snapshot',),
('contrib/non-free packages', 'non-free-package', 'Your package it targetting the <tt>contrib</tt> or <tt>non-free</tt> branches (<a href="http://www.debian.org/doc/debian-policy/ch-archive.html#s-sections">Information</a>)'),
('1.0 format packages', '1.0-format', 'Your package is using the 1.0 format (the traditional source format that is)'),
('3.0 format packages', '3.0-format', 'Your package is using the <a href="http://wiki.debian.org/Projects/DebSrc3.0">3.0/quilt</a> format'),
('Embedded code copies', 'embedded-code-copies', 'Your package <a href="http://www.debian.org/doc/debian-policy/ch-source.html#s-embeddedfiles">makes use of embedded code copies in a reasonable way</a>'),
('DEP-5 copyright', 'dep5', 'Your package does make use of <a href="http://dep.debian.net/deps/dep5/">DEP-5</a> copyright files'),
('No Lintian cleanliness (but good reasons)', 'not-lintian-clean', 'Your package is not <a href="http://lintian.debian.org/">Lintian clean</a> down to the informational level but you have a good reason why not'),
],
debexpo.lib.constants.SPONSOR_METRICS_TYPE_SOCIAL: [
('Prospective DM/DD', 'prospective-dm-dd', 'You are willing to become a <a href="http://wiki.debian.org/DebianMaintainer">Debian Maintainer</a>/<a href="http://wiki.debian.org/DebianDeveloper">Debian Developer</a> some day'),
('(Willing to be) DM', 'applicant-is-dm', 'You are a <a href="http://wiki.debian.org/DebianMaintainer">Debian Maintainer</a> already, or you plan to become one soon'),
('(Willing to enter) NM', 'applicant-is-nm', 'You are in the <a href="https://nm.debian.org/">New Maintainer</a> process to become a developer or you plan to apply soon'),
('Signed GPG key', 'signed-gpg-key', 'Your GPG matches the <a href="http://lists.debian.org/debian-devel-announce/2010/09/msg00003.html">guidelines of the Debian keyring maintainers</a> and/or is signed by any Debian developer'),
('One time uploads', 'no-one-time-upload', 'Your package is a single shot upload'),
('Sharing a time zone', 'sharing-time-zone', 'You share a time zone with your sponsors. This can be useful to get together more easily'),
('Possibility to meet-up', 'possibility-to-meetup', 'You are living close to your sponsor and you are willing to meet him eventually'),
('Having already packages in Debian', 'maintainer-already', 'You do already have one or more packages in Debian'),
('Maintainer is not upstream', 'maintainer-is-not-upstream', 'You are not upstream of the package you are proposing to Debian'),
('Notable user base', 'notable-user-base', 'You can show us that people are already using your program and consider it useful'),
('Minimizing differences to derivatives', 'package-in-a-derivative', 'Your package feeds changes introduced by any derivative back to Debian to minimize differences'),
]
}
...@@ -55,11 +55,8 @@ t_packages = sa.Table('packages', meta.metadata, ...@@ -55,11 +55,8 @@ t_packages = sa.Table('packages', meta.metadata,
class Package(OrmObject): class Package(OrmObject):
foreign = ['user'] foreign = ['user']
def get_description_nl2br(self): def get_description(self):
s = self.description return self.description
s = s.replace('<', '&lt;')
s = s.replace('\n', '<br />')
return s
orm.mapper(Package, t_packages, properties={ orm.mapper(Package, t_packages, properties={
'user' : orm.relation(User, backref='packages') 'user' : orm.relation(User, backref='packages')
......
# -*- coding: utf-8 -*-
#
# sponsor_metrics.py — rudimentary model for sponsor metrics
#
# This file is part of debexpo - http://debexpo.workaround.org
#
# Copyright © 2011 Arno Töll <debian@toell.net>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
"""
Holds Sponsor Metrics Model
"""
__author__ = 'Arno Töll'
__copyright__ = 'Copyright © 2011 Arno Töll'
__license__ = 'MIT'
import os
import datetime
import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy.ext.associationproxy import association_proxy
from debexpo.model import meta, OrmObject
from debexpo.model.users import User
from debexpo.lib import constants
import debexpo.lib.utils
t_sponsor_metrics = sa.Table(
'sponsor_metrics', meta.metadata,
sa.Column('user_id', sa.types.Integer, sa.ForeignKey('users.id'), primary_key=True),
sa.Column('availability', sa.types.Integer, nullable=False),
sa.Column('contact', sa.types.Integer, nullable=False),
sa.Column('types', sa.types.Text, nullable=True),
sa.Column('guidelines', sa.types.Integer, nullable=True),
sa.Column('guidelines_text', sa.types.Text, nullable=True),
sa.Column('social_requirements', sa.types.Text, nullable=True),
)
t_sponsor_tags = sa.Table(
'sponsor_tags', meta.metadata,
sa.Column('tag_type', sa.types.Integer, nullable=False),
sa.Column('tag', sa.types.Text, primary_key=True),
sa.Column('label', sa.types.Text, nullable=False),
sa.Column('long_description', sa.types.Text, nullable=False),
)
t_sponsor_metrics_tags = sa.Table(
'sponsor_metrics_tags', meta.metadata,
sa.Column('tag', sa.Integer, sa.ForeignKey('sponsor_tags.tag'), primary_key=True),
sa.Column('user_id', sa.Integer, sa.ForeignKey('sponsor_metrics.user_id'), primary_key=True),
sa.Column('weight', sa.Integer),
)
class SponsorMetrics(OrmObject):
foreign = ['user']
def get_all_tags_weighted(self, weight = 0):
if weight > 0:
return [x.tag for x in self.tags if x.weight > 0]
elif weight < 0:
return [x.tag for x in self.tags if x.weight < 0]
else:
return [x.tag for x in self.tags]
def get_all_tags(self):
return get_all_tags_weighted(0)
def get_technical_tags(self):
return [x.tag for x in self.get_technical_tags_full()]
def get_social_tags(self):
return [x.tag for x in self.get_social_tags_full()]
def get_technical_tags_full(self):
return [x for x in self.tags if x.full_tag.tag_type == constants.SPONSOR_METRICS_TYPE_TECHNICAL]
def get_social_tags_full(self):
return [x for x in self.tags if x.full_tag.tag_type == constants.SPONSOR_METRICS_TYPE_SOCIAL]
def get_tag_weight(self, tag):
for t in self.tags:
if t.tag == tag:
return t.weight
return 0.0
def get_guidelines(self):
"""
Return a formatted and sanitized string of the guidelines the sponsor
configured
"""
return self.guidelines_text
def get_types(self):
"""
Return a formatted and sanitized string of the packages the sponsor
is interested in
"""
if self.types:
return self.types
return ""
def get_social_requirements(self):
"""
Return a formatted and sanitized string of the social requirements the sponsor
configured
"""
if self.social_requirements:
return self.social_requirements
else:
return ""
def allowed(self, flag):
"""
Returns true if the user associated with this object allowed to display a
contact address. This method also honors the SPONSOR_METRICS_RESTRICTED flag
```flag``` A SPONSOR_CONTACT_METHOD_* flag which should be checked
"""
if self.availability == constants.SPONSOR_METRICS_RESTRICTED and self.contact == flag:
return True
elif self.availability == constants.SPONSOR_METRICS_PUBLIC:
return True
return False
class SponsorTags(OrmObject):
pass
#keywords = association_proxy('metrics', 'metric')
class SponsorMetricsTags(OrmObject):
foreign = ['user', 'tags']
orm.mapper(SponsorMetrics, t_sponsor_metrics, properties={
'tags' : orm.relationship(SponsorMetricsTags),
'user' : orm.relationship(User),
})
orm.mapper(SponsorTags, t_sponsor_tags)
orm.mapper(SponsorMetricsTags, t_sponsor_metrics_tags, properties={
'full_tag': orm.relationship(SponsorTags)
})
def create_tags():
import debexpo.model.data.tags
import logging
for metrics_type in [constants.SPONSOR_METRICS_TYPE_TECHNICAL, constants.SPONSOR_METRICS_TYPE_SOCIAL]:
for (description, tag, long_description) in debexpo.model.data.tags.TAGS[metrics_type]:
st = SponsorTags(tag_type=metrics_type, tag=tag, label=description, long_description=long_description)
logging.info("Adding tag %s" % (tag))
meta.session.merge(st)
meta.session.commit()
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
# #
# Copyright © 2008 Jonny Lamb <jonny@debian.org> # Copyright © 2008 Jonny Lamb <jonny@debian.org>
# Copyright © 2010 Jan Dittberner <jandd@debian.org> # Copyright © 2010 Jan Dittberner <jandd@debian.org>
# Copyright © 2011 Arno Töll <debian@toell.net>
# #
# Permission is hereby granted, free of charge, to any person # Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation # obtaining a copy of this software and associated documentation
...@@ -33,7 +34,7 @@ Holds the getorigtarball plugin. ...@@ -33,7 +34,7 @@ Holds the getorigtarball plugin.
""" """
__author__ = 'Jonny Lamb' __author__ = 'Jonny Lamb'
__copyright__ = 'Copyright © 2008 Jonny Lamb, Copyright © 2010 Jan Dittberner' __copyright__ = 'Copyright © 2008 Jonny Lamb, Copyright © 2010 Jan Dittberner, Copyright © 2011 Arno Töll'
__license__ = 'MIT' __license__ = 'MIT'
from debian import deb822 from debian import deb822
...@@ -42,7 +43,9 @@ import os ...@@ -42,7 +43,9 @@ import os
import urllib import urllib
import re import re
from debexpo.lib import constants
from debexpo.lib.utils import md5sum from debexpo.lib.utils import md5sum
from debexpo.lib.filesystem import CheckFiles
from debexpo.plugins import BasePlugin from debexpo.plugins import BasePlugin
import pylons import pylons
...@@ -67,27 +70,27 @@ class GetOrigTarballPlugin(BasePlugin): ...@@ -67,27 +70,27 @@ class GetOrigTarballPlugin(BasePlugin):
size = 15728640 size = 15728640
log.debug('Checking whether an orig tarball mentioned in the dsc is missing') log.debug('Checking whether an orig tarball mentioned in the dsc is missing')
dsc = deb822.Dsc(file(self.changes.get_dsc())) dsc = deb822.Dsc(file(self.changes.get_dsc()))
filecheck = CheckFiles()
if filecheck.is_native_package(self.changes):
log.debug('No orig.tar.gz file found; native package?')
return
# An orig.tar.gz was found in the dsc, and also in the upload.
(orig, orig_file_found) = filecheck.find_orig_tarball(self.changes)
if orig_file_found > constants.ORIG_TARBALL_LOCATION_NOT_FOUND:
log.debug('%s found successfully', orig)
return
orig = None
for dscfile in dsc['Files']: for dscfile in dsc['Files']:
dscfile['size'] = int(dscfile['size']) dscfile['size'] = int(dscfile['size'])
if re.search('(orig\.tar\.(gz|bz2|xz))$', dscfile['name']): if orig == dscfile['name']:
orig = dscfile
if dscfile['size'] > size: if dscfile['size'] > size:
log.warning("Skipping eventual download of orig.tar.gz %s: size %d > %d" % (dscfile['name'], dscfile['size'], size)) log.warning("Skipping eventual download of orig.tar.gz %s: size %d > %d" % (dscfile['name'], dscfile['size'], size))
return return
orig = dscfile
break break
# There is no orig.tar.gz file in the dsc file. This is probably a native package.
if orig is None:
log.debug('No orig.tar.gz file found; native package?')
return
# An orig.tar.gz was found in the dsc, and also in the upload.
if os.path.isfile(orig['name']):
log.debug('%s found successfully', orig['name'])
return
log.debug('Could not find %s; looking in Debian for it', orig['name']) log.debug('Could not find %s; looking in Debian for it', orig['name'])
url = os.path.join(pylons.config['debexpo.debian_mirror'], self.changes.get_pool_path(), orig['name']) url = os.path.join(pylons.config['debexpo.debian_mirror'], self.changes.get_pool_path(), orig['name'])
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment