Skip to content

Commit

Permalink
DISP-218 Attachment model (#9)
Browse files Browse the repository at this point in the history
* DISP-218 Add models `Attachment` and `ModelWithAttachmentsMixin`
  • Loading branch information
paulanti authored and Lev Borodin committed Jun 17, 2019
1 parent a29bed9 commit 7b7c534
Show file tree
Hide file tree
Showing 37 changed files with 746 additions and 733 deletions.
12 changes: 10 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
# Changelog

## [v1.1] - 2018-06-07
## [v2.0] - 2019-06-17
### Added
- Model `Attachment` with generic relation, that stores all attachments data
- `AttachmentSerializerMixin` for serializers for models with attachments

### Removed
- `AttachFilesSerializers`, `CreateAttachFilesSerializers`

## [v1.1] - 2019-06-07
### Added
- In url config you can set **app_labels** instead a model or app_label, model_name.
Ex: app_labels = ['project.Model', 'project.Model2']
Expand All @@ -11,7 +19,7 @@
### Deprecated
- model argument in url config will be removed in v1.2

## [v1.0] - 2018-05-30
## [v1.0] - 2019-05-30
### Added
- Uid, name, created, bucket_name fields to files json
- Retrieve files from some buckets
Expand Down
67 changes: 25 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,61 +5,53 @@ for all apiqa django projects.

## HowToUse ##

* Add apiqa-storage to requirements.txt
* Add `apiqa-storage` to `requirements.txt`.

```
# Minio file storage
git+https://github.com/pik-software/apiqa-storage.git#egg=apiqa-storage
```

* Add mixin AttachFileMixin to owned user file model. Make and run migrations
* Add `apiqa_storage` to `INSTALLED_APPS` in settings file.

```python
from apiqa_storage.models import AttachFilesMixin

class UserFile(..., AttachFilesMixin):
...
INSTALLED_APPS = [
'django.contrib.admin',
...,
'apiqa_storage'
]
```

* Add serializator mixin at the beginning and add attachments to fields.
* Add mixin `ModelWithAttachmentsMixin` to any model. Make and run migrations.

```python
from apiqa_storage.serializers import CreateAttachFilesSerializers
from apiqa_storage.models import ModelWithAttachmentsMixin

class SomeSerializer(CreateAttachFilesSerializers, ...):
class UserFile(ModelWithAttachmentsMixin, ...):
...

class Meta:
...
fields = (
...
'attachments',
)

```

* Register signal delete_file_from_storage on pre_delete. Otherwise file willn't be delete from minio
* Add serializer mixin at the beginning and add `attachments`,
`attachment_ids` to fields.

```python
from django.db.models.signals import pre_delete
from django.dispatch import receiver
from apiqa_storage.signals import delete_file_from_storage
from apiqa_storage.serializers import AttachmentsSerializerMixin

@receiver(pre_delete, sender=UserFile)
def user_file_delete_signal(sender, instance, **kwargs):
delete_file_from_storage(sender, instance, **kwargs)
class ModelWithAttachmentsSerializer(AttachmentsSerializerMixin, ...):
...

class Meta:
...
fields = (..., 'attachments', 'attachment_ids')
```

* Add download file url to urlpatterns. Add kwargs model for get user file object
* Add download and upload file urls to urlpatterns.

```python
from django.urls import path, include

urlpatterns = [ # noqa
path(
'attachments/',
include('apiqa_storage.urls'),
kwargs={'app_label': 'app.UserFile'},
),
urlpatterns = [
path('attachments/', include('apiqa_storage.urls')),
]
```

Expand All @@ -69,20 +61,10 @@ urlpatterns = [ # noqa
from django.urls import path, include

urlpatterns = [ # noqa
path(
'attachments/',
include('apiqa_storage.staff_urls'),
kwargs={'app_label': 'app.UserFile'},
),
path('attachments/', include('apiqa_storage.staff_urls')),
]
```

* Insted app_label you can set app_labels in urlpatterns

```python
kwargs={'app_labels': ['app.UserFile', 'app.StaffFile']}
```

* Add required minio settings. Create bucket on minio!
[django minio storage usage](https://django-minio-storage.readthedocs.io/en/latest/usage/)

Expand All @@ -98,6 +80,7 @@ MINIO_STORAGE_BUCKET_NAME = 'local-static'
* **MINIO_STORAGE_MAX_FILE_NAME_LEN**: File name length limit. Use for database char limit. Default 100
* **MINIO_STORAGE_MAX_FILES_COUNT**: Limit of files in one object. For example 5 files in ticket. None - is unlimited. Default None
* **MINIO_STORAGE_USE_HTTPS**: Use https for connect to minio. Default False

* Run test

```bash
Expand Down
21 changes: 21 additions & 0 deletions apiqa_storage/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from django.contrib import admin
from django.urls import reverse
from django.utils.html import format_html
from django.utils.translation import ugettext_lazy as _

from .models import Attachment


@admin.register(Attachment)
class AttachmentAdmin(admin.ModelAdmin):
list_display = (
'uid', 'created', '_name', 'size', 'content_type',
'object_content_type', 'object_id'
)
readonly_fields = ('_name',)

def _name(self, obj):
url = reverse('staff-attachments', kwargs={'attachment_uid': obj.uid})
return format_html(f'<a href="{url}">{obj.name}</a>')
_name.short_description = _('Имя')
_name.admin_order_field = 'name'
41 changes: 0 additions & 41 deletions apiqa_storage/file_response.py

This file was deleted.

9 changes: 9 additions & 0 deletions apiqa_storage/managers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.db import models


class AttachmentQuerySet(models.QuerySet):

def delete(self, *args, **kwargs):
for obj in self:
obj.delete()
super().delete(*args, **kwargs)
38 changes: 38 additions & 0 deletions apiqa_storage/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Generated by Django 2.2.2 on 2019-06-14 11:24

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid


class Migration(migrations.Migration):

initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('contenttypes', '0002_remove_content_type_name'),
]

operations = [
migrations.CreateModel(
name='Attachment',
fields=[
('uid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Создано')),
('name', models.CharField(max_length=100, verbose_name='Имя')),
('path', models.CharField(max_length=512, verbose_name='Путь')),
('size', models.BigIntegerField(verbose_name='Размер')),
('bucket_name', models.CharField(max_length=255)),
('content_type', models.CharField(max_length=255)),
('object_id', models.CharField(blank=True, max_length=255, null=True)),
('object_content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Вложение',
'verbose_name_plural': 'Вложения',
},
),
]
Empty file.
83 changes: 80 additions & 3 deletions apiqa_storage/models.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,87 @@
import uuid

from django.conf import settings
from django.contrib.contenttypes.fields import (
GenericForeignKey, GenericRelation
)
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.contrib.postgres.fields import JSONField
from django.utils.translation import gettext as _

from .managers import AttachmentQuerySet


class Attachment(models.Model):
uid = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
editable=False
)
created = models.DateTimeField(
verbose_name=_('Создано'),
editable=False,
auto_now_add=True
)
name = models.CharField(
verbose_name=_('Имя'),
max_length=settings.MINIO_STORAGE_MAX_FILE_NAME_LEN
)
path = models.CharField(
verbose_name=_('Путь'),
max_length=512
)
size = models.BigIntegerField(
verbose_name=_('Размер')
)
bucket_name = models.CharField(
max_length=255
)
content_type = models.CharField(
max_length=255
)
user = models.ForeignKey(
to=settings.AUTH_USER_MODEL,
null=True,
blank=True,
on_delete=models.SET_NULL
)
object_content_type = models.ForeignKey(
to=ContentType,
null=True,
blank=True,
on_delete=models.CASCADE
)
object_id = models.CharField(
max_length=255,
null=True,
blank=True
)
content_object = GenericForeignKey(
ct_field='object_content_type',
fk_field='object_id'
)
objects = AttachmentQuerySet.as_manager()

class Meta:
verbose_name = _('Вложение')
verbose_name_plural = _('Вложения')

def __str__(self):
return self.path

def delete(self, *args, **kwargs):
from apiqa_storage.serializers import delete_file
if not self.content_object:
delete_file(self)
return super().delete(*args, **kwargs)


class AttachFilesMixin(models.Model):
attachments = JSONField(_('Вложения'), default=list, blank=True)
class ModelWithAttachmentsMixin(models.Model):
attachments = GenericRelation(
to=Attachment,
object_id_field='object_id',
content_type_field='object_content_type'
)

class Meta:
abstract = True
6 changes: 6 additions & 0 deletions apiqa_storage/routers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from rest_framework import routers

from .viewsets import AttachmentViewSet

router = routers.SimpleRouter()
router.register('file-upload', AttachmentViewSet, base_name='file_upload')
Loading

0 comments on commit 7b7c534

Please sign in to comment.