diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000000000000000000000000000000000000..85b23c96b57c772170faa712c3d9b51c0df63f80 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,4 @@ +[run] +omit = + representatives_positions/contrib/* + representatives_recommendations/contrib/* diff --git a/memopol/settings.py b/memopol/settings.py index 1cf752ae456ded9de3fc5e7753f0f4c17ea8031b..ffdd506af1839ddc01a8eb5169ecb969ac33a6a9 100644 --- a/memopol/settings.py +++ b/memopol/settings.py @@ -258,6 +258,14 @@ LOGGING = { 'handlers': ['console'], 'level': LOG_LEVEL, }, + 'representatives_positions': { + 'handlers': ['console'], + 'level': LOG_LEVEL + }, + 'representatives_recommendations': { + 'handlers': ['console'], + 'level': LOG_LEVEL + }, 'representatives_votes': { 'handlers': ['console'], 'level': LOG_LEVEL, diff --git a/pytest.ini b/pytest.ini index 0ea23da77840032793af4df2f96df5462590ac5e..246beef238daadfd1c2652e6a64dbd86fa3a45dc 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,3 @@ [pytest] DJANGO_SETTINGS_MODULE=memopol.settings -addopts = --cov=. --create-db +addopts = --cov-config .coveragerc --cov=. --create-db diff --git a/representatives_positions/contrib/__init__.py b/representatives_positions/contrib/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/representatives_positions/contrib/import_positions.py b/representatives_positions/contrib/import_positions.py new file mode 100644 index 0000000000000000000000000000000000000000..95c1936bd1f7da8dc4d8b5957de8a1a3b976d317 --- /dev/null +++ b/representatives_positions/contrib/import_positions.py @@ -0,0 +1,107 @@ +# coding: utf-8 + +import csv +import django +from django.apps import apps +import logging +import sys +import re + +from representatives_positions.models import Position +from representatives.models import Representative + +logger = logging.getLogger(__name__) + + +class PositionImporter: + def __init__(self): + self.rep_cache = {} + + def get_rep(self, first_name, last_name): + key = '%s %s' % (first_name, last_name) + rep = self.rep_cache.get(key, None) + + if rep is None: + try: + rep = Representative.objects.get(first_name=first_name, + last_name=last_name) + self.rep_cache[key] = rep + except Representative.DoesNotExist: + rep = None + + return rep + + def import_row(self, row): + if len(row['date']) == 0: + logger.warn('Cannot import dateless position for %s %s on URL %s' % + (row['first_name'], row['last_name'], row['url'])) + return False + + rep = self.get_rep(row['first_name'], row['last_name']) + if rep is None: + logger.warn('Could not find rep %s %s' % (row['first_name'], + row['last_name'])) + return False + + text = re.sub('(^<p>|</p>$)', '', row['content']) + if row['title'] is not None and len(row['title']) > 0: + text = '%s\n%s' % (row['title'], text) + + try: + position = Position.objects.get(representative=rep, + link=row['url']) + except Position.DoesNotExist: + position = Position( + representative=rep, + link=row['url'], + datetime=row['date'], + text=text, + published=True + ) + position.save() + logger.info('Created position for %s %s on URL %s' % ( + row['first_name'], row['last_name'], row['url'])) + + return True + + +def main(stream=None): + """ + Imports positions from an old memopol instance. + + Usage: + cat positions.csv | memopol_import_positions + + The input CSV file should be generated by the following query: + SELECT CONCAT(o.content, '|', o.url, '|', o.title, '|', ro.date, '|', + r.first_name, '|', r.last_name) + FROM reps_opinion o + INNER JOIN reps_opinionrep ro ON ro.opinion_id = o.id + INNER JOIN reps_representative r ON r.id = ro.representative_id + WHERE o.institution='EU' + + """ + + if not apps.ready: + django.setup() + + importer = PositionImporter() + rejected = [] + imported = 0 + + reader = csv.DictReader(stream or sys.stdin, delimiter='|', fieldnames=[ + 'content', + 'url', + 'title', + 'date', + 'first_name', + 'last_name' + ], quoting=csv.QUOTE_NONE) + + for row in reader: + if not importer.import_row(row): + rejected.append(row) + else: + imported = imported + 1 + + logger.info('%d rows imported, %d rows rejected', imported, len(rejected)) diff --git a/representatives_positions/models.py b/representatives_positions/models.py index 491622e5b8fd92febd0b6670ee85469c9c86621e..1e68c8c977b674ab2a79d314dfb86967a925a7a5 100644 --- a/representatives_positions/models.py +++ b/representatives_positions/models.py @@ -2,10 +2,11 @@ from django.db import models from django.core.urlresolvers import reverse from django.template.defaultfilters import truncatewords from taggit.managers import TaggableManager +from representatives.models import Representative class Position(models.Model): - representative = models.ForeignKey('representatives.representative', + representative = models.ForeignKey(Representative, related_name='positions') datetime = models.DateField() text = models.TextField() diff --git a/representatives_recommendations/contrib/__init__.py b/representatives_recommendations/contrib/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/representatives_recommendations/contrib/import_data.py b/representatives_recommendations/contrib/import_data.py new file mode 100644 index 0000000000000000000000000000000000000000..4d3c3df00f4713d6a03106146d9ec4aed05cfad6 --- /dev/null +++ b/representatives_recommendations/contrib/import_data.py @@ -0,0 +1,50 @@ +# coding: utf-8 +# flake8: noqa + +dossier_mappings = { + # Patent law: patentability of computer-implemented inventions + "Directive on patentability of \"computer-implemented inventions\" (software patents), 1st reading": + "2002/0047(COD)", + + # Criminal measures aimed at ensuring the enforcement of intellectual property rights + "Criminal measures aimed at ensuring the enforcement of intellectual property rights (IPRED 2), 1st reading": + "2005/0127(COD)", + + # Electronic communications: common regulatory framework for networks and services, access, interconnection and authorisation ['Telecoms Package' (amend. Directives 2002/19/EC, 2002/20/EC and 2002/21/EC)] + "Directives reforming the EU's regulatory framework for electronic communications networks and services (telecoms package), 1st reading": + "2007/0247(COD)", + + "Directives reforming the EU's regulatory framework for electronic communications networks and services (telecoms package), 2nd reading": + "2007/0247(COD)", + + # Cultural industries in Europe + "Rapport Bono on cultural industries in Europe": + "2007/2153(INI)", + + # Strengthening security and fundamental freedoms on the Internet + "Rapport Lambrinidis on strengthening security and fundamental freedoms on the Internet": + "2008/2160(INI)", + + # Enforcement of intellectual property rights in the internal market + "Rapport Gallo on enforcement of intellectual property rights in the internal market": + "2009/2178(INI)", + + # Resolution on the Anti-Counterfeiting Trade Agreement (ACTA) + "Resolution on Anti-Counterfeiting Trade Agreement (ACTA)": + "2010/2935(RSP)", + + # Enhanced cooperation in the area of the creation of unitary patent protection: implementation + "A7-0001/2012": + "2011/0093(COD)", + + # EU/Australia, Canada, Japan, Korea, Mexico, Morocco, New Zealand, Singapore, Switzerland and United States Anti-Counterfeiting Trade Agreement (ACTA) + "A7-0204/2012": + "2011/0167(NLE)", +} + +resolutions = [ + u'résolution législative', + u'résolution', + 'legislative resolution', + 'resolution' +] diff --git a/representatives_recommendations/contrib/import_recommendations.py b/representatives_recommendations/contrib/import_recommendations.py new file mode 100644 index 0000000000000000000000000000000000000000..c056afdd8d3f9191594e93b72d40a945cfe59d9a --- /dev/null +++ b/representatives_recommendations/contrib/import_recommendations.py @@ -0,0 +1,131 @@ +# coding: utf-8 + +import csv +import django +from django.apps import apps +import logging +import sys + +from representatives_recommendations.models import Recommendation +from representatives_votes.models import Dossier, Proposal + +from .import_data import dossier_mappings, resolutions + +logger = logging.getLogger(__name__) + + +class RecommendationImporter: + def __init__(self): + self.dossier_cache = {} + + def get_dossier(self, title): + dossier = self.dossier_cache.get(title, None) + + if dossier is None: + ref = dossier_mappings.get(title, None) + if ref is not None: + query = {'reference': ref} + else: + query = {'title__iexact': title} + + try: + dossier = Dossier.objects.get(**query) + self.dossier_cache[title] = dossier + except Dossier.DoesNotExist: + dossier = None + + return dossier + + def get_proposal(self, dossier, kind): + kinds = [kind] + + try: + resolutions.index(kind.lower()) + kinds.extend(resolutions) + except ValueError: + pass + + for k in kinds: + try: + return Proposal.objects.get(dossier=dossier, kind__iexact=k) + except Proposal.DoesNotExist: + continue + + return None + + def import_row(self, row): + dossier = self.get_dossier(row['title']) + if dossier is None: + logger.warn('No dossier "%s"' % row['title']) + return False + + proposal = self.get_proposal(dossier, row['part']) + if proposal is None: + logger.warn('No proposal "%s" for dossier %s (%d): "%s"' % ( + row['part'].decode('utf-8'), dossier.reference, dossier.pk, + row['title'])) + return False + + weight = int(row['weight']) * int(row['ponderation']) + descr = row['description'].strip() + if len(descr) == 0: + descr = '%s on %s' % (row['part'], dossier.reference) + + try: + recom = Recommendation.objects.get(proposal=proposal) + except Recommendation.DoesNotExist: + recom = Recommendation( + proposal=proposal, + recommendation=row['recommendation'], + title=descr, + weight=weight + ) + recom.save() + logger.info('Created recommendation with weight %s for %s: %s' % ( + weight, + row['title'], + row['part'] + )) + + return True + + +def main(stream=None): + """ + Imports recommendations from an old memopol instance. + + Usage: + cat recommendations.csv | memopol_import_recommendations + + The input CSV file should be generated by the following query: + SELECT CONCAT(r.description, '|', r.weight, '|', r.recommendation, '|', + r.part, '|', p.title, '|', p.ponderation) + FROM votes_recommendation r + LEFT JOIN votes_proposal p ON r.proposal_id = p.id + WHERE p.institution = 'EU' + + """ + + if not apps.ready: + django.setup() + + importer = RecommendationImporter() + rejected = [] + imported = 0 + + reader = csv.DictReader(stream or sys.stdin, delimiter='|', fieldnames=[ + 'description', + 'weight', + 'recommendation', + 'part', + 'title', + 'ponderation' + ], quoting=csv.QUOTE_NONE) + + for row in reader: + if not importer.import_row(row): + rejected.append(row) + else: + imported = imported + 1 + + logger.info('%d rows imported, %d rows rejected', imported, len(rejected)) diff --git a/setup.py b/setup.py index 37e46b9d4c52ef983298a61e2a754278bad3ecb4..307e63f398ecf44200400bb92c863733eb7e5434 100644 --- a/setup.py +++ b/setup.py @@ -41,5 +41,11 @@ setup(name='political-memory', 'pytest-cov==2.2.0', 'codecov', ] + }, + entry_points={ + 'console_scripts': [ + 'memopol_import_positions = representatives_positions.contrib.import_positions:main', # noqa + 'memopol_import_recommendations = representatives_recommendations.contrib.import_recommendations:main', # noqa + ] } )