Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
  • 3 commits
  • 10 files changed
  • 0 comments
  • 2 contributors
Nov 20, 2012
Added tox.ini, updated for testing d3231eb
Completed unit test 1590866
Michael Angeletti Merge pull request #9 from jsober/master
Added tox.ini, began writing unit tests
749ed5b
11  MANIFEST
... ...
@@ -0,0 +1,11 @@
  1
+LICENSE
  2
+MANIFEST.in
  3
+README.md
  4
+setup.py
  5
+adgeletti/__init__.py
  6
+adgeletti/models.py
  7
+adgeletti/settings.py
  8
+adgeletti/static/adgeletti/adgeletti.js
  9
+adgeletti/static/adgeletti/adgeletti.min.js
  10
+adgeletti/templatetags/__init__.py
  11
+adgeletti/templatetags/adgeletti_tags.py
3  adgeletti/models.py
@@ -29,9 +29,8 @@ class Meta:
29 29
         unique_together = ('label', 'site')
30 30
 
31 31
     def __unicode__(self):
32  
-        return _(u'%s (%s)') % (self.label, self.site)
  32
+        return _(u'%s (%s)') % (self.label, self.site.name)
33 33
 
34  
-    @property
35 34
     def ad_unit_id(self):
36 35
         return u'%s/%s' % (settings.ADGELETTI_DFP_NETWORK_ID, self.ad_unit)
37 36
 
13  adgeletti/templatetags/adgeletti_tags.py
@@ -124,7 +124,7 @@ def render(self, context):
124 124
             return error(u'{% adgeletti_go %} was run without an {% ad ... %}')
125 125
 
126 126
         if context.render_context[FIRED]:
127  
-            return error(u'{% adgeletti_go %} already used, but used again')
  127
+            return error(u'{% adgeletti_go %} called more than once')
128 128
         else:
129 129
             context.render_context[FIRED] = True
130 130
 
@@ -141,7 +141,7 @@ def render(self, context):
141 141
         )
142 142
 
143 143
         if slots and not positions:
144  
-            buf.write(error(u'No ad positions exist for the slots in the page - slots were %s' % slots))
  144
+            return error(u'No ad positions exist for the slots in the page (slots: %s)' % slots)
145 145
 
146 146
         # Always output script and base data structure
147 147
         buf.write(u'<script type="text/javascript">\n')
@@ -151,14 +151,11 @@ def render(self, context):
151 151
         for pos in positions:
152 152
             _position_data = {
153 153
                 'breakpoint': pos.breakpoint,
154  
-                'ad_unit_id': pos.slot.ad_unit_id,
155  
-                'sizes': [
156  
-                    [size.width, size.height] for size in pos.sizes.all()
157  
-                ],
158  
-                'div_id': context.render_context[ADS][slot][breakpoint],
  154
+                'ad_unit_id': pos.slot.ad_unit_id(),
  155
+                'sizes': [[size.width, size.height] for size in pos.sizes.all()],
  156
+                'div_id': context.render_context[ADS][pos.slot.label][pos.breakpoint],
159 157
             }
160 158
             buf.write(AdBlock.POSITION_TPL % (json.dumps(_position_data),))
161  
-
162 159
             buf.write(u'\n')
163 160
 
164 161
         buf.write(u'</script>\n')
2  adgeletti/tests/__init__.py
... ...
@@ -0,0 +1,2 @@
  1
+from adgeletti.tests.test_models import *
  2
+from adgeletti.tests.test_tags import *
169  adgeletti/tests/settings.py
... ...
@@ -0,0 +1,169 @@
  1
+# Django settings for adgeletti
  2
+
  3
+DEBUG = True
  4
+TEMPLATE_DEBUG = DEBUG
  5
+
  6
+ADMINS = (
  7
+    # ('Your Name', 'your_email@example.com'),
  8
+)
  9
+
  10
+MANAGERS = ADMINS
  11
+
  12
+DATABASES = {
  13
+    'default': {
  14
+        'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
  15
+        'NAME': 'database.sqlite3',      # Or path to database file if using sqlite3.
  16
+        'USER': '',                      # Not used with sqlite3.
  17
+        'PASSWORD': '',                  # Not used with sqlite3.
  18
+        'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
  19
+        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
  20
+    }
  21
+}
  22
+
  23
+# Local time zone for this installation. Choices can be found here:
  24
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
  25
+# although not all choices may be available on all operating systems.
  26
+# In a Windows environment this must be set to your system time zone.
  27
+TIME_ZONE = 'America/Chicago'
  28
+
  29
+# Language code for this installation. All choices can be found here:
  30
+# http://www.i18nguy.com/unicode/language-identifiers.html
  31
+LANGUAGE_CODE = 'en-us'
  32
+
  33
+SITE_ID = 1
  34
+
  35
+# If you set this to False, Django will make some optimizations so as not
  36
+# to load the internationalization machinery.
  37
+USE_I18N = True
  38
+
  39
+# If you set this to False, Django will not format dates, numbers and
  40
+# calendars according to the current locale.
  41
+USE_L10N = True
  42
+
  43
+# If you set this to False, Django will not use timezone-aware datetimes.
  44
+USE_TZ = True
  45
+
  46
+# Absolute filesystem path to the directory that will hold user-uploaded files.
  47
+# Example: "/home/media/media.lawrence.com/media/"
  48
+MEDIA_ROOT = ''
  49
+
  50
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
  51
+# trailing slash.
  52
+# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
  53
+MEDIA_URL = ''
  54
+
  55
+# Absolute path to the directory static files should be collected to.
  56
+# Don't put anything in this directory yourself; store your static files
  57
+# in apps' "static/" subdirectories and in STATICFILES_DIRS.
  58
+# Example: "/home/media/media.lawrence.com/static/"
  59
+STATIC_ROOT = ''
  60
+
  61
+# URL prefix for static files.
  62
+# Example: "http://media.lawrence.com/static/"
  63
+STATIC_URL = '/static/'
  64
+
  65
+# Additional locations of static files
  66
+STATICFILES_DIRS = (
  67
+    # Put strings here, like "/home/html/static" or "C:/www/django/static".
  68
+    # Always use forward slashes, even on Windows.
  69
+    # Don't forget to use absolute paths, not relative paths.
  70
+)
  71
+
  72
+# List of finder classes that know how to find static files in
  73
+# various locations.
  74
+STATICFILES_FINDERS = (
  75
+    'django.contrib.staticfiles.finders.FileSystemFinder',
  76
+    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
  77
+#    'django.contrib.staticfiles.finders.DefaultStorageFinder',
  78
+)
  79
+
  80
+# Make this unique, and don't share it with anybody.
  81
+SECRET_KEY = '9^l52u_q92#pb7(=+*ch(3f#3bkv14xu0g_ud7%rf*kmq33la@'
  82
+
  83
+# List of callables that know how to import templates from various sources.
  84
+TEMPLATE_LOADERS = (
  85
+    'django.template.loaders.filesystem.Loader',
  86
+    'django.template.loaders.app_directories.Loader',
  87
+#     'django.template.loaders.eggs.Loader',
  88
+)
  89
+
  90
+MIDDLEWARE_CLASSES = (
  91
+    'django.middleware.common.CommonMiddleware',
  92
+    'django.contrib.sessions.middleware.SessionMiddleware',
  93
+    'django.middleware.csrf.CsrfViewMiddleware',
  94
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
  95
+    'django.contrib.messages.middleware.MessageMiddleware',
  96
+    # Uncomment the next line for simple clickjacking protection:
  97
+    # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
  98
+)
  99
+
  100
+ROOT_URLCONF = 'adgeletti.urls'
  101
+
  102
+# Python dotted path to the WSGI application used by Django's runserver.
  103
+WSGI_APPLICATION = 'adgeletti.wsgi.application'
  104
+
  105
+TEMPLATE_DIRS = (
  106
+    # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
  107
+    # Always use forward slashes, even on Windows.
  108
+    # Don't forget to use absolute paths, not relative paths.
  109
+)
  110
+
  111
+INSTALLED_APPS = (
  112
+    'django.contrib.auth',
  113
+    'django.contrib.contenttypes',
  114
+    'django.contrib.sessions',
  115
+    'django.contrib.sites',
  116
+    'django.contrib.messages',
  117
+    'django.contrib.staticfiles',
  118
+    # Uncomment the next line to enable the admin:
  119
+    # 'django.contrib.admin',
  120
+    # Uncomment the next line to enable admin documentation:
  121
+    # 'django.contrib.admindocs',
  122
+    'adgeletti',
  123
+)
  124
+
  125
+# A sample logging configuration. The only tangible logging
  126
+# performed by this configuration is to send an email to
  127
+# the site admins on every HTTP 500 error when DEBUG=False.
  128
+# See http://docs.djangoproject.com/en/dev/topics/logging for
  129
+# more details on how to customize your logging configuration.
  130
+LOGGING = {
  131
+    'version': 1,
  132
+    'disable_existing_loggers': False,
  133
+    'filters': {
  134
+        'require_debug_false': {
  135
+            '()': 'django.utils.log.RequireDebugFalse'
  136
+        }
  137
+    },
  138
+    'handlers': {
  139
+        'mail_admins': {
  140
+            'level': 'ERROR',
  141
+            'filters': ['require_debug_false'],
  142
+            'class': 'django.utils.log.AdminEmailHandler'
  143
+        }
  144
+    },
  145
+    'loggers': {
  146
+        'django.request': {
  147
+            'handlers': ['mail_admins'],
  148
+            'level': 'ERROR',
  149
+            'propagate': True,
  150
+        },
  151
+    }
  152
+}
  153
+
  154
+################################################################################
  155
+# Adgeletti Configuration
  156
+################################################################################
  157
+
  158
+# DoubleClick network ID
  159
+ADGELETTI_DFP_NETWORK_ID = '0123456789'
  160
+
  161
+# List of identifiers for breakpoints. This is used to integrate adgeletti into
  162
+# a Django site's own responsive framework.
  163
+ADGELETTI_BREAKPOINTS = [
  164
+    'default',
  165
+    'tablet',
  166
+    'wide',
  167
+    'mobile',
  168
+]
  169
+
30  adgeletti/tests/test_models.py
... ...
@@ -0,0 +1,30 @@
  1
+import mock
  2
+from django.utils.unittest import TestCase
  3
+from django.conf import settings
  4
+from adgeletti.models import Size, AdSlot
  5
+
  6
+
  7
+class SizeTestCase(TestCase):
  8
+    def setUp(self):
  9
+        self.size = mock.Mock(spec=Size, width=200, height=100)
  10
+
  11
+    def test_stringify(self):
  12
+        string = Size.__unicode__(self.size)
  13
+        self.assertEqual(string, '200px x 100px')
  14
+
  15
+
  16
+class AdSlotTestCase(TestCase):
  17
+    def setUp(self):
  18
+        site = mock.Mock()
  19
+        site.name = 'Test site'
  20
+        self.slot = mock.Mock(spec=AdSlot, label='LABEL', site=site, ad_unit='UNIT_ID')
  21
+
  22
+    def test_stringify(self):
  23
+        string = AdSlot.__unicode__(self.slot)
  24
+        self.assertEqual(string, 'LABEL (Test site)')
  25
+
  26
+    def test_ad_unit_id(self):
  27
+        string = AdSlot.ad_unit_id(self.slot)
  28
+        expected = '%s/%s' % (settings.ADGELETTI_DFP_NETWORK_ID, 'UNIT_ID')
  29
+        self.assertEqual(string, expected)
  30
+
159  adgeletti/tests/test_tags.py
... ...
@@ -0,0 +1,159 @@
  1
+import json
  2
+import mock
  3
+from adgeletti.models import Size, AdSlot, AdPosition
  4
+from adgeletti.templatetags import adgeletti_tags as tags
  5
+from django import template
  6
+from django.contrib.sites.models import Site
  7
+from django.utils.unittest import TestCase
  8
+
  9
+
  10
+class ErrorTestCase(TestCase):
  11
+    @mock.patch('adgeletti.templatetags.adgeletti_tags.escape')
  12
+    def test_error_strings(self, escape):
  13
+        escape.return_value = 'BAR'
  14
+        error = tags.error('FOO')
  15
+        escape.assert_called_with('FOO')
  16
+        self.assertEqual(error, '<!-- BAR -->\n')
  17
+
  18
+
  19
+class ParseAdTestCase(TestCase):
  20
+    def test_parse_ad(self):
  21
+        token = mock.Mock()
  22
+        token.split_contents = mock.Mock(return_value=['ad', 'SLOT', 'BREAKPOINT'])
  23
+        node = tags.parse_ad(None, token)
  24
+        self.assertEqual(node.slot, 'SLOT')
  25
+        self.assertListEqual(node.breakpoints, ['BREAKPOINT'])
  26
+
  27
+    def test_parse_ad_bad_args(self):
  28
+        token = mock.Mock()
  29
+        token.split_contents = mock.Mock(return_value=['ad', 'SLOT'])
  30
+        with self.assertRaises(template.TemplateSyntaxError) as exc:
  31
+            tags.parse_ad(None, token)
  32
+        self.assertEqual(str(exc.exception), u'usage: {% ad SLOT BREAKPOINT [BREAKPOINT ...] %}')
  33
+
  34
+
  35
+class AdNodeTestCase(TestCase):
  36
+    def setUp(self):
  37
+        self.node = tags.AdNode('SLOT', ['A', 'B'])
  38
+
  39
+    def test_clean_value(self):
  40
+        invalid_chars = ['!', '^', ' ', '+', '=', '\n']
  41
+        for char in invalid_chars:
  42
+            s = tags.AdNode.clean_value(char)
  43
+            self.assertEqual(s, tags.AdNode._replace)
  44
+
  45
+        valid_chars = ['a', 'A', '1', '9', '_', '-']
  46
+        for char in valid_chars:
  47
+            s = tags.AdNode.clean_value(char)
  48
+            self.assertEquals(s, char)
  49
+
  50
+    def test_div_id(self):
  51
+        divid = tags.AdNode.div_id('A', 'B')
  52
+        self.assertEqual(divid, 'adgeletti-ad-div-A-B')
  53
+
  54
+    def test_build_div(self):
  55
+        div = tags.AdNode.build_div('FOO')
  56
+        self.assertEqual(div, '<div class="adgeletti-ad-div" id="FOO" style="display:none"></div>\n')
  57
+
  58
+    def test_render(self):
  59
+        c = template.Context({})
  60
+        result = self.node.render(c)
  61
+        self.assertIn('<div class="adgeletti-ad-div" id="adgeletti-ad-div-SLOT-A" style="display:none"></div>\n', result)
  62
+        self.assertIn('<div class="adgeletti-ad-div" id="adgeletti-ad-div-SLOT-B" style="display:none"></div>\n', result)
  63
+        self.assertIn(tags.ADS, c.render_context)
  64
+        self.assertIn(tags.BREAKPOINTS, c.render_context)
  65
+        self.assertIn(tags.FIRED, c.render_context)
  66
+        self.assertSequenceEqual(set(['A', 'B']), c.render_context.get(tags.BREAKPOINTS, []))
  67
+        self.assertEqual(c.render_context[tags.ADS]['SLOT']['A'], 'adgeletti-ad-div-SLOT-A')
  68
+        self.assertEqual(c.render_context[tags.ADS]['SLOT']['B'], 'adgeletti-ad-div-SLOT-B')
  69
+
  70
+    def test_render_fired(self):
  71
+        c = template.Context({})
  72
+        c.render_context[tags.FIRED] = True
  73
+        result = self.node.render(c)
  74
+        self.assertEqual(result, tags.error('{% ad ... %} used after {% adgeletti_go %} used'))
  75
+
  76
+
  77
+class ParseAdgelettiGoTestCase(TestCase):
  78
+    def test_parse_adgeletti_go(self):
  79
+        result = tags.parse_adgeletti_go(None, None)
  80
+        self.assertIsInstance(result, tags.AdBlock)
  81
+
  82
+
  83
+class AdBlockTestCase(TestCase):
  84
+    def setUp(self):
  85
+        self.block = tags.AdBlock()
  86
+        self.nodes = [
  87
+            tags.AdNode('SLOT1', ['A']),
  88
+            tags.AdNode('SLOT2', ['A']),
  89
+            tags.AdNode('SLOT2', ['B']),
  90
+        ]
  91
+
  92
+        self.site = Site(name='SITE', domain='example.com')
  93
+        self.site.save()
  94
+
  95
+    @property
  96
+    def context(self):
  97
+        context = template.Context({})
  98
+        for node in self.nodes:
  99
+            node.render(context)
  100
+        return context
  101
+
  102
+    def test_render_missing_ads(self):
  103
+        result = self.block.render(template.Context({}))
  104
+        self.assertEqual(result, tags.error('{% adgeletti_go %} was run without an {% ad ... %}'))
  105
+
  106
+    def test_render_missing_fired(self):
  107
+        result = self.block.render(template.Context({tags.ADS: {}}))
  108
+        self.assertEqual(result, tags.error('{% adgeletti_go %} was run without an {% ad ... %}'))
  109
+
  110
+    def test_render_fired(self):
  111
+        context = self.context
  112
+        self.block.render(context) # first call sets FIRED in render context
  113
+        result = self.block.render(context)
  114
+        self.assertEqual(result, tags.error('{% adgeletti_go %} called more than once'))
  115
+
  116
+    def test_render_no_positions(self):
  117
+        result = self.block.render(self.context)
  118
+        self.assertEqual(result, tags.error("No ad positions exist for the slots in the page (slots: ['SLOT1', 'SLOT2'])"))
  119
+
  120
+    @mock.patch('adgeletti.templatetags.adgeletti_tags.Site')
  121
+    def test_render(self, site):
  122
+        site.objects = mock.Mock()
  123
+        site.objects.get_current = mock.Mock(return_value=self.site)
  124
+
  125
+        size = Size(width=100, height=100)
  126
+        size.save()
  127
+
  128
+        slot1 = AdSlot(label='SLOT1', ad_unit='ADUNIT1', site=self.site)
  129
+        slot1.save()
  130
+
  131
+        slot2 = AdSlot(label='SLOT2', ad_unit='ADUNIT2', site=self.site)
  132
+        slot2.save()
  133
+
  134
+        pos1a = AdPosition(slot=slot1, breakpoint='A')
  135
+        pos1a.save()
  136
+        pos1a.sizes.add(size)
  137
+
  138
+        pos2a = AdPosition(slot=slot2, breakpoint='A')
  139
+        pos2a.save()
  140
+        pos2a.sizes.add(size)
  141
+
  142
+        pos2b = AdPosition(slot=slot2, breakpoint='B')
  143
+        pos2b.save()
  144
+        pos2b.sizes.add(size)
  145
+
  146
+        result = self.block.render(self.context)
  147
+        self.assertIn('<script type="text/javascript">', result)
  148
+        self.assertIn('</script>', result)
  149
+
  150
+        sizes = [[size.width, size.height]]
  151
+        positions = [
  152
+            { 'breakpoint': 'A', 'ad_unit_id': slot1.ad_unit_id(), 'sizes': sizes, 'div_id': tags.AdNode.div_id('SLOT1', 'A'), },
  153
+            { 'breakpoint': 'A', 'ad_unit_id': slot2.ad_unit_id(), 'sizes': sizes, 'div_id': tags.AdNode.div_id('SLOT2', 'A'), },
  154
+            { 'breakpoint': 'B', 'ad_unit_id': slot2.ad_unit_id(), 'sizes': sizes, 'div_id': tags.AdNode.div_id('SLOT2', 'B'), },
  155
+        ]
  156
+
  157
+        for pos in positions:
  158
+            self.assertIn('Adgeletti.position(\'%s\');' % json.dumps(pos), result)
  159
+
9  adgeletti/tests/urls.py
... ...
@@ -0,0 +1,9 @@
  1
+""" A django URL specification for use during unit testing.
  2
+"""
  3
+
  4
+from django.conf.urls import patterns, include
  5
+from django.contrib import admin
  6
+
  7
+urlpatterns = patterns('',
  8
+    (r'^admin/', include(admin.site.urls)),
  9
+)
6  setup.py
... ...
@@ -1,5 +1,6 @@
1 1
 #!/usr/bin/env python
2  
-from distutils.core import setup
  2
+#from distutils.core import setup
  3
+from setuptools import setup, find_packages
3 4
 
4 5
 
5 6
 # Dynamically calculate the version based on adgeletti.VERSION
@@ -13,8 +14,6 @@
13 14
     version = version,
14 15
     author = 'Jeff.Ober and Michael.Angeletti @ CMG Digital [dot] com',
15 16
     url = 'http://github.com/orokusaki/adgeletti/',
16  
-    packages=['adgeletti'],
17  
-    package_data={'adgeletti': ['static/adgeletti/*', 'templatetags/*.py']},
18 17
     classifiers = [
19 18
         'Environment :: Web Environment',
20 19
         'Framework :: Django',
@@ -24,4 +23,5 @@
24 23
         'Programming Language :: Python',
25 24
         'Topic :: Utilities'
26 25
     ],
  26
+    packages=find_packages(),
27 27
 )
12  tox.ini
... ...
@@ -0,0 +1,12 @@
  1
+[tox]
  2
+envlist = py26
  3
+
  4
+[testenv]
  5
+deps =
  6
+    django
  7
+    mock
  8
+setenv =
  9
+	DJANGO_SETTINGS_MODULE=adgeletti.tests.settings
  10
+commands =
  11
+	{envbindir}/django-admin.py syncdb --noinput
  12
+	{envbindir}/django-admin.py test adgeletti

No commit comments for this range

Something went wrong with that request. Please try again.