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

fix merge conflict

parents 0fdb672f 4f6d0f0d
......@@ -2,7 +2,7 @@
*.mo
docs/.build/
debexpo.db*
data/
/data/
*~
*.kpf
build
......
......@@ -89,7 +89,7 @@ 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'])
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)
......
......@@ -63,7 +63,9 @@ def make_map(config):
map.connect('contact', '/contact', controller='index', action='contact')
map.connect('intro-maintainers', '/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('my', '/my', controller='my', action='index')
map.connect('login', '/login', controller='login', action='index')
......
......@@ -38,12 +38,16 @@ __license__ = 'MIT'
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 webhelpers.html import literal
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__)
......@@ -89,16 +93,14 @@ class IndexController(BaseController):
else:
c.custom_html = ''
return render('/index/intro-maintainers.mako')
def intro_sponsors(self):
"""Return an introduction page for sponsors"""
if 'debexpo.html.sponsors_intro' in config:
f = open(config['debexpo.html.sponsors_intro'])
c.custom_html = literal(f.read())
f.close()
# 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'])
self.user = meta.session.query(User).get(session['user_id'])
c.user = self.user
c.logged_in = True
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
from debexpo.lib.base import *
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.model import meta
from debexpo.model.users import User
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
......@@ -152,6 +155,45 @@ class MyController(BaseController):
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):
"""
Controller entry point. Displays forms to change user details.
......@@ -178,7 +220,8 @@ class MyController(BaseController):
return { 'details' : self._details,
'gpg' : self._gpg,
'password' : self._password,
'other_details' : self._other_details
'other_details' : self._other_details,
'metrics' : self._metrics,
}[request.params['form']]()
except KeyError:
log.error('Could not find form name; defaulting to main page')
......@@ -216,8 +259,33 @@ class MyController(BaseController):
# Enable the form to show information on the user's GPG key.
if self.user.gpg is not None:
c.currentgpg = c.user.gpg_id
else:
else:
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')
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'
import os
import logging
import subprocess
import md5
import base64
try:
......@@ -48,7 +47,7 @@ except ImportError: # for sqlalchemy 0.7.1 and above
from sqlalchemy.exc import InvalidRequestError
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.user_upload_key import UserUploadKey
......@@ -105,7 +104,7 @@ class UploadController(BaseController):
# 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)
abort(403, 'The uploaded file type is not supported')
......
......@@ -35,6 +35,12 @@ __author__ = 'Jonny Lamb'
__copyright__ = 'Copyright © 2008 Jonny Lamb'
__license__ = 'MIT'
# Importer related constants
ORIG_TARBALL_LOCATION_NOT_FOUND = 0
ORIG_TARBALL_LOCATION_LOCAL = 1
ORIG_TARBALL_LOCATION_REPOSITORY = 2
# User constants
USER_TYPE_NORMAL = 1
USER_TYPE_ADMIN = 2
......@@ -76,3 +82,21 @@ PACKAGE_COMMENT_STATUS_UPLOADED = 2
# Package subscriptions
SUBSCRIPTION_LEVEL_UPLOADS = 1
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):
def find_orig_tarball(self, changes_file):
"""
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
name of the file required.
This method returns a triple (filename, file_found, location_hint), returning the (expected)
name of the original tarball, and whether it was found in the local repository
```changes_file```
The changes file to parse for the orig.tar (note the dsc file referenced must exist)
......@@ -119,12 +119,22 @@ class CheckFiles(object):
if (file['name'].endswith('orig.tar.gz') or
file['name'].endswith('orig.tar.bz2') or
file['name'].endswith('orig.tar.xz')):
if os.path.isfile(file['name']):
return (file['name'], True)
else:
return (file['name'], False)
return (None, False)
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']):
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:
return (file['name'], constants.ORIG_TARBALL_LOCATION_NOT_FOUND)
return (None, constants.ORIG_TARBALL_LOCATION_NOT_FOUND)
def find_files_for_package(self, package, absolute_path=False):
......@@ -161,6 +171,26 @@ class CheckFiles(object):
if os.path.exists(file):
log.debug("Removing file '%s'" % (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))
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):
for item in dsc['Files']:
if item['name'] not in self.changes.get_files():
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):
shutil.copy(src_file, self.tempdir)
elif os.path.exists(repository_src_file):
shutil.copy(repository_src_file, self.tempdir)
else:
log.critical("Trying to copy non-existing file %s" % (src_file))
......
......@@ -39,10 +39,14 @@ __license__ = 'MIT'
import formencode
from pylons import config
from debexpo.lib.base import meta
from debexpo.lib import constants
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):
"""
......@@ -97,6 +101,44 @@ class OtherDetailsForm(MyForm):
jabber = formencode.validators.String()
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):
"""
Schema for the general fields in the register controller. The maintainer
......
......@@ -37,28 +37,12 @@ __license__ = 'MIT'
import logging
import hashlib
import md5
import os
from pylons import config
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):
"""
Works out the component and section from the "Section" field.
......@@ -101,7 +85,7 @@ def md5sum(filename):
except:
raise AttributeError('Failed to open file %s.' % filename)
sum = md5.new()
sum = hashlib.md5()
while True:
chunk = f.read(10240)
if not chunk:
......
......@@ -43,6 +43,7 @@ from debexpo.lib.gnupg import GnuPG
from debexpo.model import meta
from debexpo.model.users import User
from debexpo.lib import constants
import debexpo.lib.utils
......@@ -173,3 +174,17 @@ def ValidateSponsorEmail(values, state, validator):
if values['sponsor'] == '1' and not values['email'].endswith('@debian.org'):
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():
from debexpo.model import binary_packages, package_files, packages, source_packages, \
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):
"""
......@@ -85,9 +85,8 @@ class OrmObject(object):
'not column in mapped table: %s' % (key,))
def __repr__(self):
atts = []
for key in dir(self):
if not key.startswith('_') and key is not "foreign" and key not in self.foreign:
atts.append((key, getattr(self, key)))
return self.__class__.__name__ + '(' + ', '.join(x[0] + '=' + repr(x[1]) for x in atts) + ')'
attrs = []
for key in self.__dict__:
if not key.startswith('_'):
attrs.append((key, getattr(self, key)))
return self.__class__.__name__ + '(' + ', '.join(x[0] + '=' + repr(x[1]) for x in attrs) + ')'
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,
class Package(OrmObject):
foreign = ['user']
def get_description_nl2br(self):
s = self.description
s = s.replace('<', '&lt;')
s = s.replace('\n', '<br />')
return s
def get_description(self):
return self.description
orm.mapper(Package, t_packages, properties={
'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 @@
#
# Copyright © 2008 Jonny Lamb <jonny@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
# obtaining a copy of this software and associated documentation
......@@ -33,7 +34,7 @@ Holds the getorigtarball plugin.
"""
__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'
from debian import deb822
......@@ -42,7 +43,9 @@ import os
import urllib
import re
from debexpo.lib import constants
from debexpo.lib.utils import md5sum
from debexpo.lib.filesystem import CheckFiles
from debexpo.plugins import BasePlugin
import pylons
......@@ -67,27 +70,27 @@ class GetOrigTarballPlugin(BasePlugin):
size = 15728640
log.debug('Checking whether an orig tarball mentioned in the dsc is missing')
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']:
dscfile['size'] = int(dscfile['size'])
if re.search('(orig\.tar\.(gz|bz2|xz))$', dscfile['name']):
orig = dscfile
if orig == dscfile['name']:
if dscfile['size'] > size:
log.warning("Skipping eventual download of orig.tar.gz %s: size %d > %d" % (dscfile['name'], dscfile['size'], size))
return
orig = dscfile
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'])
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