Skip to content

Commit 3f81a7a

Browse files
committed
baseapp-files: initial package
1 parent ab76561 commit 3f81a7a

15 files changed

+528
-0
lines changed

baseapp-core/baseapp_core/graphql/views.py

+20
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import json, logging
2+
13
from django.http.response import HttpResponseBadRequest
24
from graphene_django.views import GraphQLView as GrapheneGraphQLView
35
from graphene_django.views import HttpError
46
from graphql import get_operation_ast, parse
57
from graphql.execution import ExecutionResult
8+
from graphene_file_upload.utils import place_files_in_operations
69

710
try:
811
import sentry_sdk
@@ -37,3 +40,20 @@ def execute_graphql_request(
3740
return super().execute_graphql_request(
3841
request, data, query, variables, operation_name, show_graphiql
3942
)
43+
44+
45+
def parse_body(self, request):
46+
"""Handle multipart request spec for multipart/form-data"""
47+
content_type = self.get_content_type(request)
48+
# logging.info('content_type: %s' % content_type)
49+
# import pdb; pdb.set_trace()
50+
if content_type == 'multipart/form-data' and 'operations' in request.POST:
51+
operations = json.loads(request.POST.get('operations', '{}'))
52+
# import pdb; pdb.set_trace()
53+
files_map = json.loads(request.POST.get('map', '{}'))
54+
return place_files_in_operations(
55+
operations,
56+
files_map,
57+
request.FILES
58+
)
59+
return super(GraphQLView, self).parse_body(request)

baseapp-files/baseapp_files/__init__.py

Whitespace-only changes.

baseapp-files/baseapp_files/admin.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from django.contrib import admin
2+
from django.contrib.contenttypes.admin import GenericStackedInline
3+
4+
from .models import File
5+
6+
7+
class FileInlineAdmin(GenericStackedInline):
8+
model = File
9+
ct_field = "parent_content_type"
10+
ct_fk_field = "parent_object_id"
11+
raw_id_fields = ("created_by",)
12+
13+
14+
@admin.register(File)
15+
class FileAdmin(admin.ModelAdmin):
16+
list_display = ("pk", "name", "content_type", "parent", "created_by", "created")
17+
search_fields = ("name",)
18+
raw_id_fields = ("created_by",)

baseapp-files/baseapp_files/apps.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from django.apps import AppConfig
2+
3+
4+
class BaseAppFilesConfig(AppConfig):
5+
name = "baseapp_files"
6+
verbose_name = "BaseApp Files"

baseapp-files/baseapp_files/graphql/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from graphene import Field, NonNull
2+
from ..models import File
3+
from graphene_django.debug import DjangoDebug
4+
from graphene_django_cud.mutations import DjangoCreateMutation, DjangoDeleteMutation, DjangoPatchMutation
5+
from baseapp_core.graphql.errors import Errors
6+
7+
from .object_types import FileNode
8+
9+
10+
class FileCreateMutation(DjangoCreateMutation):
11+
errors = Errors()
12+
debug = Field(DjangoDebug, name="_debug")
13+
file = Field(FileNode._meta.connection.Edge)
14+
# files = Field("files.object_types.FileNode", required=False)
15+
16+
class Meta:
17+
model = File
18+
login_required = True
19+
auto_context_fields = {"user": "user"}
20+
exclude_fields = ("user", "created", "modified")
21+
# field_types = {"file_type": NonNull(FileTypeEnum)}
22+
23+
24+
class PatchFileMutation(DjangoPatchMutation):
25+
class Meta:
26+
model = File
27+
login_required = True
28+
exclude_fields = ("user", "created", "modified")
29+
30+
31+
class DeleteFileMutation(DjangoDeleteMutation):
32+
class Meta:
33+
model = File
34+
login_required = True
35+
exclude_fields = ("user", "created", "modified")
36+
37+
38+
class FilesMutations(object):
39+
file_create = FileCreateMutation.Field()
40+
file_patch = PatchFileMutation.Field()
41+
file_delete = DeleteFileMutation.Field()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import django_filters
2+
import graphene
3+
from graphene import relay
4+
from graphene.types.generic import GenericScalar
5+
from baseapp_core.graphql import DjangoObjectType
6+
from graphene_django.filter import DjangoFilterConnectionField
7+
from django.contrib.contenttypes.models import ContentType
8+
9+
from ..models import File
10+
11+
12+
class FileFilter(django_filters.FilterSet):
13+
no_parent = django_filters.BooleanFilter(field_name='parent_object_id', lookup_expr='isnull')
14+
15+
class Meta:
16+
model = File
17+
fields = ['no_parent']
18+
19+
20+
class FileNode(DjangoObjectType):
21+
parent = graphene.Field(relay.Node)
22+
file = graphene.String()
23+
24+
class Meta:
25+
interfaces = (relay.Node,)
26+
model = File
27+
filterset_class = FileFilter
28+
29+
def resolve_file(self, info, **kwargs):
30+
# return self.file.url
31+
return info.context.build_absolute_uri(self.file.url)
32+
33+
# @classmethod
34+
# def get_node(cls, info, id):
35+
# if not info.context.user.is_authenticated:
36+
# return None
37+
38+
# try:
39+
# queryset = cls.get_queryset(cls._meta.model.objects, info)
40+
# return queryset.get(id=id, recipient=info.context.user)
41+
# except cls._meta.model.DoesNotExist:
42+
# return None
43+
44+
45+
class FilesNode(relay.Node):
46+
files_count = GenericScalar()
47+
files = DjangoFilterConnectionField(lambda: FileNode)
48+
49+
def resolve_files(self, info, **kwargs):
50+
parent_content_type = ContentType.objects.get_for_model(self)
51+
return File.objects.filter(
52+
parent_content_type=parent_content_type,
53+
parent_object_id=self.pk,
54+
).order_by("-created")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from graphene_django.filter import DjangoFilterConnectionField
2+
3+
from .object_types import FileNode
4+
5+
6+
class FilesQueries:
7+
my_files = DjangoFilterConnectionField(FileNode)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
# Generated by Django 4.2.3 on 2023-12-11 01:52
2+
3+
import base.utils.upload
4+
from django.conf import settings
5+
from django.db import migrations, models
6+
import django.db.models.deletion
7+
import django.utils.timezone
8+
import model_utils.fields
9+
import pgtrigger.compiler
10+
import pgtrigger.migrations
11+
12+
13+
class Migration(migrations.Migration):
14+
initial = True
15+
16+
dependencies = [
17+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
18+
("contenttypes", "0002_remove_content_type_name"),
19+
("pghistory", "0005_events_middlewareevents"),
20+
]
21+
22+
operations = [
23+
migrations.CreateModel(
24+
name="File",
25+
fields=[
26+
(
27+
"id",
28+
models.BigAutoField(
29+
auto_created=True,
30+
primary_key=True,
31+
serialize=False,
32+
verbose_name="ID",
33+
),
34+
),
35+
(
36+
"modified",
37+
model_utils.fields.AutoLastModifiedField(
38+
default=django.utils.timezone.now,
39+
editable=False,
40+
verbose_name="modified",
41+
),
42+
),
43+
("parent_object_id", models.PositiveIntegerField(null=True)),
44+
(
45+
"content_type",
46+
models.CharField(blank=True, max_length=150, null=True),
47+
),
48+
("file_name", models.CharField(blank=True, max_length=512, null=True)),
49+
(
50+
"file_size",
51+
models.PositiveIntegerField(
52+
help_text="File size in bytes", null=True
53+
),
54+
),
55+
(
56+
"file",
57+
models.FileField(
58+
max_length=512,
59+
upload_to=base.utils.upload.set_upload_to_random_filename(
60+
"files"
61+
),
62+
),
63+
),
64+
("name", models.CharField(blank=True, max_length=512, null=True)),
65+
("description", models.TextField(blank=True, null=True)),
66+
("created", models.DateTimeField(auto_now_add=True)),
67+
("updated", models.DateTimeField(auto_now=True)),
68+
(
69+
"created_by",
70+
models.ForeignKey(
71+
null=True,
72+
on_delete=django.db.models.deletion.DO_NOTHING,
73+
related_name="files_created",
74+
to=settings.AUTH_USER_MODEL,
75+
),
76+
),
77+
(
78+
"parent_content_type",
79+
models.ForeignKey(
80+
null=True,
81+
on_delete=django.db.models.deletion.CASCADE,
82+
to="contenttypes.contenttype",
83+
),
84+
),
85+
],
86+
options={
87+
"abstract": False,
88+
},
89+
),
90+
migrations.CreateModel(
91+
name="FileEvent",
92+
fields=[
93+
("pgh_id", models.AutoField(primary_key=True, serialize=False)),
94+
("pgh_created_at", models.DateTimeField(auto_now_add=True)),
95+
("pgh_label", models.TextField(help_text="The event label.")),
96+
("id", models.IntegerField()),
97+
(
98+
"modified",
99+
model_utils.fields.AutoLastModifiedField(
100+
default=django.utils.timezone.now,
101+
editable=False,
102+
verbose_name="modified",
103+
),
104+
),
105+
("parent_object_id", models.PositiveIntegerField(null=True)),
106+
(
107+
"content_type",
108+
models.CharField(blank=True, max_length=150, null=True),
109+
),
110+
("file_name", models.CharField(blank=True, max_length=512, null=True)),
111+
(
112+
"file_size",
113+
models.PositiveIntegerField(
114+
help_text="File size in bytes", null=True
115+
),
116+
),
117+
(
118+
"file",
119+
models.FileField(
120+
max_length=512,
121+
upload_to=base.utils.upload.set_upload_to_random_filename(
122+
"files"
123+
),
124+
),
125+
),
126+
("name", models.CharField(blank=True, max_length=512, null=True)),
127+
("description", models.TextField(blank=True, null=True)),
128+
("created", models.DateTimeField(auto_now_add=True)),
129+
("updated", models.DateTimeField(auto_now=True)),
130+
(
131+
"created_by",
132+
models.ForeignKey(
133+
db_constraint=False,
134+
null=True,
135+
on_delete=django.db.models.deletion.DO_NOTHING,
136+
related_name="+",
137+
related_query_name="+",
138+
to=settings.AUTH_USER_MODEL,
139+
),
140+
),
141+
(
142+
"parent_content_type",
143+
models.ForeignKey(
144+
db_constraint=False,
145+
null=True,
146+
on_delete=django.db.models.deletion.DO_NOTHING,
147+
related_name="+",
148+
related_query_name="+",
149+
to="contenttypes.contenttype",
150+
),
151+
),
152+
(
153+
"pgh_context",
154+
models.ForeignKey(
155+
db_constraint=False,
156+
null=True,
157+
on_delete=django.db.models.deletion.DO_NOTHING,
158+
related_name="+",
159+
to="pghistory.context",
160+
),
161+
),
162+
(
163+
"pgh_obj",
164+
models.ForeignKey(
165+
db_constraint=False,
166+
on_delete=django.db.models.deletion.DO_NOTHING,
167+
related_name="event",
168+
to="baseapp_files.file",
169+
),
170+
),
171+
],
172+
options={
173+
"abstract": False,
174+
},
175+
),
176+
pgtrigger.migrations.AddTrigger(
177+
model_name="file",
178+
trigger=pgtrigger.compiler.Trigger(
179+
name="snapshot_insert",
180+
sql=pgtrigger.compiler.UpsertTriggerSql(
181+
func='INSERT INTO "baseapp_files_fileevent" ("content_type", "created", "created_by_id", "description", "file", "file_name", "file_size", "id", "modified", "name", "parent_content_type_id", "parent_object_id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "updated") VALUES (NEW."content_type", NEW."created", NEW."created_by_id", NEW."description", NEW."file", NEW."file_name", NEW."file_size", NEW."id", NEW."modified", NEW."name", NEW."parent_content_type_id", NEW."parent_object_id", _pgh_attach_context(), NOW(), \'snapshot\', NEW."id", NEW."updated"); RETURN NULL;',
182+
hash="fdaabf389378057e7d8d7b2eeb304517c32c87f6",
183+
operation="INSERT",
184+
pgid="pgtrigger_snapshot_insert_03cf9",
185+
table="baseapp_files_file",
186+
when="AFTER",
187+
),
188+
),
189+
),
190+
pgtrigger.migrations.AddTrigger(
191+
model_name="file",
192+
trigger=pgtrigger.compiler.Trigger(
193+
name="snapshot_update",
194+
sql=pgtrigger.compiler.UpsertTriggerSql(
195+
condition="WHEN (OLD.* IS DISTINCT FROM NEW.*)",
196+
func='INSERT INTO "baseapp_files_fileevent" ("content_type", "created", "created_by_id", "description", "file", "file_name", "file_size", "id", "modified", "name", "parent_content_type_id", "parent_object_id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "updated") VALUES (NEW."content_type", NEW."created", NEW."created_by_id", NEW."description", NEW."file", NEW."file_name", NEW."file_size", NEW."id", NEW."modified", NEW."name", NEW."parent_content_type_id", NEW."parent_object_id", _pgh_attach_context(), NOW(), \'snapshot\', NEW."id", NEW."updated"); RETURN NULL;',
197+
hash="3985b330fe91b2800d985c283c58abf491adc075",
198+
operation="UPDATE",
199+
pgid="pgtrigger_snapshot_update_11b9c",
200+
table="baseapp_files_file",
201+
when="AFTER",
202+
),
203+
),
204+
),
205+
]

0 commit comments

Comments
 (0)