├── .gitignore ├── authenticate.py ├── concrete ├── __init__.py ├── admin.py ├── migrations │ ├── 0001_initial.py │ ├── 0001_initial.pyc │ ├── __init__.py │ └── __init__.pyc ├── models.py ├── tests.py └── views.py ├── mailchecker ├── __init__.py ├── admin.py ├── forms.py ├── mailer.py ├── manager.py ├── models.py ├── options.py ├── query.py ├── settings.py ├── test.py ├── urls.py └── wsgi.py └── manage.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | db.sqlite3 3 | gmail.storage 4 | .DS_Store 5 | client_secret.json 6 | -------------------------------------------------------------------------------- /authenticate.py: -------------------------------------------------------------------------------- 1 | from oauth2client.client import flow_from_clientsecrets 2 | 3 | 4 | def main(): 5 | flow = flow_from_clientsecrets( 6 | './client_secret.json', 7 | scope=[ 8 | 'https://www.googleapis.com/auth/gmail.modify', 9 | 'https://www.googleapis.com/auth/gmail.compose' 10 | ], 11 | redirect_uri='urn:ietf:wg:oauth:2.0:oob' 12 | ) 13 | print "Please go to '%s' and authorize this application" % flow.step1_get_authorize_url() 14 | print "Insert the code in the 'Success' screen and press ENTER" 15 | 16 | code = raw_input() 17 | credentials = flow.step2_exchange(code) 18 | 19 | from oauth2client.file import Storage 20 | storage = Storage('./gmail.storage') 21 | storage.put(credentials) 22 | 23 | 24 | if __name__ == '__main__': 25 | main() 26 | -------------------------------------------------------------------------------- /concrete/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PirosB3/django-mailer/21772d9f0901be5d416d38e70f1010d7908f8a3d/concrete/__init__.py -------------------------------------------------------------------------------- /concrete/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Post, Comment 3 | 4 | 5 | class CommentInline(admin.TabularInline): 6 | model = Comment 7 | 8 | 9 | class PostAdmin(admin.ModelAdmin): 10 | inlines = [ 11 | CommentInline, 12 | ] 13 | fields = ('title',) 14 | list_display = ('id',) 15 | ordering = ('id',) 16 | 17 | 18 | #class MessageAdmin(admin.ModelAdmin): 19 | #fields = ('sender', 'receiver', 'body') 20 | #list_display = ('id',) 21 | #ordering = ('id',) 22 | 23 | 24 | admin.site.register([Post], PostAdmin) 25 | #admin.site.register([Thread], ThreadAdmin) 26 | # Register your models here. 27 | -------------------------------------------------------------------------------- /concrete/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='Comment', 15 | fields=[ 16 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 17 | ('body', models.CharField(max_length=100)), 18 | ], 19 | options={ 20 | }, 21 | bases=(models.Model,), 22 | ), 23 | migrations.CreateModel( 24 | name='Post', 25 | fields=[ 26 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 27 | ('title', models.CharField(max_length=100)), 28 | ], 29 | options={ 30 | }, 31 | bases=(models.Model,), 32 | ), 33 | migrations.AddField( 34 | model_name='comment', 35 | name='post', 36 | field=models.ForeignKey(to='concrete.Post'), 37 | preserve_default=True, 38 | ), 39 | ] 40 | -------------------------------------------------------------------------------- /concrete/migrations/0001_initial.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PirosB3/django-mailer/21772d9f0901be5d416d38e70f1010d7908f8a3d/concrete/migrations/0001_initial.pyc -------------------------------------------------------------------------------- /concrete/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PirosB3/django-mailer/21772d9f0901be5d416d38e70f1010d7908f8a3d/concrete/migrations/__init__.py -------------------------------------------------------------------------------- /concrete/migrations/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PirosB3/django-mailer/21772d9f0901be5d416d38e70f1010d7908f8a3d/concrete/migrations/__init__.pyc -------------------------------------------------------------------------------- /concrete/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Post(models.Model): 5 | title = models.CharField(max_length=100) 6 | 7 | 8 | class Comment(models.Model): 9 | body = models.CharField(max_length=100) 10 | post = models.ForeignKey(Post) 11 | -------------------------------------------------------------------------------- /concrete/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /concrete/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /mailchecker/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PirosB3/django-mailer/21772d9f0901be5d416d38e70f1010d7908f8a3d/mailchecker/__init__.py -------------------------------------------------------------------------------- /mailchecker/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Thread, Message 4 | from .forms import MessageForm, MessageInlineForm 5 | 6 | 7 | class MessageInline(admin.TabularInline): 8 | model = Message 9 | form = MessageInlineForm 10 | 11 | 12 | class MessageAdmin(admin.ModelAdmin): 13 | ordering = ('id',) 14 | model = Message 15 | form = MessageForm 16 | 17 | 18 | class ThreadAdmin(admin.ModelAdmin): 19 | inlines = [ 20 | MessageInline, 21 | ] 22 | fields = ('number_of_messages',) 23 | list_display = ('id',) 24 | search_fields = ('to',) 25 | ordering = ('id',) 26 | 27 | 28 | admin.site.register([Thread], ThreadAdmin) 29 | admin.site.register([Message], MessageAdmin) 30 | -------------------------------------------------------------------------------- /mailchecker/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from .models import Message 3 | 4 | class MessageForm(forms.ModelForm): 5 | sender = forms.EmailField() 6 | receiver = forms.EmailField() 7 | body = forms.Textarea() 8 | 9 | class Meta: 10 | fields = ('sender', 'receiver', 'body',) 11 | model = Message 12 | 13 | class MessageInlineForm(forms.ModelForm): 14 | sender = forms.EmailField() 15 | receiver = forms.EmailField() 16 | 17 | class Meta: 18 | fields = ('sender', 'receiver', 'body',) 19 | model = Message 20 | widgets = { 21 | 'sender': forms.Textarea(attrs={'cols': 20, 'rows': 2}), 22 | 'receiver': forms.Textarea(attrs={'cols': 20, 'rows': 2}), 23 | 'body': forms.Textarea(attrs={'cols': 80, 'rows': 2}), 24 | } 25 | 26 | def clean(self): 27 | cleaned_data = super(MessageInlineForm, self).clean() 28 | if self.instance and self.instance.id is not None: 29 | for key in ['sender', 'receiver']: 30 | try: 31 | del self.errors[key] 32 | except KeyError: 33 | pass 34 | return cleaned_data 35 | -------------------------------------------------------------------------------- /mailchecker/mailer.py: -------------------------------------------------------------------------------- 1 | import httplib2 2 | from apiclient.discovery import build 3 | from email.mime.text import MIMEText 4 | 5 | import base64 6 | from django.db.models.fields import FieldDoesNotExist 7 | 8 | ME = 'me' 9 | 10 | 11 | class Bunch(object): 12 | def __init__(self, *args, **kwargs): 13 | self.__dict__ = kwargs 14 | self.pk = self.id 15 | 16 | def __unicode__(self): 17 | return self.id 18 | 19 | def serializable_value(self, field_name): 20 | try: 21 | field = self._meta.get_field(field_name) 22 | except FieldDoesNotExist: 23 | return getattr(self, field_name) 24 | return getattr(self, field.attname) 25 | 26 | 27 | def _get_gmail_service(credentials): 28 | http = httplib2.Http() 29 | http = credentials.authorize(http) 30 | return build('gmail', 'v1', http=http) 31 | 32 | 33 | def _make_message(msg, cls): 34 | try: 35 | parts = [p['body'] for p in msg['payload']['parts']] 36 | except KeyError: 37 | parts = [msg['payload']['body']] 38 | 39 | body = ''.join(base64.urlsafe_b64decode(p['data'].encode('utf-8')) 40 | for p in parts if 'data' in p) 41 | sender = [h['value'] for h in msg['payload']['headers'] 42 | if h['name'].lower() in 'from'][0] 43 | receiver = [h['value'] for h in msg['payload']['headers'] 44 | if h['name'].lower() == 'to'][0] 45 | return cls( 46 | id=msg['id'], 47 | thread_id=msg['threadId'], 48 | snippet=msg['snippet'], 49 | receiver=receiver, 50 | sender=sender, 51 | body=body 52 | ) 53 | 54 | 55 | def send_message(credentials, frm, to, message_body, subject="Hello from Pycon", thread_id=None): 56 | gmail = _get_gmail_service(credentials) 57 | message = MIMEText(message_body) 58 | message['to'] = to 59 | message['from'] = frm 60 | message['subject'] = subject 61 | 62 | payload = {'raw': base64.b64encode(message.as_string())} 63 | if thread_id: 64 | payload['threadId'] = thread_id 65 | return gmail.users().messages().send( 66 | userId=ME, 67 | body=payload, 68 | ).execute() 69 | 70 | def get_messages_by_thread_id(credentials, thread_id, cls=Bunch): 71 | gmail = _get_gmail_service(credentials) 72 | thread = gmail.users().threads().get( 73 | userId=ME, 74 | id=thread_id 75 | ).execute() 76 | 77 | return [_make_message(m, cls) for m in thread['messages']] 78 | 79 | 80 | def get_all_threads(credentials, to=None, cls=Bunch): 81 | gmail = _get_gmail_service(credentials) 82 | params = { 83 | 'userId': ME, 84 | } 85 | if to: 86 | params['q'] = 'to:%s' % to 87 | threads = gmail.users().threads().list(**params).execute() 88 | if not threads or (to != None and threads['resultSizeEstimate'] is 0): 89 | return tuple() 90 | return tuple(cls(id=t['id'], number_of_messages=None, to=None) 91 | for t in threads['threads']) 92 | 93 | 94 | def get_all_messages(credentials, cls=Bunch): 95 | gmail = _get_gmail_service(credentials) 96 | messages = gmail.users().messages().list( 97 | userId=ME, 98 | ).execute()['messages'] 99 | return [_make_message(m, cls) for m in messages] 100 | 101 | 102 | def get_thread_by_id(credentials, thread_id, cls=Bunch): 103 | gmail = _get_gmail_service(credentials) 104 | thread = gmail.users().threads().get( 105 | userId=ME, 106 | id=thread_id 107 | ).execute() 108 | return cls( 109 | id=thread['id'], 110 | to=None, 111 | number_of_messages=len(thread['messages']) 112 | ) 113 | 114 | 115 | def get_message_by_id(credentials, message_id, cls=Bunch): 116 | gmail = _get_gmail_service(credentials) 117 | message = gmail.users().messages().get( 118 | userId=ME, 119 | id=message_id 120 | ).execute() 121 | return _make_message(message, cls) 122 | -------------------------------------------------------------------------------- /mailchecker/manager.py: -------------------------------------------------------------------------------- 1 | from oauth2client.file import Storage 2 | from django.conf import settings 3 | import mailer 4 | 5 | from .query import ThreadQuerySet, MessageQuerySet 6 | 7 | 8 | class GmailManager(object): 9 | 10 | def __init__(self, model, **kwargs): 11 | storage = Storage(settings.CREDENTIALS_PATH) 12 | self.credentials = storage.get() 13 | self.model = model 14 | self.mailer = kwargs.get('mailer', mailer) 15 | self.initial_filter_query = kwargs.get('initial_filter_query', {}) 16 | 17 | def complex_filter(self, filter_obj): 18 | return self 19 | 20 | def order_by(self, *args, **kwargs): 21 | return self 22 | 23 | def using(self, *args, **kwargs): 24 | return self 25 | 26 | def iterator(self): 27 | return iter(self.get_queryset()) 28 | 29 | def all(self): 30 | return self.get_queryset() 31 | 32 | def count(self): 33 | return len(self.get_queryset()) 34 | 35 | def filter(self, *args, **kwargs): 36 | return self.get_queryset().filter(*args, **kwargs) 37 | 38 | def get_queryset(self): 39 | return self.queryset( 40 | credentials=self.credentials, 41 | model=self.model, 42 | mailer=self.mailer, 43 | filter_query=self.initial_filter_query, 44 | ) 45 | 46 | def get(self, *args, **kwargs): 47 | return self.get_queryset().get(*args, **kwargs) 48 | 49 | 50 | class ThreadManager(GmailManager): 51 | queryset = ThreadQuerySet 52 | 53 | 54 | class MessageManager(GmailManager): 55 | queryset = MessageQuerySet 56 | -------------------------------------------------------------------------------- /mailchecker/models.py: -------------------------------------------------------------------------------- 1 | from django.db.models.fields import FieldDoesNotExist 2 | from django.db.models.base import ModelState 3 | 4 | from .options import ThreadOptions, MessageOptions 5 | from .manager import ThreadManager, MessageManager 6 | 7 | 8 | class GmailModel(object): 9 | _deferred = False 10 | _state = ModelState() 11 | 12 | def __unicode__(self): 13 | return self.id 14 | 15 | def serializable_value(self, field_name): 16 | try: 17 | field = self._meta.get_field(field_name) 18 | except FieldDoesNotExist: 19 | return getattr(self, field_name) 20 | return getattr(self, field.attname) 21 | 22 | @property 23 | def pk(self): 24 | return self.id 25 | 26 | def __eq__(self, other): 27 | if isinstance(other, GmailModel): 28 | return self.pk == other.pk 29 | return False 30 | 31 | def full_clean(self, *args, **kwargs): 32 | pass 33 | 34 | def validate_unique(self, *args, **kwargs): 35 | pass 36 | 37 | def _get_unique_checks(self, *args, **kwargs): 38 | return ([], [],) 39 | 40 | def _get_pk_val(self): 41 | return None 42 | 43 | 44 | class constructor(type): 45 | def __new__(cls, name, bases, attrs): 46 | dm = attrs.pop('_default_manager') 47 | klass = super(constructor, cls).__new__(cls, name, bases, attrs) 48 | klass._default_manager = dm(klass) 49 | klass.objects = klass._default_manager 50 | return klass 51 | 52 | 53 | class Message(GmailModel): 54 | __metaclass__ = constructor 55 | _default_manager = MessageManager 56 | _meta = MessageOptions() 57 | 58 | def __init__(self, id=None, receiver=None, sender=None, snippet=None, 59 | body=None, thread=None, thread_id=None): 60 | self.id = id 61 | self.receiver = receiver 62 | self.sender = sender 63 | self.snippet = snippet 64 | self.body = body 65 | self.thread_id = thread_id 66 | 67 | from django.utils.encoding import smart_text 68 | if self.body: 69 | self.body = smart_text(self.body) 70 | if self.snippet: 71 | self.snippet = smart_text(self.snippet) 72 | 73 | @property 74 | def thread(self): 75 | return Thread.objects.get(id=self.thread_id) 76 | 77 | @thread.setter 78 | def thread(self, value): 79 | self.thread_id = value.id 80 | 81 | def __unicode__(self): 82 | from django.utils.encoding import smart_text 83 | #return smart_text("" % (self.id, self.snippet[:30])) 84 | return smart_text("" % self.id) 85 | 86 | def __repr__(self): 87 | return self.__unicode__() 88 | 89 | def save(self, *args, **kwargs): 90 | 91 | # Messages already save do not need re-sending 92 | if self.id: 93 | return 94 | 95 | # Send message and fetch ID 96 | result = self._default_manager.get_queryset()._create( 97 | frm=self.sender, 98 | to=self.receiver, 99 | message_body=self.body, 100 | thread_id=self.thread_id 101 | ) 102 | 103 | # Not all results are returned from the API, re-pull and set 104 | # all fields (basically, reassigning the entire instance) 105 | new_instance = self._default_manager.get(pk=result['id']) 106 | for field_name in (f.name for f in self._meta.get_fields()): 107 | setattr(self, field_name, getattr(new_instance, field_name)) 108 | 109 | 110 | class Thread(GmailModel): 111 | __metaclass__ = constructor 112 | _default_manager = ThreadManager 113 | _meta = ThreadOptions() 114 | 115 | def __init__(self, id=None, to=None, number_of_messages=None): 116 | self.id = id 117 | self.to = to 118 | self.number_of_messages = number_of_messages 119 | 120 | @property 121 | def messages(self): 122 | return Message.objects.filter(thread=self.id) 123 | 124 | def __unicode__(self): 125 | return "" % ( 126 | self.id, 127 | "???" if self.number_of_messages is None else self.number_of_messages 128 | ) 129 | 130 | def __repr__(self): 131 | return self.__unicode__() 132 | 133 | def save(self, *args, **kwargs): 134 | pass 135 | 136 | 137 | Thread._meta._bind() 138 | Message._meta._bind() 139 | -------------------------------------------------------------------------------- /mailchecker/options.py: -------------------------------------------------------------------------------- 1 | from django.db.models.fields import (AutoField, CharField, 2 | FieldDoesNotExist, TextField) 3 | from django.db.models import ForeignKey 4 | from django.db.models.options import CachedPropertiesMixin 5 | 6 | 7 | class GmailAutoField(AutoField): 8 | 9 | def to_python(self, value): 10 | return value 11 | 12 | 13 | class GmailOptions(CachedPropertiesMixin): 14 | has_auto_field = True 15 | auto_created = False 16 | abstract = False 17 | swapped = False 18 | virtual_fields = [] 19 | 20 | def __init__(self, *args, **kwargs): 21 | self._gmail_other_fields = {} 22 | 23 | def add_field(self, *args, **kwargs): 24 | pass 25 | 26 | def _bind(self): 27 | for field_name, field in self._gmail_fields.iteritems(): 28 | setattr(self, field_name, field) 29 | field.set_attributes_from_name(field_name) 30 | self.pk = self._gmail_fields[self._gmail_pk_field] 31 | 32 | def get_fields(self, **kwargs): 33 | return self._get_fields() 34 | 35 | def _get_fields(self, reverse=True, forward=True): 36 | return tuple( 37 | field for field_name, field in 38 | sorted(list(self._gmail_fields.items()) + 39 | list(self._gmail_other_fields.items())) 40 | ) 41 | 42 | def get_field(self, field_name): 43 | try: 44 | return self._gmail_fields[field_name] 45 | except KeyError: 46 | try: 47 | return self._gmail_other_fields[field_name] 48 | except KeyError: 49 | raise FieldDoesNotExist() 50 | 51 | 52 | class ThreadOptions(GmailOptions): 53 | auto_created = False 54 | app_label = 'mailchecker' 55 | model_name = 'thread' 56 | verbose_name = 'thread' 57 | verbose_name_raw = 'thread' 58 | verbose_name_plural = 'threads' 59 | object_name = 'thread' 60 | default_related_name = None 61 | 62 | _gmail_pk_field = 'id' 63 | _gmail_fields = { 64 | 'id': GmailAutoField(), 65 | 'to': CharField(max_length=200), 66 | 'number_of_messages': CharField(max_length=200), 67 | } 68 | 69 | 70 | class MessageOptions(GmailOptions): 71 | app_label = 'mailchecker' 72 | model_name = 'message' 73 | verbose_name = 'message' 74 | verbose_name_raw = 'message' 75 | verbose_name_plural = 'messages' 76 | object_name = 'message' 77 | default_related_name = None 78 | 79 | _gmail_pk_field = 'id' 80 | _gmail_fields = { 81 | 'id': GmailAutoField(), 82 | 'receiver': CharField(max_length=200), 83 | 'sender': CharField(max_length=200), 84 | 'snippet': CharField(max_length=200), 85 | 'body': TextField(), 86 | } 87 | 88 | def _bind(self): 89 | super(MessageOptions, self)._bind() 90 | 91 | from .models import Thread, Message 92 | self.thread = ForeignKey(Thread) 93 | self.thread.contribute_to_class(Thread, 'thread') 94 | self.concrete_model = Message 95 | self._gmail_other_fields['thread'] = self.thread 96 | -------------------------------------------------------------------------------- /mailchecker/query.py: -------------------------------------------------------------------------------- 1 | import mailer 2 | 3 | 4 | class GmailQuery(object): 5 | select_related = False 6 | order_by = tuple() 7 | 8 | 9 | class GmailQuerySet(object): 10 | 11 | def using(self, db): 12 | return self 13 | 14 | def __init__(self, *args, **kwargs): 15 | self._cache = None 16 | self.ordered = True 17 | self.model = kwargs.pop('model') 18 | self.credentials = kwargs.pop('credentials') 19 | self.mailer = kwargs.pop('mailer', mailer) 20 | self.filter_query = kwargs.pop('filter_query', {}) 21 | self.query = GmailQuery() 22 | 23 | def order_by(self, *args, **kwargs): 24 | return self 25 | 26 | def none(self, *args, **kwargs): 27 | cloned_query = self._clone() 28 | cloned_query.filter_query = {} 29 | return cloned_query 30 | 31 | def _clone(self, *args, **kwargs): 32 | return self.__class__( 33 | model=self.model, 34 | credentials=self.credentials, 35 | mailer=self.mailer, 36 | filter_query=self.filter_query 37 | ) 38 | 39 | def count(self): 40 | return len(self._get_data()) 41 | 42 | def __getitem__(self, k): 43 | return self._get_data()[k] 44 | 45 | def __repr__(self): 46 | from django.utils.encoding import smart_text 47 | return repr(self._get_data()).encode('utf-8') 48 | 49 | def __iter__(self): 50 | return iter(self._get_data()) 51 | 52 | def all(self): 53 | return self._get_data() 54 | 55 | def _set_model_attrs(self, instance): 56 | instance._meta = self.model._meta 57 | instance._state = self.model._state 58 | return instance 59 | 60 | def _get_filter_args(self, args, kwargs): 61 | filter_args = kwargs if kwargs else {} 62 | if len(args) > 0: 63 | filter_args.update(dict(args[0].children)) 64 | return filter_args 65 | 66 | def __len__(self): 67 | return len(self._get_data()) 68 | 69 | def _create(self, frm, to, message_body, thread_id=None): 70 | return mailer.send_message(self.credentials, frm, to, message_body, 71 | thread_id=thread_id) 72 | 73 | 74 | class ThreadQuerySet(GmailQuerySet): 75 | 76 | def get(self, *args, **kwargs): 77 | filter_args = self._get_filter_args(args, kwargs) 78 | if 'id' not in filter_args: 79 | raise Exception("No ID found in Thread GET") 80 | 81 | return ThreadQuerySet( 82 | model=self.model, 83 | credentials = self.credentials, 84 | mailer = self.mailer, 85 | filter_query = {'id': filter_args['id']} 86 | )[0] 87 | 88 | def _get_data(self): 89 | if not self._cache: 90 | if 'id' in self.filter_query: 91 | thread = self.mailer.get_thread_by_id(self.credentials, 92 | self.filter_query['id'], 93 | cls=self.model) 94 | self._cache = [self._set_model_attrs(thread)] 95 | else: 96 | to = (self.filter_query['to__icontains'] 97 | if 'to__icontains' in self.filter_query 98 | else None) 99 | all_threads = self.mailer.get_all_threads(self.credentials, 100 | to=to, cls=self.model) 101 | self._cache = map(self._set_model_attrs, all_threads) 102 | return self._cache 103 | 104 | def filter(self, *args, **kwargs): 105 | filter_args = self._get_filter_args(args, kwargs) 106 | if 'to__icontains' in filter_args: 107 | return ThreadQuerySet( 108 | model=self.model, 109 | credentials = self.credentials, 110 | mailer = self.mailer, 111 | filter_query = {'to__icontains': filter_args['to__icontains']} 112 | ) 113 | return self 114 | 115 | 116 | class MessageQuerySet(GmailQuerySet): 117 | 118 | def filter(self, *args, **kwargs): 119 | filter_args = self._get_filter_args(args, kwargs) 120 | if 'thread' in filter_args: 121 | 122 | try: 123 | tid = filter_args['thread'].id 124 | except AttributeError: 125 | tid = filter_args['thread'] 126 | 127 | return MessageQuerySet( 128 | model=self.model, 129 | credentials=self.credentials, 130 | mailer=self.mailer, 131 | filter_query = {'thread': tid} 132 | ) 133 | return self 134 | 135 | def _get_data(self): 136 | if not self._cache: 137 | if 'pk' in self.filter_query: 138 | message = self.mailer.get_message_by_id(self.credentials, 139 | self.filter_query['pk'], 140 | cls=self.model) 141 | self._cache = [self._set_model_attrs(message)] 142 | elif not 'thread' in self.filter_query: 143 | self._cache = [] 144 | else: 145 | messages = self.mailer.get_messages_by_thread_id( 146 | self.credentials, 147 | self.filter_query['thread'], 148 | cls=self.model 149 | ) 150 | self._cache = map(self._set_model_attrs, messages) 151 | return self._cache 152 | 153 | def get(self, *args, **kwargs): 154 | 155 | filter_args = self._get_filter_args(args, kwargs) 156 | if 'pk' not in filter_args: 157 | raise Exception("No PK found in Message GET") 158 | 159 | return MessageQuerySet( 160 | model=self.model, 161 | credentials = self.credentials, 162 | mailer = self.mailer, 163 | filter_query = {'pk': filter_args['pk']} 164 | )[0] 165 | -------------------------------------------------------------------------------- /mailchecker/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for mailchecker project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/dev/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/dev/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 14 | 15 | CREDENTIALS_PATH = os.path.join(BASE_DIR, 'gmail.storage') 16 | 17 | # Quick-start development settings - unsuitable for production 18 | # See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/ 19 | 20 | # SECURITY WARNING: keep the secret key used in production secret! 21 | SECRET_KEY = 'p=tt*1=5ia-%3cy326xz@b1ywp(&$u+cgs@(07_$!76zw@7(hr' 22 | 23 | # SECURITY WARNING: don't run with debug turned on in production! 24 | DEBUG = True 25 | 26 | TEMPLATE_DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = ( 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'mailchecker', 41 | 'concrete', 42 | ) 43 | 44 | MIDDLEWARE_CLASSES = ( 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 50 | 'django.contrib.messages.middleware.MessageMiddleware', 51 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 52 | ) 53 | 54 | ROOT_URLCONF = 'mailchecker.urls' 55 | 56 | WSGI_APPLICATION = 'mailchecker.wsgi.application' 57 | 58 | 59 | # Database 60 | # https://docs.djangoproject.com/en/dev/ref/settings/#databases 61 | 62 | DATABASES = { 63 | 'default': { 64 | 'ENGINE': 'django.db.backends.sqlite3', 65 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 66 | } 67 | } 68 | 69 | # Internationalization 70 | # https://docs.djangoproject.com/en/dev/topics/i18n/ 71 | 72 | LANGUAGE_CODE = 'en-us' 73 | 74 | TIME_ZONE = 'UTC' 75 | 76 | USE_I18N = True 77 | 78 | USE_L10N = True 79 | 80 | USE_TZ = True 81 | 82 | 83 | # Static files (CSS, JavaScript, Images) 84 | # https://docs.djangoproject.com/en/dev/howto/static-files/ 85 | 86 | STATIC_URL = '/static/' 87 | -------------------------------------------------------------------------------- /mailchecker/test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import mailer 3 | 4 | from django.conf import settings 5 | from django.db.models import Q 6 | from oauth2client.file import Storage 7 | 8 | from .models import Thread, Message 9 | from .query import ThreadQuerySet, MessageQuerySet 10 | import mock 11 | from mailer import Bunch 12 | 13 | 14 | class ThreadTestCase(unittest.TestCase): 15 | 16 | def setUp(self): 17 | self.mailer = mock.MagicMock() 18 | self._old_mailer = Thread._default_manager.mailer 19 | 20 | Message._default_manager.mailer = self.mailer 21 | Thread._default_manager.mailer = self.mailer 22 | 23 | def tearDown(self): 24 | Message._default_manager.mailer = self._old_mailer 25 | Thread._default_manager.mailer = self._old_mailer 26 | 27 | def test_reverse_relation_lookup(self): 28 | self.mailer.get_messages_by_thread_id.return_value = [ 29 | Bunch(id=str(n)) for n in xrange(10) 30 | ] 31 | t = Thread(id='123123') 32 | self.assertEqual(t.messages.count(), 10) 33 | 34 | 35 | class MessageTestCase(unittest.TestCase): 36 | 37 | def setUp(self): 38 | self.mailer = mock.MagicMock() 39 | self._old_mailer = Thread._default_manager.mailer 40 | 41 | Message._default_manager.mailer = self.mailer 42 | Thread._default_manager.mailer = self.mailer 43 | 44 | def tearDown(self): 45 | Message._default_manager.mailer = self._old_mailer 46 | Thread._default_manager.mailer = self._old_mailer 47 | 48 | def test_reverse_relation_works(self): 49 | self.mailer.get_thread_by_id.return_value = Message( 50 | id="00126" 51 | ) 52 | t = Message(id="00125") 53 | self.assertEqual(t.thread.id, "00126") 54 | self.assertEqual( 55 | self.mailer.get_thread_by_id.call_args[0][1], 56 | '00125' 57 | ) 58 | 59 | 60 | class MessageQuerySetTestCase(unittest.TestCase): 61 | 62 | def setUp(self): 63 | storage = Storage(settings.CREDENTIALS_PATH) 64 | self.credentials = storage.get() 65 | self.mailer = mock.MagicMock() 66 | 67 | def test_message_with_filter(self): 68 | self.mailer.get_messages_by_thread_id.return_value = [ 69 | Bunch(id='1'), 70 | ] 71 | mqs = MessageQuerySet( 72 | model=Message, 73 | credentials=self.credentials, 74 | mailer=self.mailer 75 | ) 76 | self.assertEqual(mqs.filter(thread='1')[0].pk, '1') 77 | self.assertEqual( 78 | self.mailer.get_messages_by_thread_id.call_args[0][1], 79 | '1' 80 | ) 81 | 82 | def test_message_with_id(self): 83 | self.mailer.get_message_by_id.return_value = Bunch(id='1') 84 | mqs = MessageQuerySet( 85 | model=Message, 86 | credentials=self.credentials, 87 | mailer=self.mailer 88 | ) 89 | self.assertEqual(mqs.get(pk='1843903').pk, '1') 90 | self.assertEqual( 91 | self.mailer.get_message_by_id.call_args[0][1], 92 | '1843903' 93 | ) 94 | 95 | 96 | class ThreadQuerySetTestCase(unittest.TestCase): 97 | 98 | def setUp(self): 99 | self.mailer = mock.MagicMock() 100 | storage = Storage(settings.CREDENTIALS_PATH) 101 | self.credentials = storage.get() 102 | 103 | def test_queryset(self): 104 | self.mailer.get_all_threads.return_value = [ 105 | Bunch(id='1'), 106 | Bunch(id='2'), 107 | Bunch(id='3'), 108 | ] 109 | 110 | tqs = ThreadQuerySet( 111 | model=Thread, 112 | credentials=self.credentials, 113 | mailer=self.mailer 114 | ) 115 | self.assertEqual(tqs.count(), 3) 116 | self.assertEqual(tqs[1].id, '2') 117 | self.assertTrue([ 118 | model._meta 119 | for model in tqs.all() 120 | ]) 121 | 122 | def test_queryset_get(self): 123 | self.mailer.get_thread_by_id.return_value = Bunch(id='target') 124 | tqs = ThreadQuerySet( 125 | model=Thread, 126 | credentials = self.credentials, 127 | mailer=self.mailer 128 | ) 129 | self.assertEqual(tqs.get(id='target').id, 'target') 130 | self.assertEqual( 131 | self.mailer.get_thread_by_id.call_args[0][1], 132 | 'target' 133 | ) 134 | 135 | def test_queryset_filter(self): 136 | self.mailer.get_all_threads.return_value = [ 137 | Bunch(id='target1'), 138 | Bunch(id='target2'), 139 | ] 140 | tqs = ThreadQuerySet( 141 | model=Thread, 142 | credentials=self.credentials, 143 | mailer=self.mailer 144 | ) 145 | tqs2 = tqs.filter(to__icontains="daniel@gmail.com") 146 | self.assertNotEqual(tqs, tqs2) 147 | 148 | self.assertEqual( 149 | [b.id for b in tqs2.all()], 150 | ['target1', 'target2'] 151 | ) 152 | self.assertEqual( 153 | self.mailer.get_all_threads.call_args_list[0][1]['to'], 154 | 'daniel@gmail.com' 155 | ) 156 | 157 | def test_queryset_filter_Q(self): 158 | self.mailer.get_all_threads.return_value = [ 159 | Bunch(id='target1'), 160 | Bunch(id='target2'), 161 | ] 162 | tqs = ThreadQuerySet( 163 | model=Thread, 164 | credentials = self.credentials, 165 | mailer=self.mailer 166 | ) 167 | query = Q(to__icontains="daniel@gmail.com") 168 | tqs2 = tqs.filter(query) 169 | self.assertEqual( 170 | [b.id for b in tqs2.all()], 171 | ['target1', 'target2'] 172 | ) 173 | self.assertEqual( 174 | self.mailer.get_all_threads.call_args_list[0][1]['to'], 175 | 'daniel@gmail.com' 176 | ) 177 | 178 | 179 | if __name__ == '__main__': 180 | unittest.main() 181 | -------------------------------------------------------------------------------- /mailchecker/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include, url 2 | from django.contrib import admin 3 | 4 | urlpatterns = [ 5 | # Examples: 6 | # url(r'^$', 'mailchecker.views.home', name='home'), 7 | # url(r'^blog/', include('blog.urls')), 8 | 9 | url(r'^admin/', include(admin.site.urls)), 10 | ] 11 | -------------------------------------------------------------------------------- /mailchecker/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for mailchecker project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/dev/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mailchecker.settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mailchecker.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | --------------------------------------------------------------------------------