Commits

Julian Moritz  committed aa61e62

initial commit

  • Participants

Comments (0)

Files changed (21)

+Development setup
+=================
+
+To create a buildout,
+
+  $ python bootstrap.py
+  $ bin/buildout
+
+Release HOWTO
+=============
+
+To make a release, 
+
+  1) Update release date/version in NEWS.txt and setup.py
+  2) Run 'python setup.py sdist'
+  3) Test the generated source distribution in dist/
+  4) Upload to PyPI: 'python setup.py sdist register upload'
+  5) Increase version in setup.py (for next release)
+
+include README.rst
+include NEWS.txt
+.. This is your project NEWS file which will contain the release notes.
+.. Example: http://www.python.org/download/releases/2.6/NEWS.txt
+.. The content of this file, along with README.rst, will appear in your
+.. project's PyPI page.
+
+News
+====
+
+0.2a1
+-----
+
+*Release date: UNRELEASED*
+
+* Example news entry for the in-development version
+
+
+0.1
+---
+
+*Release date: 15-Mar-2010*
+
+* Example news entry for a released version
+
+This file requires editing
+==========================
+
+Note to the author: Please add something informative to this README *before*
+releasing your software, as `a little documentation goes a long way`_.  Both
+README.rst (this file) and NEWS.txt (release notes) will be included in your
+package metadata which gets displayed in the PyPI page for your project.
+
+You can take a look at the README.txt of other projects, such as repoze.bfg
+(http://bfg.repoze.org/trac/browser/trunk/README.txt) for some ideas.
+
+.. _`a little documentation goes a long way`: http://www.martinaspeli.net/articles/a-little-documentation-goes-a-long-way
+
+Credits
+-------
+
+- `Distribute`_
+- `Buildout`_
+- `modern-package-template`_
+
+.. _Buildout: http://www.buildout.org/
+.. _Distribute: http://pypi.python.org/pypi/distribute
+.. _`modern-package-template`: http://pypi.python.org/pypi/modern-package-template

File bootstrap.py

+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+
+$Id: bootstrap.py 102545 2009-08-06 14:49:47Z chrisw $
+"""
+
+import os, shutil, sys, tempfile, urllib2
+from optparse import OptionParser
+
+tmpeggs = tempfile.mkdtemp()
+
+is_jython = sys.platform.startswith('java')
+
+# parsing arguments
+parser = OptionParser()
+parser.add_option("-v", "--version", dest="version",
+                          help="use a specific zc.buildout version")
+parser.add_option("-d", "--distribute",
+                   action="store_true", dest="distribute", default=True,
+                   help="Use Disribute rather than Setuptools.")
+
+options, args = parser.parse_args()
+
+if options.version is not None:
+    VERSION = '==%s' % options.version
+else:
+    VERSION = ''
+
+USE_DISTRIBUTE = options.distribute
+args = args + ['bootstrap']
+
+to_reload = False
+try:
+    import pkg_resources
+    if not hasattr(pkg_resources, '_distribute'):
+        to_reload = True
+        raise ImportError
+except ImportError:
+    ez = {}
+    if USE_DISTRIBUTE:
+        exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py'
+                         ).read() in ez
+        ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True)
+    else:
+        exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+                             ).read() in ez
+        ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+
+    if to_reload:
+        reload(pkg_resources)
+    else:
+        import pkg_resources
+
+if sys.platform == 'win32':
+    def quote(c):
+        if ' ' in c:
+            return '"%s"' % c # work around spawn lamosity on windows
+        else:
+            return c
+else:
+    def quote (c):
+        return c
+
+cmd = 'from setuptools.command.easy_install import main; main()'
+ws  = pkg_resources.working_set
+
+if USE_DISTRIBUTE:
+    requirement = 'distribute'
+else:
+    requirement = 'setuptools'
+
+if is_jython:
+    import subprocess
+
+    assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd',
+           quote(tmpeggs), 'zc.buildout' + VERSION],
+           env=dict(os.environ,
+               PYTHONPATH=
+               ws.find(pkg_resources.Requirement.parse(requirement)).location
+               ),
+           ).wait() == 0
+
+else:
+    assert os.spawnle(
+        os.P_WAIT, sys.executable, quote (sys.executable),
+        '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION,
+        dict(os.environ,
+            PYTHONPATH=
+            ws.find(pkg_resources.Requirement.parse(requirement)).location
+            ),
+        ) == 0
+
+ws.add_entry(tmpeggs)
+ws.require('zc.buildout' + VERSION)
+import zc.buildout.buildout
+zc.buildout.buildout.main(args)
+shutil.rmtree(tmpeggs)

File buildout.cfg

+[buildout]
+parts = python scripts
+develop = .
+eggs = django-photos
+
+[python]
+recipe = zc.recipe.egg
+interpreter = python
+eggs = ${buildout:eggs}
+
+[scripts]
+recipe = zc.recipe.egg:scripts
+eggs = ${buildout:eggs}
+#!/usr/bin/env python
+
+import sys
+from os.path import dirname, abspath
+from django.conf import settings
+
+if not settings.configured:
+    settings.configure(
+        DATABASE_ENGINE='sqlite3',
+        INSTALLED_APPS=[
+            'django.contrib.contenttypes',
+            'django.contrib.auth',
+            'taggit',
+            'photos'            
+        ],
+        ROOT_URLCONF='photos.urls'
+    )
+
+from django.test.simple import run_tests
+
+def runtests(*test_args):
+    if not test_args:
+        test_args = ['photos']
+    parent = dirname(abspath(__file__))
+    sys.path.insert(0, parent)
+    failures = run_tests(test_args, verbosity=1, interactive=True)
+    sys.exit(failures)
+
+if __name__ == '__main__':
+    runtests(*sys.argv[1:])
+from setuptools import setup, find_packages
+import sys, os
+
+here = os.path.abspath(os.path.dirname(__file__))
+README = open(os.path.join(here, 'README.rst')).read()
+NEWS = open(os.path.join(here, 'NEWS.txt')).read()
+
+
+version = '0.1'
+
+install_requires = [
+    'Django',
+    'django-taggit'
+]
+
+
+setup(name='django-photos',
+    version=version,
+    description="This is a simple photo-app for django.",
+    long_description=README + '\n\n' + NEWS,
+    classifiers=[
+      # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
+    ],
+    keywords='django photo gallery',
+    author='Julian Moritz',
+    author_email='jumo@gmx.de',
+    url='http://www.bitbucket.org/feuervogel/django-photos',
+    license='BSD',
+    packages=find_packages('src'),
+    package_dir = {'': 'src'},include_package_data=True,
+    zip_safe=False,
+    install_requires=install_requires,
+    entry_points={
+        'console_scripts':
+            ['django-photos=djangophotos:main']
+    },
+    test_suite='runtests.runtests'
+)

File src/photos/__init__.py

Empty file added.

File src/photos/admin.py

+from django.contrib import admin
+from photos.models import Photo, Gallery
+
+class BaseAdmin():
+    def queryset(self, request):
+        qs = super(PhotoAdmin, self).queryset(request)
+        if request.user.is_superuser:
+            return qs
+        return qs.filter(user=request.user)
+
+    def save_model(self, request, obj, form, change):
+        obj.user = request.user
+        obj.save()
+
+class PhotoAdmin(admin.ModelAdmin, BaseAdmin):
+    list_filter = ('display', 'timestamp_created', )
+    date_hierarchy = 'timestamp_created'
+    description = _('Manage your uploaded photos.')
+
+admin.site.register(PhotoAdmin, Photo)
+
+class GalleryAdmin(admin.ModelAdmin, BaseAdmin):
+    list_filter = ('display', 'timestamp_created', )
+    date_hierarchy = 'timestamp_created'
+    description = _('Manage your galleries.')
+    
+admin.site.register(GalleryAdmin, Gallery)

File src/photos/models.py

+from django.db import models
+from taggit.managers import TaggableManager
+from django.utils.translation import ugettext as _
+from django.contrib.auth.models import User
+
+class Photo(models.Model):
+    photo = models.ImageField(_('Photo'), upload_to='%y/%j/%H/%M/%S', height_field='height', width_field='width')
+    height = models.PositiveIntegerField(_('Height'))
+    width = models.PositiveIntegerField(_('Width'))
+    display = models.BooleanField(_('Display'), default=True, help_text=_('Disable to not display this photo.'))
+    description = models.CharField(_('Description'), max_length=500)
+    user = models.ForeignKey(User, editable=False, blank=True, null=True)
+    tags = TaggableManager()
+    timestamp_created = models.DateTimeField(_('Timestamp created'), auto_now_add=True, editable=False)
+    timestamp_edited = models.DateTimeField(_('Timestamp edited'), auto_now=True, editable=False)
+    
+    class Meta(object):
+        verbose_name = _('Photo')
+        verbose_name_plural = _('Photos')
+        ordering = ('-timestamp_created', )
+        
+class Album(models.Model):
+    display = models.BooleanField(_('Display'), default=True, help_text=_('Disable to not display this gallery.'))
+    description = models.TextField(_('Description'), blank=True, null=True)
+    photos = models.ManyToManyField(Photo, verbose_name=_('Photos'))
+    tags = TaggableManager()
+    user = models.ForeignKey(User, editable=False, blank=True, null=True)
+    timestamp_created = models.DateTimeField(_('Timestamp created'), auto_now_add=True, editable=False)
+    timestamp_edited = models.DateTimeField(_('Timestamp edited'), auto_now=True, editable=False)
+    
+    class Meta(object):
+        verbose_name = _('Album')
+        verbose_name_plural = _('Albums')
+        ordering = ('-timestamp_created', )

File src/photos/templates/404.html

+<h1>404</h1>

File src/photos/templates/500.html

+<h1>500</h1>

File src/photos/templates/photos/album_detail.html

+{% extends "photos/base.html" %}
+{% block title %}{{block.super}} - Album{% endblock %}

File src/photos/templates/photos/album_list.html

+{% extends "photos/base.html" %}
+{% block title %}{{block.super}} - List of Albums{% endblock %}

File src/photos/templates/photos/base.html

+<html>
+<header>
+<title>{% block title %}Django Photos{% endblock %}
+</header>
+<body>
+<ul>
+<li><a href="{% url albums %}">List of Albums</a></li>
+<li><a href="{% url photos %}">List of Photos</a></li>
+</ul>
+{% block content %}
+<h1>Nothing to see!</h1>
+{% endblock %}
+</body>
+</html>

File src/photos/templates/photos/photo_detail.html

+{% extends "photos/base.html" %}
+{% block title %}{{block.super}} - Photo{% endblock %}

File src/photos/templates/photos/photo_list.html

+{% extends "photos/base.html" %}
+{% block title %}{{block.super}} - List of Photos{% endblock %}

File src/photos/tests.py

+from django.test import TestCase
+from django.test.client import Client
+from django.core.urlresolvers import reverse
+from django.contrib.auth.models import User
+from photos.models import Photo, Album
+
+class ViewTest(TestCase):
+    def setUp(self):
+        user, created = User.objects.get_or_create(username='anna')
+        album = Album()
+        album.save()
+        # todo further creating, moving everything to /tests
+    
+    def check_url(self, url, status_code=200):
+        client = Client()
+        response = client.get(url)
+        self.assertEqual(response.status_code, status_code)
+    
+    def test_album_list(self):
+        url = reverse('albums')
+        self.check_url(url, 200)
+        
+    def test_album_list_userid(self):
+        user = User.objects.get(username='anna')
+        url = reverse('albums-userid', user.id)
+        self.check_url(url, 200)
+        
+    def test_album_list_username(self):
+        url = reverse('albums-username', args=(), kwargs={'username': 'anna'} )
+        self.check_url(url, 200)
+        
+    def test_album_detail(self):
+        album = Album.objects.latest('id')
+        url = reverse('album-detail', album.id)
+        self.check_url(url, 200)
+        
+    def test_photo_list(self):
+        url = reverse('photos')
+        self.check_url(url, 200)
+        
+    def test_photo_list_userid(self):
+        user = User.objects.get(username='anna')
+        url = reverse('photos-userid', user.id)
+        self.check_url(url, 200)
+        
+    def test_photo_list_username(self):
+        url = reverse('photos-username', args=(), kwargs={'username': 'anna'})
+        self.check_url(url, 200)
+        
+    def test_photo_detail(self):
+        album = Photo.objects.latest('id')
+        url = reverse('photo-detail', (album.id, ))
+        self.check_url(url, 200)
+    

File src/photos/urls.py

+from photos.views import album_list, photo_list, photo_detail
+from django.views.generic.list_detail import object_detail
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+    url(r'^albums/$', album_list, name='albums'),
+    url(r'^albums/(?P<userid>\d+)/$', album_list, name='albums-userid'),
+    url(r'^albums/(?P<username>.+/)$', album_list, name='albums-username'),
+    url(r'^album/(?P<object_id>\d+)/$', object_detail, name='album-detail'),
+    url(r'^photos/$', photo_list, name='photos'),
+    url(r'^photos/(?P<user_id>\d+)/$', photo_list, name='photos-userid'),
+    url(r'^photos/(?P<username>.+)/', photo_list, name='photos-username'),
+    url(r'^photos/(?P<album_id>\d+)/(?P<object_id>\d+)/$', photo_detail, name='photo-album-detail'),
+    url(r'^photos/(?P<object_id>\d)/$', object_detail, name='photo-detail')
+)

File src/photos/views.py

+from photos.models import Photo, Album
+from django.contrib.auth.models import User
+from django.views.generic.list_detail import object_list, object_detail
+from django.shortcuts import get_object_or_404
+
+def album_list(request, user_id=0, username=None, *args, **kwargs):
+    qs = Album.objects.filter(display=True)
+    
+    if user_id > 0:
+        user = get_object_or_404(User, id=user_id)
+        qs = qs.filter(user=user)
+    elif username is not None:
+        user = get_object_or_404(User, username=username)
+        qs = qs.filter(user=user)
+        
+    return object_list(request, queryset=qs, *args, **kwargs)
+
+def photo_list(request, user_id=0, username=None, *args, **kwargs):
+    qs = Photo.objects.filter(display=True)
+    
+    if user_id > 0:
+        user = get_object_or_404(User, id=user_id)
+        qs = qs.filter(user=user)
+    elif username is not None:
+        user = get_object_or_404(User, username=username)
+        qs = qs.filter(user=user)
+    
+    return object_list(request, queryset=qs, *args, **kwargs)
+
+def photo_detail(request, object_id, album_id, *args, **kwargs):
+    album = get_object_or_404(Album, id=album_id)
+    
+    extra_context = kwargs.get('extra_context', {})
+    extra_context.update({'album': album})
+    kwargs.update({'extra_context': extra_context})
+    
+    return object_detail(request, object_id=object_id, *args, **kwargs)