Skip to content
Browse files

Initial commit

  • Loading branch information...
0 parents commit 09d2896f082bddfe21735e973bc723e45b239fa7 @matthiask matthiask committed Jan 9, 2013
2 .gitignore
@@ -0,0 +1,2 @@
+*.pyc
+*.egg-info
27 LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2013, FEINHEIT GmbH and individual contributors.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ 3. Neither the name of FEINHEIT GmbH nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
8 MANIFEST.in
@@ -0,0 +1,8 @@
+include LICENSE
+include MANIFEST.in
+include README.rst
+include requirements.txt
+include setuplib.py
+recursive-include feincms_banners/static *
+recursive-include feincms_banners/locale *
+recursive-include feincms_banners/templates *
3 README.rst
@@ -0,0 +1,3 @@
+===========================================================================
+feincms-banners -- a simple banner system with views and clicks for FeinCMS
+===========================================================================
2 feincms_banners/__init__.py
@@ -0,0 +1,2 @@
+VERSION = (1, 0, 0)
+__version__ = '.'.join(map(str, VERSION))
16 feincms_banners/admin.py
@@ -0,0 +1,16 @@
+from django.contrib import admin
+
+from feincms_banners import models
+
+
+admin.site.register(models.Banner,
+ list_display=('name', 'is_active', 'type', 'url', 'active_from',
+ 'active_until', 'embeds', 'impressions', 'click_count'),
+ list_filter=('is_active', 'type'),
+ raw_id_fields=('mediafile',),
+ search_fields=('name', 'url', 'code'),
+ )
+admin.site.register(models.Click,
+ list_display=('timestamp', 'banner', 'ip', 'user_agent', 'referrer'),
+ search_fields=('banner__name', 'user_agent', 'referrer'),
+ )
47 feincms_banners/contents.py
@@ -0,0 +1,47 @@
+from django.db import models
+from django.template.loader import render_to_string
+from django.utils.translation import ugettext_lazy as _
+
+from feincms_banners.models import Banner
+
+from feincms.admin.item_editor import FeinCMSInline
+
+
+class BannerContentInline(FeinCMSInline):
+ raw_id_fields = ('specific',)
+
+
+class BannerContent(models.Model):
+ feincms_item_editor_inline = BannerContentInline
+
+ is_section_aware = True
+
+ specific = models.ForeignKey(Banner, verbose_name=_('specific'),
+ blank=True, null=True, help_text=_('If you leave this empty, a random banner will be selected.'),
+ limit_choices_to={'is_active': True})
+ type = models.CharField(_('type'), max_length=20, choices=Banner.TYPE_CHOICES)
+
+ class Meta:
+ abstract = True
+ verbose_name = _('banner')
+ verbose_name = _('banners')
+
+ def render(self, **kwargs):
+ if self.specific:
+ if self.specific.is_active:
+ banner = self.specific
+ type = banner.type
+ else:
+ return u''
+ else:
+ try:
+ banner = Banner.objects.active().filter(
+ type=self.type).select_related('mediafile').order_by('?')[0]
+ type = self.type
+ except IndexError:
+ return u''
+
+ return render_to_string([
+ 'content/banner/%s.html' % type,
+ 'content/banner/default.html',
+ ], {'content': self, 'banner': banner})
0 feincms_banners/migrations/__init__.py
No changes.
108 feincms_banners/models.py
@@ -0,0 +1,108 @@
+from random import choice
+
+from django.db import models
+from django.db.models import Q
+from django.utils import timezone
+from django.utils.translation import ugettext_lazy as _
+
+from feincms.module.medialibrary.fields import MediaFileForeignKey
+from feincms.module.medialibrary.models import MediaFile
+
+
+def generate_key():
+ return ''.join([
+ choice('abcdefghijklmnopqrstuvwxyz0123456789-_') for i in range(40)])
+
+
+class BannerManager(models.Manager):
+ def active(self):
+ return self.filter(
+ Q(is_active=True, active_from__lte=timezone.now())
+ & (
+ Q(active_until__isnull=True)
+ | Q(
+ active_until__isnull=False,
+ active_until__gte=timezone.now()
+ )
+ )
+ )
+
+
+class Banner(models.Model):
+ SKYSCRAPER = 'skyscraper'
+ LEADERBOARD = 'leaderboard'
+ BOX = 'box'
+
+ TYPE_CHOICES = (
+ (SKYSCRAPER, _('skyscraper')),
+ (LEADERBOARD, _('leaderboard')),
+ (BOX, _('box')),
+ )
+
+ is_active = models.BooleanField(_('is active'), default=True)
+ name = models.CharField(_('name'), max_length=100,
+ help_text=_('Only for internal use, will not be shown on the website.'
+ ))
+ mediafile = MediaFileForeignKey(MediaFile, verbose_name=_('media file'))
+ url = models.URLField(_('URL'), verify_exists=False)
+ type = models.CharField(_('type'), max_length=20, choices=TYPE_CHOICES)
+ code = models.CharField(_('code'), max_length=40, default=generate_key,
+ unique=True)
+
+ active_from = models.DateTimeField(_('active from'), default=timezone.now)
+ active_until = models.DateTimeField(_('active until'),
+ blank=True, null=True)
+
+ embeds = models.PositiveIntegerField(_('embeds'), default=0,
+ editable=False,
+ help_text=_('How many times has this banner been embdedded on a'
+ ' website?')
+ impressions = models.PositiveIntegerField(_('impressions'), default=0,
+ editable=False,
+ help_text=_('How many times has an impression been registered using'
+ ' a Javascript callback, verifying that it actually was a'
+ ' browser? (Too low because of network issues and deactivated'
+ ' Javascript support in some browsers.)'))
+
+ objects = BannerManager()
+
+ class Meta:
+ ordering= ['-active_from']
+ verbose_name = _('banner')
+ verbose_name_plural = _('banners')
+
+ def __unicode__(self):
+ return self.name
+
+ @models.permalink
+ def get_absolute_url(self):
+ return ('banner_click', (), {'code': self.code})
+
+ @models.permalink
+ def impression_url(self):
+ return ('banner_impression', (), {'code': self.code})
+
+ def click(self, request):
+ self.clicks.create(
+ ip=request.META.get('REMOTE_ADDR'),
+ user_agent=request.META.get('HTTP_USER_AGENT', ''),
+ referrer=request.META.get('HTTP_REFERER', ''),
+ )
+
+ def click_count(self):
+ return self.clicks.count()
+ click_count.short_description = _('click count')
+
+
+class Click(models.Model):
+ banner = models.ForeignKey(Banner, verbose_name=_('banner'),
+ related_name='clicks')
+ timestamp = models.DateTimeField(_('timestamp'), default=timezone.now)
+ ip = models.IPAddressField(_('IP'), blank=True, null=True)
+ user_agent = models.TextField(_('user agent'), blank=True, default='')
+ referrer = models.TextField(_('referrer'), blank=True, default='')
+
+ class Meta:
+ orering = ['-timestamp']
+ verbose_name = _('click')
+ verbose_name_plural = _('clicks')
9 feincms_banners/templates/content/banner/box.html
@@ -0,0 +1,9 @@
+<a href="{{ banner.get_absolute_url }}" target="_blank">
+ <img src="{{ banner.mediafile.file.url }}" alt=""></a>
+<script>
+(function() { try {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', '{{ banner.impression_url }}');
+ xhr.send(null);
+} catch(e) {}; })();
+</script>
1 feincms_banners/templates/content/banner/default.html
@@ -0,0 +1 @@
+<!-- banner type unknown -->
12 feincms_banners/templates/content/banner/leaderboard.html
@@ -0,0 +1,12 @@
+<div class="content" style="background:none;overflow:hidden;">
+ <a href="{{ banner.get_absolute_url }}" target="_blank">
+ <img src="{{ banner.mediafile.file.url }}" alt="">
+ </a>
+</div>
+<script>
+(function() { try {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', '{{ banner.impression_url }}');
+ xhr.send(null);
+} catch(e) {}; })();
+</script>
22 feincms_banners/templates/content/banner/skyscraper.html
@@ -0,0 +1,22 @@
+<div style="margin-top:35px;">
+<div style="font-size:10px;position:relative;top:8px;left:1px;">Anzeige</div>
+<div class="box" style="background:none;overflow:hidden;padding:8px 0 0 0;margin:0;">
+{% if banner.mediafile.type == 'swf' %}
+ <object type='application/x-shockwave-flash' data='{{ banner.mediafile.file.url }}' width='160' height='600'>
+ <param name='allowScriptAccess' value='always' />
+ <param name='movie' value='{{ banner.mediafile.file.url }}' />
+ </object>
+{% else %}
+ <a href="{{ banner.get_absolute_url }}" target="_blank">
+ <img src="{{ banner.mediafile.file.url }}" alt="">
+ </a>
+{% endif %}
+</div>
+</div>
+<script>
+(function() { try {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', '{{ banner.impression_url }}');
+ xhr.send(null);
+} catch(e) {}; })();
+</script>
7 feincms_banners/urls.py
@@ -0,0 +1,7 @@
+from django.conf.urls.defaults import url, patterns, include
+
+
+urlpatterns = patterns('feincms_banners.views',
+ url(r'^b/c/(?P<code>[^/]+)/$', 'click', name='banner_click'),
+ url(r'^b/i/(?P<code>[^/]+)/$', 'impression', name='banner_impression'),
+)
19 feincms_banners/views.py
@@ -0,0 +1,19 @@
+from django.db.models import F
+from django.http import HttpResponse, HttpResponseRedirect
+from django.shortcuts import get_object_or_404
+
+from feincms_banners.models import Banner
+
+
+def click(request, code):
+ banner = get_object_or_404(Banner, code=code)
+ banner.click(request)
+ return HttpResponseRedirect(banner.url)
+
+
+def impression(request, code):
+ if Banner.objects.filter(code=code).update(
+ impressions=F('impressions') + 1
+ ):
+ return HttpResponse('+1')
+ return HttpResponse('?')
1 requirements.txt
@@ -0,0 +1 @@
+feincms>=1.6
30 setup.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+
+from distutils.core import setup
+import os
+import setuplib
+
+packages, package_data = setuplib.find_packages('feincms_banners')
+
+setup(name='feincms-banners',
+ version=__import__('feincms_banners').__version__,
+ description='A simple banner system with views and clicks for FeinCMS.',
+ long_description=open(os.path.join(os.path.dirname(__file__), 'README.rst')).read(),
+ author='Matthias Kestenholz',
+ author_email='mk@feinheit.ch',
+ url='https://github.com/matthiask/feincms-banners/',
+ license='BSD License',
+ platforms=['OS Independent'],
+ packages=packages,
+ package_data=package_data,
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+ 'Environment :: Web Environment',
+ 'Framework :: Django',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: BSD License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
+ ],
+)
58 setuplib.py
@@ -0,0 +1,58 @@
+import os
+
+
+__all__ = ['find_files']
+
+
+def fullsplit(path, result=None):
+ """
+ Split a pathname into components (the opposite of os.path.join) in a
+ platform-neutral way.
+ """
+ if result is None:
+ result = []
+ head, tail = os.path.split(path)
+ if head == '':
+ return [tail] + result
+ if head == path:
+ return result
+ return fullsplit(head, [tail] + result)
+
+
+def find_packages(package_dir):
+ """
+ Returns a tuple consisting of a ``packages`` list and a ``package_data``
+ dictionary suitable for passing on to ``distutils.core.setup``
+
+ Requires the folder name containing the package files; ``find_files``
+ assumes that ``setup.py`` is located in the same folder as the folder
+ containing those files.
+
+ Code lifted from Django's ``setup.py``, with improvements by PSyton.
+ """
+
+ # Compile the list of packages available, because distutils doesn't have
+ # an easy way to do this.
+ packages = []
+ package_data = {}
+ root_dir = os.path.dirname(__file__)
+ if root_dir != '':
+ os.chdir(root_dir)
+
+ for dirpath, dirnames, filenames in sorted(os.walk(package_dir)):
+ # Ignore dirnames that start with '.'
+ for i, dirname in enumerate(dirnames):
+ if dirname.startswith('.'): del dirnames[i]
+ if '__init__.py' in filenames:
+ packages.append('.'.join(fullsplit(dirpath)))
+ elif filenames:
+ cur_pack = packages[0] # Assign all data files to the toplevel package
+ if cur_pack not in package_data:
+ package_data[cur_pack] = []
+ package_dir = os.path.join(*cur_pack.split("."))
+ dir_relpath = os.path.relpath(dirpath, package_dir)
+ for f in filenames:
+ package_data[cur_pack].append(os.path.join(dir_relpath, f))
+
+ return packages, package_data
+

0 comments on commit 09d2896

Please sign in to comment.
Something went wrong with that request. Please try again.