diff --git a/Rasaddam_Backend/settings.py b/Rasaddam_Backend/settings.py index 969fd7b..9aba70b 100644 --- a/Rasaddam_Backend/settings.py +++ b/Rasaddam_Backend/settings.py @@ -14,11 +14,9 @@ from pathlib import Path from datetime import timedelta from django.conf import settings - # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent - # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/ @@ -28,8 +26,7 @@ SECRET_KEY = 'django-insecure-@0apn-lk85pfw=z00x2ib$w9#rwz8%2v4i_n^^9jz-m9b+y55* # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'https://rasadyar.net/'] - +ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'https://rasadyar.net/', 'https://localhost:9200'] # Application definition @@ -40,6 +37,8 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'django_elasticsearch_dsl', + 'django_elasticsearch_dsl_drf', 'rest_framework', "corsheaders", 'rest_framework_simplejwt', @@ -52,6 +51,7 @@ INSTALLED_APPS = [ 'apps.pos_machine.apps.PosMachineConfig', 'apps.tag.apps.TagConfig', 'apps.warehouse.apps.WarehouseConfig', + 'apps.search.apps.SearchConfig', 'rest_captcha', 'captcha', ] @@ -85,7 +85,6 @@ TEMPLATES = [ WSGI_APPLICATION = 'Rasaddam_Backend.wsgi.application' - # Database # https://docs.djangoproject.com/en/5.2/ref/settings/#databases @@ -111,6 +110,8 @@ REST_FRAMEWORK = { 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication', ), + "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination", + "PAGE_SIZE": 25 } SIMPLE_JWT = { @@ -183,6 +184,17 @@ REST_CAPTCHA = { 'NOISE_FUNCTION': 'apps.captcha_app.api.v1.serializers.noise_default' } +ELASTICSEARCH_DSL = { + # elastic HSA256 finger print f7d94c1da0668ba7874e5e09c3b1b91284fcdda97c361e0165401dc9375531b0 # noqa + # liara elastic password uYkiQ860vLW8DIbWpNjqtz2B # noqa + # local system password =z66+LCIebq4NQRR_+=R # noqa + "default": { + "hosts": "https://127.0.0.1:9200", + "http_auth": ("elastic", "uYkiQ860vLW8DIbWpNjqtz2B"), + } +} + + # Password validation # https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators @@ -201,7 +213,6 @@ AUTH_PASSWORD_VALIDATORS = [ }, ] - # Internationalization # https://docs.djangoproject.com/en/5.2/topics/i18n/ @@ -245,7 +256,6 @@ CORS_ALLOWED_ORIGINS = ( 'https://rasadyar.net' ) - SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') SECURE_SSL_REDIRECT = False SESSION_COOKIE_SECURE = False diff --git a/apps/authentication/api/v1/api.py b/apps/authentication/api/v1/api.py index 0ae2dda..2838e49 100644 --- a/apps/authentication/api/v1/api.py +++ b/apps/authentication/api/v1/api.py @@ -4,6 +4,7 @@ from rest_framework_simplejwt.views import TokenObtainPairView from rest_framework.viewsets import ModelViewSet from rest_framework.decorators import action from apps.authentication.models import User +from rest_framework.views import APIView from django.db import transaction @@ -28,3 +29,7 @@ class Authentication(ModelViewSet): @transaction.atomic def login(self, request): pass + + +class UserViewSet(ModelViewSet): + pass \ No newline at end of file diff --git a/apps/authentication/api/v1/search_view.py b/apps/authentication/api/v1/search_view.py new file mode 100644 index 0000000..84db93c --- /dev/null +++ b/apps/authentication/api/v1/search_view.py @@ -0,0 +1,53 @@ +from apps.authentication.api.v1.serializers.serializer import UserSerializer +from rest_framework.pagination import LimitOffsetPagination +from rest_framework.viewsets import ModelViewSet, ViewSet +from apps.authentication.documents import UserDocument +from rest_framework.response import Response +from django.http.response import HttpResponse +from apps.authentication.models import User +from rest_framework.views import APIView +from elasticsearch_dsl.query import Q +import abc + + +class PaginatedElasticSearchApiView(APIView, LimitOffsetPagination): + """Base ApiView Class for elasticsearch views with pagination + Other ApiView classes should inherit from this class""" + serializer_class = None + document_class = None + + @abc.abstractmethod + def generate_q_expression(self, query): + """This method should be overridden + and return a Q() expression.""" + + def get(self, request, query): + try: + q = self.generate_q_expression(query) + search = self.document_class.search().query(q) + response = search.execute() + + print(f"Found {response.hits.total.value} hit(s) for query: '{query}'") + + results = self.paginate_queryset(response, request, view=self) # noqa + serializer = self.serializer_class(results, many=True) + return self.get_paginated_response(serializer.data) + except Exception as e: + return HttpResponse(e, status=500) + + +class SearchUsersApiView(PaginatedElasticSearchApiView): # noqa + """Search in Users""" + + serializer_class = UserSerializer + document_class = UserDocument + + def generate_q_expression(self, query): + return Q( + 'multi_match', + query=query, + fields=[ + 'username', + 'mobile' + ], fuzziness='auto' + ) diff --git a/apps/authentication/api/v1/serializers/serializer.py b/apps/authentication/api/v1/serializers/serializer.py index e69de29..3df7158 100644 --- a/apps/authentication/api/v1/serializers/serializer.py +++ b/apps/authentication/api/v1/serializers/serializer.py @@ -0,0 +1,11 @@ +from rest_framework import serializers +from apps.authentication.models import User + + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = [ + 'username', + 'mobile' + ] diff --git a/apps/authentication/api/v1/urls.py b/apps/authentication/api/v1/urls.py index 15d42ea..dd2ce67 100644 --- a/apps/authentication/api/v1/urls.py +++ b/apps/authentication/api/v1/urls.py @@ -5,10 +5,14 @@ from rest_framework_simplejwt.views import ( TokenRefreshView, TokenVerifyView ) -from .api import CustomizedTokenObtainPairView +from .api import ( + CustomizedTokenObtainPairView +) +from .search_view import SearchUsersApiView urlpatterns = [ path('login/', CustomizedTokenObtainPairView.as_view(), name='token_obtain_pair'), + path('search_user/', SearchUsersApiView.as_view(), name='search_user'), path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), path('token/verify/', TokenVerifyView.as_view(), name='token_verify'), ] diff --git a/apps/authentication/documents.py b/apps/authentication/documents.py new file mode 100644 index 0000000..accb310 --- /dev/null +++ b/apps/authentication/documents.py @@ -0,0 +1,24 @@ +from .models import User, Province +from django_elasticsearch_dsl import Document, fields +from django_elasticsearch_dsl.registries import registry + + +@registry.register_document +class UserDocument(Document): + """ElasticSearch Document for indexing users""" + + class Index: + name = 'users' + settings = { + 'number_of_shards': 1, + 'number_of_replicas': 0 # number of copies from data in document + } + + class Django: + model = User + fields = [ + "id", + "username", + "mobile", + "nationality" + ] diff --git a/apps/authentication/views.py b/apps/authentication/views.py index 91ea44a..60f00ef 100644 --- a/apps/authentication/views.py +++ b/apps/authentication/views.py @@ -1,3 +1 @@ -from django.shortcuts import render - # Create your views here. diff --git a/apps/search/__init__.py b/apps/search/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/search/admin.py b/apps/search/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/apps/search/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/search/apps.py b/apps/search/apps.py new file mode 100644 index 0000000..9bcfcd2 --- /dev/null +++ b/apps/search/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class SearchConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.search' diff --git a/apps/search/certs/http.p12 b/apps/search/certs/http.p12 new file mode 100644 index 0000000..1b88326 Binary files /dev/null and b/apps/search/certs/http.p12 differ diff --git a/apps/search/certs/http_ca.crt b/apps/search/certs/http_ca.crt new file mode 100644 index 0000000..cc5b74c --- /dev/null +++ b/apps/search/certs/http_ca.crt @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIVAJzJVclRzLeDn2zXiqaZcs009WrZMA0GCSqGSIb3DQEB +CwUAMDwxOjA4BgNVBAMTMUVsYXN0aWNzZWFyY2ggc2VjdXJpdHkgYXV0by1jb25m +aWd1cmF0aW9uIEhUVFAgQ0EwHhcNMjUwNTA2MDUyODMwWhcNMjgwNTA1MDUyODMw +WjA8MTowOAYDVQQDEzFFbGFzdGljc2VhcmNoIHNlY3VyaXR5IGF1dG8tY29uZmln +dXJhdGlvbiBIVFRQIENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +lpZfJN3SR/HhgRd6wDOEZwSanoSgI0s/y7RcLBxtH29HGmlgegX38KugErYhBOx0 +CgGivcUG7CiyWPEg8CP71+pn1iQburH1zcnKovLO5pZc7p2bnnKESNsAH9j+EEza +NVUR9+tFyKaoss8QU0r1uKHFjghWR8aFRBVjPIPZs8z2GqbRzI0NBHmhxD0Tedp/ +67amF7N/64ID1LVgUWyQH28DtguEjp4jEL3gbU7gEiorg42XHymu4sKieWJQczaH +sVDTmcT8HhY8wL7qf9KD6UPyqtT2NnxyODWnaO2epjbII0XvIrXH3mfPzxNQ2mQT +2V+sv34dHS6yCr+jHGU2Z6nD+5Kr2l8QRJaxW0WNHgcKXW45TLbEWDOxfJCGSQYU +zXP9LsLk2SBeAQ4OECs0jBymVSf4c4TaYtloVpdZTKMbm66VwfsJjSxSKd03HTWz +SESkM0YQ8y33SU+RDkVCkWlV3isf1/FzibfSeDbPlrRIfV0oRlzkiY4mLrytQO2Q +L7JDZtZ2+y04AgtzrAtHwfAH/KsKjUr63gzouIwfdahZudQvNYQUCSyOMLGGQ5VJ +UA8K5cCj+Z0C0R7/A/0uNsMhIRA2KftFJtfEnQok3m7kJnrrkD8xUaJ3P8FGdP9J +fIy6JXKBp1nBS+YAkou6tR7BsPTrroKDyK4pbhCsrt8CAwEAAaNTMFEwHQYDVR0O +BBYEFHEPMqbc3BiLqvXgYaxm2yqMQp1MMB8GA1UdIwQYMBaAFHEPMqbc3BiLqvXg +Yaxm2yqMQp1MMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBADB6 +te9AWGobEXYfEt+0rlsTWJNVksyQs94OTfBLtjgPcDb0EzXteoxrFSUh4KblioT3 ++KVGPNfgYEEH6hnPNU2ea2ZL3cVDdrSWwCYqSJAmxOKKPpISu2/HZ2xtVuEjWqSe +CJcJvb2Fh8HSSRFTwko3h8B9ie4cb3cOiHle6tM+Kc1JxxLvAurlHl0xq4wdZJqW +RJUMYs+R+gCQiT4wBZlFoUHSCOlSDPb0YxMDvISaJ4DOxGjbL2TSw6wP9LtOcUlA +Agjgsq+xzCim2vzW3h7IMdw1z/amXbyl1J3an7n8P41deB/7EePiJuNVU9zsSu68 +anlQamvPawEFsucL3QfiX0kSQd1pcT1r/XYAm0jPVBx2Qc3EOoD4wlkz5ATpE1ts +vWCgfvQsuhUoL6cD9WFzAiWXXZ3qRDcY5L7zs0c/geUybRB/gWgMh/ROypfUoxfT +F0Cy/cfm/cBpbdy7frN9XWAigh/TDnCdwnOhfcvwr3AMxVR7X7NIVwBvTOPGIfwh +FLoPiaPrGkPntItdZWqGUUpkPrIzOpSZWurJ40pPCc0uBVUudYMH686MiyB5wZSS +/sGJIjfeivKVvH77kKCXld3Z35/E47msUvtl/DuUVtXQb12tQ4boyQJ6O6JVyK2n +1cRq9qLHcpfoG81BzrGIJvTWOBXTs02lOCRObF7z +-----END CERTIFICATE----- diff --git a/apps/search/certs/transport.p12 b/apps/search/certs/transport.p12 new file mode 100644 index 0000000..32068e2 Binary files /dev/null and b/apps/search/certs/transport.p12 differ diff --git a/apps/search/models.py b/apps/search/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/apps/search/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/apps/search/tests.py b/apps/search/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/search/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/search/views.py b/apps/search/views.py new file mode 100644 index 0000000..05e66c4 --- /dev/null +++ b/apps/search/views.py @@ -0,0 +1,4 @@ +from django.shortcuts import render +from elasticsearch_dsl import Q +from apps.authentication.models import User +from apps.authentication.documents import UserDocument diff --git a/apps/tag/models.py b/apps/tag/models.py index 9511ec3..e704a29 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -13,6 +13,12 @@ class Tag(BaseModel): related_name="tag_province", null=True ) + city = models.ForeignKey( + auth_models.City, + on_delete=models.CASCADE, + related_name='tag_city', + null=True + ) organization = models.ForeignKey( auth_models.Organization, on_delete=models.CASCADE, diff --git a/requirements.txt b/requirements.txt index 985f673..de62ec2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -66,4 +66,8 @@ XlsxWriter zipp django-simple-captcha django-rest-captcha -pymemcache \ No newline at end of file +pymemcache +elasticsearch==8.11.0 +elasticsearch-dsl==8.11.0 +django-elasticsearch-dsl==8.0 +django_elasticsearch_dsl_drf \ No newline at end of file