diff --git a/.gitignore b/.gitignore index 37eb33b..6b37c43 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ *.swp build .DS_Store +dist +*.egg-info +env/ diff --git a/.travis.yml b/.travis.yml index b475185..60c08f5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,14 @@ language: python python: - - "2.6" - "2.7" script: ./runtests.sh env: - - DJANGO_VERSION=1.3.7 - - DJANGO_VERSION=1.4.16 - - DJANGO_VERSION=1.5.11 - DJANGO_VERSION=1.6.8 - - DJANGO_VERSION=1.7.1 + - DJANGO_VERSION=1.7.8 + - DJANGO_VERSION=1.8.2 + - DJANGO_VERSION=1.9.4 + +install: + - pip install -q Django==$DJANGO_VERSION + - pip install -r requirements.txt + - python setup.py -q install diff --git a/LICENSE b/LICENSE index eedd51a..6282d3e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012, Stephen Muss +Copyright (c) 2015, Stephen Muss All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.md b/README.md index 910ce2c..404f689 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ django-ios-notifications ================= +[![Build Status](https://travis-ci.org/stephenmuss/django-ios-notifications.svg?branch=master)](https://travis-ci.org/stephenmuss/django-ios-notifications) + Django iOS Notifications makes it easy to send push notifications to iOS devices. @@ -10,23 +12,43 @@ Installation Minimum Requirements -* Python 2.6 or greater -* Django 1.3 or greater +* Python 2.7 +* Django 1.6 or greater Two hard dependencies: * `pyOpenSSL >= 0.10` -* `django-fields >= 0.1.2` +* `django-fields >= 0.2.2` * * * -1. You can install with pip: `pip install django-ios-notifications`. +1. You can install with pip: + * `pip install django-ios-notifications` to get the latest release version + * `pip install git+https://github.com/stephenmuss/django-ios-notifications.git#egg=django-ios-notifications` to install the latest bleeding-edge/development version 2. Add `ios_notifications` to `INSTALLED_APPS` in settings file. 3. If you want to use the API for registering devices you will need to make the appropriate changes to your urls file. * `url(r'^ios-notifications/', include('ios_notifications.urls'))` -4. Create required database tables. - * `./manage.py syncdb` - * If using south `./manage.py migrate ios_notifications` +4. Create required database tables. + * `./manage.py syncdb` + * If using south `./manage.py migrate ios_notifications` also see older django note below. + + +Django 1.6 or below +------------------- + +You must configure south by adding the following lines to your settings file. + +``` + SOUTH_MIGRATION_MODULES = { + 'ios_notifications': 'ios_notifications.south_migrations', + } +``` + + +Notes on Upgrading to 0.2.0 +----------------- +If you are upgrading to 0.2.0 from an older verion and you use password protection in any of your `APNService`s you will need to renter the password and resave the model for each one. +This is due to changes in more recent versions of `django-fields`. Setting up the APN Services @@ -287,7 +309,7 @@ The `call_feedback_service` command takes one required argument: A full example: `./manage.py call_feedback_service --feedback-service=123` __NOTE:__ You may experience some issues testing the feedback service in a sandbox enviroment. -This occurs when an app was the last push enabled app for that particular APN Service on the device +This occurs when an app was the last push enabled app for that particular APN Service on the device Once the app is removed it tears down the persistent connection to the APN service. If you want to test a feedback service, ensure that you have at least one other app on the device installed which receives notifications from the same APN service. diff --git a/ios_notifications/__init__.py b/ios_notifications/__init__.py index eb8e8f0..2219c2d 100644 --- a/ios_notifications/__init__.py +++ b/ios_notifications/__init__.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -VERSION = '0.2' +VERSION = '0.2.0' diff --git a/ios_notifications/admin.py b/ios_notifications/admin.py index 6c87577..55c9406 100644 --- a/ios_notifications/admin.py +++ b/ios_notifications/admin.py @@ -1,14 +1,9 @@ # -*- coding: utf-8 -*- -try: - from django.conf.urls import patterns, url -except ImportError: # deprecated since Django 1.4 - from django.conf.urls.defaults import patterns, url - +from django.conf.urls import url from django.contrib import admin from django.template.response import TemplateResponse from django.shortcuts import get_object_or_404 - from .models import Device, Notification, APNService, FeedbackService from .forms import APNServiceForm @@ -34,9 +29,10 @@ class NotificationAdmin(admin.ModelAdmin): def get_urls(self): urls = super(NotificationAdmin, self).get_urls() - notification_urls = patterns('', - url(r'^(?P\d+)/push-notification/$', self.admin_site.admin_view(self.admin_push_notification), - name='admin_push_notification'),) + notification_urls = [ + url(r'^(?P\d+)/push-notification/$', self.admin_site.admin_view(self.admin_push_notification), + name='admin_push_notification'), + ] return notification_urls + urls def admin_push_notification(self, request, **kwargs): @@ -46,9 +42,9 @@ def admin_push_notification(self, request, **kwargs): service = notification.service num_devices = service.device_set.filter(is_active=True).count() notification.service.push_notification_to_devices(notification) + request.current_app = 'ios_notifications' return TemplateResponse(request, 'admin/ios_notifications/notification/push_notification.html', - {'notification': notification, 'num_devices': num_devices, 'sent': request.method == 'POST'}, - current_app='ios_notifications') + {'notification': notification, 'num_devices': num_devices, 'sent': request.method == 'POST'}) admin.site.register(Device, DeviceAdmin) admin.site.register(Notification, NotificationAdmin) diff --git a/ios_notifications/api.py b/ios_notifications/api.py index 106e279..3e7b851 100644 --- a/ios_notifications/api.py +++ b/ios_notifications/api.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import re +import django from django.http import HttpResponseNotAllowed, QueryDict from django.views.decorators.csrf import csrf_exempt from django.shortcuts import get_object_or_404 @@ -27,7 +28,7 @@ def route(self, request, **kwargs): if method in self.allowed_methods: if hasattr(self, method.lower()): if method == 'PUT': - request.PUT = QueryDict(request.raw_post_data).copy() + request.PUT = QueryDict(request.body if django.VERSION >= (1, 4) else request.raw_post_data).copy() return getattr(self, method.lower())(request, **kwargs) return HttpResponseNotImplemented() diff --git a/ios_notifications/migrations/0001_initial.py b/ios_notifications/migrations/0001_initial.py index 0f0e184..e7516f3 100644 --- a/ios_notifications/migrations/0001_initial.py +++ b/ios_notifications/migrations/0001_initial.py @@ -1,13 +1,18 @@ # -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-03-26 01:00 from __future__ import unicode_literals -from django.db import models, migrations from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + import django_fields.fields class Migration(migrations.Migration): + initial = True + dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] diff --git a/ios_notifications/migrations/__init__.py b/ios_notifications/migrations/__init__.py index e69de29..8ef445f 100644 --- a/ios_notifications/migrations/__init__.py +++ b/ios_notifications/migrations/__init__.py @@ -0,0 +1,21 @@ +""" +Django migrations for email_log app. + +This package does not contain South migrations. South migrations can be found +in the ``south_migrations`` package. +""" + +SOUTH_ERROR_MESSAGE = """\n +For South support, customize the SOUTH_MIGRATION_MODULES setting like so: + + SOUTH_MIGRATION_MODULES = { + 'ios_notifications': 'ios_notifications.south_migrations', + } +""" + +# Ensure the user is not using Django 1.6 or below with South +try: + from django.db import migrations # noqa +except ImportError: + from django.core.exceptions import ImproperlyConfigured + raise ImproperlyConfigured(SOUTH_ERROR_MESSAGE) diff --git a/ios_notifications/models.py b/ios_notifications/models.py index 5ccf4c9..9d73b3f 100644 --- a/ios_notifications/models.py +++ b/ios_notifications/models.py @@ -190,7 +190,7 @@ class Notification(models.Model): Represents a notification which can be pushed to an iOS device. """ service = models.ForeignKey(APNService) - message = models.CharField(max_length=200, blank=True, help_text='Alert message to display to the user. Leave empty if no alert should be displayed to the user.') + message = models.CharField(max_length=2048, blank=True, help_text='Alert message to display to the user. Leave empty if no alert should be displayed to the user.') badge = models.PositiveIntegerField(null=True, blank=True, help_text='New application icon badge number. Set to None if the badge number must not be changed.') silent = models.NullBooleanField(null=True, blank=True, help_text='set True to send a silent notification') sound = models.CharField(max_length=30, blank=True, help_text='Name of the sound to play. Leave empty if no sound should be played.') @@ -293,7 +293,7 @@ class Device(models.Model): is_active = models.BooleanField(default=True) deactivated_at = models.DateTimeField(null=True, blank=True) service = models.ForeignKey(APNService) - users = models.ManyToManyField(get_setting('AUTH_USER_MODEL'), null=True, blank=True, related_name='ios_devices') + users = models.ManyToManyField(get_setting('AUTH_USER_MODEL'), blank=True, related_name='ios_devices') added_at = models.DateTimeField(auto_now_add=True) last_notified_at = models.DateTimeField(null=True, blank=True) platform = models.CharField(max_length=30, blank=True, null=True) @@ -346,6 +346,8 @@ def call(self): try: while True: data = self.connection.recv(38) # 38 being the length in bytes of the binary format feedback tuple. + if len(data) == 0: + raise OpenSSL.SSL.ZeroReturnError() timestamp, token_length, token = struct.unpack(self.fmt, data) device_token = hexlify(token) device_tokens.append(device_token) diff --git a/ios_notifications/south_migrations/0001_initial.py b/ios_notifications/south_migrations/0001_initial.py new file mode 100644 index 0000000..c59cc6f --- /dev/null +++ b/ios_notifications/south_migrations/0001_initial.py @@ -0,0 +1,188 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +try: + from django.contrib.auth import get_user_model +except ImportError: # django < 1.5 + from django.contrib.auth.models import User +else: + User = get_user_model() + +# With the default User model these will be 'auth.User' and 'auth.user' +# but for having a custom User model and instead of using orm['auth.User'] +# we can use orm[user_orm_label] +user_orm_label = '%s.%s' % (User._meta.app_label, User._meta.object_name) +user_model_label = '%s.%s' % (User._meta.app_label, User._meta.module_name) + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'APNService' + db.create_table('ios_notifications_apnservice', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('hostname', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('certificate', self.gf('django.db.models.fields.TextField')()), + ('private_key', self.gf('django.db.models.fields.TextField')()), + ('passphrase', self.gf('django_fields.fields.EncryptedCharField')(max_length=101, null=True, cipher='AES', blank=True)), + )) + db.send_create_signal('ios_notifications', ['APNService']) + + # Adding unique constraint on 'APNService', fields ['name', 'hostname'] + db.create_unique('ios_notifications_apnservice', ['name', 'hostname']) + + # Adding model 'Notification' + db.create_table('ios_notifications_notification', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('service', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['ios_notifications.APNService'])), + ('message', self.gf('django.db.models.fields.CharField')(max_length=200)), + ('badge', self.gf('django.db.models.fields.PositiveIntegerField')(default=1, null=True)), + ('sound', self.gf('django.db.models.fields.CharField')(default='default', max_length=30, null=True)), + ('created_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('last_sent_at', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)), + )) + db.send_create_signal('ios_notifications', ['Notification']) + + # Adding model 'Device' + db.create_table('ios_notifications_device', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('token', self.gf('django.db.models.fields.CharField')(max_length=64)), + ('is_active', self.gf('django.db.models.fields.BooleanField')(default=True)), + ('deactivated_at', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)), + ('service', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['ios_notifications.APNService'])), + ('added_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('last_notified_at', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)), + ('platform', self.gf('django.db.models.fields.CharField')(max_length=30, null=True, blank=True)), + ('display', self.gf('django.db.models.fields.CharField')(max_length=30, null=True, blank=True)), + ('os_version', self.gf('django.db.models.fields.CharField')(max_length=20, null=True, blank=True)), + )) + db.send_create_signal('ios_notifications', ['Device']) + + # Adding unique constraint on 'Device', fields ['token', 'service'] + db.create_unique('ios_notifications_device', ['token', 'service_id']) + + # Adding M2M table for field users on 'Device' + db.create_table('ios_notifications_device_users', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('device', models.ForeignKey(orm['ios_notifications.device'], null=False)), + (User._meta.module_name, self.gf('django.db.models.fields.related.ForeignKey')(to=orm[user_orm_label])), + )) + db.create_unique('ios_notifications_device_users', ['device_id', '%s_id' % User._meta.module_name]) + + # Adding model 'FeedbackService' + db.create_table('ios_notifications_feedbackservice', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('hostname', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('apn_service', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['ios_notifications.APNService'])), + )) + db.send_create_signal('ios_notifications', ['FeedbackService']) + + # Adding unique constraint on 'FeedbackService', fields ['name', 'hostname'] + db.create_unique('ios_notifications_feedbackservice', ['name', 'hostname']) + + + def backwards(self, orm): + # Removing unique constraint on 'FeedbackService', fields ['name', 'hostname'] + db.delete_unique('ios_notifications_feedbackservice', ['name', 'hostname']) + + # Removing unique constraint on 'Device', fields ['token', 'service'] + db.delete_unique('ios_notifications_device', ['token', 'service_id']) + + # Removing unique constraint on 'APNService', fields ['name', 'hostname'] + db.delete_unique('ios_notifications_apnservice', ['name', 'hostname']) + + # Deleting model 'APNService' + db.delete_table('ios_notifications_apnservice') + + # Deleting model 'Notification' + db.delete_table('ios_notifications_notification') + + # Deleting model 'Device' + db.delete_table('ios_notifications_device') + + # Removing M2M table for field users on 'Device' + db.delete_table('ios_notifications_device_users') + + # Deleting model 'FeedbackService' + db.delete_table('ios_notifications_feedbackservice') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + user_model_label: { + 'Meta': { + 'object_name': User.__name__, + 'db_table': "'%s'" % User._meta.db_table + }, + User._meta.pk.attname: ( + 'django.db.models.fields.AutoField', [], + {'primary_key': 'True', + 'db_column': "'%s'" % User._meta.pk.column} + ), + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'ios_notifications.apnservice': { + 'Meta': {'unique_together': "(('name', 'hostname'),)", 'object_name': 'APNService'}, + 'certificate': ('django.db.models.fields.TextField', [], {}), + 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'passphrase': ('django_fields.fields.EncryptedCharField', [], {'max_length': '101', 'null': 'True', 'cipher': "'AES'", 'blank': 'True'}), + 'private_key': ('django.db.models.fields.TextField', [], {}) + }, + 'ios_notifications.device': { + 'Meta': {'unique_together': "(('token', 'service'),)", 'object_name': 'Device'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'deactivated_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'display': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'last_notified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'os_version': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}), + 'platform': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}), + 'service': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ios_notifications.APNService']"}), + 'token': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'users': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'ios_devices'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['%s']" % user_orm_label}) + }, + 'ios_notifications.feedbackservice': { + 'Meta': {'unique_together': "(('name', 'hostname'),)", 'object_name': 'FeedbackService'}, + 'apn_service': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ios_notifications.APNService']"}), + 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'ios_notifications.notification': { + 'Meta': {'object_name': 'Notification'}, + 'badge': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'null': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_sent_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'message': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'service': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ios_notifications.APNService']"}), + 'sound': ('django.db.models.fields.CharField', [], {'default': "'default'", 'max_length': '30', 'null': 'True'}) + } + } + + complete_apps = ['ios_notifications'] \ No newline at end of file diff --git a/ios_notifications/south_migrations/0002_auto__add_field_notification_custom_payload__chg_field_notification_so.py b/ios_notifications/south_migrations/0002_auto__add_field_notification_custom_payload__chg_field_notification_so.py new file mode 100644 index 0000000..9ca749d --- /dev/null +++ b/ios_notifications/south_migrations/0002_auto__add_field_notification_custom_payload__chg_field_notification_so.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'Notification.custom_payload' + db.add_column('ios_notifications_notification', 'custom_payload', + self.gf('django.db.models.fields.CharField')(default='', max_length=240, blank=True), + keep_default=False) + + + # Changing field 'Notification.sound' + if not db.dry_run: + for notification in orm['ios_notifications.notification'].objects.all(): + if notification.sound is None: + notification.sound = '' + notification.save() + db.alter_column('ios_notifications_notification', 'sound', self.gf('django.db.models.fields.CharField')(default='', max_length=30)) + + def backwards(self, orm): + # Deleting field 'Notification.custom_payload' + db.delete_column('ios_notifications_notification', 'custom_payload') + + + # Changing field 'Notification.sound' + db.alter_column('ios_notifications_notification', 'sound', self.gf('django.db.models.fields.CharField')(max_length=30, null=True)) + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'ios_notifications.apnservice': { + 'Meta': {'unique_together': "(('name', 'hostname'),)", 'object_name': 'APNService'}, + 'certificate': ('django.db.models.fields.TextField', [], {}), + 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'passphrase': ('django_fields.fields.EncryptedCharField', [], {'max_length': '101', 'null': 'True', 'cipher': "'AES'", 'blank': 'True'}), + 'private_key': ('django.db.models.fields.TextField', [], {}) + }, + 'ios_notifications.device': { + 'Meta': {'unique_together': "(('token', 'service'),)", 'object_name': 'Device'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'deactivated_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'display': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'last_notified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'os_version': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}), + 'platform': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}), + 'service': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ios_notifications.APNService']"}), + 'token': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'users': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'ios_devices'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}) + }, + 'ios_notifications.feedbackservice': { + 'Meta': {'unique_together': "(('name', 'hostname'),)", 'object_name': 'FeedbackService'}, + 'apn_service': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ios_notifications.APNService']"}), + 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'ios_notifications.notification': { + 'Meta': {'object_name': 'Notification'}, + 'badge': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'custom_payload': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_sent_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'message': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}), + 'service': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ios_notifications.APNService']"}), + 'sound': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}) + } + } + + complete_apps = ['ios_notifications'] diff --git a/ios_notifications/south_migrations/0003_auto__add_field_notification_loc_payload.py b/ios_notifications/south_migrations/0003_auto__add_field_notification_loc_payload.py new file mode 100644 index 0000000..a36c939 --- /dev/null +++ b/ios_notifications/south_migrations/0003_auto__add_field_notification_loc_payload.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'Notification.loc_payload' + db.add_column(u'ios_notifications_notification', 'loc_payload', + self.gf('django.db.models.fields.CharField')(default='', max_length=240, blank=True), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Notification.loc_payload' + db.delete_column(u'ios_notifications_notification', 'loc_payload') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'ios_notifications.apnservice': { + 'Meta': {'unique_together': "(('name', 'hostname'),)", 'object_name': 'APNService'}, + 'certificate': ('django.db.models.fields.TextField', [], {}), + 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'passphrase': ('django_fields.fields.EncryptedCharField', [], {'max_length': '110', 'null': 'True', 'block_type': "'MODE_CBC'", 'cipher': "'AES'", 'blank': 'True'}), + 'private_key': ('django.db.models.fields.TextField', [], {}) + }, + 'ios_notifications.device': { + 'Meta': {'unique_together': "(('token', 'service'),)", 'object_name': 'Device'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'deactivated_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'display': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'last_notified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'os_version': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}), + 'platform': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}), + 'service': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ios_notifications.APNService']"}), + 'token': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'users': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'ios_devices'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['auth.User']"}) + }, + 'ios_notifications.feedbackservice': { + 'Meta': {'unique_together': "(('name', 'hostname'),)", 'object_name': 'FeedbackService'}, + 'apn_service': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ios_notifications.APNService']"}), + 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'ios_notifications.notification': { + 'Meta': {'object_name': 'Notification'}, + 'badge': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'custom_payload': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_sent_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'loc_payload': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'message': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}), + 'service': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ios_notifications.APNService']"}), + 'sound': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}) + } + } + + complete_apps = ['ios_notifications'] diff --git a/ios_notifications/south_migrations/0004_auto__add_field_notification_silent.py b/ios_notifications/south_migrations/0004_auto__add_field_notification_silent.py new file mode 100644 index 0000000..9d7ecef --- /dev/null +++ b/ios_notifications/south_migrations/0004_auto__add_field_notification_silent.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'Notification.silent' + db.add_column(u'ios_notifications_notification', 'silent', + self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Notification.silent' + db.delete_column(u'ios_notifications_notification', 'silent') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'ios_notifications.apnservice': { + 'Meta': {'unique_together': "(('name', 'hostname'),)", 'object_name': 'APNService'}, + 'certificate': ('django.db.models.fields.TextField', [], {}), + 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'passphrase': ('django_fields.fields.EncryptedCharField', [], {'max_length': '110', 'null': 'True', 'block_type': "'MODE_CBC'", 'cipher': "'AES'", 'blank': 'True'}), + 'private_key': ('django.db.models.fields.TextField', [], {}) + }, + u'ios_notifications.device': { + 'Meta': {'unique_together': "(('token', 'service'),)", 'object_name': 'Device'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'deactivated_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'display': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'last_notified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'os_version': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}), + 'platform': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}), + 'service': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ios_notifications.APNService']"}), + 'token': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'users': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'ios_devices'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['auth.User']"}) + }, + u'ios_notifications.feedbackservice': { + 'Meta': {'unique_together': "(('name', 'hostname'),)", 'object_name': 'FeedbackService'}, + 'apn_service': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ios_notifications.APNService']"}), + 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + u'ios_notifications.notification': { + 'Meta': {'object_name': 'Notification'}, + 'badge': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'custom_payload': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_sent_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'loc_payload': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'message': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}), + 'service': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ios_notifications.APNService']"}), + 'silent': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'sound': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}) + } + } + + complete_apps = ['ios_notifications'] \ No newline at end of file diff --git a/ios_notifications/south_migrations/__init__.py b/ios_notifications/south_migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ios_notifications/templates/admin/ios_notifications/notification/change_form.html b/ios_notifications/templates/admin/ios_notifications/notification/change_form.html index 5e2dee6..9af4738 100644 --- a/ios_notifications/templates/admin/ios_notifications/notification/change_form.html +++ b/ios_notifications/templates/admin/ios_notifications/notification/change_form.html @@ -1,4 +1,4 @@ -{% extends "admin/change_form.html" %}{% load url from future %} +{% extends "admin/change_form.html" %} {% block extrastyle %} {{ block.super }}