generated from thclark/django-rabid-armadillo
/
models.py
193 lines (148 loc) · 7.53 KB
/
models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
from uuid import uuid4
from django.db.models import CharField, FileField, Model
from django_gcp.storage.fields import BlobField
# Disabled to show the argument range in the example:
# pylint: disable-next=unused-argument
def get_destination_path(
instance,
original_name,
attributes,
existing_path,
temporary_path,
allow_overwrite,
bucket,
):
"""An example callback, which is provided to BlobField in order that
you can set the eventual pathname of the uploaded blob, using other model fields
if you wish.
You can also alter the allow_overwrite parameter on a per-instance basis. For example,
overwrite might universally disabled at the field level, but you could choose to
allow overwrite where the path you determine is equal to the existing instance path.
In most cases you'll want to pass-through the value.
By the time this callback is executed, the file should have been successfully uploaded
to a temporary location in GCP. This method is called in the pre_save stage,
so if your model uses an autoincremented id that value may not be accessible - if you need
to set filenames based on the ID then use something like a UUID as the id field for the model,
whose value can be set a-priori to the save process.
In this example we prefix the end path with the category and a time but really you
can do whatever.
Doing instance.<your-field> gives the JSON value that was submitted with the form
so any client-side data you need for naming can be supplied via that entry (extra fields will
be removed and not persisted to the database).
You can even access the blob itself and read its contents if you want to,
but beware that this is called within the request loop on the main server so we suggest
against reading large files or processing a lot of data from them.
Depending on the value of `overwrite_mode` set in the field definition, use of an
existing filename may result in an error so this function should return a unique name.
:param django.db.models.Model instance: A model instance in the pre-save state
:param str original_name: The original name of the uploaded file
:param Union[dict, None] attributes: The attributes, if any, that will be applied to the destination blob
:param Union[str, None] existing_path: If saving a model instance and overwriting an existing blob, this is its path
:param str temporary_path: The path of the temporary upload which you can use to create a
google.cloud.storage.Blob and access its contents, in order to determine the path (not advised
as this will likely be slow).
:param bool overwrite_allowed: If true, then overwriting is allowed for the present operation (determined from the overwrite mode)
:param google.cloud.storage.Bucket bucket: The google cloud bucket object which you can use to determine whether an
object exists at the path
"""
# Demonstrate using another field to name the object
category = f"{instance.category}/" if instance.category is not None else ""
# You may wish to add a timestamp, or random string to prevent collisions
# In this case we do the very simple thing of using the original name with random prefix
random_prefix = str(uuid4())[0:8]
# If you attempt to overwrite while allow_overwrite is false, a server error will raise.
# Only set allow_overwrite = True if you really, REALLY, know what you're doing!
return f"{category}{random_prefix}-{original_name}", allow_overwrite
class ExampleStorageModel(Model):
"""
An example model containing a FileField with django's normal indirect
uploads (ie files come via the server)
"""
normal = FileField(upload_to="normal_uploads", blank=True, null=True)
class Meta:
"""Metaclass defining this model to reside in the example app"""
app_label = "example"
class ExampleBlobFieldModel(Model):
"""
An example model containing a BlobField which uploads files via a temporary
ingress.
This model was started as a FileField model then migrated, so check the migrations
to see how that worked.
"""
# This charfield is added to demonstrate that other fields of the model can be used
# in order to set the blob name
category = CharField(max_length=20, blank=True, null=True)
# Note on how to migrate from FileField (see test_storage_field_migrations.py)
# This was initially created with:
# blob = FileField(upload_to="blob_uploads", blank=True, null=True)
# Then the migration uses a temporary field:
# blob = FileField(upload_to="blob_uploads", blank=True, null=True)
# blob_temp = BlobField(store_key="media")
# Then a custom migration is used to duplicate the data (see migration 0002).
# Then the old field is removed (see migration 0003).
blob = BlobField(get_destination_path=get_destination_path, store_key="media")
class Meta:
"""Metaclass defining this model to reside in the example app"""
app_label = "example"
class ExampleBlankBlobFieldModel(Model):
"""
As ExampleBlobFieldModel but showing blankable behaviour
(This is mostly used for widget development and testing)
"""
category = CharField(max_length=20, blank=True, null=True)
blob = BlobField(
get_destination_path=get_destination_path,
store_key="media",
blank=True,
null=True,
)
class Meta:
"""Metaclass defining this model to reside in the example app"""
app_label = "example"
class ExampleUneditableBlobFieldModel(Model):
"""
As ExampleBlobFieldModel but showing behaviour when editable=False
(This is mostly used for widget development and testing)
"""
category = CharField(max_length=20, blank=True, null=True)
blob = BlobField(get_destination_path=get_destination_path, store_key="media", editable=False)
class Meta:
"""Metaclass defining this model to reside in the example app"""
app_label = "example"
class ExampleMultipleBlobFieldModel(Model):
"""
An example model containing multiple BlobFields
This model was started as a FileField model then migrated, so check the migrations
to see how that worked.
"""
# This charfield is added to demonstrate that other fields of the model can be used
# in order to set the blob name
category = CharField(max_length=20, blank=True, null=True)
blob1 = BlobField(get_destination_path=get_destination_path, store_key="media")
blob2 = BlobField(get_destination_path=get_destination_path, store_key="media")
blob3 = BlobField(get_destination_path=get_destination_path, store_key="media")
class Meta:
"""Metaclass defining this model to reside in the example app"""
app_label = "example"
def my_on_change_callback(value, instance):
"""Demonstrate the on_change callback functionality"""
if value is None:
print("You removed the blob")
else:
print(f"Do something with the value {value} and instance {instance}")
class ExampleCallbackBlobFieldModel(Model):
"""
As ExampleBlobFieldModel but showing use of the on_change callback
(This is mostly used for widget development and testing)
"""
category = CharField(max_length=20, blank=True, null=True)
blob = BlobField(
get_destination_path=get_destination_path,
store_key="media",
blank=True,
null=True,
on_change=my_on_change_callback,
)
class Meta:
"""Metaclass defining this model to reside in the example app"""
app_label = "example"