|
import base64 |
|
import os |
|
import coreapi |
|
import coreschema |
|
import re |
|
import requests |
|
from urllib.parse import urlencode |
|
|
|
from django.contrib.auth.forms import PasswordResetForm |
|
from django.contrib.auth.tokens import PasswordResetTokenGenerator |
|
from django.contrib.sites.shortcuts import get_current_site |
|
from django.core.files.base import ContentFile |
|
from django.core.validators import EmailValidator |
|
from django.db.models import Q |
|
from django.shortcuts import render, get_object_or_404 |
|
from django.contrib.auth import authenticate, login |
|
from django.template.loader import render_to_string |
|
from django.urls import reverse |
|
from django.utils.encoding import force_text, force_bytes |
|
from django.utils.http import urlsafe_base64_encode |
|
from django.utils.translation import ugettext_lazy as _ |
|
|
|
from rest_framework import permissions |
|
from rest_framework import viewsets |
|
from rest_framework import parsers |
|
from rest_framework import mixins |
|
from rest_framework.decorators import list_route, detail_route |
|
from rest_framework.fields import SkipField |
|
from rest_framework.filters import BaseFilterBackend |
|
from rest_framework.generics import ListAPIView, CreateAPIView, GenericAPIView, \ |
|
UpdateAPIView, RetrieveAPIView, RetrieveUpdateAPIView |
|
from rest_framework.pagination import LimitOffsetPagination |
|
from rest_framework.response import Response |
|
from rest_framework import status |
|
from rest_framework import serializers |
|
from rest_framework.views import APIView |
|
|
|
from urllib.parse import urlparse |
|
|
|
from common import utils as common_utils |
|
from common.vt_mail import mail_send |
|
|
|
from . import models as core_models |
|
|
|
|
|
class ExLimitOffsetPagination(LimitOffsetPagination): |
|
max_limit = 20 |
|
default_limit = 10 |
|
|
|
|
|
def _paginate_cls(i_max_limit=20, i_default_limit=10): |
|
class _LimitOffsetPagination(LimitOffsetPagination): |
|
max_limit = i_max_limit |
|
default_limit = i_default_limit |
|
|
|
return _LimitOffsetPagination |
|
|
|
|
|
class BaseFilter(BaseFilterBackend): |
|
action_map = {} |
|
|
|
def get_schema_fields(self, view): |
|
return self.action_map.get(view.action, []) |
|
|
|
|
|
class PetViewSetBackend(BaseFilter): |
|
action_map = { |
|
'list': [ |
|
coreapi.Field( |
|
name='pet', |
|
location='path', |
|
required=True, |
|
type='string' |
|
) |
|
] |
|
} |
|
|
|
def filter_queryset(self, request, queryset, view): |
|
return queryset.filter(pet_id=request.query_params.get('pet')) |
|
|
|
|
|
class BaseExViewSet(viewsets.ViewSet): |
|
# filter_backends = [PetViewSetBackend] |
|
permission_classes = [permissions.IsAuthenticated] |
|
serializer_cls = False |
|
|
|
def list(self, request, *args, **kwargs): |
|
cls = self.serializer_cls |
|
model = cls.Meta.model |
|
qset = model.objects.filter( |
|
user=request.user, pet_id=kwargs['pet_id']) |
|
return Response({'results': cls(qset, many=True).data}) |
|
|
|
@list_route(['post']) |
|
def list_create_update(self, request, *args, **kwargs): |
|
pet = get_object_or_404(core_models.Pet, |
|
pk=kwargs.get('pet_id'), |
|
user=request.user) |
|
ids = {} |
|
cls = self.serializer_cls |
|
model = cls.Meta.model |
|
queryset = model.objects.filter( |
|
user=request.user, pet=pet) |
|
instances = common_utils.qset_to_dict(queryset) |
|
|
|
for item in request.data: |
|
if 'id' in item: |
|
ids[item['id']] = item['id'] |
|
|
|
if len(ids) or not len(request.data): |
|
queryset.exclude(id__in=ids.keys()).delete() |
|
|
|
for item in request.data: |
|
instance = instances.get(item.get('id', None), None) |
|
file = item.get('file', None) |
|
|
|
if instance and hasattr(instance, 'file') and instance.file: |
|
if not file or file.startswith('/media'): |
|
item['file'] = instance.file |
|
|
|
srz = cls(instance=instance, data=item) |
|
srz.is_valid(raise_exception=True) |
|
srz.save(user=request.user, pet_id=pet.pk) |
|
|
|
return self.list(request=request, pet_id=pet.pk) |
|
|
|
|
|
class PetViewSet(viewsets.ModelViewSet): |
|
permission_classes = [permissions.IsAuthenticated] |
|
pagination_class = ExLimitOffsetPagination |
|
|
|
def get_queryset(self): |
|
return core_models.Pet.objects.filter(user=self.request.user) |
|
|
|
def get_serializer_class(self): |
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.Pet |
|
# TODO: add fields |
|
exclude = ['user', 'order', 'created_at', 'updated_at', 'state'] |
|
|
|
return _Serializer |
|
|
|
|
|
class Base64FileField(serializers.FileField): |
|
def to_internal_value(self, data): |
|
if isinstance(data, str) and 'base64' in data[0:100]: |
|
# base64 encoded image - decode |
|
# our custom format file:1.jpg;data: |
|
format, filestr = data.split( |
|
';base64,') # format ~= file:;Mime type;encoding, b64code |
|
filename = format.split(';')[0].split(':')[-1] # guess file name |
|
data = ContentFile(base64.b64decode(filestr), name=filename) |
|
return super(Base64FileField, self).to_internal_value(data) |
|
|
|
|
|
class Base64ImageField(serializers.ImageField): |
|
def to_internal_value(self, data): |
|
if isinstance(data, str) and data.startswith('data:image'): |
|
# base64 encoded image - decode |
|
format, imgstr = data.split(';base64,') # format ~= data:image/X, |
|
ext = format.split('/')[-1] # guess file extension |
|
|
|
data = ContentFile(base64.b64decode(imgstr), name='temp.' + ext) |
|
|
|
return super(Base64ImageField, self).to_internal_value(data) |
|
|
|
|
|
class PetImageViewSet(viewsets.ModelViewSet): |
|
""" |
|
Image |
|
""" |
|
permission_classes = [permissions.IsAuthenticated] |
|
pagination_class = _paginate_cls(30, 30) |
|
|
|
def filter_queryset(self, queryset): |
|
if self.action == 'list': |
|
return super(PetImageViewSet, self).filter_queryset(queryset) |
|
return queryset |
|
|
|
@list_route(['get', 'post']) |
|
def list_create_update(self, request, *args, **kwargs): |
|
queryset = self.filter_queryset(self.get_queryset()) |
|
instances = common_utils.qset_to_dict(queryset) |
|
ids = {} |
|
for item in request.data['items']: |
|
if 'id' in item: |
|
ids[item['id']] = item['id'] |
|
|
|
if len(ids) or not len(request.data['items']): |
|
queryset.exclude(id__in=ids.keys()).delete() |
|
|
|
for item in request.data['items']: |
|
instance = instances.get(item.get('id', None), None) |
|
if instance and not item['image'] or item['image'].startswith( |
|
'http'): |
|
item['image'] = instance.image if instance.image else None |
|
|
|
serializer = self.get_serializer(instance=instance, |
|
data=item) |
|
serializer.is_valid(raise_exception=True) |
|
serializer.save() |
|
|
|
return self.list(request, *args, **kwargs) |
|
|
|
def get_queryset(self): |
|
return core_models.PetImage.objects.filter( |
|
user=self.request.user) |
|
|
|
def get_serializer_class(self): |
|
class _Serializer(serializers.ModelSerializer): |
|
image = Base64ImageField() |
|
parser_classes = [parsers.FormParser, parsers.MultiPartParser] |
|
|
|
class Meta: |
|
model = core_models.PetImage |
|
fields = ['id', 'pet', 'image', 'name', 'created', 'is_cover'] |
|
|
|
return _Serializer |
|
|
|
|
|
##### HEALTH ######## |
|
class PetWeightViewSet(viewsets.ModelViewSet): |
|
""" |
|
Health - weight |
|
|
|
retrieve: |
|
Health - return the given weight. |
|
|
|
list: |
|
Health - return a list of all the existing weights. |
|
|
|
create: |
|
Health - create a new weight instance. |
|
|
|
delete: |
|
Health - delete weight |
|
""" |
|
permission_classes = [permissions.IsAuthenticated] |
|
pagination_class = ExLimitOffsetPagination |
|
|
|
def get_queryset(self): |
|
return core_models.PetWeight.objects.filter( |
|
user=self.request.user, |
|
) |
|
|
|
def get_serializer_class(self): |
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.PetWeight |
|
exclude = ['order', 'created_at', 'updated_at', 'state'] |
|
|
|
return _Serializer |
|
|
|
|
|
class PetHealthSerializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.PetHealth |
|
fields = ['id', 'is_castrated', 'is_flea_safe', 'blood_type'] |
|
|
|
|
|
class PetWeightSerializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.PetWeight |
|
fields = ['id', 'weight', 'weight_date'] |
|
|
|
class PetVaccSerializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.PetVacc |
|
fields = ['id', 'vacc', 'vacc_date', 'fio', 'company'] |
|
|
|
|
|
class PetChipSerializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.PetChip |
|
fields = ['id', 'chip', 'chip_date', 'fio', 'clinic'] |
|
|
|
|
|
class ExHealthViewSet(viewsets.ViewSet): |
|
# filter_backends = [PetViewSetBackend] |
|
permission_classes = [permissions.IsAuthenticated] |
|
|
|
def list(self, request, *args, **kwargs): |
|
health = core_models.PetHealth.objects.filter( |
|
user=request.user, |
|
pet_id=kwargs.get('pet_id')).first() |
|
|
|
if health: |
|
health_srz = PetHealthSerializer(health) |
|
dc_response = {'pethealth': health_srz.data} |
|
else: |
|
dc_response = {'pethealth': {}} |
|
|
|
for cls in [PetWeightSerializer, PetAntiHelmSerializer, |
|
PetVaccSerializer, PetChipSerializer]: |
|
model = cls.Meta.model |
|
qset = model.objects.filter(user=request.user) |
|
# return queryset.filter(pet_id=request.query_params.get('pet')) |
|
if request.query_params.get('pet'): |
|
qset = qset.filter(pet_id=request.query_params.get('pet')) |
|
dc_response[model._meta.model_name] = cls( |
|
qset, |
|
many=True).data |
|
|
|
return Response({'results': dc_response}) # serialazer |
|
|
|
@list_route(['post']) |
|
def list_create_update(self, request, *args, **kwargs): |
|
data = request.data |
|
pet = get_object_or_404(core_models.Pet, |
|
pk=kwargs.get('pet_id'), |
|
user=request.user) |
|
pethealth = core_models.PetHealth.objects.filter( |
|
user=request.user, |
|
pet=pet).first() |
|
|
|
srz = PetHealthSerializer(instance=pethealth, |
|
data=data['pethealth']) |
|
srz.is_valid(raise_exception=True) |
|
srz.save(user=request.user, pet_id=pet.pk) |
|
|
|
for cls in [PetWeightSerializer, PetAntiHelmSerializer, |
|
PetVaccSerializer, PetChipSerializer]: |
|
ids = {} |
|
model = cls.Meta.model |
|
queryset = model.objects.filter(user=request.user, pet=pet) |
|
instances = common_utils.qset_to_dict(queryset) |
|
model_name = model._meta.model_name |
|
for item in request.data[model_name]: |
|
if 'id' in item: |
|
ids[item['id']] = item['id'] |
|
|
|
if len(ids) or not len(request.data[model_name]): |
|
queryset.exclude(id__in=ids.keys()).delete() |
|
|
|
for item in request.data[model_name]: |
|
instance = instances.get(item.get('id', None), None) |
|
srz = cls(instance=instance, data=item) |
|
srz.is_valid(raise_exception=True) |
|
srz.save(user=request.user, pet_id=pet.pk) |
|
|
|
return self.list(request=request, pet_id=pet.pk) |
|
|
|
class PetAntiHelmViewSet(viewsets.ModelViewSet): |
|
""" |
|
Health - antihelm |
|
""" |
|
permission_classes = [permissions.IsAuthenticated] |
|
pagination_class = ExLimitOffsetPagination |
|
|
|
def get_queryset(self): |
|
return core_models.PetAntiHelm.objects.filter( |
|
user=self.request.user, |
|
) |
|
|
|
def get_serializer_class(self): |
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.PetAntiHelm |
|
exclude = ['order', 'created_at', 'updated_at', 'state'] |
|
|
|
return _Serializer |
|
|
|
|
|
class HelmViewSet(viewsets.ReadOnlyModelViewSet): |
|
""" |
|
Health - antihelm preparate |
|
""" |
|
permission_classes = [permissions.IsAuthenticated] |
|
pagination_class = _paginate_cls(1000, 1000) |
|
|
|
def get_queryset(self): |
|
return core_models.Helm.objects.all() |
|
|
|
def get_serializer_class(self): |
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.Helm |
|
fields = ['id', 'title'] |
|
|
|
return _Serializer |
|
|
|
|
|
class VaccViewSet(viewsets.ReadOnlyModelViewSet): |
|
""" |
|
Health - Vaccine preparate |
|
""" |
|
permission_classes = [permissions.IsAuthenticated] |
|
pagination_class = _paginate_cls(1000, 1000) |
|
|
|
def get_queryset(self): |
|
return core_models.Vacc.objects.all() |
|
|
|
def get_serializer_class(self): |
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.Vacc |
|
fields = ['id', 'title'] |
|
|
|
return _Serializer |
|
|
|
|
|
class PetVaccViewSet(viewsets.ModelViewSet): |
|
""" |
|
Health - Vaccine |
|
""" |
|
permission_classes = [permissions.IsAuthenticated] |
|
pagination_class = ExLimitOffsetPagination |
|
|
|
def get_queryset(self): |
|
return core_models.PetVacc.objects.filter( |
|
user=self.request.user, |
|
) |
|
|
|
def get_serializer_class(self): |
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.PetVacc |
|
exclude = ['order', 'created_at', 'updated_at', 'state'] |
|
|
|
return _Serializer |
|
|
|
|
|
class PetChipViewSet(viewsets.ReadOnlyModelViewSet): |
|
""" |
|
Health - Chip |
|
""" |
|
permission_classes = [permissions.IsAuthenticated] |
|
pagination_class = _paginate_cls(1000, 1000) |
|
|
|
def get_queryset(self): |
|
return core_models.PetChip.objects.filter( |
|
user=self.request.user, |
|
) |
|
|
|
def get_serializer_class(self): |
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.PetChip |
|
exclude = ['order', 'created_at', 'updated_at', 'state'] |
|
|
|
return _Serializer |
|
|
|
|
|
##### FEED #### |
|
class FeedBrandViewSet(viewsets.ReadOnlyModelViewSet): |
|
""" |
|
Feed - brand dry |
|
""" |
|
permission_classes = [permissions.IsAuthenticated] |
|
pagination_class = ExLimitOffsetPagination |
|
|
|
def get_queryset(self): |
|
return core_models.FeedBrand.objects.filter(is_wet=False) |
|
|
|
def get_serializer_class(self): |
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.FeedBrand |
|
fields = ['id', 'title', 'is_wet'] |
|
|
|
return _Serializer |
|
|
|
|
|
class FeedBrandWetViewSet(viewsets.ReadOnlyModelViewSet): |
|
""" |
|
Feed - brand wet |
|
""" |
|
permission_classes = [permissions.IsAuthenticated] |
|
pagination_class = ExLimitOffsetPagination |
|
|
|
def get_queryset(self): |
|
return core_models.FeedBrand.objects.filter(is_wet=True) |
|
|
|
def get_serializer_class(self): |
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.FeedBrand |
|
fields = ['id', 'title', 'is_wet'] |
|
|
|
return _Serializer |
|
|
|
|
|
class FillerBrandViewSet(viewsets.ReadOnlyModelViewSet): |
|
""" |
|
Feed - filler brand |
|
""" |
|
permission_classes = [permissions.IsAuthenticated] |
|
pagination_class = ExLimitOffsetPagination |
|
|
|
def get_queryset(self): |
|
return core_models.FillerBrand.objects.all() |
|
|
|
def get_serializer_class(self): |
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.FillerBrand |
|
fields = ['id', 'title'] |
|
|
|
return _Serializer |
|
|
|
|
|
# DEPRECATED |
|
class PetFeedViewSet( |
|
mixins.RetrieveModelMixin, |
|
mixins.UpdateModelMixin, |
|
viewsets.GenericViewSet): |
|
""" |
|
Feed |
|
""" |
|
permission_classes = [permissions.IsAuthenticated] |
|
pagination_class = ExLimitOffsetPagination |
|
|
|
def get_queryset(self): |
|
return core_models.Pet.objects.filter( |
|
user=self.request.user, |
|
) |
|
|
|
def get_serializer_class(self): |
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.Pet |
|
fields = ['id', |
|
'feed_brand_dry', |
|
'feed_brand_wet', |
|
'extra_feed', |
|
'is_tray', |
|
'is_filler', |
|
'filler_brand'] |
|
|
|
return _Serializer |
|
|
|
|
|
###### Exhibition #### |
|
class ExhibitionViewSet(viewsets.ModelViewSet): |
|
""" |
|
Exhibition |
|
""" |
|
permission_classes = [permissions.IsAuthenticated] |
|
pagination_class = ExLimitOffsetPagination |
|
|
|
def get_queryset(self): |
|
return core_models.Exhibition.objects.filter( |
|
user=self.request.user, |
|
).order_by('id') |
|
|
|
def get_serializer_class(self): |
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.Exhibition |
|
exclude = ['order', 'created_at', 'updated_at', 'state'] |
|
|
|
return _Serializer |
|
|
|
|
|
class ExhibitionSerializer(serializers.ModelSerializer): |
|
file = Base64FileField(required=False, |
|
allow_null=True) |
|
filename = serializers.SerializerMethodField(required=False, |
|
allow_null=True) |
|
|
|
def get_filename(self, obj): |
|
if obj.file: |
|
return os.path.basename(obj.file.name) |
|
return '' |
|
|
|
parser_classes = [parsers.FormParser, parsers.MultiPartParser] |
|
|
|
class Meta: |
|
model = core_models.Exhibition |
|
fields = ['id', 'date_from', 'date_to', 'association', 'club', |
|
'ex_class', |
|
'ex_title', 'city', 'fio', |
|
'file', 'filename'] |
|
|
|
|
|
class ExExhibitionViewSet(BaseExViewSet): |
|
serializer_cls = ExhibitionSerializer |
|
|
|
|
|
class PetRankSerializer(serializers.ModelSerializer): |
|
file = Base64FileField(required=False, |
|
allow_null=True) |
|
filename = serializers.SerializerMethodField(required=False, |
|
allow_null=True) |
|
|
|
def get_filename(self, obj): |
|
if obj.file: |
|
return os.path.basename(obj.file.name) |
|
return '' |
|
|
|
parser_classes = [parsers.FormParser, parsers.MultiPartParser] |
|
|
|
class Meta: |
|
model = core_models.PetRank |
|
fields = ['id', 'association', 'rank', 'file', 'filename'] |
|
# fields = ['id', 'association', 'rank'] |
|
|
|
|
|
class PetRankViewSet(viewsets.ModelViewSet): |
|
""" |
|
Pet Rank |
|
""" |
|
permission_classes = [permissions.IsAuthenticated] |
|
pagination_class = ExLimitOffsetPagination |
|
serializer_class = PetRankSerializer |
|
|
|
def get_queryset(self): |
|
return core_models.PetRank.objects.filter( |
|
user=self.request.user, |
|
pet_id=self.kwargs['pet_id'] |
|
) |
|
|
|
|
|
class ExPetRankViewSet(BaseExViewSet): |
|
""" |
|
Pet Rank - list C&U |
|
""" |
|
serializer_cls = PetRankSerializer |
|
|
|
|
|
class AssociationViewSet(viewsets.ReadOnlyModelViewSet): |
|
""" |
|
Exhibition - association |
|
""" |
|
permission_classes = [permissions.IsAuthenticated] |
|
pagination_class = ExLimitOffsetPagination |
|
|
|
def get_queryset(self): |
|
return core_models.Association.objects.all() |
|
|
|
def get_serializer_class(self): |
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.Association |
|
fields = ['id', 'title'] |
|
|
|
return _Serializer |
|
|
|
|
|
class RankViewSet(viewsets.ReadOnlyModelViewSet): |
|
""" |
|
Exhibition - rank |
|
""" |
|
permission_classes = [permissions.IsAuthenticated] |
|
pagination_class = ExLimitOffsetPagination |
|
|
|
def get_queryset(self): |
|
return core_models.Rank.objects.all() |
|
|
|
def get_serializer_class(self): |
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.Rank |
|
fields = ['id', 'title'] |
|
|
|
return _Serializer |
|
|
|
|
|
#### bloodline ### |
|
class ClubViewSet(viewsets.ModelViewSet): |
|
""" |
|
Bloodline - Club |
|
""" |
|
permission_classes = [permissions.IsAuthenticated] |
|
pagination_class = ExLimitOffsetPagination |
|
|
|
def get_queryset(self): |
|
return core_models.Club.objects.all() |
|
|
|
def get_serializer_class(self): |
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.Club |
|
fields = ['id', 'title'] |
|
|
|
return _Serializer |
|
|
|
|
|
class MetriсViewSet(viewsets.ModelViewSet): |
|
""" |
|
Bloodline - Metric |
|
""" |
|
permission_classes = [permissions.IsAuthenticated] |
|
pagination_class = ExLimitOffsetPagination |
|
filter_backends = [PetViewSetBackend] |
|
|
|
def get_queryset(self): |
|
return core_models.PetMetric.objects.filter( |
|
user=self.request.user, |
|
) |
|
|
|
def filter_queryset(self, queryset): |
|
if self.action == 'list': |
|
return super(MetriсViewSet, self).filter_queryset(queryset) |
|
return queryset |
|
|
|
def get_serializer_class(self): |
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.PetMetric |
|
exclude = ['order', 'created_at', 'updated_at', 'state'] |
|
|
|
return _Serializer |
|
|
|
|
|
class MetriсSerializer(serializers.ModelSerializer): |
|
file = Base64FileField(required=False, |
|
allow_null=True) |
|
filename = serializers.SerializerMethodField(required=False, |
|
allow_null=True) |
|
|
|
def get_filename(self, obj): |
|
if obj.file: |
|
return os.path.basename(obj.file.name) |
|
return '' |
|
|
|
parser_classes = [parsers.FormParser, parsers.MultiPartParser] |
|
|
|
class Meta: |
|
model = core_models.PetMetric |
|
fields = ['id', 'metric_number', 'club', 'club_system', |
|
'file', 'filename' |
|
] |
|
|
|
class BloodlineSerializer(serializers.ModelSerializer): |
|
file = Base64FileField(required=False, |
|
allow_null=True) |
|
filename = serializers.SerializerMethodField(required=False, |
|
allow_null=True) |
|
|
|
def get_filename(self, obj): |
|
if obj.file: |
|
return os.path.basename(obj.file.name) |
|
return '' |
|
|
|
parser_classes = [parsers.FormParser, parsers.MultiPartParser] |
|
|
|
class Meta: |
|
model = core_models.PetBloodline |
|
fields = ['id', 'metric_number', 'club', 'club_system', |
|
'file', 'filename' |
|
] |
|
|
|
|
|
class PetParentSerializer(serializers.ModelSerializer): |
|
file = Base64FileField(required=False, |
|
allow_null=True) |
|
filename = serializers.SerializerMethodField(required=False, |
|
allow_null=True) |
|
|
|
def get_filename(self, obj): |
|
if obj.file: |
|
return os.path.basename(obj.file.name) |
|
return '' |
|
|
|
class Meta: |
|
model = core_models.PetParent |
|
fields = ['id', 'pet', 'pet_parent', 'gender', 'breed', |
|
'nickname', 'breeder_fio', 'owner_fio', 'color', 'rank', |
|
'file', 'filename'] |
|
|
|
|
|
class ExBloodlineViewSet(viewsets.ViewSet): |
|
filter_backends = [PetViewSetBackend] |
|
permission_classes = [permissions.IsAuthenticated] |
|
srzs_list = [BloodlineSerializer, MetriсSerializer, |
|
# PetParentSerializer # don't need it any more |
|
] |
|
|
|
def list(self, request, *args, **kwargs): |
|
dc_response = {} |
|
|
|
for cls in self.srzs_list: |
|
model = cls.Meta.model |
|
qset = model.objects.filter(user=request.user) |
|
if kwargs.get('pet_id'): |
|
qset = qset.filter( |
|
pet_id=kwargs.get('pet_id')).order_by('id') |
|
dc_response[model._meta.model_name] = cls( |
|
qset, many=True).data |
|
|
|
q_parents = core_models.PetParent.objects.filter( |
|
user=request.user, pet_id=kwargs.get('pet_id')) |
|
try: |
|
dc_response['mother'] = PetParentSerializer( |
|
q_parents.get(gender=2)).data |
|
except core_models.PetParent.DoesNotExist: |
|
pass |
|
|
|
try: |
|
dc_response['father'] = PetParentSerializer( |
|
q_parents.get(gender=3)).data |
|
except core_models.PetParent.DoesNotExist: |
|
pass |
|
|
|
return Response({'results': dc_response}) |
|
|
|
@list_route(['post']) |
|
def list_create_update(self, request, *args, **kwargs): |
|
data = request.data |
|
pet = get_object_or_404(core_models.Pet, |
|
pk=kwargs.get('pet_id'), |
|
user=request.user) |
|
|
|
for cls in self.srzs_list: |
|
ids = {} |
|
model = cls.Meta.model |
|
queryset = model.objects.filter(user=request.user, pet=pet) |
|
instances = common_utils.qset_to_dict(queryset) |
|
model_name = model._meta.model_name |
|
|
|
for item in request.data[model_name]: |
|
if 'id' in item: |
|
ids[item['id']] = item['id'] |
|
|
|
if len(ids) or not len(request.data[model_name]): |
|
queryset.exclude(id__in=ids.keys()).delete() |
|
|
|
for item in request.data[model_name]: |
|
instance = instances.get(item.get('id', None), None) |
|
file = item.get('file', None) |
|
|
|
if instance and hasattr(instance, 'file') and instance.file: |
|
if not file or file.startswith('/media'): |
|
item['file'] = instance.file |
|
|
|
srz = cls(instance=instance, data=item) |
|
srz.is_valid(raise_exception=True) |
|
srz.save(user=request.user, pet_id=pet.pk) |
|
|
|
mother = request.data['mother'] |
|
if len(mother) >= 1: |
|
queryset = core_models.PetParent.objects.filter( |
|
user=request.user, pet=pet) |
|
instances = common_utils.qset_to_dict(queryset) |
|
instance = instances.get(mother.get('id', None), None) |
|
file = mother.get('file', None) |
|
|
|
if instance and hasattr(instance, 'file') and instance.file: |
|
if not file or file.startswith('/media'): |
|
mother['file'] = instance.file |
|
|
|
srz = PetParentSerializer(instance=instance, data=mother) |
|
srz.is_valid(raise_exception=True) |
|
srz.save(user=request.user, pet_id=pet.pk) |
|
|
|
father = request.data['father'] |
|
if len(father) >= 1: |
|
queryset = core_models.PetParent.objects.filter( |
|
user=request.user, pet=pet) |
|
instances = common_utils.qset_to_dict(queryset) |
|
instance = instances.get(father.get('id', None), None) |
|
file = father.get('file', None) |
|
|
|
if instance and hasattr(instance, 'file') and instance.file: |
|
if not file or file.startswith('/media'): |
|
father['file'] = instance.file |
|
|
|
srz = PetParentSerializer(instance=instance, data=father) |
|
srz.is_valid(raise_exception=True) |
|
srz.save(user=request.user, pet_id=pet.pk) |
|
|
|
return self.list(request=request, pet_id=pet.pk) |
|
|
|
|
|
class BreedViewSet(viewsets.ModelViewSet): |
|
""" |
|
Bloodline - Breed |
|
""" |
|
permission_classes = [permissions.IsAuthenticated] |
|
pagination_class = ExLimitOffsetPagination |
|
|
|
def get_queryset(self): |
|
return core_models.Breed.objects.all() |
|
|
|
def get_serializer_class(self): |
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.Breed |
|
fields = ['id', 'title'] |
|
|
|
return _Serializer |
|
|
|
|
|
##### SELL #### |
|
class CurrencyViewSet(viewsets.ReadOnlyModelViewSet): |
|
""" |
|
Currency |
|
""" |
|
permission_classes = [permissions.IsAuthenticated] |
|
pagination_class = ExLimitOffsetPagination |
|
|
|
def get_queryset(self): |
|
return core_models.Currency.objects.all() |
|
|
|
def get_serializer_class(self): |
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.Currency |
|
fields = ['id', 'title', 'rate'] |
|
|
|
return _Serializer |
|
|
|
|
|
class SellTypeViewSet(viewsets.ReadOnlyModelViewSet): |
|
""" |
|
Sell Type |
|
""" |
|
permission_classes = [permissions.IsAuthenticated] |
|
pagination_class = ExLimitOffsetPagination |
|
|
|
def get_queryset(self): |
|
return core_models.SellType.objects.all() |
|
|
|
def get_serializer_class(self): |
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.SellType |
|
fields = ['id', 'title'] |
|
|
|
return _Serializer |
|
|
|
|
|
class ExBreedViewSet(viewsets.ViewSet): |
|
pagination_class = _paginate_cls(1000, 1000) |
|
|
|
def _response_get(self, md): |
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = md |
|
fields = ['id', 'title'] |
|
|
|
srz = _Serializer(md.objects.all(), many=True) |
|
return Response({'results': srz.data}) |
|
|
|
def list(self, request, *args, **kwargs): |
|
return self._response_get(core_models.Breed) |
|
|
|
@list_route(['GET']) |
|
def search(self, request, *args, **kwargs): |
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.Breed |
|
fields = ['id', 'title'] |
|
|
|
srz = _Serializer(core_models.Breed.objects.filter( |
|
title__icontains=request.query_params.get('term', ''))[0:40], |
|
many=True) |
|
# return Response({'results': srz.data}) |
|
return Response({'results': srz.data}) |
|
|
|
def retrieve(self, request, pk=None): |
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.ColorData |
|
fields = ['id', 'title'] |
|
|
|
breed = get_object_or_404(core_models.Breed, pk=pk) |
|
return Response(_Serializer(breed).data) |
|
|
|
|
|
class ColorViewSet(viewsets.ViewSet): |
|
pagination_class = _paginate_cls(1000, 1000) |
|
|
|
def _response_get(self, md): |
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = md |
|
fields = ['id', 'title'] |
|
|
|
srz = _Serializer(md.objects.all(), many=True) |
|
return Response({'results': srz.data}) |
|
|
|
@list_route(['GET']) |
|
def list_color_main(self, request, *args, **kwargs): |
|
return self._response_get(core_models.ColorMain) |
|
|
|
@list_route(['GET']) |
|
def list_color_eye(self, request, *args, **kwargs): |
|
return self._response_get(core_models.ColorEye) |
|
|
|
@list_route(['GET']) |
|
def list_color_image(self, request, *args, **kwargs): |
|
return self._response_get(core_models.ColorImage) |
|
|
|
@list_route(['GET']) |
|
def list_color_white_image(self, request, *args, **kwargs): |
|
return self._response_get(core_models.ColorWhiteImage) |
|
|
|
@list_route(['GET']) |
|
def list_color_spot(self, request, *args, **kwargs): |
|
return self._response_get(core_models.ColorSpot) |
|
|
|
@list_route(['GET']) |
|
def list_color_point(self, request, *args, **kwargs): |
|
return self._response_get(core_models.ColorPoint) |
|
|
|
def retrieve(self, request, pk=None): |
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.ColorData |
|
fields = ['id', 'title', 'color_main', 'color_image', |
|
'color_white_image', 'color_point', |
|
'color_gold_silver'] |
|
|
|
color_data = get_object_or_404(core_models.ColorData, pk=pk) |
|
return Response(_Serializer(color_data).data) |
|
|
|
@list_route(['GET']) |
|
def sub_color_search(self, request, *args, **kwargs): |
|
query = {} |
|
for field in ['color_main', 'color_image', 'color_white_image', |
|
'color_point', 'color_gold_silver']: |
|
field_name = '{}_id'.format(field) |
|
query[field_name] = request.query_params.get(field, '') |
|
if not query[field_name]: |
|
query[field_name] = None |
|
|
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.ColorData |
|
fields = ['id', 'title', 'color_main', 'color_image', |
|
'color_white_image', 'color_point', |
|
'color_gold_silver'] |
|
|
|
item = core_models.ColorData.objects.filter(**query).first() |
|
if item: |
|
return Response(_Serializer(item).data) |
|
|
|
return Response(status=status.HTTP_204_NO_CONTENT) |
|
|
|
@list_route(['GET']) |
|
def color_search(self, request, *args, **kwargs): |
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
model = core_models.ColorData |
|
fields = ['id', 'title'] |
|
|
|
srz = _Serializer(core_models.ColorData.objects.filter( |
|
title__icontains=request.query_params.get('term', ''))[0:40], |
|
many=True) |
|
# return Response({'results': srz.data}) |
|
return Response({'results': srz.data}) |
|
|
|
|
|
class ExCharField(serializers.CharField): |
|
def get_attribute(self, instance): |
|
raise SkipField() |
|
|
|
|
|
class ExEmailField(serializers.EmailField): |
|
def get_attribute(self, instance): |
|
raise SkipField() |
|
|
|
|
|
class ExBooleanField(serializers.BooleanField): |
|
def get_attribute(self, instance): |
|
raise SkipField() |
|
|
|
|
|
class UserCreateAPIView(CreateAPIView): |
|
permission_classes = [permissions.AllowAny] |
|
|
|
def get_serializer_class(self): |
|
request = self.request |
|
|
|
class _Serializer(serializers.ModelSerializer): |
|
password = ExCharField() |
|
password_repeat = ExCharField() |
|
sms = ExCharField() |
|
is_agreement = ExBooleanField() |
|
|
|
def validate(self, attrs): |
|
if core_models.User.objects.filter(email=attrs['email']): |
|
raise serializers.ValidationError( |
|
{'email': [_('User with same email exists')]}) |
|
|
|
if core_models.User.objects.filter(phone=attrs['phone']): |
|
raise serializers.ValidationError( |
|
{'phone': [_('User with same phone exists')]}) |
|
|
|
if attrs['password'] != attrs['password_repeat']: |
|
raise serializers.ValidationError({ |
|
'password': [_('Passwords not equal')], |
|
'password_repeat': [_('Passwords not equal')] |
|
}) |
|
|
|
if attrs['sms'] != request.session.get('sms'): |
|
raise serializers.ValidationError({ |
|
'sms': [_('Wrong sms')] |
|
}) |
|
|
|
|
|
return attrs |
|
|
|
def create(self, validated_data): |
|
user = core_models.User.objects.create( |
|
email=validated_data['email'], |
|
first_name=validated_data['first_name'], |
|
last_name=validated_data['last_name'], |
|
phone=validated_data['phone'] |
|
) |
|
|
|
user.set_password(validated_data['password']) |
|
user.save() |
|
|
|
user_auth = authenticate( |
|
username=user.email, |
|
password=validated_data['password'], |
|
) |
|
|
|
login(request, user_auth) |
|
return user |
|
|
|
class Meta: |
|
model = core_models.User |
|
fields = ['first_name', |
|
'last_name', |
|
'middle_name', |
|
'is_agreement', |
|
'sms', |
|
'email', |
|
'phone', |
|
'password', |
|
'password_repeat'] |
|
write_only_fields = ['password', 'password_repeat'] |
|
read_only_fields = ['id'] |
|
|
|
return _Serializer |
|
|
|
|
|
class ProfileUpdateAPIView(RetrieveUpdateAPIView): |
|
permission_classes = [permissions.IsAuthenticated] |
|
pagination_class = None |
|
|
|
def get_object(self): |
|
return self.request.user |
|
|
|
def get_serializer_class(self): |
|
data = self.request.data |
|
required = {'required': True, 'allow_blank': False} |
|
ekw = {'first_name': required, 'last_name': required} |
|
user = self.request.user |
|
|
|
if data and data['is_breeder']: |
|
for field_name in ['address', 'office', 'education']: |
|
ekw[field_name] = required |
|
|
|
ekw['language'] = {'required': True, 'allow_null': False} |
|
ekw['country'] = {'required': True, 'allow_null': False} |
|
ekw['birth'] = {'required': True, 'allow_null': False} |
|
|
|
if self.request.method == 'PUT' and data.get('image') and data[ |
|
'image'].startswith('http') and user.image: |
|
data['image'] = user.image |
|
|
|
class _Serializer(serializers.ModelSerializer): |
|
image = Base64ImageField(allow_null=True) |
|
parser_classes = [parsers.FormParser, parsers.MultiPartParser] |
|
|
|
class Meta: |
|
model = core_models.User |
|
fields = ['first_name', 'last_name', 'middle_name', |
|
'image', |
|
'is_breeder', |
|
'country', 'city', 'address', 'office', 'birth', |
|
'about', 'language', 'education', 'course', 'link'] |
|
extra_kwargs = ekw |
|
|
|
return _Serializer |
|
|
|
|
|
class SmsAPIView(APIView): |
|
permission_classes = [permissions.AllowAny] |
|
|
|
def post(self, request, *args, **kwargs): |
|
from django.utils.crypto import get_random_string |
|
from django.conf import settings |
|
from apps.core.smsc_api import SMSC |
|
|
|
if getattr(settings, 'SKIP_SMS'): |
|
request.session['sms'] = '123' |
|
return Response({'success': True}) |
|
|
|
try: |
|
phone = re.match('\d{11}', request.data.get('phone', '')).group(0) |
|
code = get_random_string(length=4, allowed_chars='1234567890') |
|
smsc = SMSC() |
|
|
|
smsc.send_sms(phone, 'Onpet.me Код: {}'.format(code)) |
|
request.session['sms'] = code |
|
|
|
return Response({'success': True}) |
|
except Exception as e: |
|
return Response({'phone': [_('invalid phone number')]}, |
|
status=status.HTTP_400_BAD_REQUEST) |
|
|
|
|
|
class BaseDictViewSet(viewsets.ReadOnlyModelViewSet): |
|
class BaseDictBackend(BaseFilter): |
|
action_map = { |
|
'list': [ |
|
coreapi.Field( |
|
name='term', |
|
location='path', |
|
required=False, |
|
type='string' |
|
) |
|
] |
|
} |
|
|
|
def filter_queryset(self, request, queryset, view): |
|
term = request.query_params.get('term') |
|
if term: |
|
return queryset.filter(title__icontains=term) |
|
return queryset |
|
|
|
permission_classes = [permissions.AllowAny] |
|
pagination_class = _paginate_cls(1000, 1000) |
|
filter_backends = [BaseDictBackend] |
|
|
|
def get_serializer_class(self): |
|
self_model = self.queryset.model |
|
|
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
fields = ['id', 'title'] |
|
model = self_model |
|
|
|
return _Serializer |
|
|
|
|
|
class CountryViewSet(BaseDictViewSet): |
|
queryset = core_models.Country.objects.all() |
|
|
|
|
|
class CityViewSet(viewsets.ReadOnlyModelViewSet): |
|
queryset = core_models.City.objects.all() |
|
permission_classes = [permissions.AllowAny] |
|
pagination_class = _paginate_cls(1000, 1000) |
|
|
|
def get_serializer_class(self): |
|
self_model = self.queryset.model |
|
|
|
class _Serializer(serializers.ModelSerializer): |
|
class Meta: |
|
fields = ['id', 'title'] |
|
model = self_model |
|
|
|
return _Serializer |
|
|
|
class CityDictBackend(BaseFilter): |
|
action_map = { |
|
'list': [ |
|
coreapi.Field( |
|
name='term', |
|
location='path', |
|
required=False, |
|
type='string' |
|
), |
|
coreapi.Field( |
|
name='country_id', |
|
location='path', |
|
required=False, |
|
type='string' |
|
) |
|
], |
|
|
|
} |
|
|
|
def filter_queryset(self, request, queryset, view): |
|
term = request.query_params.get('term') |
|
country_id = request.query_params.get('country_id') |
|
|
|
if term: |
|
queryset = queryset.filter(title__icontains=term) |
|
|
|
if country_id: |
|
queryset = queryset.filter(country_id=country_id) |
|
|
|
return queryset |
|
|
|
filter_backends = [CityDictBackend] |
|
|
|
@list_route(['post']) |
|
def city_set(self, request, *args, **kwargs): |
|
city = get_object_or_404(core_models.City, |
|
pk=request.data.get('city_id')) |
|
if request.user.is_authenticated(): |
|
request.user.city = city |
|
request.user.country_id = city.country_id |
|
request.user.save() |
|
else: |
|
request.session['current_city_id'] = city.pk |
|
|
|
return Response({'city': city.title, 'country': city.country.title}) |
|
|
|
|
|
class LanguageViewSet(BaseDictViewSet): |
|
queryset = core_models.Language.objects.all() |
|
|
|
|
|
class NurseryViewSet(viewsets.ModelViewSet): |
|
permission_classes = [permissions.AllowAny] |
|
pagination_class = None |
|
|
|
def get_queryset(self): |
|
return core_models.Nursery.objects.filter(user=self.request.user) |
|
|
|
def perform_create(self, serializer): |
|
# TODO: remove with list edit |
|
core_models.Nursery.objects.filter(user=self.request.user).delete() |
|
serializer.save(user=self.request.user) |
|
|
|
def update(self, request, *args, **kwargs): |
|
instance = self.get_object() |
|
|
|
if not request.data['cert'] or 'media/' in request.data['cert']: |
|
request.data['cert'] = instance.cert if instance.cert else None |
|
|
|
if not request.data['diploma'] or 'media/' in request.data['diploma']: |
|
request.data[ |
|
'diploma'] = instance.diploma if instance.diploma else None |
|
|
|
return super(NurseryViewSet, self).update(request, *args, **kwargs) |
|
|
|
def get_serializer_class(self): |
|
class _Serializer(serializers.ModelSerializer): |
|
cert = Base64FileField() |
|
diploma = Base64FileField(allow_null=True) |
|
|
|
parser_classes = [parsers.FormParser, parsers.MultiPartParser] |
|
|
|
cert_filename = serializers.SerializerMethodField(required=False, |
|
allow_null=True) |
|
diploma_filename = serializers.SerializerMethodField(required=False, |
|
allow_null=True) |
|
|
|
def get_cert_filename(self, obj): |
|
if obj.cert: |
|
return os.path.basename(obj.cert.name) |
|
return '' |
|
|
|
def is_valid(self, raise_exception=False): |
|
return super(_Serializer, self).is_valid( |
|
raise_exception=raise_exception) |
|
|
|
def get_diploma_filename(self, obj): |
|
if obj.diploma: |
|
return os.path.basename(obj.diploma.name) |
|
return '' |
|
|
|
|
|
class Meta: |
|
fields = ['id', 'title', 'register_at', 'association', 'club', |
|
'link', 'cert', 'diploma', 'cert_filename', |
|
'diploma_filename'] |
|
model = core_models.Nursery |
|
extra_kwargs = { |
|
'register_at': {'allow_null': True}} |
|
|
|
return _Serializer |
|
|
|
|
|
class LoginAPIView(APIView): |
|
permission_classes = [permissions.AllowAny] |
|
|
|
class _Serializer(serializers.Serializer): |
|
username = serializers.CharField() |
|
password = serializers.CharField() |
|
|
|
def validate(self, attrs): |
|
user = core_models.User.objects.filter(Q(email=attrs['username']) |
|
| Q( |
|
phone=attrs['username'])).first() |
|
err = { |
|
'username': [_('wrong password or email or phone')], |
|
'password': [_('wrong password or email or phone')] |
|
} |
|
|
|
if not user: |
|
raise serializers.ValidationError(err) |
|
|
|
user_auth = authenticate( |
|
username=user.email, |
|
password=attrs['password'], |
|
) |
|
|
|
if not user_auth: |
|
raise serializers.ValidationError(err) |
|
|
|
attrs['user_auth'] = user_auth |
|
return attrs |
|
|
|
def post(self, request, *args, **kwargs): |
|
serializer = LoginAPIView._Serializer(data=request.data) |
|
serializer.is_valid(raise_exception=True) |
|
validated_data = serializer.validated_data |
|
login(request, validated_data['user_auth']) |
|
|
|
return Response({'success': True}) |
|
|
|
|
|
class AuthManagerViewSet(viewsets.GenericViewSet): |
|
# filter_backends = [PetViewSetBackend] |
|
email_template_name = 'apps/activation/mails/password_reset.j2', |
|
|
|
permission_classes = [permissions.AllowAny] |
|
|
|
|
|
def get_serializer_class(self): |
|
if self.action == 'reset_password': |
|
class _Serializer(serializers.Serializer): |
|
username = serializers.CharField() |
|
|
|
def validate(self, attrs): |
|
self.is_phone = False |
|
# EmailValidator(message=self.error_messages['invalid']) |
|
# +7950 0376541 (11) |
|
username = attrs['username'].strip('+') |
|
if '@' in username: |
|
try: |
|
EmailValidator(message=_('invalid email'))(username) |
|
self.user = core_models.User.objects.get( |
|
email=username) |
|
except Exception as e: |
|
raise serializers.ValidationError( |
|
{'username': [_('invalid email')]}) |
|
else: |
|
self.is_phone = True |
|
try: |
|
re.match('\d{11}', username).group(0) |
|
self.user = core_models.User.objects.get( |
|
phone=username) |
|
except Exception as e: |
|
raise serializers.ValidationError( |
|
{'username': [_('invalid phone')]}) |
|
|
|
return attrs |
|
|
|
return _Serializer |
|
return None |
|
|
|
@list_route(['post']) |
|
def reset_password(self, request, *args, **kwargs): |
|
srz = self.get_serializer(data=request.data) |
|
srz.is_valid(raise_exception=True) |
|
|
|
if srz.is_phone: |
|
# TODO: maybe add phone rcovery |
|
pass |
|
|
|
site = get_current_site(request) |
|
token = PasswordResetTokenGenerator().make_token(srz.user) |
|
|
|
url = 'http://{}{}'.format(site.domain, |
|
reverse('activation:password_reset_confirm', |
|
kwargs={'uidb64': urlsafe_base64_encode( |
|
force_bytes(srz.user.pk)), 'token': token})) |
|
|
|
html = render_to_string(self.email_template_name, { |
|
'user': srz.user, |
|
'url': url, |
|
'site': site |
|
}) |
|
try: |
|
mail_send(srz.user.email, message_html=html, |
|
subject=_('password reset')) |
|
return Response({'success': True, 'email': srz.user.email}) |
|
except Exception as e: |
|
return Response({'success': False, 'error': str(e)}) |
|
|
|
return Response({'hello': 'world'}) |