prepare allocate tags to organizations api

This commit is contained in:
2025-05-27 15:09:22 +03:30
parent bc710c74c6
commit 6f8f9463b0
29 changed files with 951 additions and 48 deletions

View File

@@ -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')

View File

@@ -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),

View File

@@ -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),
),
]

View 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),
),
]

View File

@@ -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}'

View 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

View File

@@ -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
View 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'

View File

@@ -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),
),
]

View 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),
),
]

View 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),
),
]

View File

@@ -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',
),
]

View 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',
),
]

View 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),
),
]

View 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),
),
]

View File

@@ -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'),
),
]

View 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),
),
]

View 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),
),
]

View File

@@ -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',
),
]

View 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),
),
]

View 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),
),
]

View 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),
),
]

View File

@@ -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
View 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

View File

@@ -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)

View File

@@ -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

View File

@@ -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))

View File

@@ -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
View 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}")