Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
matthiask committed Jan 9, 2013
0 parents commit 09d2896
Show file tree
Hide file tree
Showing 18 changed files with 372 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
@@ -0,0 +1,2 @@
*.pyc
*.egg-info
27 changes: 27 additions & 0 deletions 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 changes: 8 additions & 0 deletions 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 changes: 3 additions & 0 deletions README.rst
@@ -0,0 +1,3 @@
===========================================================================
feincms-banners -- a simple banner system with views and clicks for FeinCMS
===========================================================================
2 changes: 2 additions & 0 deletions feincms_banners/__init__.py
@@ -0,0 +1,2 @@
VERSION = (1, 0, 0)
__version__ = '.'.join(map(str, VERSION))
16 changes: 16 additions & 0 deletions 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 changes: 47 additions & 0 deletions 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})
Empty file.
108 changes: 108 additions & 0 deletions 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 changes: 9 additions & 0 deletions 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 change: 1 addition & 0 deletions feincms_banners/templates/content/banner/default.html
@@ -0,0 +1 @@
<!-- banner type unknown -->
12 changes: 12 additions & 0 deletions 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 changes: 22 additions & 0 deletions 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 changes: 7 additions & 0 deletions 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 changes: 19 additions & 0 deletions 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 change: 1 addition & 0 deletions requirements.txt
@@ -0,0 +1 @@
feincms>=1.6
30 changes: 30 additions & 0 deletions 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 changes: 58 additions & 0 deletions 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.