Commits

catapela committed 3e9c6b2

small fixes

Comments (0)

Files changed (5)

dynamicLink/admin.py

         self.request = request
         return super(DownLinkAdmin, self).queryset(request)
 
-    list_display = ('slug', 'active', 'file', 'valid', 'clicks', 'timestamp_creation', 'link')
+    list_display = ('slug', 'active', 'file', 'valid', 'clicks',
+                    'timestamp_creation', 'link')
     actions = ['make_link']
     search_fields = ['slug', 'file_path', 'timestamp_creation', 'link_key']
     list_per_page = 50
                  (_(u'Link'), {
                  'fields': ('slug', 'file_path')
                  }),
-                 (_(u'Aditional values'), {
+                 (_(u'Additional values'), {
                  'classes': ('collapse',),
-                 'fields': ('active', 'current_clicks', 'timeout_hours', 'max_clicks')
+                 'fields': ('active', 'current_clicks', 'timeout_hours',
+                            'max_clicks')
                  }),
                  )
 
     def valid(self, obj):
-        """Shows timestamp expired or active time"""
+        """Shows time stamp expired or active time"""
         diff = unicode(obj.get_timout_time()).split('.')[0]
         if obj.timeout_time():
             if obj.active:
     valid.short_description = _(u'valid')
 
     def file(self, obj):
-        """Shows truncated filename on platform indepentend length."""
+        """Shows truncated filename on platform independent length."""
         return unicode(obj.file_path).split(presettings.DYNAMIC_LINK_MEDIA)[-1]
     file.allow_tags = True
     file.short_description = _(u'file')
 
     def clicks(self, obj):
         """Shows current and max allowed clicks in the list display"""
-        txt = '%s %s %s' % (obj.current_clicks, unicode(_(u'from')), obj.max_clicks)
+        txt = '%s %s %s' % (obj.current_clicks, unicode(_(u'from')),
+                            obj.max_clicks)
         if obj.timeout_clicks():
             if obj.active == True:
                 # set active to false
             % (unicode(_('max clicks reached')), txt)
         elif obj.max_clicks == 0:
             return '%s %s <span style="color: #FF7F00; ">%s</span>' \
-            % (obj.current_clicks, unicode(_(u'from')), unicode(_(u'unlimited')))
+            % (obj.current_clicks, unicode(_(u'from')),
+               unicode(_(u'unlimited')))
         else:
             return txt
     clicks.allow_tags = True
         siteurl = api.DownloadSiteUrl([obj.link_key])
         sitelink = siteurl.get_site_url(self.request)
         sitelink = u'<span style="color: #FF7F00; ">%s:</span> \
-        <a target="new" href="%s/">%s/</a><br/>' % (unicode(_(u'Site')), sitelink, sitelink)
+        <a target="new" href="%s/">%s/</a><br/>' \
+        % (unicode(_(u'Site')), sitelink, sitelink)
 
         # direct accessable link
         filelink = api.file_link_url(self.request, obj)
-        filelink = '<span style="color: #FF7F00; ">%s:</span> %s' % (unicode(_(u'File')), filelink)
+        filelink = '<span style="color: #FF7F00; ">%s:</span> %s' \
+        % (unicode(_(u'File')), filelink)
 
         return sitelink + filelink
     link.allow_tags = True
             li.append(obj.link_key)
         siteurl = api.DownloadSiteUrl(li)
         sitelink = siteurl.get_site_url(request)
-        # roesponse
+        # response
         from django.http import HttpResponse
-        return HttpResponse('<a target="new" href="%s/">%s/</a><br/>' % (sitelink, sitelink))
+        return HttpResponse('<a target="new" href="%s/">%s/</a><br/>' \
+                            % (sitelink, sitelink))
     make_link.short_description = _("Make from selected a download site link")
 
 admin.site.register(Download, DownLinkAdmin)

dynamicLink/models.py

 from django.utils.translation import ugettext_lazy as _
 import random
 import os
+import time
 import datetime
+from django.db import IntegrityError
 try:
     from django.utils import timezone
 except ImportError as e:
         """
         function for generating random keys
         """
-        # key = str(time.time()).replace('.', '')
         key = ''  # for shorter keys
-        characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890'
+        char = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890'
         key_length = 30
         for y in range(key_length):
-            key += characters[random.randint(0, len(characters) - 1)]
+            key += char[random.randint(0, len(char) - 1)]
         return key
 
     def save(self, * args, ** kwargs):
-        """Perform custom methodes before saving."""
-        # If not avalible set unique link key bevor saving
+        """Perform custom methods before saving."""
+        # If not available set a link key before saving
         if not self.link_key:
             self.link_key = self.__gen_key()
+
         # call the real save method
-        super(Download, self).save(*args, ** kwargs)
+        try:
+            super(Download, self).save(*args, ** kwargs)
+        except IntegrityError:
+            # enforce a unique key (with a longer string)
+            key = str(time.time()).replace('.', '')
+            self.link_key = key + self.__gen_key()
+            # call the save method again
+            super(Download, self).save(*args, ** kwargs)
 
     def __unicode__(self):
         return '%s: %s, %s: %s' % (

dynamicLink/tests.py

 
 
 class DownloadTestCase(unittest.TestCase):
-    """Test of the Doanload modell"""
+    """Test of the download model"""
     def setUp(self):
         self.path = '/static/public/testrunner/test.jpg'
         self.file = 'test.jpg'
     (r'^$', direct_to_template, {'template': 'home.html'}),
 
     # for django-dynamic-link. By default it catch url/serve/some-dynamic-link/
-    (r'^\w+/%s/' % presettings.DYNAMIC_LINK_URL_BASE_COMPONENT, include('dynamicLink.urls')),
+    (r'^\w+/%s/' % presettings.DYNAMIC_LINK_URL_BASE_COMPONENT,
+                                    include('dynamicLink.urls')),
 
     # Uncomment the admin/doc line below to enable admin documentation:
     (r'^admin/doc/', include('django.contrib.admindocs.urls')),

readme.html

-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head>
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
-<meta name="generator" content="Docutils 0.8.1: http://docutils.sourceforge.net/" />
-<title></title>
-<style type="text/css">
-
-/*
-:Author: David Goodger (goodger@python.org)
-:Id: $Id: html4css1.css 7056 2011-06-17 10:50:48Z milde $
-:Copyright: This stylesheet has been placed in the public domain.
-
-Default cascading style sheet for the HTML output of Docutils.
-
-See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
-customize this style sheet.
-*/
-
-/* used to remove borders from tables and images */
-.borderless, table.borderless td, table.borderless th {
-  border: 0 }
-
-table.borderless td, table.borderless th {
-  /* Override padding for "table.docutils td" with "! important".
-     The right padding separates the table cells. */
-  padding: 0 0.5em 0 0 ! important }
-
-.first {
-  /* Override more specific margin styles with "! important". */
-  margin-top: 0 ! important }
-
-.last, .with-subtitle {
-  margin-bottom: 0 ! important }
-
-.hidden {
-  display: none }
-
-a.toc-backref {
-  text-decoration: none ;
-  color: black }
-
-blockquote.epigraph {
-  margin: 2em 5em ; }
-
-dl.docutils dd {
-  margin-bottom: 0.5em }
-
-object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
-  overflow: hidden;
-}
-
-/* Uncomment (and remove this text!) to get bold-faced definition list terms
-dl.docutils dt {
-  font-weight: bold }
-*/
-
-div.abstract {
-  margin: 2em 5em }
-
-div.abstract p.topic-title {
-  font-weight: bold ;
-  text-align: center }
-
-div.admonition, div.attention, div.caution, div.danger, div.error,
-div.hint, div.important, div.note, div.tip, div.warning {
-  margin: 2em ;
-  border: medium outset ;
-  padding: 1em }
-
-div.admonition p.admonition-title, div.hint p.admonition-title,
-div.important p.admonition-title, div.note p.admonition-title,
-div.tip p.admonition-title {
-  font-weight: bold ;
-  font-family: sans-serif }
-
-div.attention p.admonition-title, div.caution p.admonition-title,
-div.danger p.admonition-title, div.error p.admonition-title,
-div.warning p.admonition-title {
-  color: red ;
-  font-weight: bold ;
-  font-family: sans-serif }
-
-/* Uncomment (and remove this text!) to get reduced vertical space in
-   compound paragraphs.
-div.compound .compound-first, div.compound .compound-middle {
-  margin-bottom: 0.5em }
-
-div.compound .compound-last, div.compound .compound-middle {
-  margin-top: 0.5em }
-*/
-
-div.dedication {
-  margin: 2em 5em ;
-  text-align: center ;
-  font-style: italic }
-
-div.dedication p.topic-title {
-  font-weight: bold ;
-  font-style: normal }
-
-div.figure {
-  margin-left: 2em ;
-  margin-right: 2em }
-
-div.footer, div.header {
-  clear: both;
-  font-size: smaller }
-
-div.line-block {
-  display: block ;
-  margin-top: 1em ;
-  margin-bottom: 1em }
-
-div.line-block div.line-block {
-  margin-top: 0 ;
-  margin-bottom: 0 ;
-  margin-left: 1.5em }
-
-div.sidebar {
-  margin: 0 0 0.5em 1em ;
-  border: medium outset ;
-  padding: 1em ;
-  background-color: #ffffee ;
-  width: 40% ;
-  float: right ;
-  clear: right }
-
-div.sidebar p.rubric {
-  font-family: sans-serif ;
-  font-size: medium }
-
-div.system-messages {
-  margin: 5em }
-
-div.system-messages h1 {
-  color: red }
-
-div.system-message {
-  border: medium outset ;
-  padding: 1em }
-
-div.system-message p.system-message-title {
-  color: red ;
-  font-weight: bold }
-
-div.topic {
-  margin: 2em }
-
-h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
-h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
-  margin-top: 0.4em }
-
-h1.title {
-  text-align: center }
-
-h2.subtitle {
-  text-align: center }
-
-hr.docutils {
-  width: 75% }
-
-img.align-left, .figure.align-left, object.align-left {
-  clear: left ;
-  float: left ;
-  margin-right: 1em }
-
-img.align-right, .figure.align-right, object.align-right {
-  clear: right ;
-  float: right ;
-  margin-left: 1em }
-
-img.align-center, .figure.align-center, object.align-center {
-  display: block;
-  margin-left: auto;
-  margin-right: auto;
-}
-
-.align-left {
-  text-align: left }
-
-.align-center {
-  clear: both ;
-  text-align: center }
-
-.align-right {
-  text-align: right }
-
-/* reset inner alignment in figures */
-div.align-right {
-  text-align: inherit }
-
-/* div.align-center * { */
-/*   text-align: left } */
-
-ol.simple, ul.simple {
-  margin-bottom: 1em }
-
-ol.arabic {
-  list-style: decimal }
-
-ol.loweralpha {
-  list-style: lower-alpha }
-
-ol.upperalpha {
-  list-style: upper-alpha }
-
-ol.lowerroman {
-  list-style: lower-roman }
-
-ol.upperroman {
-  list-style: upper-roman }
-
-p.attribution {
-  text-align: right ;
-  margin-left: 50% }
-
-p.caption {
-  font-style: italic }
-
-p.credits {
-  font-style: italic ;
-  font-size: smaller }
-
-p.label {
-  white-space: nowrap }
-
-p.rubric {
-  font-weight: bold ;
-  font-size: larger ;
-  color: maroon ;
-  text-align: center }
-
-p.sidebar-title {
-  font-family: sans-serif ;
-  font-weight: bold ;
-  font-size: larger }
-
-p.sidebar-subtitle {
-  font-family: sans-serif ;
-  font-weight: bold }
-
-p.topic-title {
-  font-weight: bold }
-
-pre.address {
-  margin-bottom: 0 ;
-  margin-top: 0 ;
-  font: inherit }
-
-pre.literal-block, pre.doctest-block, pre.math {
-  margin-left: 2em ;
-  margin-right: 2em }
-
-span.classifier {
-  font-family: sans-serif ;
-  font-style: oblique }
-
-span.classifier-delimiter {
-  font-family: sans-serif ;
-  font-weight: bold }
-
-span.interpreted {
-  font-family: sans-serif }
-
-span.option {
-  white-space: nowrap }
-
-span.pre {
-  white-space: pre }
-
-span.problematic {
-  color: red }
-
-span.section-subtitle {
-  /* font-size relative to parent (h1..h6 element) */
-  font-size: 80% }
-
-table.citation {
-  border-left: solid 1px gray;
-  margin-left: 1px }
-
-table.docinfo {
-  margin: 2em 4em }
-
-table.docutils {
-  margin-top: 0.5em ;
-  margin-bottom: 0.5em }
-
-table.footnote {
-  border-left: solid 1px black;
-  margin-left: 1px }
-
-table.docutils td, table.docutils th,
-table.docinfo td, table.docinfo th {
-  padding-left: 0.5em ;
-  padding-right: 0.5em ;
-  vertical-align: top }
-
-table.docutils th.field-name, table.docinfo th.docinfo-name {
-  font-weight: bold ;
-  text-align: left ;
-  white-space: nowrap ;
-  padding-left: 0 }
-
-h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
-h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
-  font-size: 100% }
-
-ul.auto-toc {
-  list-style-type: none }
-
-</style>
-</head>
-<body>
-<div class="document">
-
-
-<div class="section" id="description">
-<h1>Description</h1>
-<p>Django file streaming application to provide download links without showing the real path to the served file. The links can be set to expire by date or by clicks. It is also possible to use it for counting clicks on a download link.</p>
-<p><strong>License</strong></p>
-<blockquote>
-New BSD license</blockquote>
-<p><strong>Notes</strong></p>
-<blockquote>
-<ul class="simple">
-<li>Tested with Django 1.2 / 1.3</li>
-<li>Example project is included</li>
-</ul>
-</blockquote>
-</div>
-<div class="section" id="features">
-<h1>Features</h1>
-<blockquote>
-<ul class="simple">
-<li>Link expires by clicks (optional)</li>
-<li>Link expires by time (optional)</li>
-<li>Is usable for counting clicks</li>
-</ul>
-</blockquote>
-</div>
-<div class="section" id="installation">
-<h1>Installation</h1>
-<p><strong>Dependences</strong></p>
-<blockquote>
-<ul class="simple">
-<li>This app</li>
-<li>Django itself, its redirects from django.contrib and its locale middleware.</li>
-</ul>
-</blockquote>
-<p><strong>Installation</strong></p>
-<blockquote>
-<p><em>Installation with pip (recommended)</em></p>
-<blockquote>
-<p><em>Pip will download and install the package and take care of all the dependences.
-If you havn't pip on your system then install setuptools first after that run &quot;easy_install pip&quot;.</em></p>
-<ul>
-<li><p class="first">Use the stable release (recommended). For that type in your terminal:</p>
-<pre class="literal-block">
-pip install django-dynamic-link
-</pre>
-</li>
-<li><p class="first">Or the development version:</p>
-<pre class="literal-block">
-pip install -e hg+https://bitbucket.org/catapela/django-dynamic-link/#egg=django-dynamic-link
-</pre>
-</li>
-<li><p class="first">With pip you can also uninstall:</p>
-<pre class="literal-block">
-pip uninstall django-dynamic-link
-</pre>
-</li>
-</ul>
-</blockquote>
-<p><em>other Installation methodes</em></p>
-<blockquote>
-First of all download the package from pypi (stable) or bitbucket (development). Find a download location <a class="reference external" href="http://pypi.python.org/pypi/django-dynamic-link/#downloads">here</a>.</blockquote>
-</blockquote>
-<blockquote>
-<ul>
-<li><p class="first">Either copy the sources in your project root</p>
-<blockquote>
-<ol class="arabic simple">
-<li>Unpack your package.</li>
-<li>Copy the folder &quot;dynamic_link&quot; in your project root.</li>
-<li>Make sure all package dependences are fulfilled.</li>
-</ol>
-</blockquote>
-</li>
-<li><p class="first">Or use setup.py</p>
-<blockquote>
-<ol class="arabic simple">
-<li>Unpack your package.</li>
-<li>Open a terminal and change to the folder which contains the setup.py and type</li>
-</ol>
-<pre class="literal-block">
-python setup.py install
-</pre>
-</blockquote>
-</li>
-</ul>
-</blockquote>
-<p><strong>test your installation</strong></p>
-<blockquote>
-<p>Go to console and type:</p>
-<pre class="literal-block">
-python
-</pre>
-<p>... then type:</p>
-<pre class="literal-block">
-&gt;&gt;&gt; import dynamicLink
-&gt;&gt;&gt; dynamicLink.VERSION
-&gt;&gt;&gt; help(dynamicLink)
-&gt;&gt;&gt; exit()
-</pre>
-</blockquote>
-</div>
-<div class="section" id="setup">
-<h1>Setup</h1>
-<blockquote>
-<ul>
-<li><p class="first">Add &quot;dynamicLink&quot; to you installed apps in the settings file.</p>
-</li>
-<li><p class="first">Make sure that:</p>
-<blockquote>
-<ol class="arabic simple">
-<li>'django.middleware.locale.LocaleMiddleware', is in your MIDDLEWARE_CLASSES.</li>
-<li>Your Admin is enabled ('django.contrib.admin', is in your INSTALLED_APPS).</li>
-<li>'django.contrib.auth.context_processors.auth', (also for admin) is in TEMPLATE_CONTEXT_PROCESSORS.</li>
-<li>'django.core.context_processors.request', is in TEMPLATE_CONTEXT_PROCESSORS.</li>
-</ol>
-</blockquote>
-</li>
-<li><p class="first">Add the following to your urls.py:</p>
-<blockquote>
-<ol class="arabic simple">
-<li>from dynamicLink import presettings</li>
-<li>(r'^w+/%s/' % presettings.DYNAMIC_LINK_URL_BASE_COMPONENT, include('dynamicLink.urls')),</li>
-</ol>
-</blockquote>
-</li>
-<li><p class="first">Finally run:</p>
-<pre class="literal-block">
-python manage.py syncdb
-python manage.py runserver
-</pre>
-</li>
-</ul>
-</blockquote>
-<p><strong>Make it custom</strong></p>
-<blockquote>
-<p>Use the global settings.py in your projects root to overwrite the applications presettings with the following variables.</p>
-<p>DYNAMIC_LINK_MEDIA</p>
-<blockquote>
-<ul class="simple">
-<li>Default: settings.MEDIA_ROOT</li>
-<li>A path to a directory. From this point you can walk down the subdirectories to choose your files to serve.</li>
-</ul>
-</blockquote>
-<p>DYNAMIC_LINK_URL_BASE_COMPONENT</p>
-<blockquote>
-<ul class="simple">
-<li>Default: 'serve'</li>
-<li>A string that modifies your url serve path.</li>
-<li>Example: www.example.com/DYNAMIC_LINK_URL_BASE_COMPONENT/link/3839hd8HKl3/example.zip.</li>
-</ul>
-</blockquote>
-</blockquote>
-</div>
-<div class="section" id="usage">
-<h1>Usage</h1>
-<p>Open the admin interface and go to &quot;Dynamiclink&quot; section. The rest should be self-explanatory.</p>
-<p><strong>Hints</strong></p>
-<blockquote>
-<ul class="simple">
-<li>Zero value for link age means never expires.</li>
-<li>Zero value for clicks means unlimited clicks.</li>
-<li>If a link never expires you can use it for click counting.</li>
-<li>Trough the action menu you can serve a site with several links.</li>
-<li>The filename from the created links are only for human readability. You can delete or change these filenames in any way you want.</li>
-</ul>
-</blockquote>
-</div>
-<div class="section" id="example-project">
-<h1>Example project</h1>
-<p>djang-dynamic-links ships with an example proect.</p>
-<blockquote>
-<ol class="arabic simple">
-<li>First you need the example project folder which is shipped within the package. See the &quot;other Installation methodes&quot; section above to find out where to download it.</li>
-<li>After you got the desired package install it (see install section).</li>
-<li>Next you have to extract the example folder within the package to any location you want.</li>
-<li>Open a terminal and change directory into the previous extracted example folder</li>
-</ol>
-<pre class="literal-block">
-cd /path/to/example
-</pre>
-<ol class="arabic simple" start="5">
-<li>After that run</li>
-</ol>
-<pre class="literal-block">
-python manage.py syncdb
-python manage.py runserver
-</pre>
-<ol class="arabic simple" start="6">
-<li>Finaly open a Browser and go to: <a class="reference external" href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a></li>
-</ol>
-</blockquote>
-</div>
-</div>
-</body>
-</html>