prepare allocate tags to organizations api
This commit is contained in:
@@ -35,7 +35,8 @@ ALLOWED_HOSTS = [
|
||||
'https://api.rasadyaar.net',
|
||||
'https://api.dam.rasadyaar.net',
|
||||
'http://localhost:3000',
|
||||
'http://192.168.88.130:3000'
|
||||
'http://192.168.88.130:3000',
|
||||
'https://rasaddam-front.liara.run'
|
||||
]
|
||||
|
||||
# Application definition
|
||||
@@ -151,6 +152,7 @@ REST_FRAMEWORK = {
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
'rest_framework.authentication.BasicAuthentication',
|
||||
),
|
||||
'EXCEPTION_HANDLER': 'apps.core.error_handler.custom_exception_handler',
|
||||
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination",
|
||||
"PAGE_SIZE": 25,
|
||||
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema'
|
||||
@@ -282,16 +284,6 @@ DATA_UPLOAD_MAX_MEMORY_SIZE = 50242880
|
||||
CORS_ORIGIN_ALLOW_ALL = False
|
||||
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 = (
|
||||
'http://localhost:8080',
|
||||
'http://127.0.0.1:8080',
|
||||
@@ -299,6 +291,7 @@ CORS_ALLOWED_ORIGINS = (
|
||||
'http://localhost:3000',
|
||||
'http://192.168.88.130:3000',
|
||||
'https://rasadyar.net'
|
||||
'https://rasaddam-front.liara.run'
|
||||
)
|
||||
|
||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||
|
||||
@@ -17,6 +17,14 @@ Including another URLconf
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
from apps.core.swagger import schema_view
|
||||
from django.conf.urls import (
|
||||
handler400,
|
||||
handler403,
|
||||
handler500,
|
||||
handler404,
|
||||
)
|
||||
|
||||
# handler500 = 'apps.core.error_handler.handler500' # noqa
|
||||
|
||||
urlpatterns = [
|
||||
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)
|
||||
name = models.CharField(max_length=50, null=True)
|
||||
code = models.IntegerField(default=0)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.key}-{self.name}'
|
||||
@@ -118,6 +119,7 @@ class Organization(BaseModel):
|
||||
related_name='parents',
|
||||
null=True
|
||||
)
|
||||
additional_data = models.JSONField(default=dict)
|
||||
|
||||
def __str__(self):
|
||||
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 AuthorAllStaffAllButEditOrReadOnly(permissions.BasePermission):
|
||||
edit_methods = ("PUT", "PATCH")
|
||||
class HerdCreatePermission(permissions.BasePermission):
|
||||
""" permission to create herd """
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
if obj.author == request.user:
|
||||
return True
|
||||
|
||||
if request.user.is_staff and request.method not in self.edit_methods:
|
||||
return True
|
||||
class HerdDeletePermission(permissions.BasePermission):
|
||||
""" 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.authentication import models as auth_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 crum import get_current_user
|
||||
from django.db import models
|
||||
from jdatetime import datetime
|
||||
|
||||
|
||||
class Tag(BaseModel):
|
||||
code = models.CharField(max_length=20)
|
||||
province = models.ForeignKey(
|
||||
auth_models.Province,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="tag_province",
|
||||
null=True
|
||||
)
|
||||
city = models.ForeignKey(
|
||||
auth_models.City,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='tag_city',
|
||||
null=True
|
||||
)
|
||||
country_code = models.IntegerField(default=364)
|
||||
static_code = models.IntegerField(default=0)
|
||||
ownership_code = models.IntegerField(default=0)
|
||||
species_code = models.IntegerField(default=0)
|
||||
serial = models.CharField(max_length=8)
|
||||
tag_code = models.CharField(max_length=20, unique=True, null=True)
|
||||
organization = models.ForeignKey(
|
||||
auth_models.Organization,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='tag_organization',
|
||||
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):
|
||||
return f'{self.code}'
|
||||
return f'{self.id}-{self.tag_code}'
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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 rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
from .serializers import TagSerializer
|
||||
from .serializers import (
|
||||
TagSerializer,
|
||||
TagAssignmentSerializer,
|
||||
AllocatedTagsSerializer
|
||||
)
|
||||
from rest_framework.decorators import action
|
||||
from django.db import transaction
|
||||
from rest_framework.exceptions import APIException
|
||||
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):
|
||||
@@ -23,9 +34,47 @@ def delete(queryset, pk):
|
||||
|
||||
|
||||
class TagViewSet(viewsets.ModelViewSet):
|
||||
""" Tag View Set """
|
||||
queryset = tag_models.Tag.objects.all()
|
||||
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(
|
||||
methods=['put'],
|
||||
detail=True,
|
||||
@@ -55,4 +104,157 @@ class TagViewSet(viewsets.ModelViewSet):
|
||||
delete(self.queryset, pk)
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
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 apps.tag import models as tag_models
|
||||
from apps.authentication.api.v1.serializers import serializer as auth_serializers
|
||||
|
||||
|
||||
class TagSerializer(serializers.ModelSerializer):
|
||||
""" Tag Model Serializer """
|
||||
|
||||
class Meta:
|
||||
model = tag_models.Tag
|
||||
fields = [
|
||||
'id',
|
||||
'code',
|
||||
'province',
|
||||
'city',
|
||||
'country_code',
|
||||
'static_code',
|
||||
'ownership_code',
|
||||
'species_code',
|
||||
'serial',
|
||||
'tag_code',
|
||||
'organization',
|
||||
'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):
|
||||
""" Customize output of serializer """
|
||||
representation = super().to_representation(instance)
|
||||
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
|
||||
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
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .api import TagViewSet
|
||||
from .api import (
|
||||
TagViewSet,
|
||||
TagAssignmentViewSet,
|
||||
AllocatedTagsViewSet
|
||||
)
|
||||
|
||||
router = DefaultRouter()
|
||||
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 = [
|
||||
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