prepare allocate tags to organizations api
This commit is contained in:
@@ -35,7 +35,8 @@ ALLOWED_HOSTS = [
|
|||||||
'https://api.rasadyaar.net',
|
'https://api.rasadyaar.net',
|
||||||
'https://api.dam.rasadyaar.net',
|
'https://api.dam.rasadyaar.net',
|
||||||
'http://localhost:3000',
|
'http://localhost:3000',
|
||||||
'http://192.168.88.130:3000'
|
'http://192.168.88.130:3000',
|
||||||
|
'https://rasaddam-front.liara.run'
|
||||||
]
|
]
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
@@ -151,6 +152,7 @@ REST_FRAMEWORK = {
|
|||||||
'rest_framework.authentication.SessionAuthentication',
|
'rest_framework.authentication.SessionAuthentication',
|
||||||
'rest_framework.authentication.BasicAuthentication',
|
'rest_framework.authentication.BasicAuthentication',
|
||||||
),
|
),
|
||||||
|
'EXCEPTION_HANDLER': 'apps.core.error_handler.custom_exception_handler',
|
||||||
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination",
|
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination",
|
||||||
"PAGE_SIZE": 25,
|
"PAGE_SIZE": 25,
|
||||||
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema'
|
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema'
|
||||||
@@ -282,16 +284,6 @@ DATA_UPLOAD_MAX_MEMORY_SIZE = 50242880
|
|||||||
CORS_ORIGIN_ALLOW_ALL = False
|
CORS_ORIGIN_ALLOW_ALL = False
|
||||||
CORS_ALLOW_CREDENTIALS = True
|
CORS_ALLOW_CREDENTIALS = True
|
||||||
|
|
||||||
# CORS_ORIGIN_WHITELIST = (
|
|
||||||
# # 'http://localhost:8080',
|
|
||||||
# # 'http://127.0.0.1:8080',
|
|
||||||
# # 'http://127.0.0.1:3000',
|
|
||||||
# # 'http://localhost:3000',
|
|
||||||
# # 'http://192.168.88.130:3000',
|
|
||||||
# # 'https://rasadyar.net'
|
|
||||||
# '*',
|
|
||||||
# )
|
|
||||||
#
|
|
||||||
CORS_ALLOWED_ORIGINS = (
|
CORS_ALLOWED_ORIGINS = (
|
||||||
'http://localhost:8080',
|
'http://localhost:8080',
|
||||||
'http://127.0.0.1:8080',
|
'http://127.0.0.1:8080',
|
||||||
@@ -299,6 +291,7 @@ CORS_ALLOWED_ORIGINS = (
|
|||||||
'http://localhost:3000',
|
'http://localhost:3000',
|
||||||
'http://192.168.88.130:3000',
|
'http://192.168.88.130:3000',
|
||||||
'https://rasadyar.net'
|
'https://rasadyar.net'
|
||||||
|
'https://rasaddam-front.liara.run'
|
||||||
)
|
)
|
||||||
|
|
||||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||||
|
|||||||
@@ -17,6 +17,14 @@ Including another URLconf
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from apps.core.swagger import schema_view
|
from apps.core.swagger import schema_view
|
||||||
|
from django.conf.urls import (
|
||||||
|
handler400,
|
||||||
|
handler403,
|
||||||
|
handler500,
|
||||||
|
handler404,
|
||||||
|
)
|
||||||
|
|
||||||
|
# handler500 = 'apps.core.error_handler.handler500' # noqa
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.0 on 2025-05-25 10:07
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('authentication', '0017_bankaccountinformation_creator_info_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='organization',
|
||||||
|
name='additional_data',
|
||||||
|
field=models.JSONField(default=dict),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/authentication/migrations/0019_organizationtype_code.py
Normal file
18
apps/authentication/migrations/0019_organizationtype_code.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.0 on 2025-05-26 11:50
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('authentication', '0018_organization_additional_data'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='organizationtype',
|
||||||
|
name='code',
|
||||||
|
field=models.IntegerField(default=0),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -76,6 +76,7 @@ class OrganizationType(BaseModel):
|
|||||||
)
|
)
|
||||||
key = models.CharField(choices=organization_keys, max_length=3)
|
key = models.CharField(choices=organization_keys, max_length=3)
|
||||||
name = models.CharField(max_length=50, null=True)
|
name = models.CharField(max_length=50, null=True)
|
||||||
|
code = models.IntegerField(default=0)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.key}-{self.name}'
|
return f'{self.key}-{self.name}'
|
||||||
@@ -118,6 +119,7 @@ class Organization(BaseModel):
|
|||||||
related_name='parents',
|
related_name='parents',
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
|
additional_data = models.JSONField(default=dict)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.name}-{self.type}'
|
return f'{self.name}-{self.type}'
|
||||||
|
|||||||
15
apps/core/error_handler.py
Normal file
15
apps/core/error_handler.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from django.http import JsonResponse
|
||||||
|
from rest_framework.views import exception_handler
|
||||||
|
|
||||||
|
|
||||||
|
def custom_exception_handler(exc, context):
|
||||||
|
response = exception_handler(exc, context)
|
||||||
|
|
||||||
|
if response is not None:
|
||||||
|
response.data['status_code'] = response.status_code
|
||||||
|
response.data['message'] = response.data.get('detail', str(exc))
|
||||||
|
del response.data['detail']
|
||||||
|
else:
|
||||||
|
response = JsonResponse({'message': str(exc), 'status_code': 500})
|
||||||
|
response.status_code = 500
|
||||||
|
return response
|
||||||
@@ -1,25 +1,37 @@
|
|||||||
from rest_framework import permissions
|
from apps.core import permissions
|
||||||
|
|
||||||
|
|
||||||
# example Code
|
class HerdCreatePermission(permissions.BasePermission):
|
||||||
class AuthorAllStaffAllButEditOrReadOnly(permissions.BasePermission):
|
""" permission to create herd """
|
||||||
edit_methods = ("PUT", "PATCH")
|
|
||||||
|
|
||||||
def has_permission(self, request, view):
|
def has_permission(self, request, view):
|
||||||
if request.user.is_authenticated:
|
user_level_info = self.get_user_permissions(request, view)
|
||||||
|
if 'herd_create' in user_level_info['permissions']:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def has_object_permission(self, request, view, obj):
|
|
||||||
if request.user.is_superuser:
|
class HerdUpdatePermission(permissions.BasePermission):
|
||||||
|
""" permission to update herd """
|
||||||
|
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
user_level_info = self.get_user_permissions(request, view)
|
||||||
|
if 'herd_update' in user_level_info['permissions']:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if request.method in permissions.SAFE_METHODS:
|
|
||||||
|
class HerdTrashPermission(permissions.BasePermission):
|
||||||
|
""" permission to trash herd """
|
||||||
|
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
user_level_info = self.get_user_permissions(request, view)
|
||||||
|
if 'herd_trash' in user_level_info['permissions']:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if obj.author == request.user:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if request.user.is_staff and request.method not in self.edit_methods:
|
class HerdDeletePermission(permissions.BasePermission):
|
||||||
return True
|
""" permission to delete herd """
|
||||||
|
|
||||||
return False
|
def has_permission(self, request, view):
|
||||||
|
user_level_info = self.get_user_permissions(request, view)
|
||||||
|
if 'herd_delete' in user_level_info['permissions']:
|
||||||
|
return True
|
||||||
|
|||||||
8
apps/tag/exceptions.py
Normal file
8
apps/tag/exceptions.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from rest_framework.exceptions import APIException
|
||||||
|
from rest_framework import status
|
||||||
|
|
||||||
|
|
||||||
|
class SpeciesNumberCheckException(APIException):
|
||||||
|
status_code = status.HTTP_403_FORBIDDEN
|
||||||
|
default_detail = 'Entered species number is more than user free tags'
|
||||||
|
default_code = 'more than free tags'
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Generated by Django 5.0 on 2025-05-25 10:07
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tag', '0007_tag_creator_info_tag_modifier_info'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='tag',
|
||||||
|
name='code',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='tag',
|
||||||
|
name='country_code',
|
||||||
|
field=models.IntegerField(default=364),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='tag',
|
||||||
|
name='ownership_code',
|
||||||
|
field=models.IntegerField(default=0),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='tag',
|
||||||
|
name='serial',
|
||||||
|
field=models.IntegerField(default=0),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='tag',
|
||||||
|
name='species_code',
|
||||||
|
field=models.IntegerField(default=0),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='tag',
|
||||||
|
name='static_code',
|
||||||
|
field=models.IntegerField(default=0),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='tag',
|
||||||
|
name='tag_code',
|
||||||
|
field=models.CharField(max_length=20, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/tag/migrations/0009_alter_tag_tag_code.py
Normal file
18
apps/tag/migrations/0009_alter_tag_tag_code.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.0 on 2025-05-25 11:22
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tag', '0008_remove_tag_code_tag_country_code_tag_ownership_code_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tag',
|
||||||
|
name='tag_code',
|
||||||
|
field=models.CharField(max_length=20, unique=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/tag/migrations/0010_alter_tag_tag_code.py
Normal file
18
apps/tag/migrations/0010_alter_tag_tag_code.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.0 on 2025-05-25 11:22
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tag', '0009_alter_tag_tag_code'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tag',
|
||||||
|
name='tag_code',
|
||||||
|
field=models.CharField(max_length=20, null=True, unique=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Generated by Django 5.0 on 2025-05-25 12:20
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tag', '0010_alter_tag_tag_code'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='tag',
|
||||||
|
name='city',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='tag',
|
||||||
|
name='province',
|
||||||
|
),
|
||||||
|
]
|
||||||
17
apps/tag/migrations/0012_remove_tag_serial.py
Normal file
17
apps/tag/migrations/0012_remove_tag_serial.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 5.0 on 2025-05-26 05:35
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tag', '0011_remove_tag_city_remove_tag_province'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='tag',
|
||||||
|
name='serial',
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/tag/migrations/0013_tag_serial.py
Normal file
18
apps/tag/migrations/0013_tag_serial.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.0 on 2025-05-26 05:36
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tag', '0012_remove_tag_serial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='tag',
|
||||||
|
name='serial',
|
||||||
|
field=models.CharField(default=0, max_length=8),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/tag/migrations/0014_alter_tag_serial.py
Normal file
18
apps/tag/migrations/0014_alter_tag_serial.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.0 on 2025-05-26 07:22
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tag', '0013_tag_serial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tag',
|
||||||
|
name='serial',
|
||||||
|
field=models.CharField(max_length=8, unique=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# Generated by Django 5.0 on 2025-05-26 11:50
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('authentication', '0019_organizationtype_code'),
|
||||||
|
('tag', '0014_alter_tag_serial'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tag',
|
||||||
|
name='status',
|
||||||
|
field=models.CharField(default='Free', max_length=20),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AllocatedTags',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('create_date', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('modify_date', models.DateTimeField(auto_now=True)),
|
||||||
|
('creator_info', models.CharField(max_length=100, null=True)),
|
||||||
|
('modifier_info', models.CharField(max_length=100, null=True)),
|
||||||
|
('trash', models.BooleanField(default=False)),
|
||||||
|
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_createddby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_modifiedby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('tag', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='allocated_tags', to='tag.tag')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TagAssignment',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('create_date', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('modify_date', models.DateTimeField(auto_now=True)),
|
||||||
|
('creator_info', models.CharField(max_length=100, null=True)),
|
||||||
|
('modifier_info', models.CharField(max_length=100, null=True)),
|
||||||
|
('trash', models.BooleanField(default=False)),
|
||||||
|
('document', models.CharField(max_length=50, null=True)),
|
||||||
|
('serial_sender_part', models.IntegerField(default=0)),
|
||||||
|
('serial_recipient_part', models.IntegerField(default=0)),
|
||||||
|
('serial_date_part', models.CharField(max_length=3, null=True)),
|
||||||
|
('serial_random_part', models.IntegerField(default=0)),
|
||||||
|
('serial', models.CharField(max_length=20, null=True)),
|
||||||
|
('status', models.CharField(choices=[('A', 'Accept'), ('W', 'Waiting'), ('C', 'Cancel')], default='W', max_length=1)),
|
||||||
|
('allocated_tags', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tag_assignment_alloc', to='tag.allocatedtags')),
|
||||||
|
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_createddby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_modifiedby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tag_assignment_org', to='authentication.organization')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='allocatedtags',
|
||||||
|
name='assignment',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='assigned_allocated_tags', to='tag.tagassignment'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/tag/migrations/0016_alter_tag_serial.py
Normal file
18
apps/tag/migrations/0016_alter_tag_serial.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.0 on 2025-05-26 12:14
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tag', '0015_alter_tag_status_allocatedtags_tagassignment_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tag',
|
||||||
|
name='serial',
|
||||||
|
field=models.CharField(max_length=8),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/tag/migrations/0017_alter_tag_status.py
Normal file
18
apps/tag/migrations/0017_alter_tag_status.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.0 on 2025-05-26 12:47
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tag', '0016_alter_tag_serial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tag',
|
||||||
|
name='status',
|
||||||
|
field=models.CharField(default='F', max_length=20),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 5.0 on 2025-05-26 13:09
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tag', '0017_alter_tag_status'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='tagassignment',
|
||||||
|
name='allocated_tags',
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/tag/migrations/0019_allocatedtags_status.py
Normal file
18
apps/tag/migrations/0019_allocatedtags_status.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.0 on 2025-05-27 06:11
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tag', '0018_remove_tagassignment_allocated_tags'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='allocatedtags',
|
||||||
|
name='status',
|
||||||
|
field=models.CharField(default='W', max_length=1),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/tag/migrations/0020_alter_tagassignment_document.py
Normal file
18
apps/tag/migrations/0020_alter_tagassignment_document.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.0 on 2025-05-27 08:02
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tag', '0019_allocatedtags_status'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tagassignment',
|
||||||
|
name='document',
|
||||||
|
field=models.CharField(max_length=150, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/tag/migrations/0021_allocatedtags_species_code.py
Normal file
18
apps/tag/migrations/0021_allocatedtags_species_code.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.0 on 2025-05-27 10:23
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tag', '0020_alter_tagassignment_document'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='allocatedtags',
|
||||||
|
name='species_code',
|
||||||
|
field=models.IntegerField(default=0),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,36 +1,121 @@
|
|||||||
from apps.livestock import models as livestock_models
|
from apps.livestock import models as livestock_models
|
||||||
from apps.authentication import models as auth_models
|
from apps.authentication import models as auth_models
|
||||||
from apps.authorization import models as authoriz_models
|
from apps.authorization import models as authoriz_models
|
||||||
|
from apps.tag.tools import tag_code_serial_scanning
|
||||||
from apps.core.models import BaseModel
|
from apps.core.models import BaseModel
|
||||||
|
from crum import get_current_user
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from jdatetime import datetime
|
||||||
|
|
||||||
|
|
||||||
class Tag(BaseModel):
|
class Tag(BaseModel):
|
||||||
code = models.CharField(max_length=20)
|
country_code = models.IntegerField(default=364)
|
||||||
province = models.ForeignKey(
|
static_code = models.IntegerField(default=0)
|
||||||
auth_models.Province,
|
ownership_code = models.IntegerField(default=0)
|
||||||
on_delete=models.CASCADE,
|
species_code = models.IntegerField(default=0)
|
||||||
related_name="tag_province",
|
serial = models.CharField(max_length=8)
|
||||||
null=True
|
tag_code = models.CharField(max_length=20, unique=True, null=True)
|
||||||
)
|
|
||||||
city = models.ForeignKey(
|
|
||||||
auth_models.City,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name='tag_city',
|
|
||||||
null=True
|
|
||||||
)
|
|
||||||
organization = models.ForeignKey(
|
organization = models.ForeignKey(
|
||||||
auth_models.Organization,
|
auth_models.Organization,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name='tag_organization',
|
related_name='tag_organization',
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
status = models.CharField(max_length=20, null=True)
|
status_choices = (
|
||||||
|
('F', 'Free'),
|
||||||
|
('A', 'Assigned')
|
||||||
|
)
|
||||||
|
status = models.CharField(max_length=20, default="F")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.code}'
|
return f'{self.id}-{self.tag_code}'
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
# set zeros for serial
|
||||||
|
if not self.tag_code:
|
||||||
|
self.serial = tag_code_serial_scanning(self.serial)
|
||||||
|
if not self.tag_code:
|
||||||
|
# set total tag code
|
||||||
|
self.tag_code = f"{self.country_code}" \
|
||||||
|
f"{self.static_code}" \
|
||||||
|
f"{self.ownership_code}" \
|
||||||
|
f"{self.species_code}" \
|
||||||
|
f"{self.serial}"
|
||||||
|
if not self.organization:
|
||||||
|
# set user organization for tag
|
||||||
|
user = get_current_user()
|
||||||
|
self.organization = (
|
||||||
|
authoriz_models.UserRelations.objects.select_related(
|
||||||
|
'organization'
|
||||||
|
).get(
|
||||||
|
user=user
|
||||||
|
)).organization
|
||||||
|
|
||||||
super(Tag, self).save(*args, **kwargs)
|
super(Tag, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class TagAssignment(BaseModel):
|
||||||
|
organization = models.ForeignKey(
|
||||||
|
auth_models.Organization,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='tag_assignment_org',
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
document = models.CharField(max_length=150, null=True)
|
||||||
|
serial_sender_part = models.IntegerField(default=0)
|
||||||
|
serial_recipient_part = models.IntegerField(default=0)
|
||||||
|
serial_date_part = models.CharField(max_length=3, null=True)
|
||||||
|
serial_random_part = models.IntegerField(default=0)
|
||||||
|
serial = models.CharField(max_length=20, null=True)
|
||||||
|
status_choices = (
|
||||||
|
('A', 'Accept'),
|
||||||
|
('W', 'Waiting'),
|
||||||
|
('C', 'Cancel'),
|
||||||
|
)
|
||||||
|
status = models.CharField(max_length=1, choices=status_choices, default="W")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.serial}-{self.organization.name}'
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
self.serial_recipient_part = self.organization.type.code
|
||||||
|
self.serial_date_part = str(
|
||||||
|
datetime.now().year
|
||||||
|
)[3] + str(
|
||||||
|
datetime.now().month
|
||||||
|
)
|
||||||
|
if not self.serial:
|
||||||
|
self.serial = f"" \
|
||||||
|
f"{self.serial_sender_part}" \
|
||||||
|
f"{self.serial_recipient_part}" \
|
||||||
|
f"{self.serial_date_part}" \
|
||||||
|
f"{self.serial_random_part}"
|
||||||
|
super(TagAssignment, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class AllocatedTags(BaseModel):
|
||||||
|
tag = models.ForeignKey(
|
||||||
|
Tag,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='allocated_tags',
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
assignment = models.ForeignKey(
|
||||||
|
TagAssignment,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='assigned_allocated_tags',
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
species_code = models.IntegerField(default=0)
|
||||||
|
status_choices = (
|
||||||
|
('A', 'Accept'),
|
||||||
|
('W', 'Waiting'),
|
||||||
|
('C', 'Cancel')
|
||||||
|
)
|
||||||
|
status = models.CharField(max_length=1, default='W')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.tag.tag_code}-{self.assignment.serial}'
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
super(AllocatedTags, self).save(*args, **kwargs)
|
||||||
|
|||||||
20
apps/tag/tools.py
Normal file
20
apps/tag/tools.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import typing
|
||||||
|
|
||||||
|
|
||||||
|
def tag_code_serial_scanning(serial: str = None) -> typing.AnyStr:
|
||||||
|
"""
|
||||||
|
serial code is 8 number serial,
|
||||||
|
set 4 first numbers to 0
|
||||||
|
"""
|
||||||
|
if len(str(serial)) == 4:
|
||||||
|
scanned_serial = "0000" + str(serial)
|
||||||
|
if len(str(serial)) == 5:
|
||||||
|
scanned_serial = "000" + str(serial)
|
||||||
|
if len(str(serial)) == 6:
|
||||||
|
scanned_serial = "00" + str(serial)
|
||||||
|
if len(str(serial)) == 7:
|
||||||
|
scanned_serial = "0" + str(serial)
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
return scanned_serial
|
||||||
|
|
||||||
@@ -2,11 +2,22 @@ from rest_framework import viewsets
|
|||||||
from apps.tag import models as tag_models
|
from apps.tag import models as tag_models
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from .serializers import TagSerializer
|
from .serializers import (
|
||||||
|
TagSerializer,
|
||||||
|
TagAssignmentSerializer,
|
||||||
|
AllocatedTagsSerializer
|
||||||
|
)
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from rest_framework.exceptions import APIException
|
from rest_framework.exceptions import APIException
|
||||||
from apps.tag import permissions as tag_permissions
|
from apps.tag import permissions as tag_permissions
|
||||||
|
from apps.authorization import models as authorize_models
|
||||||
|
from apps.tag.tools import tag_code_serial_scanning
|
||||||
|
from apps.tag import exceptions as tag_exceptions
|
||||||
|
from common.helpers import detect_file_extension
|
||||||
|
from common.liara_tools import upload_to_liara
|
||||||
|
from django.db import IntegrityError
|
||||||
|
import typing
|
||||||
|
|
||||||
|
|
||||||
def trash(queryset, pk):
|
def trash(queryset, pk):
|
||||||
@@ -23,9 +34,47 @@ def delete(queryset, pk):
|
|||||||
|
|
||||||
|
|
||||||
class TagViewSet(viewsets.ModelViewSet):
|
class TagViewSet(viewsets.ModelViewSet):
|
||||||
|
""" Tag View Set """
|
||||||
queryset = tag_models.Tag.objects.all()
|
queryset = tag_models.Tag.objects.all()
|
||||||
serializer_class = TagSerializer
|
serializer_class = TagSerializer
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def create(self, request: object, *args: list, **kwargs: dict) -> typing.Any:
|
||||||
|
""" Create tag for livestocks """ # noqa
|
||||||
|
tag_objects = []
|
||||||
|
serial_start_range, serial_end_range = request.data['serial_range'] # serial_range is like [500, 550]
|
||||||
|
while serial_start_range <= serial_end_range:
|
||||||
|
try:
|
||||||
|
request.data.update({'serial': str(serial_start_range)})
|
||||||
|
serializer = self.serializer_class(data=request.data)
|
||||||
|
if serializer.is_valid(raise_exception=True):
|
||||||
|
tag_objects.append(serializer.save())
|
||||||
|
except IntegrityError as e: # if tag exists before
|
||||||
|
if 'unique constraint' in e.args[0]:
|
||||||
|
return Response("tag exists", status.HTTP_406_NOT_ACCEPTABLE)
|
||||||
|
serial_start_range += 1
|
||||||
|
|
||||||
|
serializer = self.serializer_class(tag_objects, many=True)
|
||||||
|
return Response(serializer.data, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
@action(
|
||||||
|
methods=['get'],
|
||||||
|
detail=False,
|
||||||
|
url_path='ownership_code',
|
||||||
|
url_name='ownership_code',
|
||||||
|
name='ownership_code'
|
||||||
|
)
|
||||||
|
@transaction.atomic
|
||||||
|
def ownership_code(self, request) -> typing.Any:
|
||||||
|
""" just show ownership code of organization """
|
||||||
|
try:
|
||||||
|
ownership_code = authorize_models.UserRelations.objects.select_related('organization').get(
|
||||||
|
user=request.user
|
||||||
|
)
|
||||||
|
return Response(ownership_code.organization.additional_data, status.HTTP_200_OK)
|
||||||
|
except APIException as e:
|
||||||
|
return Response(e, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
@action(
|
@action(
|
||||||
methods=['put'],
|
methods=['put'],
|
||||||
detail=True,
|
detail=True,
|
||||||
@@ -55,4 +104,157 @@ class TagViewSet(viewsets.ModelViewSet):
|
|||||||
delete(self.queryset, pk)
|
delete(self.queryset, pk)
|
||||||
return Response(status=status.HTTP_200_OK)
|
return Response(status=status.HTTP_200_OK)
|
||||||
except APIException as e:
|
except APIException as e:
|
||||||
return Response(e, status=status.HTTP_204_NO_CONTENT)
|
return Response(e, status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
class TagAssignmentViewSet(viewsets.ModelViewSet):
|
||||||
|
""" assignment of tags """
|
||||||
|
queryset = tag_models.TagAssignment.objects.all()
|
||||||
|
serializer_class = TagAssignmentSerializer
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def create(self, request: object, *args: list, **kwargs: dict) -> typing.Any:
|
||||||
|
""" assign tags to organizations """
|
||||||
|
response = {}
|
||||||
|
serializer = self.serializer_class(data=request.data)
|
||||||
|
if serializer.is_valid(raise_exception=True):
|
||||||
|
tag_assignment = serializer.save()
|
||||||
|
|
||||||
|
# get tags by species number like: 2 tags of species code 4
|
||||||
|
tags_to_allocate = request.data['allocated_tags']
|
||||||
|
for tag in tags_to_allocate:
|
||||||
|
tags = tag_models.Tag.objects.filter(
|
||||||
|
species_code=tag['species_code'],
|
||||||
|
status='F'
|
||||||
|
).order_by('id')[:tag['number']]
|
||||||
|
|
||||||
|
# check species number
|
||||||
|
if tags.count() < tag['number']:
|
||||||
|
raise tag_exceptions.SpeciesNumberCheckException()
|
||||||
|
|
||||||
|
# create assignment
|
||||||
|
for tag_to_allocate in tags:
|
||||||
|
tag_models.AllocatedTags(
|
||||||
|
tag=tag_to_allocate,
|
||||||
|
assignment=tag_assignment,
|
||||||
|
status='W',
|
||||||
|
species_code=tag['species_code']
|
||||||
|
).save() # noqa
|
||||||
|
tag_to_allocate.status = 'W' # change tag status from free to waiting
|
||||||
|
tag_to_allocate.save()
|
||||||
|
|
||||||
|
return Response(serializer.data, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
@action(
|
||||||
|
methods=['post', ],
|
||||||
|
detail=True,
|
||||||
|
url_name='assign_document',
|
||||||
|
url_path='assign_document',
|
||||||
|
name='assign_document'
|
||||||
|
)
|
||||||
|
@transaction.atomic
|
||||||
|
def assign_document(self, request, pk=None):
|
||||||
|
""" set document for tag assignment """
|
||||||
|
|
||||||
|
# get tag assignment object & set document url
|
||||||
|
assignment = self.queryset.get(id=pk)
|
||||||
|
|
||||||
|
# upload document file to liara storage
|
||||||
|
document = request.FILES.get('document')
|
||||||
|
document_url = upload_to_liara(
|
||||||
|
document,
|
||||||
|
f'tag_assignment_document_{assignment.serial}.{str(document).split(".")[1]}'
|
||||||
|
)
|
||||||
|
assignment.document = document_url
|
||||||
|
assignment.status = 'A'
|
||||||
|
assignment.save()
|
||||||
|
serializer = self.serializer_class(assignment)
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@action(
|
||||||
|
methods=['post'],
|
||||||
|
detail=True,
|
||||||
|
url_path='remove_tags_by_group',
|
||||||
|
url_name='remove_tags_by_group',
|
||||||
|
name='remove_tags_by_group'
|
||||||
|
)
|
||||||
|
def remove_assigned_tags_by_group(self, request, pk=None):
|
||||||
|
""" remove assigned tags """
|
||||||
|
|
||||||
|
tag_assignment = self.queryset.get(id=pk)
|
||||||
|
for species in request.data['species_group']:
|
||||||
|
allocated_tags = tag_assignment.assigned_allocated_tags.filter(species_code=species)
|
||||||
|
for allocate in allocated_tags:
|
||||||
|
# change status of tag from allocated to free
|
||||||
|
allocate.tag.status = 'F'
|
||||||
|
allocate.tag.save()
|
||||||
|
allocated_tags.delete()
|
||||||
|
|
||||||
|
return Response(status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@action(
|
||||||
|
methods=['put'],
|
||||||
|
detail=True,
|
||||||
|
url_path='trash',
|
||||||
|
url_name='trash',
|
||||||
|
name='trash',
|
||||||
|
)
|
||||||
|
@transaction.atomic
|
||||||
|
def trash(self, request, pk=None):
|
||||||
|
""" Sent TagAssigment to trash """
|
||||||
|
try:
|
||||||
|
trash(self.queryset, pk)
|
||||||
|
except APIException as e:
|
||||||
|
return Response(e, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
@action(
|
||||||
|
methods=['post'],
|
||||||
|
detail=True,
|
||||||
|
url_name='delete',
|
||||||
|
url_path='delete',
|
||||||
|
name='delete'
|
||||||
|
)
|
||||||
|
@transaction.atomic
|
||||||
|
def delete(self, request, pk=None):
|
||||||
|
""" Full delete of TagAssignment object """
|
||||||
|
try:
|
||||||
|
delete(self.queryset, pk)
|
||||||
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
except APIException as e:
|
||||||
|
return Response(e, status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
class AllocatedTagsViewSet(viewsets.ModelViewSet):
|
||||||
|
queryset = tag_models.AllocatedTags.objects.all()
|
||||||
|
serializer_class = AllocatedTagsSerializer
|
||||||
|
|
||||||
|
@action(
|
||||||
|
methods=['put'],
|
||||||
|
detail=True,
|
||||||
|
url_path='trash',
|
||||||
|
url_name='trash',
|
||||||
|
name='trash',
|
||||||
|
)
|
||||||
|
@transaction.atomic
|
||||||
|
def trash(self, request, pk=None):
|
||||||
|
""" Sent AllocatedTag to trash """
|
||||||
|
try:
|
||||||
|
trash(self.queryset, pk)
|
||||||
|
except APIException as e:
|
||||||
|
return Response(e, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
@action(
|
||||||
|
methods=['post'],
|
||||||
|
detail=True,
|
||||||
|
url_name='delete',
|
||||||
|
url_path='delete',
|
||||||
|
name='delete'
|
||||||
|
)
|
||||||
|
@transaction.atomic
|
||||||
|
def delete(self, request, pk=None):
|
||||||
|
""" Full delete of AllocatedTag object """
|
||||||
|
try:
|
||||||
|
delete(self.queryset, pk)
|
||||||
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
except APIException as e:
|
||||||
|
return Response(e, status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|||||||
@@ -1,27 +1,104 @@
|
|||||||
|
from apps.authentication.api.v1.serializers import serializer as auth_serializers
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from apps.tag import models as tag_models
|
from apps.tag import models as tag_models
|
||||||
from apps.authentication.api.v1.serializers import serializer as auth_serializers
|
|
||||||
|
|
||||||
|
|
||||||
class TagSerializer(serializers.ModelSerializer):
|
class TagSerializer(serializers.ModelSerializer):
|
||||||
""" Tag Model Serializer """
|
""" Tag Model Serializer """
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = tag_models.Tag
|
model = tag_models.Tag
|
||||||
fields = [
|
fields = [
|
||||||
'id',
|
'id',
|
||||||
'code',
|
'country_code',
|
||||||
'province',
|
'static_code',
|
||||||
'city',
|
'ownership_code',
|
||||||
|
'species_code',
|
||||||
|
'serial',
|
||||||
|
'tag_code',
|
||||||
'organization',
|
'organization',
|
||||||
'status',
|
'status',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
""" update tag information """
|
||||||
|
|
||||||
|
instance.country_code = validated_data.get(
|
||||||
|
'country_code', instance.country_code
|
||||||
|
)
|
||||||
|
instance.static_code = validated_data.get(
|
||||||
|
'static_code', instance.static_code
|
||||||
|
)
|
||||||
|
instance.ownership_code = validated_data.get(
|
||||||
|
'ownership_code', instance.ownership_code
|
||||||
|
)
|
||||||
|
instance.species_code = validated_data.get(
|
||||||
|
'species_code', instance.species_code
|
||||||
|
)
|
||||||
|
instance.serial = validated_data.get(
|
||||||
|
'serial', instance.serial
|
||||||
|
)
|
||||||
|
instance.tag_code = validated_data.get(
|
||||||
|
'tag_code', instance.tag_code
|
||||||
|
)
|
||||||
|
if validated_data.get('organization'):
|
||||||
|
instance.organization = tag_models.auth_models.Organization.objects.get(
|
||||||
|
id=validated_data.get('organization', instance.organization).id
|
||||||
|
)
|
||||||
|
instance.status = validated_data.get('status', instance.status)
|
||||||
|
instance.save()
|
||||||
|
return instance
|
||||||
|
|
||||||
def to_representation(self, instance):
|
def to_representation(self, instance):
|
||||||
""" Customize output of serializer """
|
""" Customize output of serializer """
|
||||||
representation = super().to_representation(instance)
|
representation = super().to_representation(instance)
|
||||||
if isinstance(instance, tag_models.Tag):
|
if isinstance(instance, tag_models.Tag):
|
||||||
representation['province'] = auth_serializers.ProvinceSerializer(instance.province).data
|
|
||||||
representation['city'] = auth_serializers.CitySerializer(instance.city).data
|
|
||||||
representation['organization'] = auth_serializers.OrganizationSerializer(instance.organization).data
|
representation['organization'] = auth_serializers.OrganizationSerializer(instance.organization).data
|
||||||
|
return representation
|
||||||
|
|
||||||
|
|
||||||
|
class TagAssignmentSerializer(serializers.ModelSerializer):
|
||||||
|
""" assigned tag serializer """
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = tag_models.TagAssignment
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'organization',
|
||||||
|
'document',
|
||||||
|
'serial',
|
||||||
|
]
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
""" custom output for serializer """
|
||||||
|
representation = super().to_representation(instance)
|
||||||
|
representation['serial'] = instance.serial
|
||||||
|
representation['document'] = instance.document
|
||||||
|
representation['organization'] = auth_serializers.OrganizationSerializer(
|
||||||
|
instance.organization
|
||||||
|
).data
|
||||||
|
representation['allocated_tags'] = AllocatedTagsSerializer(
|
||||||
|
instance.assigned_allocated_tags.all(), many=True
|
||||||
|
).data
|
||||||
|
return representation
|
||||||
|
|
||||||
|
|
||||||
|
class AllocatedTagsSerializer(serializers.ModelSerializer):
|
||||||
|
""" allocated tags serializer """
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = tag_models.AllocatedTags
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'tag',
|
||||||
|
'assignment',
|
||||||
|
'status'
|
||||||
|
]
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
""" custom output for serializer """
|
||||||
|
representation = super().to_representation(instance)
|
||||||
|
representation['tag'] = TagSerializer(instance.tag).data
|
||||||
|
representation['status'] = instance.status
|
||||||
|
|
||||||
return representation
|
return representation
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
from .api import TagViewSet
|
from .api import (
|
||||||
|
TagViewSet,
|
||||||
|
TagAssignmentViewSet,
|
||||||
|
AllocatedTagsViewSet
|
||||||
|
)
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
router.register(r'tag', TagViewSet, basename='tag')
|
router.register(r'tag', TagViewSet, basename='tag')
|
||||||
|
router.register(r'tag_assignment', TagAssignmentViewSet, basename='tag_assignment')
|
||||||
|
router.register(r'allocated_tag', AllocatedTagsViewSet, basename='allocated_tag')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('v1/', include(router.urls))
|
path('v1/', include(router.urls))
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import typing
|
||||||
|
|
||||||
|
|
||||||
|
def detect_file_extension(file_name: str) -> typing.AnyStr:
|
||||||
|
""" detect extension of a file like: jpg, png, pdf """
|
||||||
|
extended = file_name.split('.')
|
||||||
|
return extended[1]
|
||||||
|
|||||||
99
common/liara_tools.py
Normal file
99
common/liara_tools.py
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
from botocore.exceptions import NoCredentialsError
|
||||||
|
import boto3
|
||||||
|
import logging
|
||||||
|
from PIL import Image
|
||||||
|
import io
|
||||||
|
import base64
|
||||||
|
|
||||||
|
LIARA_ENDPOINT = 'https://storage.c2.liara.space'
|
||||||
|
LIARA_BUCKET_NAME = 'ticket-rasadyar'
|
||||||
|
LIARA_ACCESS_KEY = "gvqohestrakmqi6n"
|
||||||
|
LIARA_SECRET_KEY = '7240fdd8-59bc-4f02-b5e6-4a124e37fa0e'
|
||||||
|
|
||||||
|
|
||||||
|
def upload_to_liara(file_obj, file_name):
|
||||||
|
try:
|
||||||
|
s3 = boto3.client(
|
||||||
|
's3',
|
||||||
|
endpoint_url=LIARA_ENDPOINT,
|
||||||
|
aws_access_key_id=LIARA_ACCESS_KEY,
|
||||||
|
aws_secret_access_key=LIARA_SECRET_KEY
|
||||||
|
)
|
||||||
|
|
||||||
|
s3.upload_fileobj(
|
||||||
|
file_obj,
|
||||||
|
LIARA_BUCKET_NAME,
|
||||||
|
file_name,
|
||||||
|
ExtraArgs={'ACL': 'public-read'} # دسترسی عمومی
|
||||||
|
)
|
||||||
|
|
||||||
|
return f"{LIARA_ENDPOINT}/{LIARA_BUCKET_NAME}/{file_name}"
|
||||||
|
|
||||||
|
except NoCredentialsError:
|
||||||
|
raise Exception("اعتبارنامههای AWS معتبر نیستند")
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"خطا در آپلود فایل: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def connect():
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
try:
|
||||||
|
s3 = boto3.client(
|
||||||
|
's3',
|
||||||
|
endpoint_url=LIARA_ENDPOINT,
|
||||||
|
aws_access_key_id=LIARA_ACCESS_KEY,
|
||||||
|
aws_secret_access_key=LIARA_SECRET_KEY
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
logging.info(exc)
|
||||||
|
return s3
|
||||||
|
|
||||||
|
|
||||||
|
def upload_object_resize_to_liara(image_data, object_name):
|
||||||
|
try:
|
||||||
|
imgdata = base64.b64decode(image_data)
|
||||||
|
img = Image.open(io.BytesIO(imgdata))
|
||||||
|
|
||||||
|
img.thumbnail((500, 500))
|
||||||
|
|
||||||
|
buffer = io.BytesIO()
|
||||||
|
img.save(buffer, format="PNG")
|
||||||
|
buffer.seek(0)
|
||||||
|
|
||||||
|
s3_resource = boto3.resource(
|
||||||
|
's3',
|
||||||
|
endpoint_url=LIARA_ENDPOINT,
|
||||||
|
aws_access_key_id=LIARA_ACCESS_KEY,
|
||||||
|
aws_secret_access_key=LIARA_SECRET_KEY
|
||||||
|
)
|
||||||
|
|
||||||
|
bucket = s3_resource.Bucket(LIARA_BUCKET_NAME)
|
||||||
|
bucket.put_object(
|
||||||
|
ACL='public-read',
|
||||||
|
Body=buffer,
|
||||||
|
Key=object_name,
|
||||||
|
ContentType='image/png'
|
||||||
|
)
|
||||||
|
|
||||||
|
return f"{LIARA_ENDPOINT}/{LIARA_BUCKET_NAME}/{object_name}"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"خطا در آپلود فایل: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def delete_file_from_liara(file_name):
|
||||||
|
try:
|
||||||
|
s3 = boto3.client(
|
||||||
|
's3',
|
||||||
|
endpoint_url=LIARA_ENDPOINT,
|
||||||
|
aws_access_key_id=LIARA_ACCESS_KEY,
|
||||||
|
aws_secret_access_key=LIARA_SECRET_KEY
|
||||||
|
)
|
||||||
|
|
||||||
|
s3.delete_object(Bucket=LIARA_BUCKET_NAME, Key=file_name)
|
||||||
|
|
||||||
|
except NoCredentialsError:
|
||||||
|
raise Exception("اعتبارنامههای AWS معتبر نیستند")
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"خطا در آپلود فایل: {e}")
|
||||||
Reference in New Issue
Block a user