├── muphan ├── __init__.py ├── 2011-05-02-110615.jpeg ├── models.py ├── views.py └── tests.py ├── .gitignore ├── AUTHORS ├── MANIFEST.in ├── README.creole ├── LICENSE └── setup.py /muphan/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *~ 3 | 4 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | nferrier@ferrier.me.uk 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS 2 | include LICENSE 3 | include README.creole 4 | -------------------------------------------------------------------------------- /muphan/2011-05-02-110615.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicferrier/django-muphan/master/muphan/2011-05-02-110615.jpeg -------------------------------------------------------------------------------- /README.creole: -------------------------------------------------------------------------------- 1 | a media hub app. 2 | 3 | This is to handle a lot of the boring drudge of doing media uploading 4 | (ie: photos) to your app. 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) WooMedia Inc. 2 | All rights reserved. 3 | 4 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 5 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 6 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 7 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 8 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 9 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 10 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 11 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 12 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 13 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | classifiers = [ 4 | 'Development Status :: 3 - Alpha', 5 | 'Intended Audience :: Developers', 6 | 'License :: OSI Approved :: BSD License', 7 | 'Operating System :: OS Independent', 8 | 'Programming Language :: Python', 9 | 'Topic :: Utilities', 10 | ] 11 | 12 | setup( 13 | name = "django-muphan", 14 | version = "0.1", 15 | author = "Nic Ferrier", 16 | author_email = "nferrier@ferrier.me.uk", 17 | description = "A mediahub app to take away drudgery of doing file upload.", 18 | license = "GPLv2", 19 | keywords = "django web file upload photo", 20 | url = "http://github.com/nicferrier/django-muphan", 21 | packages=['muphan'], 22 | requires=['django'], 23 | long_description="""An app that you can use to upload and serve 24 | media files such as photos.""", 25 | classifiers=classifiers, 26 | ) 27 | -------------------------------------------------------------------------------- /muphan/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | 4 | from datetime import datetime 5 | from django.conf import settings 6 | 7 | MIME_MAPPINGS = { 8 | "image/jpeg": "jpg", 9 | "image/png": "png", 10 | "image/gif": "gif", 11 | } 12 | 13 | class PhotoManager(models.Manager): 14 | def make_photo(self, user, description, media_type): 15 | """Use this as a convieniance method to create a record of a photo""" 16 | photo = self.create(of=user, description=description) 17 | unique = "1.2" 18 | timestr = datetime.utcnow().strftime("%Y%m%d%H%M%S") 19 | url = "http://%(host)s/%(unique)s/%(user)s/%(timestr)s.%(type)s" % { 20 | "host": "%s.%s" % (unique, settings.MEDIA_DOMAIN), 21 | "unique": unique, 22 | "user": user.username, 23 | "timestr": timestr, 24 | "type": MIME_MAPPINGS[media_type.lower()] 25 | } 26 | photo.url = url 27 | photo.save() 28 | return photo 29 | 30 | class Photo(models.Model): 31 | """A photo of a user captured with the camera""" 32 | of = models.ForeignKey(User) 33 | url = models.URLField() 34 | created = models.DateTimeField(default=datetime.utcnow, db_index=True) 35 | description = models.TextField(blank=True) 36 | objects = PhotoManager() 37 | 38 | # End 39 | -------------------------------------------------------------------------------- /muphan/views.py: -------------------------------------------------------------------------------- 1 | 2 | from os import makedirs 3 | from os.path import join 4 | from os.path import dirname 5 | from urlparse import urlparse 6 | 7 | from django import forms 8 | from django.http import HttpResponse 9 | from django.http import Http404 10 | from django.conf import settings 11 | from django.contrib.auth.decorators import login_required 12 | from django.template import RequestContext 13 | 14 | from djangoxslt.xslt import render_to_response 15 | from muphan.models import Photo 16 | 17 | 18 | HTTP_CREATED = 201 19 | 20 | def handle(photo, content_type, uploaded_file): 21 | """This sends data in uploaded_file to the the specified photo url. 22 | 23 | FIXME Right now this is just file storage. But it will have to use 24 | HTTP to send the file to whatever server maps to the generated 25 | shard. 26 | """ 27 | url = urlparse(photo.url) 28 | path = url.path 29 | filename = join(settings.MEDIA_ROOT, path[1:] if path[0] == "/" else path) 30 | try: 31 | makedirs(dirname(filename)) 32 | except OSError: 33 | # We should check this is error 17 34 | pass 35 | 36 | with open(filename, 'wb+') as destination: 37 | for chunk in uploaded_file.chunks(): 38 | destination.write(chunk) 39 | 40 | class Upload(forms.Form): 41 | title = forms.CharField(max_length=150) 42 | photo = forms.FileField() 43 | 44 | @login_required 45 | def upload_photo(request): 46 | if request.method == "POST": 47 | form = Upload(request.POST, request.FILES) 48 | if form.is_valid(): 49 | content_type = form.files["photo"].content_type 50 | photo = Photo.objects.make_photo( 51 | request.user, 52 | media_type = content_type, 53 | description = form["title"] 54 | ) 55 | file_data = request.FILES["photo"] 56 | handle(photo, content_type, file_data) 57 | response = HttpResponse(status=HTTP_CREATED) 58 | response["Location"] = photo.url 59 | return response 60 | 61 | form = Upload() 62 | return HttpResponse() 63 | 64 | def photo_list(request, username): 65 | pass 66 | 67 | def photo(request, username, dbid): 68 | try: 69 | photo = Photo.objects.get(id=dbid) 70 | except Photo.DoesNotExist: 71 | raise Http404 72 | else: 73 | return render_to_response("photo.xslt", RequestContext(request, { 74 | "photo": photo, 75 | })) 76 | 77 | # End 78 | -------------------------------------------------------------------------------- /muphan/tests.py: -------------------------------------------------------------------------------- 1 | import urlparse 2 | import os.path 3 | 4 | from datetime import datetime 5 | 6 | from django.test import TestCase 7 | from django.test import Client 8 | from django.conf import settings 9 | 10 | from django.contrib.auth.models import User 11 | from muphan.models import Photo 12 | 13 | def _make_username(): 14 | return "user%s" % datetime.utcnow().strftime("%Y%j%H%M%S%f") 15 | 16 | def _make_user(): 17 | username = _make_username() 18 | user = User.objects.create( 19 | username = username, 20 | ) 21 | user.set_password("secret") 22 | user.save() 23 | return username, user 24 | 25 | # Just a test file 26 | UPLOAD_FILE=os.path.join( 27 | os.path.dirname(__file__), 28 | "2011-05-02-110615.jpeg" 29 | ) 30 | 31 | class UserMediaTest(TestCase): 32 | def test_make_photo(self): 33 | """Can we make a photo url""" 34 | username, user = _make_user() 35 | photo = Photo.objects.make_photo(user, description="picture of me", media_type="image/jpeg") 36 | urlparse.urlparse(photo.url) 37 | 38 | def test_upload(self): 39 | """Can we create an image on the filestore?""" 40 | username, user = _make_user() 41 | c = Client() 42 | 43 | with open(UPLOAD_FILE) as photo_fd: 44 | response = c.post("/umedia/", { 45 | "title": "a picture of me I just took", 46 | "photo": photo_fd 47 | }) 48 | self.assertEquals(302, response.status_code) 49 | parsed = urlparse.urlparse(response["Location"]) 50 | self.assertEquals(parsed.path, settings.LOGIN_URL) 51 | 52 | # Now login 53 | loggedin = c.login(username=username, password="secret") 54 | self.assertTrue(loggedin) 55 | with open(UPLOAD_FILE) as photo_fd: 56 | response = c.post("/umedia/", { 57 | "title": "a picture of me I just took", 58 | "photo": photo_fd 59 | }) 60 | self.assertEquals(201, response.status_code) 61 | 62 | # What's the url 63 | parsed = urlparse.urlparse(response["Location"]) 64 | 65 | # Check we've got the correct file type 66 | self.assertEquals(os.path.splitext(parsed.path)[1][1:], "jpg") 67 | 68 | # Check that is starts with something under the MEDIA_DOMAIN 69 | self.assertEquals( 70 | settings.MEDIA_DOMAIN, 71 | ".".join(parsed.netloc.split(".")[2:]) 72 | ) 73 | 74 | # End 75 | --------------------------------------------------------------------------------