{"action":"created","comment":{"url":"https://api.github.com/repos/dokterbob/django-newsletter/pulls/comments/48779847","id":48779847,"diff_hunk":"@@ -0,0 +1,333 @@\n+import logging\n+logger = logging.getLogger(__name__)\n+\n+from django import forms\n+from django.conf import settings\n+from django.core.exceptions import ValidationError\n+from django.core.validators import validate_email\n+from django.utils.translation import ugettext_lazy as _, ugettext\n+\n+from newsletter.models import Subscription\n+\n+\n+class AddressList(object):\n+    \"\"\" List with unique addresses. \"\"\"\n+\n+    def __init__(self, newsletter, ignore_errors=False):\n+        self.newsletter = newsletter\n+        self.ignore_errors = ignore_errors\n+        self.addresses = {}\n+\n+    def add(self, email, name=None, location='unknown location'):\n+        \"\"\" Add name to list. \"\"\"\n+\n+        logger.debug(\"Going to add %s <%s>\", name, email)\n+\n+        name = check_name(name, self.ignore_errors)\n+        email = check_email(email, self.ignore_errors)\n+\n+        try:\n+            validate_email(email)\n+        except ValidationError:\n+            logger.warn(\n+                \"Entry '%s' does not contain a valid e-mail address at %s.\"\n+                % (email, location)\n+            )\n+\n+            if not self.ignore_errors:\n+                raise forms.ValidationError(_(\n+                    \"Entry '%s' does not contain a valid \"\n+                    \"e-mail address.\") % name\n+                )\n+\n+        if email in self.addresses:\n+            logger.warn(\n+                \"Entry '%s' contains a duplicate entry at %s.\"\n+                % (email, location)\n+            )\n+\n+            if not self.ignore_errors:\n+                raise forms.ValidationError(_(\n+                    \"The address file contains duplicate entries \"\n+                    \"for '%s'.\") % email)\n+\n+        try:\n+            subscription_exists(self.newsletter, email, name)\n+        except ValidationError:\n+            logger.warn(\n+                \"Entry '%s' is already subscribed to at %s.\"\n+                % (email, location)\n+            )\n+            # \"Entry '%s' at line %d is already subscribed to \"\n+            # \"with email '%s'\",\n+            # name, myreader.line_num, email, extra=dict(data={'row': row}))\n+\n+            if not self.ignore_errors:\n+                raise forms.ValidationError(\n+                    _(\"Some entries are already subscribed to.\"))\n+\n+        self.addresses[email] = name\n+\n+\n+def subscription_exists(newsletter, email, name=None):\n+    \"\"\"\n+    Return wheter or not a subscription exists.\n+    \"\"\"\n+    qs = Subscription.objects.filter(\n+        newsletter__id=newsletter.id,\n+        subscribed=True,\n+        email_field__exact=email)\n+\n+    return not qs.exists()\n+\n+\n+def check_email(email, ignore_errors=False):\n+    \"\"\"\n+    Check (length of) email address.\n+\n+    TODO: Update this to perform full validation on email.\n+    \"\"\"\n+\n+    if settings.DEBUG:\n+        logger.debug(\"Checking e-mail address %s\", email)\n+\n+    email_length = \\\n+        Subscription._meta.get_field_by_name('email_field')[0].max_length\n+\n+    if len(email) <= email_length or ignore_errors:\n+        return email[:email_length].strip()","path":"newsletter/addressimport/parsers.py","position":98,"original_position":98,"commit_id":"f285247aac400c437de447a832fb8c5b4323c931","original_commit_id":"f285247aac400c437de447a832fb8c5b4323c931","user":{"login":"claudep","id":143192,"avatar_url":"https://avatars.githubusercontent.com/u/143192?v=3","gravatar_id":"","url":"https://api.github.com/users/claudep","html_url":"https://github.com/claudep","followers_url":"https://api.github.com/users/claudep/followers","following_url":"https://api.github.com/users/claudep/following{/other_user}","gists_url":"https://api.github.com/users/claudep/gists{/gist_id}","starred_url":"https://api.github.com/users/claudep/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/claudep/subscriptions","organizations_url":"https://api.github.com/users/claudep/orgs","repos_url":"https://api.github.com/users/claudep/repos","events_url":"https://api.github.com/users/claudep/events{/privacy}","received_events_url":"https://api.github.com/users/claudep/received_events","type":"User","site_admin":false},"body":"To answer a comment on the previous patch, I've got an error with one of the example file because of a trailing space on an email line. Instead of fixing the sample file, I found better to handle leading/trailing spaces gracefully here.","created_at":"2016-01-04T20:54:26Z","updated_at":"2016-01-04T20:54:26Z","html_url":"https://github.com/dokterbob/django-newsletter/pull/163#discussion_r48779847","pull_request_url":"https://api.github.com/repos/dokterbob/django-newsletter/pulls/163","_links":{"self":{"href":"https://api.github.com/repos/dokterbob/django-newsletter/pulls/comments/48779847"},"html":{"href":"https://github.com/dokterbob/django-newsletter/pull/163#discussion_r48779847"},"pull_request":{"href":"https://api.github.com/repos/dokterbob/django-newsletter/pulls/163"}}},"pull_request":{"url":"https://api.github.com/repos/dokterbob/django-newsletter/pulls/163","id":54969373,"html_url":"https://github.com/dokterbob/django-newsletter/pull/163","diff_url":"https://github.com/dokterbob/django-newsletter/pull/163.diff","patch_url":"https://github.com/dokterbob/django-newsletter/pull/163.patch","issue_url":"https://api.github.com/repos/dokterbob/django-newsletter/issues/163","number":163,"state":"open","locked":false,"title":"Import refactor","user":{"login":"dokterbob","id":22742,"avatar_url":"https://avatars.githubusercontent.com/u/22742?v=3","gravatar_id":"","url":"https://api.github.com/users/dokterbob","html_url":"https://github.com/dokterbob","followers_url":"https://api.github.com/users/dokterbob/followers","following_url":"https://api.github.com/users/dokterbob/following{/other_user}","gists_url":"https://api.github.com/users/dokterbob/gists{/gist_id}","starred_url":"https://api.github.com/users/dokterbob/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dokterbob/subscriptions","organizations_url":"https://api.github.com/users/dokterbob/orgs","repos_url":"https://api.github.com/users/dokterbob/repos","events_url":"https://api.github.com/users/dokterbob/events{/privacy}","received_events_url":"https://api.github.com/users/dokterbob/received_events","type":"User","site_admin":false},"body":"Full refactor of address import code, based on #157. Fixes #104 \r\n\r\nMore cleanup could be done (i.e. left over comments) but the structural and important stuff is contained here. Mainly; much less code duplication, tests for the improt (thanks so much @claudep!).\r\n\r\n@claudep Please review and merge and/or comment. Thanks!","created_at":"2016-01-04T12:18:02Z","updated_at":"2016-01-04T20:54:26Z","closed_at":null,"merged_at":null,"merge_commit_sha":"a73a28a7b8e18e90cd155465e74ad8aa6ff2fae9","assignee":{"login":"claudep","id":143192,"avatar_url":"https://avatars.githubusercontent.com/u/143192?v=3","gravatar_id":"","url":"https://api.github.com/users/claudep","html_url":"https://github.com/claudep","followers_url":"https://api.github.com/users/claudep/followers","following_url":"https://api.github.com/users/claudep/following{/other_user}","gists_url":"https://api.github.com/users/claudep/gists{/gist_id}","starred_url":"https://api.github.com/users/claudep/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/claudep/subscriptions","organizations_url":"https://api.github.com/users/claudep/orgs","repos_url":"https://api.github.com/users/claudep/repos","events_url":"https://api.github.com/users/claudep/events{/privacy}","received_events_url":"https://api.github.com/users/claudep/received_events","type":"User","site_admin":false},"milestone":null,"commits_url":"https://api.github.com/repos/dokterbob/django-newsletter/pulls/163/commits","review_comments_url":"https://api.github.com/repos/dokterbob/django-newsletter/pulls/163/comments","review_comment_url":"https://api.github.com/repos/dokterbob/django-newsletter/pulls/comments{/number}","comments_url":"https://api.github.com/repos/dokterbob/django-newsletter/issues/163/comments","statuses_url":"https://api.github.com/repos/dokterbob/django-newsletter/statuses/f285247aac400c437de447a832fb8c5b4323c931","head":{"label":"dokterbob:import_refactor","ref":"import_refactor","sha":"f285247aac400c437de447a832fb8c5b4323c931","user":{"login":"dokterbob","id":22742,"avatar_url":"https://avatars.githubusercontent.com/u/22742?v=3","gravatar_id":"","url":"https://api.github.com/users/dokterbob","html_url":"https://github.com/dokterbob","followers_url":"https://api.github.com/users/dokterbob/followers","following_url":"https://api.github.com/users/dokterbob/following{/other_user}","gists_url":"https://api.github.com/users/dokterbob/gists{/gist_id}","starred_url":"https://api.github.com/users/dokterbob/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dokterbob/subscriptions","organizations_url":"https://api.github.com/users/dokterbob/orgs","repos_url":"https://api.github.com/users/dokterbob/repos","events_url":"https://api.github.com/users/dokterbob/events{/privacy}","received_events_url":"https://api.github.com/users/dokterbob/received_events","type":"User","site_admin":false},"repo":{"id":228466,"name":"django-newsletter","full_name":"dokterbob/django-newsletter","owner":{"login":"dokterbob","id":22742,"avatar_url":"https://avatars.githubusercontent.com/u/22742?v=3","gravatar_id":"","url":"https://api.github.com/users/dokterbob","html_url":"https://github.com/dokterbob","followers_url":"https://api.github.com/users/dokterbob/followers","following_url":"https://api.github.com/users/dokterbob/following{/other_user}","gists_url":"https://api.github.com/users/dokterbob/gists{/gist_id}","starred_url":"https://api.github.com/users/dokterbob/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dokterbob/subscriptions","organizations_url":"https://api.github.com/users/dokterbob/orgs","repos_url":"https://api.github.com/users/dokterbob/repos","events_url":"https://api.github.com/users/dokterbob/events{/privacy}","received_events_url":"https://api.github.com/users/dokterbob/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/dokterbob/django-newsletter","description":"An email newsletter application for the Django web application framework, including an extended admin interface, web (un)subscription, dynamic e-mail templates, an archive and HTML email support.","fork":false,"url":"https://api.github.com/repos/dokterbob/django-newsletter","forks_url":"https://api.github.com/repos/dokterbob/django-newsletter/forks","keys_url":"https://api.github.com/repos/dokterbob/django-newsletter/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dokterbob/django-newsletter/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dokterbob/django-newsletter/teams","hooks_url":"https://api.github.com/repos/dokterbob/django-newsletter/hooks","issue_events_url":"https://api.github.com/repos/dokterbob/django-newsletter/issues/events{/number}","events_url":"https://api.github.com/repos/dokterbob/django-newsletter/events","assignees_url":"https://api.github.com/repos/dokterbob/django-newsletter/assignees{/user}","branches_url":"https://api.github.com/repos/dokterbob/django-newsletter/branches{/branch}","tags_url":"https://api.github.com/repos/dokterbob/django-newsletter/tags","blobs_url":"https://api.github.com/repos/dokterbob/django-newsletter/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dokterbob/django-newsletter/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dokterbob/django-newsletter/git/refs{/sha}","trees_url":"https://api.github.com/repos/dokterbob/django-newsletter/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dokterbob/django-newsletter/statuses/{sha}","languages_url":"https://api.github.com/repos/dokterbob/django-newsletter/languages","stargazers_url":"https://api.github.com/repos/dokterbob/django-newsletter/stargazers","contributors_url":"https://api.github.com/repos/dokterbob/django-newsletter/contributors","subscribers_url":"https://api.github.com/repos/dokterbob/django-newsletter/subscribers","subscription_url":"https://api.github.com/repos/dokterbob/django-newsletter/subscription","commits_url":"https://api.github.com/repos/dokterbob/django-newsletter/commits{/sha}","git_commits_url":"https://api.github.com/repos/dokterbob/django-newsletter/git/commits{/sha}","comments_url":"https://api.github.com/repos/dokterbob/django-newsletter/comments{/number}","issue_comment_url":"https://api.github.com/repos/dokterbob/django-newsletter/issues/comments{/number}","contents_url":"https://api.github.com/repos/dokterbob/django-newsletter/contents/{+path}","compare_url":"https://api.github.com/repos/dokterbob/django-newsletter/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dokterbob/django-newsletter/merges","archive_url":"https://api.github.com/repos/dokterbob/django-newsletter/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dokterbob/django-newsletter/downloads","issues_url":"https://api.github.com/repos/dokterbob/django-newsletter/issues{/number}","pulls_url":"https://api.github.com/repos/dokterbob/django-newsletter/pulls{/number}","milestones_url":"https://api.github.com/repos/dokterbob/django-newsletter/milestones{/number}","notifications_url":"https://api.github.com/repos/dokterbob/django-newsletter/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dokterbob/django-newsletter/labels{/name}","releases_url":"https://api.github.com/repos/dokterbob/django-newsletter/releases{/id}","created_at":"2009-06-16T09:51:20Z","updated_at":"2015-12-28T23:52:58Z","pushed_at":"2016-01-04T12:18:02Z","git_url":"git://github.com/dokterbob/django-newsletter.git","ssh_url":"git@github.com:dokterbob/django-newsletter.git","clone_url":"https://github.com/dokterbob/django-newsletter.git","svn_url":"https://github.com/dokterbob/django-newsletter","homepage":"","size":3984,"stargazers_count":275,"watchers_count":275,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":94,"mirror_url":null,"open_issues_count":29,"forks":94,"open_issues":29,"watchers":275,"default_branch":"master"}},"base":{"label":"dokterbob:master","ref":"master","sha":"e528894aad9ad880d1d3ef328cf76794f23b3e22","user":{"login":"dokterbob","id":22742,"avatar_url":"https://avatars.githubusercontent.com/u/22742?v=3","gravatar_id":"","url":"https://api.github.com/users/dokterbob","html_url":"https://github.com/dokterbob","followers_url":"https://api.github.com/users/dokterbob/followers","following_url":"https://api.github.com/users/dokterbob/following{/other_user}","gists_url":"https://api.github.com/users/dokterbob/gists{/gist_id}","starred_url":"https://api.github.com/users/dokterbob/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dokterbob/subscriptions","organizations_url":"https://api.github.com/users/dokterbob/orgs","repos_url":"https://api.github.com/users/dokterbob/repos","events_url":"https://api.github.com/users/dokterbob/events{/privacy}","received_events_url":"https://api.github.com/users/dokterbob/received_events","type":"User","site_admin":false},"repo":{"id":228466,"name":"django-newsletter","full_name":"dokterbob/django-newsletter","owner":{"login":"dokterbob","id":22742,"avatar_url":"https://avatars.githubusercontent.com/u/22742?v=3","gravatar_id":"","url":"https://api.github.com/users/dokterbob","html_url":"https://github.com/dokterbob","followers_url":"https://api.github.com/users/dokterbob/followers","following_url":"https://api.github.com/users/dokterbob/following{/other_user}","gists_url":"https://api.github.com/users/dokterbob/gists{/gist_id}","starred_url":"https://api.github.com/users/dokterbob/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dokterbob/subscriptions","organizations_url":"https://api.github.com/users/dokterbob/orgs","repos_url":"https://api.github.com/users/dokterbob/repos","events_url":"https://api.github.com/users/dokterbob/events{/privacy}","received_events_url":"https://api.github.com/users/dokterbob/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/dokterbob/django-newsletter","description":"An email newsletter application for the Django web application framework, including an extended admin interface, web (un)subscription, dynamic e-mail templates, an archive and HTML email support.","fork":false,"url":"https://api.github.com/repos/dokterbob/django-newsletter","forks_url":"https://api.github.com/repos/dokterbob/django-newsletter/forks","keys_url":"https://api.github.com/repos/dokterbob/django-newsletter/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dokterbob/django-newsletter/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dokterbob/django-newsletter/teams","hooks_url":"https://api.github.com/repos/dokterbob/django-newsletter/hooks","issue_events_url":"https://api.github.com/repos/dokterbob/django-newsletter/issues/events{/number}","events_url":"https://api.github.com/repos/dokterbob/django-newsletter/events","assignees_url":"https://api.github.com/repos/dokterbob/django-newsletter/assignees{/user}","branches_url":"https://api.github.com/repos/dokterbob/django-newsletter/branches{/branch}","tags_url":"https://api.github.com/repos/dokterbob/django-newsletter/tags","blobs_url":"https://api.github.com/repos/dokterbob/django-newsletter/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dokterbob/django-newsletter/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dokterbob/django-newsletter/git/refs{/sha}","trees_url":"https://api.github.com/repos/dokterbob/django-newsletter/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dokterbob/django-newsletter/statuses/{sha}","languages_url":"https://api.github.com/repos/dokterbob/django-newsletter/languages","stargazers_url":"https://api.github.com/repos/dokterbob/django-newsletter/stargazers","contributors_url":"https://api.github.com/repos/dokterbob/django-newsletter/contributors","subscribers_url":"https://api.github.com/repos/dokterbob/django-newsletter/subscribers","subscription_url":"https://api.github.com/repos/dokterbob/django-newsletter/subscription","commits_url":"https://api.github.com/repos/dokterbob/django-newsletter/commits{/sha}","git_commits_url":"https://api.github.com/repos/dokterbob/django-newsletter/git/commits{/sha}","comments_url":"https://api.github.com/repos/dokterbob/django-newsletter/comments{/number}","issue_comment_url":"https://api.github.com/repos/dokterbob/django-newsletter/issues/comments{/number}","contents_url":"https://api.github.com/repos/dokterbob/django-newsletter/contents/{+path}","compare_url":"https://api.github.com/repos/dokterbob/django-newsletter/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dokterbob/django-newsletter/merges","archive_url":"https://api.github.com/repos/dokterbob/django-newsletter/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dokterbob/django-newsletter/downloads","issues_url":"https://api.github.com/repos/dokterbob/django-newsletter/issues{/number}","pulls_url":"https://api.github.com/repos/dokterbob/django-newsletter/pulls{/number}","milestones_url":"https://api.github.com/repos/dokterbob/django-newsletter/milestones{/number}","notifications_url":"https://api.github.com/repos/dokterbob/django-newsletter/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dokterbob/django-newsletter/labels{/name}","releases_url":"https://api.github.com/repos/dokterbob/django-newsletter/releases{/id}","created_at":"2009-06-16T09:51:20Z","updated_at":"2015-12-28T23:52:58Z","pushed_at":"2016-01-04T12:18:02Z","git_url":"git://github.com/dokterbob/django-newsletter.git","ssh_url":"git@github.com:dokterbob/django-newsletter.git","clone_url":"https://github.com/dokterbob/django-newsletter.git","svn_url":"https://github.com/dokterbob/django-newsletter","homepage":"","size":3984,"stargazers_count":275,"watchers_count":275,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":94,"mirror_url":null,"open_issues_count":29,"forks":94,"open_issues":29,"watchers":275,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/dokterbob/django-newsletter/pulls/163"},"html":{"href":"https://github.com/dokterbob/django-newsletter/pull/163"},"issue":{"href":"https://api.github.com/repos/dokterbob/django-newsletter/issues/163"},"comments":{"href":"https://api.github.com/repos/dokterbob/django-newsletter/issues/163/comments"},"review_comments":{"href":"https://api.github.com/repos/dokterbob/django-newsletter/pulls/163/comments"},"review_comment":{"href":"https://api.github.com/repos/dokterbob/django-newsletter/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/dokterbob/django-newsletter/pulls/163/commits"},"statuses":{"href":"https://api.github.com/repos/dokterbob/django-newsletter/statuses/f285247aac400c437de447a832fb8c5b4323c931"}}}}