Skip to content

Commit

Permalink
Merge branch 'release-0.1b1'
Browse files Browse the repository at this point in the history
  • Loading branch information
bbengfort committed Jul 19, 2016
2 parents 80822db + a680d0c commit 393211a
Show file tree
Hide file tree
Showing 89 changed files with 4,180 additions and 41 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ DJANGO_TEST_SETTINGS_MODULE = $(PROJECT).settings.$(TEST_SETTINGS)
DJANGO_TEST_POSTFIX := --settings=$(DJANGO_TEST_SETTINGS_MODULE) --pythonpath=$(PYTHONPATH)

# Apps to test
APPS := partisan
APPS := partisan members

# Export targets not associated with files
.PHONY: test showenv coverage bootstrap pip virtualenv clean virtual_env_set truncate
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@ This small web application is intended to highlight how to operationalize machin

The image used in this README, [Partisan Fail][partisan.jpg] by [David Colarusso](https://www.flickr.com/photos/dcolarusso/) is licensed under [CC BY-NC 2.0](https://creativecommons.org/licenses/by-nc/2.0/)

## Changelog

The release versions that are deployed to the web servers are also tagged in GitHub. You can see the tags through the GitHub web application and download the tarball of the version you'd like.

The versioning uses a three part version system, "a.b.c" - "a" represents a major release that may not be backwards compatible. "b" is incremented on minor releases that may contain extra features, but are backwards compatible. "c" releases are bug fixes or other micro changes that developers should feel free to immediately update to.

### Version 0.1 Beta 1

* **tag**: [v0.1b1](https://github.com/DistrictDataLabs/partisan-discourse/releases/tag/v0.1b1)
* **deployment**: Monday, July 18, 2016
* **commit**: [see tag](#)

This is the first beta release of the Political Discourse application. Right now this simple web application allows users to sign in, then add links to go fetch web content to the global corpus. These links are then preprocessed using NLP foo. Users can tag the documents as Republican or Democrat, allowing us to build a political classifier.

<!-- References -->
[travis_img]: https://travis-ci.org/DistrictDataLabs/partisan-discourse.svg
[travis_href]: https://travis-ci.org/DistrictDataLabs/partisan-discourse
Expand Down
25 changes: 25 additions & 0 deletions corpus/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# corpus
# An application that allows the management of our classifier corpus.
#
# Author: Benjamin Bengfort <[email protected]>
# Created: Sun Jul 17 19:29:55 2016 -0400
#
# Copyright (C) 2016 District Data Labs
# For license information, see LICENSE.txt
#
# ID: __init__.py [] [email protected] $

"""
An application that allows the management of our classifier corpus.
"""

##########################################################################
## Imports
##########################################################################


##########################################################################
## Configuration
##########################################################################

default_app_config = 'corpus.apps.CorpusConfig'
29 changes: 29 additions & 0 deletions corpus/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# corpus.admin
# Register models with the Django Admin for the corpus app.
#
# Author: Benjamin Bengfort <[email protected]>
# Created: Sun Jul 17 19:30:33 2016 -0400
#
# Copyright (C) 2016 District Data Labs
# For license information, see LICENSE.txt
#
# ID: admin.py [] [email protected] $

"""
Register models with the Django Admin for the corpus app.
"""

##########################################################################
## Imports
##########################################################################

from django.contrib import admin
from corpus.models import Document, Annotation, Label

##########################################################################
## Register Admin
##########################################################################

admin.site.register(Label)
admin.site.register(Annotation)
admin.site.register(Document)
33 changes: 33 additions & 0 deletions corpus/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# corpus.apps
# Application definition for the corpus app.
#
# Author: Benjamin Bengfort <[email protected]>
# Created: Sun Jul 17 19:31:28 2016 -0400
#
# Copyright (C) 2016 District Data Labs
# For license information, see LICENSE.txt
#
# ID: apps.py [] [email protected] $

"""
Application definition for the corpus app.
"""

##########################################################################
## Imports
##########################################################################

from django.apps import AppConfig


##########################################################################
## Corpus Config
##########################################################################

class CorpusConfig(AppConfig):

name = 'corpus'
verbose_name = "Corpora"

def ready(self):
import corpus.signals
58 changes: 58 additions & 0 deletions corpus/bitly.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# corpus.bitly
# Access the bit.ly url shortening service.
#
# Author: Benjamin Bengfort <[email protected]>
# Created: Mon Jul 18 09:59:27 2016 -0400
#
# Copyright (C) 2016 District Data Labs
# For license information, see LICENSE.txt
#
# ID: bitly.py [] [email protected] $

"""
Access the bit.ly url shortening service.
"""

##########################################################################
## Imports
##########################################################################

import requests

from django.conf import settings
from urllib.parse import urljoin
from corpus.exceptions import BitlyAPIError

##########################################################################
## Shorten function
##########################################################################

def shorten(url, token=None):
"""
Shortens a URL using the bit.ly API.
"""

# Get the bit.ly access token from settings
token = settings.BITLY_ACCESS_TOKEN or token
if not token:
raise BitlyAPIError(
"Cannot call shorten URL without a bit.ly access token"
)

# Compute and make the request to the API
endpoint = urljoin(settings.BITLY_API_ADDRESS, "v3/shorten")
params = {
"access_token": token,
"longUrl": url,
}

# bit.ly tends not to send status code errors
response = requests.get(endpoint, params=params)

# Parse and return the result
data = response.json()
if data['status_code'] != 200:
raise BitlyAPIError(
"Could not shorten link: {}".format(data['status_txt'])
)
return data['data']['url']
38 changes: 38 additions & 0 deletions corpus/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# corpus.exceptions
# Custom exceptions for corpus handling.
#
# Author: Benjamin Bengfort <[email protected]>
# Created: Mon Jul 18 09:57:26 2016 -0400
#
# Copyright (C) 2016 District Data Labs
# For license information, see LICENSE.txt
#
# ID: exceptions.py [] [email protected] $

"""
Custom exceptions for corpus handling.
"""

##########################################################################
## Corpus Exceptions
##########################################################################

class CorpusException(Exception):
"""
Something went wrong in the corpus app.
"""
pass


class BitlyAPIError(CorpusException):
"""
Something went wrong trying to shorten a url.
"""
pass


class FetchError(CorpusException):
"""
Something went wrong trying to fetch a url using requests.
"""
pass
35 changes: 35 additions & 0 deletions corpus/fixtures/labels.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[
{
"model": "corpus.label",
"pk": 1,
"fields": {
"created": "2016-07-18T14:56:26.706Z",
"modified": "2016-07-18T14:56:26.708Z",
"name": "USA Political Parties",
"slug": "usa-political-parties",
"parent": null
}
},
{
"model": "corpus.label",
"pk": 2,
"fields": {
"created": "2016-07-18T14:57:33.059Z",
"modified": "2016-07-18T14:57:33.062Z",
"name": "Democratic",
"slug": "democratic",
"parent": 1
}
},
{
"model": "corpus.label",
"pk": 3,
"fields": {
"created": "2016-07-18T14:57:59.531Z",
"modified": "2016-07-18T14:57:59.534Z",
"name": "Republican",
"slug": "republican",
"parent": 1
}
}
]
39 changes: 39 additions & 0 deletions corpus/managers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# corpus.managers
# Model managers for the corpus application.
#
# Author: Benjamin Bengfort <[email protected]>
# Created: Mon Jul 18 23:09:19 2016 -0400
#
# Copyright (C) 2016 District Data Labs
# For license information, see LICENSE.txt
#
# ID: managers.py [] [email protected] $

"""
Model managers for the corpus application.
"""

##########################################################################
## Imports
##########################################################################

from django.db import models


##########################################################################
## Annotation Manager
##########################################################################

class AnnotationManager(models.Manager):

def republican(self):
"""
Filters the annotations for only republican annotations.
"""
return self.filter(label__slug='republican')

def democratic(self):
"""
Filters the annotations for only democratic annotations.
"""
return self.filter(label__slug='democratic')
95 changes: 95 additions & 0 deletions corpus/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-07-18 17:31
from __future__ import unicode_literals

import autoslug.fields
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import model_utils.fields
import picklefield.fields


class Migration(migrations.Migration):

initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='Annotation',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
],
options={
'db_table': 'annotations',
'get_latest_by': 'created',
},
),
migrations.CreateModel(
name='Document',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('title', models.CharField(blank=True, default=None, max_length=255, null=True)),
('long_url', models.URLField(max_length=2000, unique=True)),
('short_url', models.URLField(blank=True, default=None, max_length=30, null=True)),
('raw_html', models.TextField(blank=True, default=None, null=True)),
('content', picklefield.fields.PickledObjectField(blank=True, default=None, editable=False, null=True)),
('signature', models.CharField(blank=True, default=None, editable=False, max_length=44, null=True)),
('n_words', models.SmallIntegerField(blank=True, default=None, null=True)),
('n_vocab', models.SmallIntegerField(blank=True, default=None, null=True)),
('users', models.ManyToManyField(related_name='documents', through='corpus.Annotation', to=settings.AUTH_USER_MODEL)),
],
options={
'db_table': 'documents',
'get_latest_by': 'created',
},
),
migrations.CreateModel(
name='Label',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('name', models.CharField(max_length=64, unique=True)),
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name', unique=True)),
('documents', models.ManyToManyField(related_name='labels', through='corpus.Annotation', to='corpus.Document')),
('parent', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='corpus.Label')),
],
options={
'db_table': 'labels',
'get_latest_by': 'created',
},
),
migrations.AddField(
model_name='annotation',
name='document',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='annotations', to='corpus.Document'),
),
migrations.AddField(
model_name='annotation',
name='label',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='annotations', to='corpus.Label'),
),
migrations.AddField(
model_name='annotation',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='annotations', to=settings.AUTH_USER_MODEL),
),
migrations.AlterUniqueTogether(
name='document',
unique_together=set([('long_url', 'short_url')]),
),
migrations.AlterUniqueTogether(
name='annotation',
unique_together=set([('document', 'user')]),
),
]
Loading

0 comments on commit 393211a

Please sign in to comment.