| 1 | |
| 2 | Here's a class I created to integrate my site with amazon's storage service. |
| 3 | |
| 4 | I like this because in the admin interface it looks pretty much like a file field and you can just click to upload. In theory you could integrate it into any oldforms system. Note that you have to set the amazon key, bucket, etc. manually, and that you have to put your amazon access keys into your site's settings module. |
| 5 | |
| 6 | This hasn't been tested extensively, so I invite you to contribute any fixes right here on the page. Maybe the django gods will see fit to include this right into django. |
| 7 | |
| 8 | {{{ |
| 9 | from amazon import S3 |
| 10 | from django.conf import settings |
| 11 | from django.db import models |
| 12 | from mimetypes import guess_type |
| 13 | from mx.DateTime import now |
| 14 | from django.core import validators |
| 15 | from django import oldforms |
| 16 | from django.dispatch import dispatcher |
| 17 | from django.utils.functional import curry |
| 18 | from django.utils.translation import gettext_lazy |
| 19 | from django.db.models import signals |
| 20 | import os |
| 21 | |
| 22 | conn = S3.AWSAuthConnection(settings.AWS_ACCESS_KEY_ID, |
| 23 | settings.AWS_SECRET_ACCESS_KEY) |
| 24 | generator = S3.QueryStringAuthGenerator(settings.AWS_ACCESS_KEY_ID, |
| 25 | settings.AWS_SECRET_ACCESS_KEY) |
| 26 | |
| 27 | class S3FileField(models.FileField): |
| 28 | def __init__(self, verbose_name=None, name=None, bucket='', is_image=False, **kwargs): |
| 29 | models.FileField.__init__(self, verbose_name, name, upload_to="s3", **kwargs) |
| 30 | self.bucket = bucket |
| 31 | self.is_image = is_image |
| 32 | |
| 33 | def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True): |
| 34 | field_list = models.Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel, follow) |
| 35 | if not self.blank: |
| 36 | if rel: |
| 37 | # This validator makes sure FileFields work in a related context. |
| 38 | class RequiredFileField(object): |
| 39 | def __init__(self, other_field_names, other_file_field_name): |
| 40 | self.other_field_names = other_field_names |
| 41 | self.other_file_field_name = other_file_field_name |
| 42 | self.always_test = True |
| 43 | def __call__(self, field_data, all_data): |
| 44 | if not all_data.get(self.other_file_field_name, False): |
| 45 | c = validators.RequiredIfOtherFieldsGiven(self.other_field_names, gettext_lazy("This field is required.")) |
| 46 | c(field_data, all_data) |
| 47 | # First, get the core fields, if any. |
| 48 | core_field_names = [] |
| 49 | for f in opts.fields: |
| 50 | if f.core and f != self: |
| 51 | core_field_names.extend(f.get_manipulator_field_names(name_prefix)) |
| 52 | # Now, if there are any, add the validator to this FormField. |
| 53 | if core_field_names: |
| 54 | field_list[0].validator_list.append(RequiredFileField(core_field_names, field_list[1].field_name)) |
| 55 | else: |
| 56 | v = validators.RequiredIfOtherFieldNotGiven(field_list[1].field_name, gettext_lazy("This field is required.")) |
| 57 | v.always_test = True |
| 58 | field_list[0].validator_list.append(v) |
| 59 | field_list[0].is_required = field_list[1].is_required = False |
| 60 | |
| 61 | return field_list |
| 62 | |
| 63 | def get_internal_type(self): |
| 64 | return "FileField" |
| 65 | |
| 66 | def contribute_to_class(self, cls, name): |
| 67 | super(S3FileField, self).contribute_to_class(cls, name) |
| 68 | #models.CharField(maxlength=200, blank=self.blank, null=self.null).contribute_to_class(cls, "%s_filename"%(self.name)) |
| 69 | models.CharField(maxlength=200, blank=self.blank, null=self.null).contribute_to_class(cls, "%s_key"%(self.name)) |
| 70 | models.CharField(maxlength=200, blank=self.blank, null=self.null, default=settings.DEFAULT_BUCKET).contribute_to_class(cls, "%s_bucket"%(self.name)) |
| 71 | models.CharField(maxlength=200, blank=True, null=True, default="").contribute_to_class(cls, "%s_content_type"%(self.name)) |
| 72 | models.IntegerField(blank=True, null=True).contribute_to_class(cls, "%s_size"%(self.name)) |
| 73 | if self.is_image: |
| 74 | models.IntegerField(blank=True, null=True).contribute_to_class(cls, "%s_width"%(self.name)) |
| 75 | models.IntegerField(blank=True, null=True).contribute_to_class(cls, "%s_height"%(self.name)) |
| 76 | |
| 77 | # Getter for the file url |
| 78 | def get_url(instance, field): |
| 79 | return field.get_url(instance) |
| 80 | setattr(cls, 'get_%s_url' % self.name, curry(get_url, field=self)) |
| 81 | |
| 82 | dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls) |
| 83 | |
| 84 | def delete_file(self, instance): |
| 85 | if getattr(instance, self.attname): |
| 86 | bucket = self.get_bucket(instance) |
| 87 | key = self.get_key(instance) |
| 88 | conn.delete(bucket, key) |
| 89 | |
| 90 | def get_url(self, instance): |
| 91 | print 'get_url called with instance %s'%instance |
| 92 | bucket = self.get_bucket(instance) |
| 93 | key = self.get_key(instance) |
| 94 | print 'key %s bucket %s'%(key, bucket) |
| 95 | import sys |
| 96 | sys.stdout.flush() |
| 97 | if bucket and key: |
| 98 | url = generator.make_bare_url(bucket, key) |
| 99 | print 'url', url |
| 100 | sys.stdout.flush() |
| 101 | return url |
| 102 | return None |
| 103 | |
| 104 | def get_bucket(self, instance): |
| 105 | return getattr(instance, "%s_bucket"%self.name) |
| 106 | |
| 107 | def set_bucket(self, instance, bucket): |
| 108 | setattr(instance, "%s_bucket"%self.name, bucket) |
| 109 | |
| 110 | def get_key(self, instance): |
| 111 | return getattr(instance, "%s_key"%self.name) |
| 112 | |
| 113 | def set_key(self, instance, key): |
| 114 | setattr(instance, "%s_key"%self.name, key) |
| 115 | |
| 116 | def get_content_type(self, instance): |
| 117 | return getattr(instance, "%s_content_type"%self.name) |
| 118 | |
| 119 | def set_content_type(self, instance, content_type): |
| 120 | setattr(instance, "%s_content_type"%self.name, content_type) |
| 121 | |
| 122 | def get_size(self, instance): |
| 123 | return getattr(instance, "%s_size"%self.name) |
| 124 | |
| 125 | def set_size(self, instance, size): |
| 126 | setattr(instance, "%s_size"%self.name, size) |
| 127 | |
| 128 | def get_filename(self, instance): |
| 129 | return getattr(instance, self.name) |
| 130 | |
| 131 | def set_filename(self, instance, filename): |
| 132 | setattr(instance, self.name, filename) |
| 133 | |
| 134 | def get_width(self, instance): |
| 135 | return getattr(instance, "%s_width"%self.name) |
| 136 | |
| 137 | def set_width(self, instance, width): |
| 138 | setattr(instance, "%s_width"%self.name, width) |
| 139 | |
| 140 | def get_height(self, instance): |
| 141 | return getattr(instance, "%s_height"%self.name) |
| 142 | |
| 143 | def set_height(self, instance, height): |
| 144 | setattr(instance, "%s_height"%self.name, height) |
| 145 | |
| 146 | def get_manipulator_field_objs(self): |
| 147 | if self.is_image: uploadType = oldforms.ImageUploadField |
| 148 | else: uploadType = oldforms.FileUploadField |
| 149 | return [uploadType, oldforms.HiddenField] |
| 150 | |
| 151 | def get_manipulator_field_names(self, name_prefix): |
| 152 | return [name_prefix + self.name + '_file', name_prefix + self.name] |
| 153 | |
| 154 | def save_file(self, new_data, new_object, original_object, change, rel, save=True): |
| 155 | upload_field_name = self.get_manipulator_field_names('')[0] |
| 156 | if new_data.get(upload_field_name, False): |
| 157 | if rel: |
| 158 | new_content = new_data[upload_field_name][0]["content"] |
| 159 | new_filename = new_data[upload_field_name][0]["filename"] |
| 160 | else: |
| 161 | new_content = new_data[upload_field_name]["content"] |
| 162 | new_filename = new_data[upload_field_name]["filename"] |
| 163 | |
| 164 | self.set_filename(new_object, new_filename) |
| 165 | key = new_data["%s_key"%self.name] |
| 166 | bucket = new_data["%s_bucket"%self.name] |
| 167 | if not bucket: |
| 168 | bucket = self.bucket |
| 169 | content_type = new_data["%s_content_type"%self.name] |
| 170 | if new_filename and not content_type: |
| 171 | content_type = guess_type(new_filename)[0] |
| 172 | if not content_type: |
| 173 | content_type = "application/x-octet-stream" |
| 174 | print 'Uploading %d bytes of data to bucket %s key %s filename %s'%(len(new_content), bucket, key, new_filename) |
| 175 | conn.put(bucket, key, S3.S3Object(new_content), |
| 176 | { 'x-amz-acl': 'public-read' , 'Content-Type': content_type}) |
| 177 | # TODO Calculate image width/height |
| 178 | |
| 179 | |
| 180 | }}} |