diff --git a/apps/accounts_oauth2/admin.py b/apps/accounts_oauth2/admin.py deleted file mode 100755 index 60d341d..0000000 --- a/apps/accounts_oauth2/admin.py +++ /dev/null @@ -1,36 +0,0 @@ -""" -Django admin configuration for the gooyal-restrict-scopes package. -""" - -from django.contrib import admin -from django.contrib.admin.sites import NotRegistered - -from oauth2_provider.admin import ApplicationAdmin - -from .models import Application, Resource, Scope -from .forms import ApplicationForm - - -# The restricted application is registered by Django OAuth Toolkit, but we want -# to provide our own admin that uses our form -try: - admin.site.unregister(Application) -except NotRegistered: - pass - -@admin.register(Application) -class ApplicationAdmin(ApplicationAdmin): - form = ApplicationForm - - -@admin.register(Resource) -class ResourceAdmin(admin.ModelAdmin): - list_display = ("name", "user", "expires") - - -@admin.register(Scope) -class ScopeAdmin(admin.ModelAdmin): - list_display = ('name', "resource", 'description', 'is_default') - - -# admin.site.register(RestrictedApplication, RestrictedApplicationAdmin) diff --git a/apps/accounts_oauth2/apps.py b/apps/accounts_oauth2/apps.py deleted file mode 100755 index 527c34d..0000000 --- a/apps/accounts_oauth2/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class GooyalOauthConfig(AppConfig): - name = 'apps.accounts_oauth2' diff --git a/apps/accounts_oauth2/forms.py b/apps/accounts_oauth2/forms.py deleted file mode 100644 index a7d83bd..0000000 --- a/apps/accounts_oauth2/forms.py +++ /dev/null @@ -1,51 +0,0 @@ -""" -Django forms for use with the gooyal-restrict-scopes package. -""" - -from django import forms - -from oauth2_provider.scopes import get_scopes_backend - - -class DelimitedListField(forms.MultipleChoiceField): - """ - Django form field that allows for the use of list widgets with a text field - containing a delimited list. - """ - delimiter = ',' - - def __init__(self, delimiter=None, *args, **kwargs): - super().__init__(*args, **kwargs) - self.delimiter = delimiter or self.delimiter - - def prepare_value(self, value): - #  If the value is already a list or tuple, just use it as-is - if isinstance(value, (list, tuple)): return value - #  Otherwise, prepare the value by splitting on the delimiter, trimming - #  leading and trailing whitespace and excluding empty values - return [p.strip() for p in value.split(self.delimiter) if p.strip()] - - def clean(self, value): - #  Let the parent clean the value first, then join the result using the - # specified delimiter - return self.delimiter.join(super().clean(value)) - - -class ApplicationForm(forms.ModelForm): - """ - Form for creating or updating a restricted application. - """ - #  allowed_scope is a space-delimited list, but we want to present - #  a selection of valid scopes with checkboxes - allowed_scope = DelimitedListField( - label='Allowed scopes', - #  The choices and initial values are callables, because the scopes might - #  not be available at import type, e.g. if coming from the database - choices=lambda: get_scopes_backend().get_all_scopes().items(), - initial=lambda: get_scopes_backend().get_default_scopes(), - delimiter=' ', - widget=forms.CheckboxSelectMultiple - ) - - class Meta: - exclude = () diff --git a/apps/accounts_oauth2/migrations/0001_initial.py b/apps/accounts_oauth2/migrations/0001_initial.py deleted file mode 100644 index 3ef40b3..0000000 --- a/apps/accounts_oauth2/migrations/0001_initial.py +++ /dev/null @@ -1,123 +0,0 @@ -# Generated by Django 4.1 on 2022-08-17 19:05 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import oauth2_provider.generators -import oauth2_provider.models -import uuid - - -class Migration(migrations.Migration): - - initial = True - - run_before = [ - ('oauth2_provider', '0001_initial'), - ] - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='AccessToken', - fields=[ - ('id', models.BigAutoField(primary_key=True, serialize=False)), - ('token', models.CharField(max_length=255, unique=True)), - ('expires', models.DateTimeField()), - ('scope', models.TextField(blank=True)), - ('created', models.DateTimeField(auto_now_add=True)), - ('updated', models.DateTimeField(auto_now=True)), - ('detail', models.JSONField(blank=True, null=True)), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='Application', - fields=[ - ('id', models.BigAutoField(primary_key=True, serialize=False)), - ('client_id', models.CharField(db_index=True, default=oauth2_provider.generators.generate_client_id, max_length=100, unique=True)), - ('redirect_uris', models.TextField(blank=True, help_text='Allowed URIs list, space separated')), - ('client_type', models.CharField(choices=[('confidential', 'Confidential'), ('public', 'Public')], max_length=32)), - ('authorization_grant_type', models.CharField(choices=[('authorization-code', 'Authorization code'), ('implicit', 'Implicit'), ('password', 'Resource owner password-based'), ('client-credentials', 'Client credentials'), ('openid-hybrid', 'OpenID connect hybrid')], max_length=32)), - ('client_secret', oauth2_provider.models.ClientSecretField(blank=True, db_index=True, default=oauth2_provider.generators.generate_client_secret, help_text='Hashed on Save. Copy it now if this is a new secret.', max_length=255)), - ('name', models.CharField(blank=True, max_length=255)), - ('skip_authorization', models.BooleanField(default=False)), - ('created', models.DateTimeField(auto_now_add=True)), - ('updated', models.DateTimeField(auto_now=True)), - ('algorithm', models.CharField(blank=True, choices=[('', 'No OIDC support'), ('RS256', 'RSA with SHA-2 256'), ('HS256', 'HMAC with SHA-2 256')], default='', max_length=5)), - ('allowed_scope', models.TextField(blank=True)), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='Grant', - fields=[ - ('id', models.BigAutoField(primary_key=True, serialize=False)), - ('code', models.CharField(max_length=255, unique=True)), - ('expires', models.DateTimeField()), - ('redirect_uri', models.TextField()), - ('scope', models.TextField(blank=True)), - ('created', models.DateTimeField(auto_now_add=True)), - ('updated', models.DateTimeField(auto_now=True)), - ('code_challenge', models.CharField(blank=True, default='', max_length=128)), - ('code_challenge_method', models.CharField(blank=True, choices=[('plain', 'plain'), ('S256', 'S256')], default='', max_length=10)), - ('nonce', models.CharField(blank=True, default='', max_length=255)), - ('claims', models.TextField(blank=True)), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='IDToken', - fields=[ - ('id', models.BigAutoField(primary_key=True, serialize=False)), - ('jti', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='JWT Token ID')), - ('expires', models.DateTimeField()), - ('scope', models.TextField(blank=True)), - ('created', models.DateTimeField(auto_now_add=True)), - ('updated', models.DateTimeField(auto_now=True)), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='RefreshToken', - fields=[ - ('id', models.BigAutoField(primary_key=True, serialize=False)), - ('token', models.CharField(max_length=255)), - ('created', models.DateTimeField(auto_now_add=True)), - ('updated', models.DateTimeField(auto_now=True)), - ('revoked', models.DateTimeField(null=True)), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='Resource', - fields=[ - ('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), - ('name', models.CharField(max_length=255)), - ('expires', models.DateTimeField()), - ], - ), - migrations.CreateModel( - name='Scope', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(help_text='The name of the scope.', max_length=255, unique=True)), - ('description', models.TextField(help_text='A brief description of the scope. This text is displayed to users when authorising access for the scope.')), - ('is_default', models.BooleanField(default=False, help_text='Indicates if this scope should be included in the default scopes.')), - ('application', models.ForeignKey(blank=True, help_text='The application to which the scope belongs.', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='scopes', to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL)), - ('resource', models.ForeignKey(blank=True, help_text='The resource of scope.', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='scopes', to='accounts_oauth2.resource')), - ], - ), - ] diff --git a/apps/accounts_oauth2/migrations/0002_initial.py b/apps/accounts_oauth2/migrations/0002_initial.py deleted file mode 100644 index bb55815..0000000 --- a/apps/accounts_oauth2/migrations/0002_initial.py +++ /dev/null @@ -1,92 +0,0 @@ -# Generated by Django 4.1 on 2022-08-17 19:05 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('accounts_oauth2', '0001_initial'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AddField( - model_name='resource', - name='user', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='resources', to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='refreshtoken', - name='access_token', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='refresh_token', to=settings.OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL), - ), - migrations.AddField( - model_name='refreshtoken', - name='application', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='refresh_tokens', to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL), - ), - migrations.AddField( - model_name='refreshtoken', - name='user', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s', to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='idtoken', - name='application', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL), - ), - migrations.AddField( - model_name='idtoken', - name='user', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s', to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='grant', - name='application', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='grants', to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL), - ), - migrations.AddField( - model_name='grant', - name='user', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s', to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='application', - name='resource', - field=models.OneToOneField(blank=True, help_text='The resource of application.', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='application', to='accounts_oauth2.resource'), - ), - migrations.AddField( - model_name='application', - name='user', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='%(app_label)s_%(class)s', to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='accesstoken', - name='application', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='access_tokens', to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL), - ), - migrations.AddField( - model_name='accesstoken', - name='id_token', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='access_token', to=settings.OAUTH2_PROVIDER_ID_TOKEN_MODEL), - ), - migrations.AddField( - model_name='accesstoken', - name='source_refresh_token', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='refreshed_access_token', to=settings.OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL), - ), - migrations.AddField( - model_name='accesstoken', - name='user', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s', to=settings.AUTH_USER_MODEL), - ), - migrations.AlterUniqueTogether( - name='refreshtoken', - unique_together={('token', 'revoked')}, - ), - ] diff --git a/apps/accounts_oauth2/migrations/0003_application_allowed_origins_and_more.py b/apps/accounts_oauth2/migrations/0003_application_allowed_origins_and_more.py deleted file mode 100644 index e3a4a5c..0000000 --- a/apps/accounts_oauth2/migrations/0003_application_allowed_origins_and_more.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 5.0.6 on 2024-06-26 12:34 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('accounts_oauth2', '0002_initial'), - ] - - operations = [ - migrations.AddField( - model_name='application', - name='allowed_origins', - field=models.TextField(blank=True, default='', help_text='Allowed origins list to enable CORS, space separated'), - ), - migrations.AddField( - model_name='application', - name='hash_client_secret', - field=models.BooleanField(default=True), - ), - migrations.AddField( - model_name='application', - name='post_logout_redirect_uris', - field=models.TextField(blank=True, default='', help_text='Allowed Post Logout URIs list, space separated'), - ), - migrations.AlterField( - model_name='accesstoken', - name='token', - field=models.CharField(db_index=True, max_length=255, unique=True), - ), - migrations.AlterField( - model_name='scope', - name='id', - field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), - ), - ] diff --git a/apps/accounts_oauth2/migrations/__init__.py b/apps/accounts_oauth2/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/apps/accounts_oauth2/models.py b/apps/accounts_oauth2/models.py deleted file mode 100644 index 471587e..0000000 --- a/apps/accounts_oauth2/models.py +++ /dev/null @@ -1,168 +0,0 @@ -import requests -from django.conf import settings -from django.db import models -from django.db.models import JSONField -from oauth2_provider.models import AbstractApplication, AbstractAccessToken, AbstractGrant, AbstractRefreshToken, \ - AbstractIDToken -from oauth2_provider.scopes import get_scopes_backend -from oauth2_provider.settings import oauth2_settings -import uuid - - -class Resource(models.Model): - uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True) - name = models.CharField(max_length=255) - - user = models.ForeignKey( - settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True, - related_name="resources" - ) - expires = models.DateTimeField() - - def __str__(self): - return self.name - - -class Application(AbstractApplication): - """ - Application model for use with Django OAuth Toolkit that allows the scopes - available to an application to be restricted on a per-application basis. - """ - user = models.ForeignKey( - settings.AUTH_USER_MODEL, - related_name="%(app_label)s_%(class)s", - on_delete=models.PROTECT - ) - allowed_scope = models.TextField(blank=True) - resource = models.OneToOneField( - Resource, - models.PROTECT, - blank=True, null=True, - help_text='The resource of application.', - related_name='application' - ) - - @property - def allowed_scopes(self): - """ - Returns the set of allowed scope names for this application. - """ - all_scopes = set(get_scopes_backend().get_all_scopes().keys()) - app_scopes = set(self.allowed_scope.split()) - return app_scopes.intersection(all_scopes) - - -class Scope(models.Model): - """ - Django model for an OAuth scope. - """ - #: The application that created the scope - # NOTE: This is not used to limit access to the scope in any way - we want the - # scope to be available to other applications in order to request access - # to the resource it protects! - application = models.ForeignKey( - oauth2_settings.APPLICATION_MODEL, - models.CASCADE, - # This field is nullable because it is only set for scopes created by - # external resource servers, which have a corresponding OAuth application - # record on the authorisation server - blank=True, null=True, - help_text='The application to which the scope belongs.', - related_name='scopes' - ) - resource = models.ForeignKey( - Resource, - models.PROTECT, - blank=True, null=True, - help_text='The resource of scope.', - related_name='scopes' - ) - #: The name of the scope - name = models.CharField( - max_length=255, - unique=True, - help_text='The name of the scope.' - ) - #: A brief description of the scope - description = models.TextField( - help_text='A brief description of the scope. This text is displayed ' - 'to users when authorising access for the scope.' - ) - #: Indicates if the scope should be included in the default scopes - is_default = models.BooleanField( - default=False, - help_text='Indicates if this scope should be included in the default scopes.' - ) - - @property - def final_name(self): - args = [] - if self.resource: - args.append(self.resource.name) - - args.append(self.name) - return '.'.join(args) - - @property - def final_description(self): - resource_name = self.resource and self.resource.name - - if resource_name: - return f"{resource_name} -> {self.description}" - return self.description - - @classmethod - def register(cls, name, description, is_default=False): - """ - Registers a scope with the given values. It always creates an instance in - the local database, but if this resource server has an external authorisation - server, it will also register the scope there. - - Returns ``True`` on success. Should raise on failure. - """ - endpoint = settings.RESOURCE_SERVER_REGISTER_SCOPE_URL - if endpoint: - #  If the endpoint is set, make the callout to the authz server - token = "Bearer {}".format(oauth2_settings.RESOURCE_SERVER_AUTH_TOKEN) - #  Let any failures bubble up - # The idea is to call this method during deployment as a post-migrate - #  hook, so we want failures to halt the deployment - response = requests.post( - endpoint, - json={ - 'name': name, - 'description': description, - 'is_default': is_default - }, - headers={"Authorization": token} - ) - #  Raise the exception for anything other than 20x responses - response.raise_for_status() - #  Always create/update the scope record locally - _ = Scope.objects.update_or_create( - name=name, - defaults={'description': description, 'is_default': is_default} - ) - return True - - -class Grant(AbstractGrant): - application = models.ForeignKey( - oauth2_settings.APPLICATION_MODEL, on_delete=models.CASCADE, related_name='grants' - ) - - -class RefreshToken(AbstractRefreshToken): - application = models.ForeignKey( - oauth2_settings.APPLICATION_MODEL, on_delete=models.CASCADE, related_name='refresh_tokens') - - -class AccessToken(AbstractAccessToken): - detail = JSONField(null=True, blank=True) - application = models.ForeignKey( - oauth2_settings.APPLICATION_MODEL, on_delete=models.CASCADE, blank=True, null=True, related_name='access_tokens' - ) - - -class IDToken(AbstractIDToken): - pass \ No newline at end of file diff --git a/apps/accounts_oauth2/scopes.py b/apps/accounts_oauth2/scopes.py deleted file mode 100644 index d7387b9..0000000 --- a/apps/accounts_oauth2/scopes.py +++ /dev/null @@ -1,33 +0,0 @@ -""" -Django OAuth Toolkit scopes backend for the gooyal-dynamic-scopes package. -""" - -from django.conf import settings -from django.utils import module_loading - -from oauth2_provider.scopes import BaseScopes - -from .models import Scope - - -class Scopes(BaseScopes): - """ - Scopes backend that provides scopes from a Django model. - """ - - def get_queryset(self, application=None): - queryset = Scope.objects.all() - if application: - queryset = queryset.filter(name__in=application.allowed_scopes).order_by('resource__uuid') - return queryset - - - def get_all_scopes(self): - return {scope.final_name: scope.final_description for scope in self.get_queryset().all()} - - def get_available_scopes(self, application=None, request=None, *args, **kwargs): - scopes = [scope.final_name for scope in self.get_queryset(application).all()] - return scopes - - def get_default_scopes(self, application=None, request=None, *args, **kwargs): - return [scope.final_name for scope in self.get_queryset(application).filter(is_default=True).all()] diff --git a/apps/accounts_oauth2/settings.py b/apps/accounts_oauth2/settings.py deleted file mode 100644 index 7aa7128..0000000 --- a/apps/accounts_oauth2/settings.py +++ /dev/null @@ -1,20 +0,0 @@ -from oauth2_provider.settings import OAuth2ProviderSettings, USER_SETTINGS, DEFAULTS, IMPORT_STRINGS, MANDATORY - -GOOYAL_DEFAULTS = { - # Resource Server with Token Introspection additions - "RESOURCE_SERVER_CLIENT_ID": None, - "RESOURCE_SERVER_CLIENT_SECRET": None, - - # set default - "INTROSPECTION_USER_CREATE_METHOD": None, - } - -GOOYAL_IMPORT_STRINGS = ("INTROSPECTION_USER_CREATE_METHOD",) - -GOOYAL_MANDATORY = ("INTROSPECTION_USER_CREATE_METHOD",) - -DEFAULTS.update(GOOYAL_DEFAULTS) -IMPORT_STRINGS = IMPORT_STRINGS + GOOYAL_IMPORT_STRINGS -MANDATORY = MANDATORY + GOOYAL_MANDATORY - -oauth2_settings = OAuth2ProviderSettings(USER_SETTINGS, DEFAULTS, IMPORT_STRINGS, MANDATORY) diff --git a/apps/accounts_oauth2/signals.py b/apps/accounts_oauth2/signals.py deleted file mode 100644 index 3f2af94..0000000 --- a/apps/accounts_oauth2/signals.py +++ /dev/null @@ -1,26 +0,0 @@ -""" -Django signal handlers for the gooyal-dynamic-scopes package. -""" - -from django.conf import settings - -from oauth2_provider.settings import oauth2_settings - -from .models import Scope - - -def register_scopes(app_config, verbosity=2, interactive=True, **kwargs): - """ - ``post_migrate`` signal handler that ensures the scopes required for the - introspection and register-scope endpoints are registered when running as an - authorisation server. - - The signal is connected in ``apps.py``. - """ - #  Register any scopes in the oauth2_provider settings - for name, description in oauth2_settings.SCOPES.items(): - Scope.register( - name, - description, - name in oauth2_settings.DEFAULT_SCOPES - ) diff --git a/apps/accounts_oauth2/validators.py b/apps/accounts_oauth2/validators.py deleted file mode 100755 index 7153aaf..0000000 --- a/apps/accounts_oauth2/validators.py +++ /dev/null @@ -1,288 +0,0 @@ -import base64 -import binascii -import logging -from datetime import datetime, timedelta -from urllib.parse import unquote_plus - -import requests -# import service_clients -from django.contrib.auth import get_user_model -from django.utils.timezone import make_aware -from oauth2_provider.models import get_access_token_model -from oauth2_provider.oauth2_validators import OAuth2Validator as BaseOAuth2Validator -from .settings import oauth2_settings -from django.conf import settings - -log = logging.getLogger("oauth2_provider") - -AccessTokenModel = get_access_token_model() -UserModel = get_user_model() - - -class OAuth2Validator(BaseOAuth2Validator): # pylint: disable=w0223 - introspection_client = None - - def get_introspection_client(self, introspection_client_id, introspection_client_secret): - if not OAuth2Validator.introspection_client: - OAuth2Validator.introspection_client = service_clients.Client(client_id=introspection_client_id, - client_secret=introspection_client_secret, - grant_type=service_clients.AccountsClient.GRANT_CLIENT_CREDENTIALS, - scopes=['introspection']) - - return OAuth2Validator.introspection_client - - def validate_user(self, username, password, client, request, *args, **kwargs): - auth_fields = getattr(request, 'auth_fields', 'username:password').split(':') - - if len(auth_fields) != 2: - return False - - user_field, pass_field = auth_fields - - if user_field not in ['phone_number', 'username', 'email']: - return False - - if pass_field not in ['password', 'otp', 'ott']: - return False - - if not username or not password: - return False - - user = UserModel.objects.filter(**{user_field: username}).first() - - if not user: - return False - - if not user.check_auth(pass_field, password): - return False - - if user.is_active: - request.user = user - return True - - return False - - def _get_token_from_gooyal_authentication_server( - self, token, introspection_url, introspection_token, introspection_credentials, introspection_client_id, - introspection_client_secret - ): - """Use external introspection endpoint to "crack open" the token. - :param introspection_url: introspection endpoint URL - :param introspection_token: Bearer token - :param introspection_credentials: Basic Auth credentials (id,secret) - :return: :class:`models.AccessToken` - - Some RFC 7662 implementations (including this one) use a Bearer token while others use Basic - Auth. Depending on the external AS's implementation, provide either the introspection_token - or the introspection_credentials. - - If the resulting access_token identifies a username (e.g. Authorization Code grant), add - that user to the UserModel. Also cache the access_token up until its expiry time or a - configured maximum time. - - """ - - headers = None - response = None - if introspection_token: - headers = {"Authorization": "Bearer {}".format(introspection_token)} - try: - response = requests.post( - introspection_url, - data={"token": token}, headers=headers - ) - except requests.exceptions.RequestException: - log.exception("Introspection: Failed POST to %r in token lookup", introspection_url) - return None - - elif introspection_credentials: - client_id = introspection_credentials[0].encode("utf-8") - client_secret = introspection_credentials[1].encode("utf-8") - basic_auth = base64.b64encode(client_id + b":" + client_secret) - headers = {"Authorization": "Basic {}".format(basic_auth.decode("utf-8"))} - try: - response = requests.post( - introspection_url, - data={"token": token}, headers=headers - ) - except requests.exceptions.RequestException: - log.exception("Introspection: Failed POST to %r in token lookup", introspection_url) - return None - - elif introspection_client_id and introspection_client_secret: - data = {"token": token} - introspection_client = self.get_introspection_client(introspection_client_id, introspection_client_secret) - response = introspection_client.request(url=introspection_url, method='post', data=data, - required_scopes=['introspection'], login_required=True) - - try: - content: dict = response.json() - except ValueError: - log.exception("Introspection: Failed to parse response as json") - return None - - user = None - if "active" in content and content["active"] is True: - if "username" in content: - user, content = oauth2_settings.INTROSPECTION_USER_CREATE_METHOD(token, content) - - max_caching_time = datetime.now() + timedelta( - seconds=oauth2_settings.RESOURCE_SERVER_TOKEN_CACHING_SECONDS - ) - - if "exp" in content: - expires = datetime.utcfromtimestamp(content["exp"]) - if expires > max_caching_time: - expires = max_caching_time - else: - expires = max_caching_time - - scope = content.get("scope", "") - expires = make_aware(expires) - - access_token, _created = AccessTokenModel.objects.update_or_create( - token=token, - defaults={ - "user": user, - "application": None, - "scope": scope, - "expires": expires, - "detail": content, - }) - - # try: - # access_token = AccessTokenModel.objects.select_related("application", "user").get(token=token) - # except AccessTokenModel.DoesNotExist: - # access_token = AccessTokenModel.objects.create( - # user=user, - # token=token, - # application=None, - # scope=scope, - # expires=expires, - # detail=content - # ) - # else: - # access_token.expires = expires - # access_token.scope = scope - # access_token.detail = content - # access_token.save() - - return access_token - - # def validate_bearer_token(self, token, scopes, request): - # """ - # When users try to access resources, check that provided token is valid - # """ - # if not token: - # return False - # - # introspection_url = oauth2_settings.RESOURCE_SERVER_INTROSPECTION_URL - # introspection_token = oauth2_settings.RESOURCE_SERVER_AUTH_TOKEN - # introspection_credentials = oauth2_settings.RESOURCE_SERVER_INTROSPECTION_CREDENTIALS - # introspection_client_id = oauth2_settings.RESOURCE_SERVER_CLIENT_ID - # introspection_client_secret = oauth2_settings.RESOURCE_SERVER_CLIENT_SECRET - # - # try: - # access_token = AccessTokenModel.objects.select_related("application", "user").get(token=token) - # except AccessTokenModel.DoesNotExist: - # access_token = None - # - # # if there is no token or it's invalid then introspect the token if there's an external OAuth server - # if not access_token or not access_token.is_valid(scopes): - # if introspection_url and (introspection_token or introspection_credentials or (introspection_client_id and - # introspection_client_secret)): - # access_token = self._get_token_from_gooyal_authentication_server( - # token, - # introspection_url, - # introspection_token, - # introspection_credentials, - # introspection_client_id, - # introspection_client_secret - # ) - # - # if access_token and access_token.is_valid(scopes): - # request.client = access_token.application - # request.user = access_token.user - # request.scopes = scopes - # - # # this is needed by django rest framework - # request.access_token = access_token - # return True - # else: - # self._set_oauth2_error_on_request(request, access_token, scopes) - # return False - - def _authenticate_basic_auth(self, request): - """ - Authenticates with HTTP Basic Auth. - - Note: as stated in rfc:`2.3.1`, client_id and client_secret must be encoded with - "application/x-www-form-urlencoded" encoding algorithm. - """ - auth_string = self._extract_basic_auth(request) - if not auth_string: - return False - - try: - encoding = request.encoding or settings.DEFAULT_CHARSET or "utf-8" - except AttributeError: - encoding = "utf-8" - - try: - b64_decoded = base64.b64decode(auth_string) - except (TypeError, binascii.Error): - log.debug("Failed basic auth: %r can't be decoded as base64", auth_string) - return False - - try: - auth_string_decoded = b64_decoded.decode(encoding) - except UnicodeDecodeError: - log.debug("Failed basic auth: %r can't be decoded as unicode by %r", auth_string, encoding) - return False - - try: - client_id, client_secret = map(unquote_plus, auth_string_decoded.split(":", 1)) - except ValueError: - log.debug("Failed basic auth, Invalid base64 encoding.") - return False - - if self._load_application(client_id, request) is None: - log.debug("Failed basic auth: Application %s does not exist" % client_id) - return False - elif request.client.client_id != client_id: - log.debug("Failed basic auth: wrong client id %s" % client_id) - return False - - # TODO: check why not work - elif not client_secret == request.client.client_secret: - log.debug("Failed basic auth: wrong client secret %s" % client_secret) - return False - else: - return True - - def _authenticate_request_body(self, request): - """ - Try to authenticate the client using client_id and client_secret - parameters included in body. - - Remember that this method is NOT RECOMMENDED and SHOULD be limited to - clients unable to directly utilize the HTTP Basic authentication scheme. - See rfc:`2.3.1` for more details. - """ - # TODO: check if oauthlib has already unquoted client_id and client_secret - try: - client_id = request.client_id - client_secret = request.client_secret - except AttributeError: - return False - - if self._load_application(client_id, request) is None: - log.debug("Failed body auth: Application %s does not exists" % client_id) - return False - # TODO: check why not work - elif not client_secret == request.client.client_secret: - log.debug("Failed body auth: wrong client secret %s" % client_secret) - return False - else: - return True - diff --git a/apps/accounts_oauth2/__init__.py b/apps/gooyal_oauth2/__init__.py similarity index 100% rename from apps/accounts_oauth2/__init__.py rename to apps/gooyal_oauth2/__init__.py diff --git a/apps/gooyal_oauth2/admin.py b/apps/gooyal_oauth2/admin.py new file mode 100755 index 0000000..40d618b --- /dev/null +++ b/apps/gooyal_oauth2/admin.py @@ -0,0 +1,13 @@ +""" +Django admin configuration for the gooyal-restrict-scopes package. +""" + +from django.contrib import admin +from django.contrib.admin.sites import NotRegistered + +from oauth2_provider.admin import ApplicationAdmin + + + +# The restricted application is registered by Django OAuth Toolkit, but we want +# to provide our own admin that uses our form diff --git a/apps/gooyal_oauth2/apps.py b/apps/gooyal_oauth2/apps.py new file mode 100755 index 0000000..e0ce9c1 --- /dev/null +++ b/apps/gooyal_oauth2/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class GooyalOauthConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.gooyal_oauth2' diff --git a/apps/gooyal_oauth2/forms.py b/apps/gooyal_oauth2/forms.py new file mode 100644 index 0000000..b4b54bd --- /dev/null +++ b/apps/gooyal_oauth2/forms.py @@ -0,0 +1,9 @@ +""" +Django forms for use with the gooyal-restrict-scopes package. +""" + +from django import forms + +from oauth2_provider.scopes import get_scopes_backend + + diff --git a/apps/gooyal_oauth2/models.py b/apps/gooyal_oauth2/models.py new file mode 100644 index 0000000..f668dca --- /dev/null +++ b/apps/gooyal_oauth2/models.py @@ -0,0 +1 @@ +# models \ No newline at end of file diff --git a/apps/accounts_oauth2/rest_framework.py b/apps/gooyal_oauth2/rest_framework.py similarity index 100% rename from apps/accounts_oauth2/rest_framework.py rename to apps/gooyal_oauth2/rest_framework.py diff --git a/apps/gooyal_oauth2/validators.py b/apps/gooyal_oauth2/validators.py new file mode 100755 index 0000000..92206c5 --- /dev/null +++ b/apps/gooyal_oauth2/validators.py @@ -0,0 +1,30 @@ +import base64 +import binascii +import logging +from datetime import datetime, timedelta +from urllib.parse import unquote_plus + +import requests +# import service_clients +from django.contrib.auth import get_user_model +from django.utils.timezone import make_aware +from oauth2_provider.models import get_access_token_model +from oauth2_provider.oauth2_validators import OAuth2Validator as BaseOAuth2Validator + +log = logging.getLogger("oauth2_provider") + +AccessTokenModel = get_access_token_model() +UserModel = get_user_model() + +class OAuth2Validator(BaseOAuth2Validator): # pylint: disable=w0223 + def get_or_create_user_from_content(self, content): + """ + An optional layer to define where to store the profile in `UserModel` or a separate model. + For example `UserOAuth`, where `user = models.OneToOneField(UserModel)` . + + The function is called after checking that username is in the content. + + Returns an UserModel instance; + """ + user, _ = UserModel.objects.get_or_create(pk=content["username"]) + return user diff --git a/apps/users/admin.py b/apps/users/admin.py index 59bcb9e..41a4e09 100644 --- a/apps/users/admin.py +++ b/apps/users/admin.py @@ -2,31 +2,4 @@ from django.contrib import admin from .models import User -class UserAdmin(admin.ModelAdmin): - fields = [ - 'uuid', - 'name', - 'avatar', - 'is_active', - 'first_name', - 'last_name', - 'username', - 'email', - 'phone_number', - 'password', - 'otp', - 'is_staff', - 'is_superuser', - 'groups', - 'user_permissions', - 'last_login', - 'date_joined', - 'last_update', - 'otp_expire', - 'otp_try', - 'balance', - ] - readonly_fields = ['last_update', 'uuid'] - - -admin.site.register(User, UserAdmin) +admin.site.register(User) diff --git a/apps/users/apps.py b/apps/users/apps.py index e551ce4..2bb189c 100644 --- a/apps/users/apps.py +++ b/apps/users/apps.py @@ -2,4 +2,5 @@ from django.apps import AppConfig class UsersConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' name = 'apps.users' diff --git a/apps/users/backends.py b/apps/users/backends.py deleted file mode 100644 index 0db0b90..0000000 --- a/apps/users/backends.py +++ /dev/null @@ -1,137 +0,0 @@ -from django.contrib.auth import get_user_model -from django.contrib.auth.backends import ModelBackend - -UserModel = get_user_model() - -class OTPBackend(ModelBackend): - - """ - Authenticates against settings.AUTH_USER_MODEL. - """ - - def authenticate(self, request, phone_number=None, otp=None, **kwargs): - if phone_number is None or otp is None: - return - - try: - user = UserModel.objects.get(phone_number=phone_number) - # user = UserModel._default_manager.get_by_natural_key(phone_number) - except UserModel.DoesNotExist: - return - - else: - if user.check_otp(otp) and self.user_can_authenticate(user): - return user - - def user_can_authenticate(self, user): - """ - Reject users with is_active=False. Custom user models that don't have - that attribute are allowed. - """ - is_active = getattr(user, 'is_active', None) - return is_active or is_active is None - - # def _get_user_permissions(self, user_obj): - # return user_obj.user_permissions.all() - # - # def _get_group_permissions(self, user_obj): - # user_groups_field = get_user_model()._meta.get_field('groups') - # user_groups_query = 'group__%s' % user_groups_field.related_query_name() - # return Permission.objects.filter(**{user_groups_query: user_obj}) - # - # def _get_permissions(self, user_obj, obj, from_name): - # """ - # Return the permissions of `user_obj` from `from_name`. `from_name` can - # be either "group" or "user" to return permissions from - # `_get_group_permissions` or `_get_user_permissions` respectively. - # """ - # if not user_obj.is_active or user_obj.is_anonymous or obj is not None: - # return set() - # - # perm_cache_name = '_%s_perm_cache' % from_name - # if not hasattr(user_obj, perm_cache_name): - # if user_obj.is_superuser: - # perms = Permission.objects.all() - # else: - # perms = getattr(self, '_get_%s_permissions' % from_name)(user_obj) - # perms = perms.values_list('content_type__app_label', 'codename').order_by() - # setattr(user_obj, perm_cache_name, {"%s.%s" % (ct, name) for ct, name in perms}) - # return getattr(user_obj, perm_cache_name) - # - # def get_user_permissions(self, user_obj, obj=None): - # """ - # Return a set of permission strings the user `user_obj` has from their - # `user_permissions`. - # """ - # return self._get_permissions(user_obj, obj, 'user') - # - # def get_group_permissions(self, user_obj, obj=None): - # """ - # Return a set of permission strings the user `user_obj` has from the - # groups they belong. - # """ - # return self._get_permissions(user_obj, obj, 'group') - # - # def get_all_permissions(self, user_obj, obj=None): - # if not user_obj.is_active or user_obj.is_anonymous or obj is not None: - # return set() - # if not hasattr(user_obj, '_perm_cache'): - # user_obj._perm_cache = super().get_all_permissions(user_obj) - # return user_obj._perm_cache - # - # def has_perm(self, user_obj, perm, obj=None): - # return user_obj.is_active and super().has_perm(user_obj, perm, obj=obj) - # - # def has_module_perms(self, user_obj, app_label): - # """ - # Return True if user_obj has any permissions in the given app_label. - # """ - # return user_obj.is_active and any( - # perm[:perm.index('.')] == app_label - # for perm in self.get_all_permissions(user_obj) - # ) - # - # def with_perm(self, perm, is_active=True, include_superusers=True, obj=None): - # """ - # Return users that have permission "perm". By default, filter out - # inactive users and include superusers. - # """ - # if isinstance(perm, str): - # try: - # app_label, codename = perm.split('.') - # except ValueError: - # raise ValueError( - # 'Permission name should be in the form ' - # 'app_label.permission_codename.' - # ) - # elif not isinstance(perm, Permission): - # raise TypeError( - # 'The `perm` argument must be a string or a permission instance.' - # ) - # - # UserModel = get_user_model() - # if obj is not None: - # return UserModel._default_manager.none() - # - # permission_q = Q(group__user=OuterRef('pk')) | Q(user=OuterRef('pk')) - # if isinstance(perm, Permission): - # permission_q &= Q(pk=perm.pk) - # else: - # permission_q &= Q(codename=codename, content_type__app_label=app_label) - # - # user_q = Exists(Permission.objects.filter(permission_q)) - # if include_superusers: - # user_q |= Q(is_superuser=True) - # if is_active is not None: - # user_q &= Q(is_active=is_active) - # - # return UserModel._default_manager.filter(user_q) - # - # def get_user(self, user_id): - # try: - # user = UserModel._default_manager.get(pk=user_id) - # except UserModel.DoesNotExist: - # return None - # return user if self.user_can_authenticate(user) else None - # - # diff --git a/apps/users/cities.py b/apps/users/cities.py deleted file mode 100644 index ad6c6f0..0000000 --- a/apps/users/cities.py +++ /dev/null @@ -1,1250 +0,0 @@ -# from pprint import pprint -# a = 'https://en.wikipedia.org/wiki/List_of_cities_in_Iran_by_province' - -provinces = {'alborz': ['karaj', - 'fardis', - 'kamal shahr', - 'nazarabad', - 'mohammadshahr', - 'hashtgerd', - 'mahdasht', - 'meshkin dasht', - 'chaharbagh', - 'shahr-e jadid-e hashtgerd', - 'eshtehard', - 'garmdarreh', - 'golsar', - 'kuhsar', - 'taleqan', - 'asara', - 'tankaman'], - 'ardabil': ['ardabil', - 'parsabad', - 'meshginshahr', - 'khalkhal', - 'germi', - 'bileh savar', - 'namin', - 'jafarabad', - 'kivi', - 'anbaran', - 'abi beyglu', - 'nir', - 'hashatjin', - 'sareyn', - 'aslan duz', - 'eslamabad-e qadim', - 'tazeh kand-e qadim', - 'qasabeh', - 'lahrud', - 'hir', - 'kolowr', - 'razey', - 'tazeh kand-e angut', - 'fakhrabad', - 'kuraim', - 'moradlu'], - 'bushehr': ['bushehr', - 'borazjan', - 'bandar ganaveh', - 'khormoj', - 'bandar kangan', - 'jam', - 'bandar deylam', - 'bandar-e deyr', - 'ali shahr', - 'choghadak', - 'ab pakhsh', - 'ahram', - 'vahdatiyeh', - 'kaki', - 'bank', - 'nakhl taqi', - 'asaluyeh', - 'kharg', - 'dalaki', - 'sadabad', - 'shabankareh', - 'abdan', - 'bandar rig', - 'bardestan', - 'bord khun', - 'delvar', - 'bandar siraf', - 'dowrahak', - 'baduleh', - 'tang-e eram', - 'abad', - 'anarestan', - 'shonbeh', - 'imam hassan', - 'kalameh', - 'riz', - 'bushkan'], - 'chaharmahal and bakhtiari': ['shahr-e kord', - 'borujen', - 'farsan', - 'farrokh shahr', - 'lordegan', - 'hafshejan', - 'saman', - 'junqan', - 'kian', - 'faradonbeh', - 'ben', - 'sureshjan', - 'babaheydar', - 'boldaji', - 'ardal', - 'naqneh', - 'pardanjan', - 'shalamzar', - 'gahru', - 'gujan', - 'sefiddasht', - 'gandoman', - 'taqanak', - 'sardasht', - 'sudjan', - 'naghan', - 'dastana', - 'cholicheh', - 'vardanjan', - 'kaj', - 'dashtak', - 'nafech', - 'mal-e khalifeh', - 'chelgard', - 'aluni', - 'haruni', - 'sar khun', - 'bazoft', - 'manj-e nesa', - 'samsami'], - 'east azerbaijan': ['tabriz', - 'maragheh', - 'marand', - 'ahar', - 'mianeh', - 'bonab', - 'sahand', - 'sarab', - 'azarshahr', - 'hadishahr', - 'ajab shir', - 'sardrud', - 'malekan', - 'shabestar', - 'bostanabad', - 'hashtrud', - 'osku', - 'ilkhchi', - 'mamqan', - 'khosrowshah', - 'basmenj', - 'gugan', - 'heris', - 'yamchi', - 'kaleybar', - 'shendabad', - 'sufian', - 'jolfa', - 'koshksaray', - 'tasuj', - 'torkamanchay', - 'kolvanaq', - 'leylan', - 'sis', - 'bakhshayesh', - 'qarah aghaj', - 'mehraban', - 'teymurlu', - 'varzaqan', - 'zarnaq', - 'hurand', - 'khajeh', - 'benab e marand', - 'sharabian', - 'vayqan', - 'mobarak shahr', - 'sharafkhaneh', - 'kuzeh kanan', - 'achachi', - 'duzduzan', - 'kharvana', - 'khamaneh', - 'tekmeh dash', - 'aqkand', - 'abish ahmad', - 'zonuz', - 'tark', - 'khomarlu', - 'kharaju', - 'siah rud', - 'nazarkahrizi', - 'jowan qaleh', - 'malek kian', - 'shahriar'], - 'fars': ['shiraz', - 'marvdasht', - 'jahrom', - 'fasa', - 'kazerun', - 'sadra', - 'darab', - 'firuzabad', - 'lar', - 'abadeh', - 'nurabad', - 'neyriz', - 'eqlid', - 'estahban', - 'gerash', - 'zarqan', - 'kavar', - 'lamerd', - 'safashahr', - 'qaemiyeh', - 'hajjiabad', - 'farashband', - 'qir', - 'evaz', - 'khonj', - 'kharameh', - 'sarvestan', - 'arsanjan', - 'saadat shahr', - 'qaderabad', - 'ardakan', - 'dobiran', - 'jannat shahr', - 'galleh dar', - 'soghad', - 'meymand', - 'darian', - 'zahedshahr', - 'khesht', - 'surian', - 'banaruiyeh', - 'masiri', - 'lapui', - 'karzin', - 'seyyedan', - 'eshkanan', - 'shahr-e pir', - 'beyram', - 'bahman', - 'qotbabad', - 'juyom', - 'abadeh tashk', - 'khur', - 'mohr', - 'beyza', - 'latifi', - 'bab anar', - 'qarah bolagh', - 'ij', - 'khumeh zar', - 'konartakhteh', - 'miyanshahr', - 'izadkhast', - 'emam shahr', - 'runiz', - 'mobarakabad', - 'sedeh', - 'sheshdeh', - 'khavaran', - 'meshkan', - 'varavi', - 'emad deh', - 'fadami', - 'alamarvdasht', - 'khaneh zenyan', - 'baladeh', - 'nujin', - 'korehi', - 'dezhkord', - 'surmaq', - 'mazayjan', - 'dehram', - 'kuhenjan', - 'khuzi', - 'kupon', - 'now bandegan', - 'ahel', - 'hesami', - 'khaniman', - 'do borji', - 'qatruyeh', - 'nowdan', - 'kamfiruz', - 'hamashahr', - 'efzar', - 'asir', - 'ramjerd', - 'hasanabad', - 'soltan shahr', - 'madar-e soleyman', - 'baba monir', - 'duzeh', - 'arad', - 'fishvar'], - 'gilan': ['rasht', - 'bandar-e anzali', - 'lahijan', - 'langarud', - 'hashtpar', - 'astara', - "sowme'eh sara", - 'astaneh-ye ashrafiyeh', - 'rudsar', - 'fuman', - 'khomam', - 'siahkal', - 'rezvanshahr', - 'manjil', - 'amlash', - 'kiashahr', - 'rostamabad', - 'lowshan', - 'rudbar', - 'kelachay', - 'masal', - 'lasht-e nesha', - 'lavandevil', - 'kuchesfahan', - 'asalem', - 'rahimabad', - 'sangar', - 'chaf and chamkhaleh', - 'chaboksar', - 'shaft', - 'pareh sar', - 'luleman', - 'khoshk-e bijar', - 'marjaghal', - 'kumeleh', - 'shalman', - 'bazar jomeh', - 'chubar', - 'gurab zarmikh', - 'vajargah', - 'haviq', - 'rudboneh', - 'lisar', - 'jirandeh', - 'rankuh', - 'ahmadsargurab', - 'maklavan', - 'tutkabon', - 'barehsar', - 'otaqvar', - 'deylaman', - 'masuleh'], - 'golestan': ['gorgan', - 'gonbad-e kavus', - 'aliabad-e katul', - 'bandar torkaman', - 'azadshahr', - 'kordkuy', - 'kalaleh', - 'aqqala', - 'minudasht', - 'galikash', - 'bandar-e gaz', - 'gomishan', - 'siminshahr', - 'fazelabad', - 'ramian', - 'khan bebin', - 'daland', - 'neginshahr', - 'now kandeh', - 'sarkhon kalateh', - 'jelin-e olya', - 'anbar olum', - 'maraveh tappeh', - 'faraghi', - 'tatar-e olya', - 'sangdevin', - 'mazraeh', - 'now deh khanduz', - 'incheh borun'], - 'hamadan': ['hamadan', - 'malayer', - 'asadabad', - 'nahavand', - 'tuyserkan', - 'bahar', - 'kabudarahang', - 'lalejin', - 'famenin', - 'razan', - 'maryanaj', - 'azandarian', - 'qorveh-e darjazin', - 'juraqan', - 'giyan', - 'mohajeran', - 'salehabad', - 'sarkan', - 'firuzan', - 'samen', - 'damaq', - 'barzul', - 'qahavand', - 'ajin', - 'shirin su', - 'jowkar', - 'gol tappeh', - 'farasfaj', - 'zangeneh'], - 'hormozgan': ['bandar abbas', - 'minab', - 'qeshm', - 'rudan', - 'bandar lengeh', - 'kish', - 'hajjiabad', - 'kong', - 'dargahan', - 'bandar khamir', - 'jask', - 'parsian', - 'bastak', - 'bika', - 'jenah', - 'hasht bandi', - 'ruydar', - 'hormuz', - 'suza', - 'qaleh qazi', - 'sirik', - 'tirur', - 'dashti', - 'tazian-e pain', - 'abu musa', - 'bandar charak', - 'garuk', - 'ziarat-e ali', - 'fin', - 'kushk-e nar', - 'takht', - 'kuhestak', - 'lamazan', - 'senderk', - 'fareghan', - 'sardasht', - 'gowharan', - 'sargaz', - 'dezghan', - 'kukherd'], - 'ilam': ['ilam', - 'abdanan', - 'eyvan', - 'dehloran', - 'darreh shahr', - 'arakvaz', - 'mehran', - 'sarableh', - 'asemanabad', - 'towhid', - 'chavar', - 'badreh', - 'shabab', - 'pahleh', - 'delgosha', - 'murmuri', - 'zarneh', - 'lumar', - 'musian', - 'meymeh', - 'sarabbagh', - 'salehabad', - 'mirza hoseynabad', - 'mehr', - 'balavah'], - 'isfahan': ['isfahan', - 'kashan', - 'khomeyni shahr', - 'najafabad', - 'shahin shahr', - 'shahreza', - 'fuladshahr', - 'baharestan', - 'mobarakeh', - 'aran va bidgol', - 'golpayegan', - 'zarrin shahr', - 'dorcheh piaz', - 'dowlatabad', - 'falavarjan', - 'qahderijan', - 'khvorzuq', - 'nain', - 'semirom', - 'kelishad va sudarjan', - 'goldasht', - 'gaz', - 'abrisham', - 'khansar', - 'tiran', - 'daran', - 'sedeh lenjan', - 'dizicheh', - 'varnamkhast', - 'dehaqan', - 'dastgerd', - 'ardestan', - 'chamgardan', - 'badrud', - 'imanshahr', - 'natanz', - 'chermahin', - 'fereydunshahr', - 'pir bakran', - 'kushk', - 'varzaneh', - 'nushabad', - 'baharan shahr', - 'kahriz sang', - 'bagh-e bahadoran', - 'zibashahr', - 'chadegan', - 'talkhuncheh', - 'golshahr', - 'buin va miandasht', - 'qahjaverestan', - 'zayandeh rud', - 'gorgab', - 'habibabad', - 'majlesi', - 'zavareh', - 'dehaq', - 'alavicheh', - 'zazeran', - 'manzariyeh', - 'karkevand', - 'asgharabad', - 'khur', - 'nasrabad', - 'guged', - 'harand', - 'jowzdan', - 'shadpurabad', - 'abuzeydabad', - 'sefidshahr', - 'meymeh', - 'barf anbar', - 'meshkat', - 'hana', - 'rozveh', - 'komeshcheh', - 'vazvan', - 'kuhpayeh', - 'sin', - 'golshan', - 'damaneh', - 'sagzi', - 'hasanabad', - 'nikabad', - 'mohammadabad', - 'mahabad', - 'asgaran', - 'jandaq', - 'baghshad', - 'tudeshk', - 'jowsheqan va kamu', - 'ziar', - 'qamsar', - 'afus', - 'rezvanshahr', - 'ezhiyeh', - 'khaledabad', - 'farrokhi', - 'kamu va chugan', - 'barzok', - 'tarq', - 'vanak', - 'komeh', - 'neyasar', - 'bafran', - 'anarak', - 'lay bid'], - 'kerman': ['kerman', - 'sirjan', - 'rafsanjan', - 'jiroft', - 'bam', - 'zarand', - 'kahnuj', - 'shahr-e babak', - 'baft', - 'bardsir', - 'baravat', - 'ravar', - 'mohamadabad', - 'najaf shahr', - 'mahan', - 'anbarabad', - 'manujan', - 'anar', - 'rudbar', - 'rabor', - 'qaleh ganj', - 'kuhbanan', - 'darb-e behesht', - 'baghin', - 'rayen', - 'negar', - 'mes-e sarcheshmeh', - 'ekhtiarabad', - 'golbaf', - 'zeydabad', - 'zangiabad', - 'khursand', - 'pariz', - 'dehaj', - 'koshkuiyeh', - 'golzar', - 'mohammadabad-e gonbaki', - 'fahraj', - 'arzuiyeh', - 'zeh-e kalut', - 'jebalbarez', - 'chatrud', - 'yazdan shahr', - 'nowdezh', - 'faryab', - 'reyhan shahr', - 'kian shahr', - 'bezenjan', - 'lalehzar', - 'aminshahr', - 'dow sari', - 'boluk', - 'bahreman', - 'narmashir', - 'shahdad', - 'khatunabad', - 'jupar', - 'balvard', - 'jowzam', - 'kazemabad', - 'khanuk', - 'mohiabad', - 'anduhjerd', - 'khvaju shahr', - 'safayyeh', - 'hamashahr', - 'dashtkar', - 'mardehek', - 'nezamshahr', - 'hanza', - 'hojedk'], - 'kermanshah': ['kermanshah', - 'eslamabad-e gharb', - 'kangavar', - 'harsin', - 'javanrud', - 'sarpol-e zahab', - 'sonqor', - 'sahneh', - 'gilan-e gharb', - 'paveh', - 'ravansar', - 'qasr-e shirin', - 'tazehabad', - 'kerend-gharb', - 'gahvareh', - 'kuzaran', - 'banevreh', - 'gowdin', - 'nowdeshah', - 'shahrak-e rijab', - 'shahu', - 'sarmast', - 'bisotun', - 'bayangan', - 'nowsud', - 'ezgeleh', - 'homeyl', - 'satar', - 'robat', - 'miyan rahan', - 'halashi', - 'sumar'], - 'khuzestan': ['ahvaz', - 'dezful', - 'abadan', - 'bandar-e mahshahr', - 'andimeshk', - 'khorramshahr', - 'behbahan', - 'izeh', - 'shushtar', - 'masjed soleyman', - 'omidiyeh', - 'bandar-e emam khomeyni', - 'kut-e abdollah', - 'shush', - 'ramhormoz', - 'shadegan', - 'susangerd', - 'hendijan', - 'ramshir', - 'sheyban', - 'hamidiyeh', - 'gotvand', - 'bagh-e malek', - 'chamran', - 'lali', - 'haftkel', - 'hoveyzeh', - 'veys', - 'mollasani', - 'aghajari', - 'sharaft', - 'dezab', - 'arvandkenar', - 'shamsabad', - 'mianrud', - 'qaleh tall', - 'safiabad', - 'horr', - 'hosseinabad', - 'bostan', - 'chavibdeh', - 'elhayi', - 'seydun', - 'saleh shahr', - 'sardasht', - 'alvan', - 'hamzeh', - 'torkalaki', - 'darkhoveyn', - 'siah mansur', - 'jannat makan', - 'abu homeyzeh', - 'dehdez', - 'cham golak', - 'mansuriyeh', - 'sardaran', - 'shahrak-e babak', - 'kut-e seyyed naim', - 'tashan', - 'khanafereh', - 'rafi', - 'meydavud', - 'saleh moshatat', - 'guriyeh', - 'jayezan', - 'saland', - 'minushahr', - 'qaleh-ye khvajeh', - 'bidrubeh', - 'moshrageh', - 'choghamish', - 'hoseyniyeh', - 'somaleh', - 'abezhdan', - 'zahreh', - 'golgir', - 'moqavemat', - 'shirin shahr'], - 'kohgiluyeh and boyer-ahmad': ['yasuj', - 'dogonbadan', - 'dehdasht', - 'charam', - 'likak', - 'madavan', - 'landeh', - 'basht', - 'sisakht', - 'suq', - 'dishmok', - 'qaleh raisi', - 'margown', - 'pataveh', - 'sarfaryab', - 'chitab', - 'garab-e sofla'], - 'kurdistan': ['sanandaj', - 'saqqez', - 'qorveh', - 'marivan', - 'baneh', - 'kamyaran', - 'divandarreh', - 'bijar', - 'dehgolan', - 'kani dinar', - 'serishabad', - 'delbaran', - 'sarvabad', - 'bolbanabad', - 'yasukand', - 'muchesh', - 'aavraman takht', - 'dezej', - 'armardeh', - 'saheb', - 'zarrineh', - 'tup aghaj', - 'buin-e sofla', - 'shuyesheh', - 'kani sur', - 'pir taj', - 'bardeh rasheh', - 'babarashani', - 'chenareh'], - 'lorestan': ['khorramabad', - 'borujerd', - 'dorud', - 'kuhdasht', - 'aligudarz', - 'nurabad', - 'azna', - 'aleshtar', - 'pol-e dokhtar', - 'kunani', - 'mamulan', - 'chaqabol', - 'oshtorinan', - 'garab', - 'sepiddasht', - 'firuzabad', - 'zagheh', - 'darb-e gonbad', - 'veysian', - 'bayranshahr', - 'sarab-e dowreh', - 'momenabad', - 'chalanchulan', - 'shulehabad-e olya', - 'haft cheshmeh'], - 'markazi': ['arak', - 'saveh', - 'khomeyn', - 'mahallat', - 'delijan', - 'shazand', - 'mamuniyeh', - 'mahajeran', - 'tafresh', - 'milajerd', - 'ashtian', - 'komijan', - 'khondab', - 'shahbaz', - 'astaneh', - 'zaviyeh', - 'parandak', - 'nimvar', - 'farmahin', - 'davudabad', - 'gharqabad', - 'khoshkrud', - 'javersiyan', - 'aveh', - 'karchan', - 'nowbaran', - 'khenejin', - 'naraq', - 'tureh', - 'saruq', - 'hendudur', - 'qurchi bashi', - 'razeghi', - 'bazneh'], - 'mazandaran': ['sari', - 'babol', - 'amol', - 'qaem shahr', - 'behshahr', - 'chalus', - 'neka', - 'babolsar', - 'tonekabon', - 'nowshahr', - 'fereydunkenar', - 'ramsar', - 'juybar', - 'mahmudabad', - 'amirkola', - 'nur', - 'galugah', - 'ketalem and sadat shahr', - 'zirab', - 'abbasabad', - 'kelardasht', - 'rostamkola', - 'khorramabad', - 'shirud', - 'chamestan', - 'khalil shahr', - 'hachirud', - 'arateh', - 'salman shahr', - 'surak', - 'shirgah', - 'pol sefid', - 'kiakola', - 'bahnemir', - 'royan', - 'izadshahr', - 'gatab', - 'sorkhrud', - 'marzanabad', - 'nashtarud', - 'kelarabad', - 'galugah', - 'kiasar', - 'kalleh bast', - 'zargarmahalleh', - 'pul', - 'kojur', - 'khush rudpey', - 'imamzadeh abdollah', - 'kuhi kheyl', - 'baladeh', - 'dabudasht', - 'alasht', - 'rineh', - 'pain hular', - 'marzikola', - 'gazanak', - 'farim'], - 'north khorasan': ['bojnord', - 'shirvan', - 'esfarayen', - 'jajarm', - 'garmeh', - 'ashkhaneh', - 'faruj', - 'raz', - 'daraq', - 'ziarat', - 'safiabad', - 'ivar', - 'shahrabad-e khavar', - 'titkanlu', - 'chenaran', - 'qazi', - 'shoqan', - 'sankhvast', - 'pish qaleh', - 'lujali', - 'hesar-e garmkhan', - 'qushkhaneh'], - 'qazvin': ['qazvin', - 'alvand', - 'eqbaliyeh', - 'takestan', - 'abyek', - 'mohammadiyeh', - 'bidestan', - 'mahmudabad nemuneh', - 'buin zahra', - 'sharifiyeh', - 'shal', - 'esfarvarin', - 'danesfahan', - 'ziaabad', - 'khorramdasht', - 'narjeh', - 'abgarm', - 'sagzabad', - 'ardak', - 'avaj', - 'khak-e ali', - 'moallem kalayeh', - 'kuhin', - 'razmian', - 'sirdan'], - 'qom': ['qom', 'qanavat', 'jafariyeh', 'dastjerd', 'kahak', 'salafchegan'], - 'razavi khorasan': ['mashhad', - 'nishapur', - 'sabzevar', - 'torbat-e heydarieh', - 'kashmar', - 'quchan', - 'torbat-e jam', - 'taybad', - 'chenaran', - 'sarakhs', - 'gonabad', - 'fariman', - 'shahr jadid-e golbahar', - 'dargaz', - 'khaf', - 'bardaskan', - 'torqabeh', - 'feyzabad', - 'neqab', - 'shandiz', - 'kharv', - 'khalilabad', - 'sangan', - 'bajestan', - 'kariz', - 'mashhad rizeh', - 'dowlatabad', - 'bakharz', - 'razaviyeh', - 'salehabad', - 'farhadgerd', - 'golmakan', - 'ahmadabad-e sowlat', - 'nasrabad', - 'nilshahr', - 'nashtifan', - 'kalat', - 'jangal', - 'salami', - 'joghatai', - 'anabad', - 'kondor', - 'roshtkhar', - 'firuzeh', - 'darrud', - 'sefid sang', - 'soltanabad', - 'rivash', - 'bidokht', - 'qasemabad', - 'qalandarabad', - 'kakhk', - 'rud ab', - 'bar', - 'shahr-e zow', - 'bayg', - 'yunesi', - 'shadmehr', - 'kadkan', - 'qadamgah', - 'davarzan', - 'now khandan', - 'chapeshlu', - 'sheshtomad', - 'shahrabad', - 'lotfabad', - 'robat-e sang', - 'chekneh', - 'eshqabad', - 'hemmatabad', - 'malekabad', - 'mazdavand', - 'bajgiran'], - 'semnan': ['semnan', - 'shahrud', - 'damghan', - 'garmsar', - 'mehdishahr', - 'aradan', - 'eyvanki', - 'bastam', - 'sorkheh', - 'shahmirzad', - 'mojen', - 'kalateh khij', - 'darjazin', - 'meyami', - 'kalateh rudbar', - 'dibaj', - 'beyarjomand', - 'amiriyeh', - 'rudian', - 'kohanabad'], - 'sistan and baluchestan': ['zahedan', - 'zabol', - 'iranshahr', - 'chabahar', - 'saravan', - 'khash', - 'konarak', - 'jaleq', - 'nik shahr', - 'pishin', - 'zehak', - 'qasr-e qand', - 'suran', - 'fanuj', - 'bampur', - 'zaboli', - 'mohamadan', - 'galmurti', - 'rasak', - 'mirjaveh', - 'dust mohammad', - 'bent', - 'nosratabad', - 'bazman', - 'negur', - 'mohamadi', - 'bonjar', - 'gosht', - 'ali akbar', - 'espakeh', - 'zarabad', - 'adimi', - 'nukabad', - 'mohammadabad', - 'sirkan', - 'hiduj', - 'sarbaz', - 'ramshar'], - 'south khorasan': ['birjand', - 'qaen', - 'tabas', - 'ferdows', - 'nehbandan', - 'boshruyeh', - 'sarayan', - 'sarbisheh', - 'eslamiyeh', - 'ayask', - 'khezri dasht beyaz', - 'eshqabad', - 'hajjiabad', - 'asadiyeh', - 'seh qaleh', - 'nimbeluk', - 'tabas-e masina', - 'khusf', - 'esfeden', - 'arianshahr', - 'deyhuk', - 'mud', - 'shusef', - 'eresk', - 'qohestan', - 'gazik', - 'zohan', - 'mohammadshahr'], - 'tehran': ['tehran', - 'eslamshahr', - 'golestan', - 'qods', - 'malard', - 'varamin', - 'shahriar', - 'qarchak', - 'nasimshahr', - 'pakdasht', - 'pardis', - 'andisheh', - 'robat karim', - 'parand', - 'salehieh', - 'damavand', - 'baqershahr', - 'baghestan', - 'bumahen', - 'chahardangeh', - 'pishva', - 'sabashahr', - 'kahrizak', - 'vahidieh', - 'nasirshahr', - 'ferdowsieh', - 'hasanabad', - 'safadasht', - 'rudehen', - 'shahedshahr', - 'ferunabad', - 'lavasan', - 'firuzkuh', - 'ahmadabad-e mostowfi', - 'absard', - 'sharifabad', - 'fasham', - 'javadabad', - 'shemshak', - 'kilan', - 'abali', - 'arjomand'], - 'west azerbaijan': ['urmia', - 'khoy', - 'bukan', - 'mahabad', - 'miandoab', - 'salmas', - 'piranshahr', - 'naqadeh', - 'takab', - 'maku', - 'oshnavieh', - 'sardasht', - 'shahin dezh', - 'qarah zia od din', - 'showt', - 'rabat', - 'siah cheshmeh', - 'poldasht', - 'bazargan', - 'tazeh shahr', - 'mohammadyar', - 'chahar borj', - 'firuraq', - 'dizaj diz', - 'nushin', - 'mahmudabad', - 'mirabad', - 'qatur', - 'baruq', - 'keshavarz', - 'gerd kashaneh', - 'ivughli', - 'nazok-e olya', - 'qushchi', - 'nalus', - 'marganlar', - 'avajiq', - 'serow', - 'silvaneh', - 'simmineh', - 'zurabad', - 'khalifan'], - 'yazd': ['yazd', - 'meybod', - 'ardakan', - 'bafq', - 'hamidiya', - 'mehriz', - 'abarkuh', - 'taft', - 'shahediyeh', - 'ashkezar', - 'herat', - 'zarach', - 'marvast', - 'mehrdasht', - 'behabad', - 'bafruiyeh', - 'ahmadabad', - 'nadushan', - 'aqda', - 'nir', - 'khezrabad'], - 'zanjan': ['zanjan', - 'abhar', - 'khorramdarreh', - 'qeydar', - 'hidaj', - 'sain qaleh', - 'soltaniyeh', - 'sojas', - 'sohrevard', - 'zarrin rud', - 'ab bar', - 'mah neshan', - 'garmab', - 'dandi', - 'nurabad', - 'karasf', - 'zarrinabad', - 'armaghankhaneh', - 'chavarzaq', - 'halab', - 'nik pey']} diff --git a/apps/users/constans.py b/apps/users/constans.py index 24ca9e5..e69de29 100644 --- a/apps/users/constans.py +++ b/apps/users/constans.py @@ -1,2 +0,0 @@ -MAX_OTP_TRY = 3 -DEVELOPMENT_PHONE_NUMBERS = ['+989999999999', '+989999999998'] diff --git a/apps/users/forms.py b/apps/users/forms.py index 9a0afd2..60b3411 100644 --- a/apps/users/forms.py +++ b/apps/users/forms.py @@ -6,85 +6,3 @@ from django.utils.translation import gettext_lazy as _ UserModel = get_user_model() -class OTPAuthenticationForm(forms.Form): - """ - Base class for authenticating users. Extend this to get a form that accepts - username/password logins. - """ - phone_number = UsernameField(widget=forms.TextInput(attrs={'autofocus': True})) - otp = forms.CharField( - label=_("otp"), - strip=False, - widget=forms.PasswordInput(attrs={'autocomplete': 'current-password'}), - ) - - error_messages = { - 'invalid_login': _( - "Please enter a correct %(phone_number)s and otp. Note that both " - "fields may be case-sensitive." - ), - 'inactive': _("This account is inactive."), - } - - def __init__(self, request=None, *args, **kwargs): - """ - The 'request' parameter is set for custom auth use by subclasses. - The form data comes in via the standard 'data' kwarg. - """ - self.request = request - self.user_cache = None - super().__init__(*args, **kwargs) - - # Set the max length and label for the "username" field. - self.phone_number_field = UserModel._meta.get_field('phone_number') - phone_number_max_length = self.phone_number_field.max_length or 24 - self.fields['phone_number'].max_length = phone_number_max_length - self.fields['phone_number'].widget.attrs['maxlength'] = phone_number_max_length - if self.fields['phone_number'].label is None: - self.fields['phone_number'].label = capfirst(self.phone_number_field.verbose_name) - - def clean(self): - phone_number = self.cleaned_data.get('phone_number') - otp = self.cleaned_data.get('otp') - - if phone_number is not None and otp: - self.user_cache = authenticate(self.request, phone_number=phone_number, otp=otp) - if self.user_cache is None: - raise self.get_invalid_login_error() - else: - self.confirm_login_allowed(self.user_cache) - - return self.cleaned_data - - def confirm_login_allowed(self, user): - """ - Controls whether the given User may log in. This is a policy setting, - independent of end-user authentication. This default behavior is to - allow login by active users, and reject login by inactive users. - - If the given user cannot log in, this method should raise a - ``forms.ValidationError``. - - If the given user may log in, this method should return None. - """ - if not user.is_active: - raise forms.ValidationError( - self.error_messages['inactive'], - code='inactive', - ) - - def get_user(self): - return self.user_cache - - def get_invalid_login_error(self): - return forms.ValidationError( - self.error_messages['invalid_login'], - code='invalid_login', - params={'phone_number': self.phone_number_field.verbose_name}, - ) - - -class ProfileUpdateForm(forms.ModelForm): - class Meta: - model = UserModel - fields = ['name', 'first_name', 'last_name', 'email', 'iban', 'avatar'] diff --git a/apps/users/migrations/0001_initial.py b/apps/users/migrations/0001_initial.py index 91e2693..dd69561 100644 --- a/apps/users/migrations/0001_initial.py +++ b/apps/users/migrations/0001_initial.py @@ -1,10 +1,10 @@ -# Generated by Django 4.1 on 2022-08-17 19:05 +# Generated by Django 5.0.6 on 2024-07-03 14:33 import apps.users.models import django.contrib.auth.validators -from django.db import migrations, models import django.utils.timezone import uuid +from django.db import migrations, models class Migration(migrations.Migration): @@ -20,28 +20,18 @@ class Migration(migrations.Migration): name='User', fields=[ ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), - ('name', models.CharField(blank=True, max_length=30, verbose_name='name')), - ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), - ('province', models.IntegerField(blank=True, null=True, verbose_name='Province')), - ('city', models.IntegerField(blank=True, null=True, verbose_name='City')), - ('postal_code', models.CharField(blank=True, max_length=20, null=True, verbose_name='postal_code')), - ('address', models.TextField(blank=True, null=True, verbose_name='address')), ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), ('password', models.CharField(max_length=128, verbose_name='password')), ('username', models.CharField(blank=True, error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, null=True, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), - ('phone_number', models.CharField(blank=True, max_length=30, null=True, unique=True, verbose_name='phone number')), - ('otp', models.CharField(blank=True, max_length=6, null=True, verbose_name='otp')), - ('otp_expire', models.DateTimeField(blank=True, null=True, verbose_name='otp expire')), - ('otp_try', models.IntegerField(blank=True, default=0, null=True, verbose_name='otp try')), ('last_checkout_request', models.DateTimeField(blank=True, max_length=30, null=True, verbose_name='otp expire')), - ('balance', models.IntegerField(default=0, verbose_name='balance')), + ('balance', models.BigIntegerField(default=0, verbose_name='balance')), ('iban', models.CharField(blank=True, max_length=30, null=True)), ('iban_verified', models.BooleanField(null=True)), - ('avatar', models.ImageField(blank=True, null=True, upload_to='avatars')), ('last_update', models.DateTimeField(auto_now=True, max_length=30, null=True, verbose_name='last update')), ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), diff --git a/apps/users/migrations/0002_user_email_verified.py b/apps/users/migrations/0002_user_email_verified.py deleted file mode 100644 index 1f38c2f..0000000 --- a/apps/users/migrations/0002_user_email_verified.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.1 on 2022-09-08 13:01 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('users', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='user', - name='email_verified', - field=models.BooleanField(null=True), - ), - ] diff --git a/apps/users/migrations/0003_user_ott_user_ott_expire.py b/apps/users/migrations/0003_user_ott_user_ott_expire.py deleted file mode 100644 index 09ec203..0000000 --- a/apps/users/migrations/0003_user_ott_user_ott_expire.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.1 on 2022-09-15 07:28 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('users', '0002_user_email_verified'), - ] - - operations = [ - migrations.AddField( - model_name='user', - name='ott', - field=models.CharField(blank=True, max_length=36, null=True, verbose_name='one time token'), - ), - migrations.AddField( - model_name='user', - name='ott_expire', - field=models.DateTimeField(blank=True, null=True, verbose_name='otp expire'), - ), - ] diff --git a/apps/users/migrations/0004_alter_user_balance.py b/apps/users/migrations/0004_alter_user_balance.py deleted file mode 100644 index 1d53da8..0000000 --- a/apps/users/migrations/0004_alter_user_balance.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.1 on 2022-09-25 19:05 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('users', '0003_user_ott_user_ott_expire'), - ] - - operations = [ - migrations.AlterField( - model_name='user', - name='balance', - field=models.BigIntegerField(default=0, verbose_name='balance'), - ), - ] diff --git a/apps/users/models.py b/apps/users/models.py index 5da1e0e..65c53c9 100644 --- a/apps/users/models.py +++ b/apps/users/models.py @@ -12,40 +12,22 @@ from django.utils import timezone from django.utils.translation import gettext_lazy as _ import uuid -from .provinces_and_cities import state -# from .tasks import send_notification - -from apps.users.constans import MAX_OTP_TRY, DEVELOPMENT_PHONE_NUMBERS class UserManager(BaseUserManager): use_in_migrations = True - def _create_user(self, phone_number=None, username=None, email=None, password=None, **extra_fields): - if not phone_number and not email and not username: - raise ValueError('The given phone_number or email must be set') - - email = self.normalize_email(email) - username = self.model.normalize_username(username) - # TODO: validate phone number - user = self.model(phone_number=phone_number, username=username, email=email, **extra_fields) - user.set_password(password) - user.set_otp() + def _create_user(self, pk, **extra_fields): + user = self.model(pk=pk **extra_fields) user.date_joined = timezone.now() user.save(using=self._db) - return user - def create_user(self, phone_number=None, username=None, email=None, password=None, **extra_fields): - + def create_user(self, pk, **extra_fields): extra_fields.setdefault('is_staff', False) extra_fields.setdefault('is_superuser', False) - return self._create_user(phone_number=phone_number, - username=username, - email=email, - password=password, - **extra_fields) + return self._create_user(pk=pk, **extra_fields) def create_superuser(self, username, email, password, **extra_fields): if not username: @@ -64,36 +46,6 @@ class UserManager(BaseUserManager): class User(AbstractUser): uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True) - name = models.CharField(_('name'), max_length=30, blank=True) - first_name = models.CharField(_('first name'), max_length=30, blank=True) - last_name = models.CharField(_('last name'), max_length=150, blank=True) - email = models.EmailField(_('email address'), blank=True) - email_verified = models.BooleanField(null=True) - - # country = models.CharField(_('country'), max_length=50, blank=True, null=True) - # province = models.CharField(_('province'), max_length=50, blank=True, null=True) - # city = models.CharField(_('city'), max_length=50, blank=True, null=True) - # postal_code = models.CharField(_('postal_code'), max_length=20, blank=True, null=True) - # address = models.TextField(_('address'), null=True, blank=True) - - province = models.IntegerField(verbose_name=_("Province"), blank=True, null=True) - city = models.IntegerField(verbose_name=_("City"), blank=True, null=True) - postal_code = models.CharField(_('postal_code'), max_length=20, blank=True, null=True) - address = models.TextField(_('address'), null=True, blank=True) - - is_staff = models.BooleanField( - _('staff status'), - default=False, - help_text=_('Designates whether the user can log into this admin site.'), - ) - is_active = models.BooleanField( - _('active'), - default=True, - help_text=_( - 'Designates whether this user should be treated as active. ' - 'Unselect this instead of deleting accounts.' - ), - ) password = models.CharField(_('password'), max_length=128) username_validator = UnicodeUsernameValidator() username = models.CharField( @@ -108,157 +60,17 @@ class User(AbstractUser): blank=True, null=True ) - phone_number = models.CharField(_('phone number'), max_length=30, blank=True, null=True, unique=True) - otp = models.CharField(_('otp'), max_length=6, blank=True, null=True) - otp_expire = models.DateTimeField(_('otp expire'), blank=True, null=True) - otp_try = models.IntegerField(_('otp try'), blank=True, null=True, default=0) - - ott = models.CharField(_('one time token'), max_length=36, blank=True, null=True) - ott_expire = models.DateTimeField(_('otp expire'), blank=True, null=True) - last_checkout_request = models.DateTimeField(_('otp expire'), max_length=30, blank=True, null=True) balance = models.BigIntegerField(_('balance'), default=0) iban = models.CharField(null=True, max_length=30, blank=True) iban_verified = models.BooleanField(null=True) - avatar = models.ImageField(upload_to='avatars', null=True, blank=True) - last_update = models.DateTimeField(_('last update'), max_length=30, blank=True, null=True, auto_now=True) last_login = models.DateTimeField(_('last login'), blank=True, null=True) date_joined = models.DateTimeField(_('date joined'), default=timezone.now) objects = UserManager() - @property - def city_name(self): - return state.get_city_by_id(self.city) - - @property - def province_name(self): - return state.get_province_by_id(self.province) - - def set_otp(self): - if settings.DEBUG or self.phone_number in DEVELOPMENT_PHONE_NUMBERS: - self.otp = '12345' - - else: - self.otp = ''.join(random.choice('0123456789') for _ in range(5)) - - self.otp_expire = timezone.now() + timedelta(minutes=5) - self.otp_try = 0 - - def set_ott(self): - self.ott = ''.join(random.choice(string.ascii_letters+string.digits) for _ in range(32)) - # self.ott = ''.join(random.choice(string.ascii_letters+string.digits+string.punctuation) for _ in range(32)) - self.ott_expire = timezone.now() + timedelta(seconds=300) - - def otp_is_valid(self): - return bool(self.otp and timezone.now() <= self.otp_expire) - - def ott_is_valid(self): - return bool(self.ott and timezone.now() <= self.ott_expire) - - def check_otp(self, otp): - if self.otp_try <= MAX_OTP_TRY: - result = otp and self.otp == otp and self.otp_is_valid() - if result: - self.otp = None - self.date_joined = timezone.now() - else: - self.otp_try += 1 - - else: - self.otp = None - result = False - - self.save() - return result - - def check_ott(self, ott:str): - ott = ott.strip() - if ott and self.ott == ott and self.ott_is_valid(): - self.ott = None - self.date_joined = timezone.now() - result = True - - else: - self.ott = None - result = False - - self.save() - return result - - def check_auth(self, field, value): - result = False - if field == 'otp': - result = self.check_otp(value) - - elif field == 'password': - result = self.check_password(value) - - elif field == 'ott': - result = self.check_ott(value) - - if result: - self.last_login = timezone.now() - self.save() - - return result - - def send_otp(self): - print(self.otp) - self.notify(body=str(self.otp), title='OTP') - - def notify(self, body, title=None, notification_type='sms'): - # TODO: enable celery - send_notification(phone_number=self.phone_number, - title=title, - body=body, - user_uuid=self.uuid, - notification_type=1, - ) - def __str__(self): - return self.name or self.get_full_name() or self.username or self.phone_number or self.email or _('no name') - - -def create_user_in_introspection(token, content): - # content = { - # "active": True, - # "scope": "read write email", - # "client_id": "J8NFmU4tJVgDxKaJFmXTWvaHO", - # "username": "aaronpk", - # "exp": 1437275311 - # } - - user_client = service_clients.Client(access_token=token, - scopes=content.get('scope', '').split(), - expires_in=100) - - user_account = user_client.accounts.account() - if user_account.get('uuid'): - content.update(user_account) - - # content = {'active': True, 'scope': 'ipg.payment:submit accounts.account:retrieve', 'exp': 1602619137, - # 'client_id': 'bd2hEGXytrqMjbFnplRyHJTeoW1vwKzCZJtH6ro0', 'username': '', - # 'uuid': '94255117-117c-4a9f-af50-35a6c47503ac', 'email': '', 'phone_number': '+989106853582', - # 'first_name': '', 'last_name': '', 'name': '', 'balance': 0, - # 'avatar': 'http://accounts.gooyal.com/media/avatars/1691899.jpg', 'iban': '', 'iban_verified': None} - - user = User.objects.filter(uuid=content['uuid']).first() - if user: - pass - - else: - user = User.objects.create_user( - **{User.USERNAME_FIELD: content["username"]}, - uuid=content['uuid'], - email=content['email'], - phone_number=content['phone_number'], - first_name=content['first_name'], - last_name=content['last_name'], - name=content['name'], - avatar=content['avatar'] - ) - return user, content + return str(self.username or self.pk) diff --git a/apps/users/provinces_and_cities.py b/apps/users/provinces_and_cities.py deleted file mode 100644 index 498efc7..0000000 --- a/apps/users/provinces_and_cities.py +++ /dev/null @@ -1,1337 +0,0 @@ -PROVINCES = { - 1: {'cities': [{'id': 1, 'name': 'آذرشهر'}, - {'id': 2, 'name': 'تیمورلو'}, - {'id': 3, 'name': 'گوگان'}, - {'id': 4, 'name': 'ممقان'}, - {'id': 5, 'name': 'اسکو'}, - {'id': 6, 'name': 'ایلخچی'}, - {'id': 7, 'name': 'سهند'}, - {'id': 8, 'name': 'اهر'}, - {'id': 9, 'name': 'هوراند'}, - {'id': 10, 'name': 'بستان آباد'}, - {'id': 11, 'name': 'تیکمه داش'}, - {'id': 12, 'name': 'بناب'}, - {'id': 13, 'name': 'باسمنج'}, - {'id': 14, 'name': 'تبریز'}, - {'id': 15, 'name': 'خسروشاه'}, - {'id': 16, 'name': 'سردرود'}, - {'id': 17, 'name': 'جلفا'}, - {'id': 18, 'name': 'سیه رود'}, - {'id': 19, 'name': 'هادیشهر'}, - {'id': 20, 'name': 'قره آغاج'}, - {'id': 21, 'name': 'خمارلو'}, - {'id': 22, 'name': 'دوزدوزان'}, - {'id': 23, 'name': 'سراب'}, - {'id': 24, 'name': 'شربیان'}, - {'id': 25, 'name': 'مهربان'}, - {'id': 26, 'name': 'تسوج'}, - {'id': 27, 'name': 'خامنه'}, - {'id': 28, 'name': 'سیس'}, - {'id': 29, 'name': 'شبستر'}, - {'id': 30, 'name': 'شرفخانه'}, - {'id': 31, 'name': 'شندآباد'}, - {'id': 32, 'name': 'صوفیان'}, - {'id': 33, 'name': 'کوزه کنان'}, - {'id': 34, 'name': 'وایقان'}, - {'id': 35, 'name': 'جوان قلعه'}, - {'id': 36, 'name': 'عجب شیر'}, - {'id': 37, 'name': 'آبش احمد'}, - {'id': 38, 'name': 'کلیبر'}, - {'id': 39, 'name': 'خداجو(خراجو)'}, - {'id': 40, 'name': 'مراغه'}, - {'id': 41, 'name': 'بناب مرند'}, - {'id': 42, 'name': 'زنوز'}, - {'id': 43, 'name': 'کشکسرای'}, - {'id': 44, 'name': 'مرند'}, - {'id': 45, 'name': 'یامچی'}, - {'id': 46, 'name': 'لیلان'}, - {'id': 47, 'name': 'مبارک شهر'}, - {'id': 48, 'name': 'ملکان'}, - {'id': 49, 'name': 'آقکند'}, - {'id': 50, 'name': 'اچاچی'}, - {'id': 51, 'name': 'ترک'}, - {'id': 52, 'name': 'ترکمانچای'}, - {'id': 53, 'name': 'میانه'}, - {'id': 54, 'name': 'خاروانا'}, - {'id': 55, 'name': 'ورزقان'}, - {'id': 56, 'name': 'بخشایش'}, - {'id': 57, 'name': 'خواجه'}, - {'id': 58, 'name': 'زرنق'}, - {'id': 59, 'name': 'کلوانق'}, - {'id': 60, 'name': 'هریس'}, - {'id': 61, 'name': 'نظرکهریزی'}, - {'id': 62, 'name': 'هشترود'}], - 'en_name': 'azerbaijan-east', - 'name': 'آذربایجان شرقی'}, - 2: {'cities': [{'id': 63, 'name': 'ارومیه'}, - {'id': 64, 'name': 'سرو'}, - {'id': 65, 'name': 'سیلوانه'}, - {'id': 66, 'name': 'قوشچی'}, - {'id': 67, 'name': 'نوشین'}, - {'id': 68, 'name': 'اشنویه'}, - {'id': 69, 'name': 'نالوس'}, - {'id': 70, 'name': 'بوکان'}, - {'id': 71, 'name': 'سیمینه'}, - {'id': 72, 'name': 'پلدشت'}, - {'id': 73, 'name': 'نازک علیا'}, - {'id': 74, 'name': 'پیرانشهر'}, - {'id': 75, 'name': 'گردکشانه'}, - {'id': 76, 'name': 'تکاب'}, - {'id': 77, 'name': 'آواجیق'}, - {'id': 78, 'name': 'سیه چشمه'}, - {'id': 79, 'name': 'قره ضیاءالدین'}, - {'id': 80, 'name': 'ایواوغلی'}, - {'id': 81, 'name': 'خوی'}, - {'id': 82, 'name': 'دیزج دیز'}, - {'id': 83, 'name': 'زرآباد'}, - {'id': 84, 'name': 'فیرورق'}, - {'id': 85, 'name': 'قطور'}, - {'id': 86, 'name': 'ربط'}, - {'id': 87, 'name': 'سردشت'}, - {'id': 88, 'name': 'میرآباد'}, - {'id': 89, 'name': 'تازه شهر'}, - {'id': 90, 'name': 'سلماس'}, - {'id': 91, 'name': 'شاهین دژ'}, - {'id': 92, 'name': 'کشاورز'}, - {'id': 93, 'name': 'محمودآباد'}, - {'id': 94, 'name': 'شوط'}, - {'id': 95, 'name': 'مرگنلر'}, - {'id': 96, 'name': 'بازرگان'}, - {'id': 97, 'name': 'ماکو'}, - {'id': 98, 'name': 'خلیفان'}, - {'id': 99, 'name': 'مهاباد'}, - {'id': 100, 'name': 'باروق'}, - {'id': 101, 'name': 'چهاربرج'}, - {'id': 102, 'name': 'میاندوآب'}, - {'id': 103, 'name': 'محمدیار'}, - {'id': 104, 'name': 'نقده'}], - 'en_name': 'azerbaijan-west', - 'name': 'آذربایجان غربی'}, - 3: {'cities': [{'id': 105, 'name': 'اردبیل'}, - {'id': 106, 'name': 'هیر'}, - {'id': 107, 'name': 'بیله سوار'}, - {'id': 108, 'name': 'جعفرآباد'}, - {'id': 109, 'name': 'اسلام اباد'}, - {'id': 110, 'name': 'اصلاندوز'}, - {'id': 111, 'name': 'پارس آباد'}, - {'id': 112, 'name': 'تازه کند'}, - {'id': 113, 'name': 'خلخال'}, - {'id': 114, 'name': 'کلور'}, - {'id': 115, 'name': 'هشتجین'}, - {'id': 116, 'name': 'سرعین'}, - {'id': 117, 'name': 'گیوی'}, - {'id': 118, 'name': 'تازه کندانگوت'}, - {'id': 119, 'name': 'گرمی'}, - {'id': 120, 'name': 'رضی'}, - {'id': 121, 'name': 'فخراباد'}, - {'id': 122, 'name': 'قصابه'}, - {'id': 123, 'name': 'لاهرود'}, - {'id': 124, 'name': 'مرادلو'}, - {'id': 125, 'name': 'مشگین شهر'}, - {'id': 126, 'name': 'آبی بیگلو'}, - {'id': 127, 'name': 'عنبران'}, - {'id': 128, 'name': 'نمین'}, - {'id': 129, 'name': 'کوراییم'}, - {'id': 130, 'name': 'نیر'}], - 'en_name': 'ardebil', - 'name': 'اردبیل'}, - 4: {'cities': [{'id': 131, 'name': 'آران وبیدگل'}, - {'id': 132, 'name': 'ابوزیدآباد'}, - {'id': 133, 'name': 'سفیدشهر'}, - {'id': 134, 'name': 'نوش آباد'}, - {'id': 135, 'name': 'اردستان'}, - {'id': 136, 'name': 'زواره'}, - {'id': 137, 'name': 'مهاباد'}, - {'id': 138, 'name': 'اژیه'}, - {'id': 139, 'name': 'اصفهان'}, - {'id': 140, 'name': 'بهارستان'}, - {'id': 141, 'name': 'تودشک'}, - {'id': 142, 'name': 'حسن اباد'}, - {'id': 143, 'name': 'زیار'}, - {'id': 144, 'name': 'سجزی'}, - {'id': 145, 'name': 'قهجاورستان'}, - {'id': 146, 'name': 'کوهپایه'}, - {'id': 147, 'name': 'محمدآباد'}, - {'id': 148, 'name': 'نصرآباد'}, - {'id': 149, 'name': 'نیک آباد'}, - {'id': 150, 'name': 'ورزنه'}, - {'id': 151, 'name': 'هرند'}, - {'id': 152, 'name': 'حبیب آباد'}, - {'id': 153, 'name': 'خورزوق'}, - {'id': 154, 'name': 'دستگرد'}, - {'id': 155, 'name': 'دولت آباد'}, - {'id': 156, 'name': 'سین'}, - {'id': 157, 'name': 'شاپورآباد'}, - {'id': 158, 'name': 'کمشچه'}, - {'id': 159, 'name': 'افوس'}, - {'id': 160, 'name': 'بویین ومیاندشت'}, - {'id': 161, 'name': 'تیران'}, - {'id': 162, 'name': 'رضوانشهر'}, - {'id': 163, 'name': 'عسگران'}, - {'id': 164, 'name': 'چادگان'}, - {'id': 165, 'name': 'رزوه'}, - {'id': 166, 'name': 'اصغرآباد'}, - {'id': 167, 'name': 'خمینی شهر'}, - {'id': 168, 'name': 'درچه'}, - {'id': 169, 'name': 'کوشک'}, - {'id': 170, 'name': 'خوانسار'}, - {'id': 171, 'name': 'جندق'}, - {'id': 172, 'name': 'خور'}, - {'id': 173, 'name': 'فرخی'}, - {'id': 174, 'name': 'دهاقان'}, - {'id': 175, 'name': 'گلشن'}, - {'id': 176, 'name': 'حنا'}, - {'id': 177, 'name': 'سمیرم'}, - {'id': 178, 'name': 'کمه'}, - {'id': 179, 'name': 'ونک'}, - {'id': 180, 'name': 'شاهین شهر'}, - {'id': 181, 'name': 'گرگاب'}, - {'id': 182, 'name': 'گزبرخوار'}, - {'id': 183, 'name': 'لای بید'}, - {'id': 184, 'name': 'میمه'}, - {'id': 185, 'name': 'وزوان'}, - {'id': 186, 'name': 'شهرضا'}, - {'id': 187, 'name': 'منظریه'}, - {'id': 188, 'name': 'داران'}, - {'id': 189, 'name': 'دامنه'}, - {'id': 190, 'name': 'برف انبار'}, - {'id': 191, 'name': 'فریدونشهر'}, - {'id': 192, 'name': 'ابریشم'}, - {'id': 193, 'name': 'ایمانشهر'}, - {'id': 194, 'name': 'بهاران شهر'}, - {'id': 195, 'name': 'پیربکران'}, - {'id': 196, 'name': 'زازران'}, - {'id': 197, 'name': 'فلاورجان'}, - {'id': 198, 'name': 'قهدریجان'}, - {'id': 199, 'name': 'کلیشادوسودرجان'}, - {'id': 200, 'name': 'برزک'}, - {'id': 201, 'name': 'جوشقان قالی'}, - {'id': 202, 'name': 'قمصر'}, - {'id': 203, 'name': 'کاشان'}, - {'id': 204, 'name': 'کامو و چوگان'}, - {'id': 205, 'name': 'مشکات'}, - {'id': 206, 'name': 'نیاسر'}, - {'id': 207, 'name': 'گلپایگان'}, - {'id': 208, 'name': 'گلشهر'}, - {'id': 209, 'name': 'گوگد'}, - {'id': 210, 'name': 'باغ بهادران'}, - {'id': 211, 'name': 'باغشاد'}, - {'id': 212, 'name': 'چرمهین'}, - {'id': 213, 'name': 'چمگردان'}, - {'id': 214, 'name': 'زاینده رود'}, - {'id': 215, 'name': 'زرین شهر'}, - {'id': 216, 'name': 'سده لنجان'}, - {'id': 217, 'name': 'فولادشهر'}, - {'id': 218, 'name': 'ورنامخواست'}, - {'id': 219, 'name': 'دیزیچه'}, - {'id': 220, 'name': 'زیباشهر'}, - {'id': 221, 'name': 'طالخونچه'}, - {'id': 222, 'name': 'کرکوند'}, - {'id': 223, 'name': 'مبارکه'}, - {'id': 224, 'name': 'مجلسی'}, - {'id': 225, 'name': 'انارک'}, - {'id': 226, 'name': 'بافران'}, - {'id': 227, 'name': 'نایین'}, - {'id': 228, 'name': 'جوزدان'}, - {'id': 229, 'name': 'دهق'}, - {'id': 230, 'name': 'علویجه'}, - {'id': 231, 'name': 'کهریزسنگ'}, - {'id': 232, 'name': 'گلدشت'}, - {'id': 233, 'name': 'نجف آباد'}, - {'id': 234, 'name': 'بادرود'}, - {'id': 235, 'name': 'خالدآباد'}, - {'id': 236, 'name': 'طرق رود'}, - {'id': 237, 'name': 'نطنز'}], - 'en_name': 'isfahan', - 'name': 'اصفهان'}, - 5: {'cities': [{'id': 238, 'name': 'اشتهارد'}, - {'id': 239, 'name': 'چهارباغ'}, - {'id': 240, 'name': 'شهرجدیدهشتگرد'}, - {'id': 241, 'name': 'کوهسار'}, - {'id': 242, 'name': 'گلسار'}, - {'id': 243, 'name': 'هشتگرد'}, - {'id': 244, 'name': 'طالقان'}, - {'id': 245, 'name': 'فردیس'}, - {'id': 246, 'name': 'مشکین دشت'}, - {'id': 247, 'name': 'آسارا'}, - {'id': 248, 'name': 'کرج'}, - {'id': 249, 'name': 'کمال شهر'}, - {'id': 250, 'name': 'گرمدره'}, - {'id': 251, 'name': 'ماهدشت'}, - {'id': 252, 'name': 'محمدشهر'}, - {'id': 253, 'name': 'تنکمان'}, - {'id': 254, 'name': 'نظرآباد'}], - 'en_name': 'alborz', - 'name': 'البرز'}, - 6: {'cities': [{'id': 255, 'name': 'آبدانان'}, - {'id': 256, 'name': 'سراب باغ'}, - {'id': 257, 'name': 'مورموری'}, - {'id': 258, 'name': 'ایلام'}, - {'id': 259, 'name': 'چوار'}, - {'id': 260, 'name': 'ایوان'}, - {'id': 261, 'name': 'زرنه'}, - {'id': 262, 'name': 'بدره'}, - {'id': 263, 'name': 'آسمان آباد'}, - {'id': 264, 'name': 'بلاوه'}, - {'id': 265, 'name': 'توحید'}, - {'id': 266, 'name': 'سرابله'}, - {'id': 267, 'name': 'شباب'}, - {'id': 268, 'name': 'دره شهر'}, - {'id': 269, 'name': 'ماژین'}, - {'id': 270, 'name': 'پهله'}, - {'id': 271, 'name': 'دهلران'}, - {'id': 272, 'name': 'موسیان'}, - {'id': 273, 'name': 'میمه'}, - {'id': 274, 'name': 'لومار'}, - {'id': 275, 'name': 'ارکواز'}, - {'id': 276, 'name': 'دلگشا'}, - {'id': 277, 'name': 'مهر'}, - {'id': 278, 'name': 'صالح آباد'}, - {'id': 279, 'name': 'مهران'}], - 'en_name': 'ilam', - 'name': 'ایلام'}, - 7: {'cities': [{'id': 280, 'name': 'بوشهر'}, - {'id': 281, 'name': 'چغادک'}, - {'id': 282, 'name': 'خارک'}, - {'id': 283, 'name': 'عالی شهر'}, - {'id': 284, 'name': 'آباد'}, - {'id': 285, 'name': 'اهرم'}, - {'id': 286, 'name': 'دلوار'}, - {'id': 287, 'name': 'انارستان'}, - {'id': 288, 'name': 'جم'}, - {'id': 289, 'name': 'ریز'}, - {'id': 290, 'name': 'آب پخش'}, - {'id': 291, 'name': 'برازجان'}, - {'id': 292, 'name': 'بوشکان'}, - {'id': 293, 'name': 'تنگ ارم'}, - {'id': 294, 'name': 'دالکی'}, - {'id': 295, 'name': 'سعد آباد'}, - {'id': 296, 'name': 'شبانکاره'}, - {'id': 297, 'name': 'کلمه'}, - {'id': 298, 'name': 'وحدتیه'}, - {'id': 299, 'name': 'بادوله'}, - {'id': 300, 'name': 'خورموج'}, - {'id': 301, 'name': 'شنبه'}, - {'id': 302, 'name': 'کاکی'}, - {'id': 303, 'name': 'آبدان'}, - {'id': 304, 'name': 'بردخون'}, - {'id': 305, 'name': 'بردستان'}, - {'id': 306, 'name': 'بندردیر'}, - {'id': 307, 'name': 'دوراهک'}, - {'id': 308, 'name': 'امام حسن'}, - {'id': 309, 'name': 'بندردیلم'}, - {'id': 310, 'name': 'عسلویه'}, - {'id': 311, 'name': 'نخل تقی'}, - {'id': 312, 'name': 'بندرکنگان'}, - {'id': 313, 'name': 'بنک'}, - {'id': 314, 'name': 'سیراف'}, - {'id': 315, 'name': 'بندرریگ'}, - {'id': 316, 'name': 'بندرگناوه'}], - 'en_name': 'bushehr', - 'name': 'بوشهر'}, - 8: {'cities': [{'id': 317, 'name': 'احمد آباد مستوفی'}, - {'id': 318, 'name': 'اسلامشهر'}, - {'id': 319, 'name': 'چهاردانگه'}, - {'id': 320, 'name': 'صالحیه'}, - {'id': 321, 'name': 'گلستان'}, - {'id': 322, 'name': 'نسیم شهر'}, - {'id': 323, 'name': 'پاکدشت'}, - {'id': 324, 'name': 'شریف آباد'}, - {'id': 325, 'name': 'فرون اباد'}, - {'id': 326, 'name': 'بومهن'}, - {'id': 327, 'name': 'پردیس'}, - {'id': 328, 'name': 'پیشوا'}, - {'id': 329, 'name': 'تهران'}, - {'id': 330, 'name': 'آبسرد'}, - {'id': 331, 'name': 'آبعلی'}, - {'id': 332, 'name': 'دماوند'}, - {'id': 333, 'name': 'رودهن'}, - {'id': 334, 'name': 'کیلان'}, - {'id': 335, 'name': 'پرند'}, - {'id': 336, 'name': 'رباطکریم'}, - {'id': 337, 'name': 'نصیرشهر'}, - {'id': 338, 'name': 'باقرشهر'}, - {'id': 339, 'name': 'حسن آباد'}, - {'id': 340, 'name': 'ری'}, - {'id': 341, 'name': 'کهریزک'}, - {'id': 342, 'name': 'تجریش'}, - {'id': 343, 'name': 'شمشک'}, - {'id': 344, 'name': 'فشم'}, - {'id': 345, 'name': 'لواسان'}, - {'id': 346, 'name': 'اندیشه'}, - {'id': 347, 'name': 'باغستان'}, - {'id': 348, 'name': 'شاهدشهر'}, - {'id': 349, 'name': 'شهریار'}, - {'id': 350, 'name': 'صباشهر'}, - {'id': 351, 'name': 'فردوسیه'}, - {'id': 352, 'name': 'وحیدیه'}, - {'id': 353, 'name': 'ارجمند'}, - {'id': 354, 'name': 'فیروزکوه'}, - {'id': 355, 'name': 'قدس'}, - {'id': 356, 'name': 'قرچک'}, - {'id': 357, 'name': 'صفادشت'}, - {'id': 358, 'name': 'ملارد'}, - {'id': 359, 'name': 'جوادآباد'}, - {'id': 360, 'name': 'ورامین'}], - 'en_name': 'tehran', - 'name': 'تهران'}, - 9: {'cities': [{'id': 361, 'name': 'اردل'}, - {'id': 362, 'name': 'دشتک'}, - {'id': 363, 'name': 'سرخون'}, - {'id': 364, 'name': 'کاج'}, - {'id': 365, 'name': 'بروجن'}, - {'id': 366, 'name': 'بلداجی'}, - {'id': 367, 'name': 'سفیددشت'}, - {'id': 368, 'name': 'فرادبنه'}, - {'id': 369, 'name': 'گندمان'}, - {'id': 370, 'name': 'نقنه'}, - {'id': 371, 'name': 'بن'}, - {'id': 372, 'name': 'وردنجان'}, - {'id': 373, 'name': 'سامان'}, - {'id': 374, 'name': 'سودجان'}, - {'id': 375, 'name': 'سورشجان'}, - {'id': 376, 'name': 'شهرکرد'}, - {'id': 377, 'name': 'طاقانک'}, - {'id': 378, 'name': 'فرخ شهر'}, - {'id': 379, 'name': 'کیان'}, - {'id': 380, 'name': 'نافچ'}, - {'id': 381, 'name': 'هارونی'}, - {'id': 382, 'name': 'هفشجان'}, - {'id': 383, 'name': 'باباحیدر'}, - {'id': 384, 'name': 'پردنجان'}, - {'id': 385, 'name': 'جونقان'}, - {'id': 386, 'name': 'چلیچه'}, - {'id': 387, 'name': 'فارسان'}, - {'id': 388, 'name': 'گوجان'}, - {'id': 389, 'name': 'بازفت'}, - {'id': 390, 'name': 'چلگرد'}, - {'id': 391, 'name': 'صمصامی'}, - {'id': 392, 'name': 'دستنا'}, - {'id': 393, 'name': 'شلمزار'}, - {'id': 394, 'name': 'گهرو'}, - {'id': 395, 'name': 'ناغان'}, - {'id': 396, 'name': 'آلونی'}, - {'id': 397, 'name': 'سردشت'}, - {'id': 398, 'name': 'لردگان'}, - {'id': 399, 'name': 'مال خلیفه'}, - {'id': 400, 'name': 'منج'}], - 'en_name': 'chahar-mahaal-bakhtiari', - 'name': 'چهارمحال وبختیاری'}, - 10: {'cities': [{'id': 401, 'name': 'ارسک'}, - {'id': 402, 'name': 'بشرویه'}, - {'id': 403, 'name': 'بیرجند'}, - {'id': 404, 'name': 'خوسف'}, - {'id': 405, 'name': 'محمدشهر'}, - {'id': 406, 'name': 'اسدیه'}, - {'id': 407, 'name': 'طبس مسینا'}, - {'id': 408, 'name': 'قهستان'}, - {'id': 409, 'name': 'گزیک'}, - {'id': 410, 'name': 'حاجی آباد'}, - {'id': 411, 'name': 'زهان'}, - {'id': 412, 'name': 'آیسک'}, - {'id': 413, 'name': 'سرایان'}, - {'id': 414, 'name': 'سه قلعه'}, - {'id': 415, 'name': 'سربیشه'}, - {'id': 416, 'name': 'مود'}, - {'id': 417, 'name': 'دیهوک'}, - {'id': 418, 'name': 'طبس'}, - {'id': 419, 'name': 'عشق آباد'}, - {'id': 420, 'name': 'اسلامیه'}, - {'id': 421, 'name': 'فردوس'}, - {'id': 422, 'name': 'آرین شهر'}, - {'id': 423, 'name': 'اسفدن'}, - {'id': 424, 'name': 'خضری دشت بیاض'}, - {'id': 425, 'name': 'قاین'}, - {'id': 426, 'name': 'نیمبلوک'}, - {'id': 427, 'name': 'شوسف'}, - {'id': 428, 'name': 'نهبندان'}], - 'en_name': 'khorasan-south', - 'name': 'خراسان جنوبی'}, - 11: {'cities': [{'id': 429, 'name': 'باخرز'}, - {'id': 430, 'name': 'بجستان'}, - {'id': 431, 'name': 'یونسی'}, - {'id': 432, 'name': 'انابد'}, - {'id': 433, 'name': 'بردسکن'}, - {'id': 434, 'name': 'شهراباد'}, - {'id': 435, 'name': 'شاندیز'}, - {'id': 436, 'name': 'طرقبه'}, - {'id': 437, 'name': 'تایباد'}, - {'id': 438, 'name': 'کاریز'}, - {'id': 439, 'name': 'مشهدریزه'}, - {'id': 440, 'name': 'احمدابادصولت'}, - {'id': 441, 'name': 'تربت جام'}, - {'id': 442, 'name': 'صالح آباد'}, - {'id': 443, 'name': 'نصرآباد'}, - {'id': 444, 'name': 'نیل شهر'}, - {'id': 445, 'name': 'بایک'}, - {'id': 446, 'name': 'تربت حیدریه'}, - {'id': 447, 'name': 'رباط سنگ'}, - {'id': 448, 'name': 'کدکن'}, - {'id': 449, 'name': 'جغتای'}, - {'id': 450, 'name': 'نقاب'}, - {'id': 451, 'name': 'چناران'}, - {'id': 452, 'name': 'گلبهار'}, - {'id': 453, 'name': 'گلمکان'}, - {'id': 454, 'name': 'خلیل آباد'}, - {'id': 455, 'name': 'کندر'}, - {'id': 456, 'name': 'خواف'}, - {'id': 457, 'name': 'سلامی'}, - {'id': 458, 'name': 'سنگان'}, - {'id': 459, 'name': 'قاسم آباد'}, - {'id': 460, 'name': 'نشتیفان'}, - {'id': 461, 'name': 'سلطان آباد'}, - {'id': 462, 'name': 'داورزن'}, - {'id': 463, 'name': 'چاپشلو'}, - {'id': 464, 'name': 'درگز'}, - {'id': 465, 'name': 'لطف آباد'}, - {'id': 466, 'name': 'نوخندان'}, - {'id': 467, 'name': 'جنگل'}, - {'id': 468, 'name': 'رشتخوار'}, - {'id': 469, 'name': 'دولت آباد'}, - {'id': 470, 'name': 'روداب'}, - {'id': 471, 'name': 'سبزوار'}, - {'id': 472, 'name': 'ششتمد'}, - {'id': 473, 'name': 'سرخس'}, - {'id': 474, 'name': 'مزدآوند'}, - {'id': 475, 'name': 'سفیدسنگ'}, - {'id': 476, 'name': 'فرهادگرد'}, - {'id': 477, 'name': 'فریمان'}, - {'id': 478, 'name': 'قلندرآباد'}, - {'id': 479, 'name': 'فیروزه'}, - {'id': 480, 'name': 'همت آباد'}, - {'id': 481, 'name': 'باجگیران'}, - {'id': 482, 'name': 'قوچان'}, - {'id': 483, 'name': 'ریوش'}, - {'id': 484, 'name': 'کاشمر'}, - {'id': 485, 'name': 'شهرزو'}, - {'id': 486, 'name': 'کلات'}, - {'id': 487, 'name': 'بیدخت'}, - {'id': 488, 'name': 'کاخک'}, - {'id': 489, 'name': 'گناباد'}, - {'id': 490, 'name': 'رضویه'}, - {'id': 491, 'name': 'مشهد'}, - {'id': 492, 'name': 'مشهد ثامن'}, - {'id': 493, 'name': 'ملک آباد'}, - {'id': 494, 'name': 'شادمهر'}, - {'id': 495, 'name': 'فیض آباد'}, - {'id': 496, 'name': 'بار'}, - {'id': 497, 'name': 'چکنه'}, - {'id': 498, 'name': 'خرو'}, - {'id': 499, 'name': 'درود'}, - {'id': 500, 'name': 'عشق آباد'}, - {'id': 501, 'name': 'قدمگاه'}, - {'id': 502, 'name': 'نیشابور'}], - 'en_name': 'khorasan-razavi', - 'name': 'خراسان رضوی'}, - 12: {'cities': [{'id': 503, 'name': 'اسفراین'}, - {'id': 504, 'name': 'صفی آباد'}, - {'id': 505, 'name': 'بجنورد'}, - {'id': 506, 'name': 'چناران شهر'}, - {'id': 507, 'name': 'حصارگرمخان'}, - {'id': 508, 'name': 'جاجرم'}, - {'id': 509, 'name': 'سنخواست'}, - {'id': 510, 'name': 'شوقان'}, - {'id': 511, 'name': 'راز'}, - {'id': 512, 'name': 'زیارت'}, - {'id': 513, 'name': 'شیروان'}, - {'id': 514, 'name': 'قوشخانه'}, - {'id': 515, 'name': 'لوجلی'}, - {'id': 516, 'name': 'تیتکانلو'}, - {'id': 517, 'name': 'فاروج'}, - {'id': 518, 'name': 'ایور'}, - {'id': 519, 'name': 'درق'}, - {'id': 520, 'name': 'گرمه'}, - {'id': 521, 'name': 'آشخانه'}, - {'id': 522, 'name': 'آوا'}, - {'id': 523, 'name': 'پیش قلعه'}, - {'id': 524, 'name': 'قاضی'}], - 'en_name': 'khorasan-north', - 'name': 'خراسان شمالی'}, - 13: {'cities': [{'id': 525, 'name': 'آبادان'}, - {'id': 526, 'name': 'اروندکنار'}, - {'id': 527, 'name': 'چویبده'}, - {'id': 528, 'name': 'آغاجاری'}, - {'id': 529, 'name': 'امیدیه'}, - {'id': 530, 'name': 'جایزان'}, - {'id': 531, 'name': 'آبژدان'}, - {'id': 532, 'name': 'قلعه خواجه'}, - {'id': 533, 'name': 'آزادی'}, - {'id': 534, 'name': 'اندیمشک'}, - {'id': 535, 'name': 'بیدروبه'}, - {'id': 536, 'name': 'چم گلک'}, - {'id': 537, 'name': 'حسینیه'}, - {'id': 538, 'name': 'الهایی'}, - {'id': 539, 'name': 'اهواز'}, - {'id': 540, 'name': 'ایذه'}, - {'id': 541, 'name': 'دهدز'}, - {'id': 542, 'name': 'باغ ملک'}, - {'id': 543, 'name': 'صیدون'}, - {'id': 544, 'name': 'قلعه تل'}, - {'id': 545, 'name': 'میداود'}, - {'id': 546, 'name': 'شیبان'}, - {'id': 547, 'name': 'ملاثانی'}, - {'id': 548, 'name': 'ویس'}, - {'id': 549, 'name': 'بندرامام خمینی'}, - {'id': 550, 'name': 'بندرماهشهر'}, - {'id': 551, 'name': 'چمران'}, - {'id': 552, 'name': 'بهبهان'}, - {'id': 553, 'name': 'تشان'}, - {'id': 554, 'name': 'سردشت'}, - {'id': 555, 'name': 'منصوریه'}, - {'id': 556, 'name': 'حمیدیه'}, - {'id': 557, 'name': 'خرمشهر'}, - {'id': 558, 'name': 'مقاومت'}, - {'id': 559, 'name': 'مینوشهر'}, - {'id': 560, 'name': 'چغامیش'}, - {'id': 561, 'name': 'حمزه'}, - {'id': 562, 'name': 'دزفول'}, - {'id': 563, 'name': 'سالند'}, - {'id': 564, 'name': 'سیاه منصور'}, - {'id': 565, 'name': 'شمس آباد'}, - {'id': 566, 'name': 'شهر امام'}, - {'id': 567, 'name': 'صفی آباد'}, - {'id': 568, 'name': 'میانرود'}, - {'id': 569, 'name': 'ابوحمیظه'}, - {'id': 570, 'name': 'بستان'}, - {'id': 571, 'name': 'سوسنگرد'}, - {'id': 572, 'name': 'کوت سیدنعیم'}, - {'id': 573, 'name': 'رامشیر'}, - {'id': 574, 'name': 'مشراگه'}, - {'id': 575, 'name': 'رامهرمز'}, - {'id': 576, 'name': 'خنافره'}, - {'id': 577, 'name': 'دارخوین'}, - {'id': 578, 'name': 'شادگان'}, - {'id': 579, 'name': 'الوان'}, - {'id': 580, 'name': 'حر'}, - {'id': 581, 'name': 'شاوور'}, - {'id': 582, 'name': 'شوش'}, - {'id': 583, 'name': 'فتح المبین'}, - {'id': 584, 'name': 'سرداران'}, - {'id': 585, 'name': 'شرافت'}, - {'id': 586, 'name': 'شوشتر'}, - {'id': 587, 'name': 'گوریه'}, - {'id': 588, 'name': 'کوت عبداله'}, - {'id': 589, 'name': 'ترکالکی'}, - {'id': 590, 'name': 'جنت مکان'}, - {'id': 591, 'name': 'سماله'}, - {'id': 592, 'name': 'صالح شهر'}, - {'id': 593, 'name': 'گتوند'}, - {'id': 594, 'name': 'لالی'}, - {'id': 595, 'name': 'گلگیر'}, - {'id': 596, 'name': 'مسجدسلیمان'}, - {'id': 597, 'name': 'هفتگل'}, - {'id': 598, 'name': 'زهره'}, - {'id': 599, 'name': 'هندیجان'}, - {'id': 600, 'name': 'رفیع'}, - {'id': 601, 'name': 'هویزه'}], - 'en_name': 'khuzestan', - 'name': 'خوزستان'}, - 14: {'cities': [{'id': 602, 'name': 'ابهر'}, - {'id': 603, 'name': 'صایین قلعه'}, - {'id': 604, 'name': 'هیدج'}, - {'id': 605, 'name': 'حلب'}, - {'id': 606, 'name': 'زرین آباد'}, - {'id': 607, 'name': 'زرین رود'}, - {'id': 608, 'name': 'سجاس'}, - {'id': 609, 'name': 'سهرورد'}, - {'id': 610, 'name': 'قیدار'}, - {'id': 611, 'name': 'کرسف'}, - {'id': 612, 'name': 'گرماب'}, - {'id': 613, 'name': 'نوربهار'}, - {'id': 614, 'name': 'خرمدره'}, - {'id': 615, 'name': 'ارمغانخانه'}, - {'id': 616, 'name': 'زنجان'}, - {'id': 617, 'name': 'نیک پی'}, - {'id': 618, 'name': 'سلطانیه'}, - {'id': 619, 'name': 'آب بر'}, - {'id': 620, 'name': 'چورزق'}, - {'id': 621, 'name': 'دندی'}, - {'id': 622, 'name': 'ماه نشان'}], - 'en_name': 'zanjan', - 'name': 'زنجان'}, - 15: {'cities': [{'id': 623, 'name': 'آرادان'}, - {'id': 624, 'name': 'کهن آباد'}, - {'id': 625, 'name': 'امیریه'}, - {'id': 626, 'name': 'دامغان'}, - {'id': 627, 'name': 'دیباج'}, - {'id': 628, 'name': 'کلاته'}, - {'id': 629, 'name': 'سرخه'}, - {'id': 630, 'name': 'سمنان'}, - {'id': 631, 'name': 'بسطام'}, - {'id': 632, 'name': 'بیارجمند'}, - {'id': 633, 'name': 'رودیان'}, - {'id': 634, 'name': 'شاهرود'}, - {'id': 635, 'name': 'کلاته خیج'}, - {'id': 636, 'name': 'مجن'}, - {'id': 637, 'name': 'ایوانکی'}, - {'id': 638, 'name': 'گرمسار'}, - {'id': 639, 'name': 'درجزین'}, - {'id': 640, 'name': 'شهمیرزاد'}, - {'id': 641, 'name': 'مهدی شهر'}, - {'id': 642, 'name': 'میامی'}], - 'en_name': 'semnan', - 'name': 'سمنان'}, - 16: {'cities': [{'id': 643, 'name': 'ایرانشهر'}, - {'id': 644, 'name': 'بزمان'}, - {'id': 645, 'name': 'بمپور'}, - {'id': 646, 'name': 'محمدان'}, - {'id': 647, 'name': 'چابهار'}, - {'id': 648, 'name': 'نگور'}, - {'id': 649, 'name': 'خاش'}, - {'id': 650, 'name': 'نوک آباد'}, - {'id': 651, 'name': 'گلمورتی'}, - {'id': 652, 'name': 'بنجار'}, - {'id': 653, 'name': 'زابل'}, - {'id': 654, 'name': 'زاهدان'}, - {'id': 655, 'name': 'نصرت آباد'}, - {'id': 656, 'name': 'زهک'}, - {'id': 657, 'name': 'جالق'}, - {'id': 658, 'name': 'سراوان'}, - {'id': 659, 'name': 'سیرکان'}, - {'id': 660, 'name': 'گشت'}, - {'id': 661, 'name': 'محمدی'}, - {'id': 662, 'name': 'پیشین'}, - {'id': 663, 'name': 'راسک'}, - {'id': 664, 'name': 'سرباز'}, - {'id': 665, 'name': 'سوران'}, - {'id': 666, 'name': 'هیدوچ'}, - {'id': 667, 'name': 'فنوج'}, - {'id': 668, 'name': 'قصرقند'}, - {'id': 669, 'name': 'زرآباد'}, - {'id': 670, 'name': 'کنارک'}, - {'id': 671, 'name': 'مهرستان'}, - {'id': 672, 'name': 'میرجاوه'}, - {'id': 673, 'name': 'اسپکه'}, - {'id': 674, 'name': 'بنت'}, - {'id': 675, 'name': 'نیک شهر'}, - {'id': 676, 'name': 'ادیمی'}, - {'id': 677, 'name': 'شهرک علی اکبر'}, - {'id': 678, 'name': 'محمدآباد'}, - {'id': 679, 'name': 'دوست محمد'}], - 'en_name': 'sistan-baluchestan', - 'name': 'سیستان و بلوچستان'}, - 17: {'cities': [{'id': 680, 'name': 'آباده'}, - {'id': 681, 'name': 'ایزدخواست'}, - {'id': 682, 'name': 'بهمن'}, - {'id': 683, 'name': 'سورمق'}, - {'id': 684, 'name': 'صغاد'}, - {'id': 685, 'name': 'ارسنجان'}, - {'id': 686, 'name': 'استهبان'}, - {'id': 687, 'name': 'ایج'}, - {'id': 688, 'name': 'رونیز'}, - {'id': 689, 'name': 'اقلید'}, - {'id': 690, 'name': 'حسن اباد'}, - {'id': 691, 'name': 'دژکرد'}, - {'id': 692, 'name': 'سده'}, - {'id': 693, 'name': 'بوانات'}, - {'id': 694, 'name': 'حسامی'}, - {'id': 695, 'name': 'کره ای'}, - {'id': 696, 'name': 'مزایجان'}, - {'id': 697, 'name': 'سعادت شهر'}, - {'id': 698, 'name': 'مادرسلیمان'}, - {'id': 699, 'name': 'باب انار'}, - {'id': 700, 'name': 'جهرم'}, - {'id': 701, 'name': 'خاوران'}, - {'id': 702, 'name': 'دوزه'}, - {'id': 703, 'name': 'قطب آباد'}, - {'id': 704, 'name': 'خرامه'}, - {'id': 705, 'name': 'سلطان شهر'}, - {'id': 706, 'name': 'صفاشهر'}, - {'id': 707, 'name': 'قادراباد'}, - {'id': 708, 'name': 'خنج'}, - {'id': 709, 'name': 'جنت شهر'}, - {'id': 710, 'name': 'داراب'}, - {'id': 711, 'name': 'دوبرجی'}, - {'id': 712, 'name': 'فدامی'}, - {'id': 713, 'name': 'کوپن'}, - {'id': 714, 'name': 'مصیری'}, - {'id': 715, 'name': 'حاجی آباد'}, - {'id': 716, 'name': 'دبیران'}, - {'id': 717, 'name': 'شهرپیر'}, - {'id': 718, 'name': 'اردکان'}, - {'id': 719, 'name': 'بیضا'}, - {'id': 720, 'name': 'هماشهر'}, - {'id': 721, 'name': 'سروستان'}, - {'id': 722, 'name': 'کوهنجان'}, - {'id': 723, 'name': 'خانه زنیان'}, - {'id': 724, 'name': 'داریان'}, - {'id': 725, 'name': 'زرقان'}, - {'id': 726, 'name': 'شهرصدرا'}, - {'id': 727, 'name': 'شیراز'}, - {'id': 728, 'name': 'لپویی'}, - {'id': 729, 'name': 'دهرم'}, - {'id': 730, 'name': 'فراشبند'}, - {'id': 731, 'name': 'نوجین'}, - {'id': 732, 'name': 'زاهدشهر'}, - {'id': 733, 'name': 'ششده'}, - {'id': 734, 'name': 'فسا'}, - {'id': 735, 'name': 'قره بلاغ'}, - {'id': 736, 'name': 'میانشهر'}, - {'id': 737, 'name': 'نوبندگان'}, - {'id': 738, 'name': 'فیروزآباد'}, - {'id': 739, 'name': 'میمند'}, - {'id': 740, 'name': 'افزر'}, - {'id': 741, 'name': 'امام شهر'}, - {'id': 742, 'name': 'قیر'}, - {'id': 743, 'name': 'کارزین (فتح آباد)'}, - {'id': 744, 'name': 'مبارک آباددیز'}, - {'id': 745, 'name': 'بالاده'}, - {'id': 746, 'name': 'خشت'}, - {'id': 747, 'name': 'قایمیه'}, - {'id': 748, 'name': 'کازرون'}, - {'id': 749, 'name': 'کنارتخته'}, - {'id': 750, 'name': 'نودان'}, - {'id': 751, 'name': 'کوار'}, - {'id': 752, 'name': 'گراش'}, - {'id': 753, 'name': 'اوز'}, - {'id': 754, 'name': 'بنارویه'}, - {'id': 755, 'name': 'بیرم'}, - {'id': 756, 'name': 'جویم'}, - {'id': 757, 'name': 'خور'}, - {'id': 758, 'name': 'عمادده'}, - {'id': 759, 'name': 'لار'}, - {'id': 760, 'name': 'لطیفی'}, - {'id': 761, 'name': 'اشکنان'}, - {'id': 762, 'name': 'اهل'}, - {'id': 763, 'name': 'علامرودشت'}, - {'id': 764, 'name': 'لامرد'}, - {'id': 765, 'name': 'خانیمن'}, - {'id': 766, 'name': 'رامجرد'}, - {'id': 767, 'name': 'سیدان'}, - {'id': 768, 'name': 'کامفیروز'}, - {'id': 769, 'name': 'مرودشت'}, - {'id': 770, 'name': 'بابامنیر'}, - {'id': 771, 'name': 'خومه زار'}, - {'id': 772, 'name': 'نورآباد'}, - {'id': 773, 'name': 'اسیر'}, - {'id': 774, 'name': 'خوزی'}, - {'id': 775, 'name': 'گله دار'}, - {'id': 776, 'name': 'مهر'}, - {'id': 777, 'name': 'وراوی'}, - {'id': 778, 'name': 'آباده طشک'}, - {'id': 779, 'name': 'قطرویه'}, - {'id': 780, 'name': 'مشکان'}, - {'id': 781, 'name': 'نی ریز'}], - 'en_name': 'fars', - 'name': 'فارس'}, - 18: {'cities': [{'id': 782, 'name': 'آبیک'}, - {'id': 783, 'name': 'خاکعلی'}, - {'id': 784, 'name': 'آبگرم'}, - {'id': 785, 'name': 'آوج'}, - {'id': 786, 'name': 'الوند'}, - {'id': 787, 'name': 'بیدستان'}, - {'id': 788, 'name': 'شریفیه'}, - {'id': 789, 'name': 'محمدیه'}, - {'id': 790, 'name': 'ارداق'}, - {'id': 791, 'name': 'بویین زهرا'}, - {'id': 792, 'name': 'دانسفهان'}, - {'id': 793, 'name': 'سگزآباد'}, - {'id': 794, 'name': 'شال'}, - {'id': 795, 'name': 'اسفرورین'}, - {'id': 796, 'name': 'تاکستان'}, - {'id': 797, 'name': 'خرمدشت'}, - {'id': 798, 'name': 'ضیاڈآباد'}, - {'id': 799, 'name': 'نرجه'}, - {'id': 800, 'name': 'اقبالیه'}, - {'id': 801, 'name': 'رازمیان'}, - {'id': 802, 'name': 'سیردان'}, - {'id': 803, 'name': 'قزوین'}, - {'id': 804, 'name': 'کوهین'}, - {'id': 805, 'name': 'محمودآبادنمونه'}, - {'id': 806, 'name': 'معلم کلایه'}], - 'en_name': 'qazvin', - 'name': 'قزوین'}, - 19: {'cities': [{'id': 807, 'name': 'جعفریه'}, - {'id': 808, 'name': 'دستجرد'}, - {'id': 809, 'name': 'سلفچگان'}, - {'id': 810, 'name': 'قم'}, - {'id': 811, 'name': 'قنوات'}, - {'id': 812, 'name': 'کهک'}], - 'en_name': 'qom', - 'name': 'قم'}, - 20: {'cities': [{'id': 813, 'name': 'آرمرده'}, - {'id': 814, 'name': 'بانه'}, - {'id': 815, 'name': 'بویین سفلی'}, - {'id': 816, 'name': 'کانی سور'}, - {'id': 817, 'name': 'بابارشانی'}, - {'id': 818, 'name': 'بیجار'}, - {'id': 819, 'name': 'پیرتاج'}, - {'id': 820, 'name': 'توپ آغاج'}, - {'id': 821, 'name': 'یاسوکند'}, - {'id': 822, 'name': 'بلبان آباد'}, - {'id': 823, 'name': 'دهگلان'}, - {'id': 824, 'name': 'دیواندره'}, - {'id': 825, 'name': 'زرینه'}, - {'id': 826, 'name': 'اورامان تخت'}, - {'id': 827, 'name': 'سروآباد'}, - {'id': 828, 'name': 'سقز'}, - {'id': 829, 'name': 'صاحب'}, - {'id': 830, 'name': 'سنندج'}, - {'id': 831, 'name': 'شویشه'}, - {'id': 832, 'name': 'دزج'}, - {'id': 833, 'name': 'دلبران'}, - {'id': 834, 'name': 'سریش آباد'}, - {'id': 835, 'name': 'قروه'}, - {'id': 836, 'name': 'کامیاران'}, - {'id': 837, 'name': 'موچش'}, - {'id': 838, 'name': 'برده رشه'}, - {'id': 839, 'name': 'چناره'}, - {'id': 840, 'name': 'کانی دینار'}, - {'id': 841, 'name': 'مریوان'}], - 'en_name': 'kurdistan', - 'name': 'کردستان'}, - 21: {'cities': [{'id': 842, 'name': 'ارزوییه'}, - {'id': 843, 'name': 'امین شهر'}, - {'id': 844, 'name': 'انار'}, - {'id': 845, 'name': 'بافت'}, - {'id': 846, 'name': 'بزنجان'}, - {'id': 847, 'name': 'بردسیر'}, - {'id': 848, 'name': 'دشتکار'}, - {'id': 849, 'name': 'گلزار'}, - {'id': 850, 'name': 'لاله زار'}, - {'id': 851, 'name': 'نگار'}, - {'id': 852, 'name': 'بروات'}, - {'id': 853, 'name': 'بم'}, - {'id': 854, 'name': 'بلوک'}, - {'id': 855, 'name': 'جبالبارز'}, - {'id': 856, 'name': 'جیرفت'}, - {'id': 857, 'name': 'درب بهشت'}, - {'id': 858, 'name': 'رابر'}, - {'id': 859, 'name': 'هنزا'}, - {'id': 860, 'name': 'راور'}, - {'id': 861, 'name': 'هجدک'}, - {'id': 862, 'name': 'بهرمان'}, - {'id': 863, 'name': 'رفسنجان'}, - {'id': 864, 'name': 'صفاییه'}, - {'id': 865, 'name': 'کشکوییه'}, - {'id': 866, 'name': 'مس سرچشمه'}, - {'id': 867, 'name': 'رودبار'}, - {'id': 868, 'name': 'زهکلوت'}, - {'id': 869, 'name': 'گنبکی'}, - {'id': 870, 'name': 'محمدآباد'}, - {'id': 871, 'name': 'خانوک'}, - {'id': 872, 'name': 'ریحان'}, - {'id': 873, 'name': 'زرند'}, - {'id': 874, 'name': 'یزدان شهر'}, - {'id': 875, 'name': 'بلورد'}, - {'id': 876, 'name': 'پاریز'}, - {'id': 877, 'name': 'خواجو شهر'}, - {'id': 878, 'name': 'زیدآباد'}, - {'id': 879, 'name': 'سیرجان'}, - {'id': 880, 'name': 'نجف شهر'}, - {'id': 881, 'name': 'هماشهر'}, - {'id': 882, 'name': 'جوزم'}, - {'id': 883, 'name': 'خاتون اباد'}, - {'id': 884, 'name': 'خورسند'}, - {'id': 885, 'name': 'دهج'}, - {'id': 886, 'name': 'شهربابک'}, - {'id': 887, 'name': 'دوساری'}, - {'id': 888, 'name': 'عنبرآباد'}, - {'id': 889, 'name': 'مردهک'}, - {'id': 890, 'name': 'فاریاب'}, - {'id': 891, 'name': 'فهرج'}, - {'id': 892, 'name': 'قلعه گنج'}, - {'id': 893, 'name': 'اختیارآباد'}, - {'id': 894, 'name': 'اندوهجرد'}, - {'id': 895, 'name': 'باغین'}, - {'id': 896, 'name': 'جوپار'}, - {'id': 897, 'name': 'چترود'}, - {'id': 898, 'name': 'راین'}, - {'id': 899, 'name': 'زنگی آباد'}, - {'id': 900, 'name': 'شهداد'}, - {'id': 901, 'name': 'کاظم آباد'}, - {'id': 902, 'name': 'کرمان'}, - {'id': 903, 'name': 'گلباف'}, - {'id': 904, 'name': 'ماهان'}, - {'id': 905, 'name': 'محی آباد'}, - {'id': 906, 'name': 'کوهبنان'}, - {'id': 907, 'name': 'کیانشهر'}, - {'id': 908, 'name': 'کهنوج'}, - {'id': 909, 'name': 'منوجان'}, - {'id': 910, 'name': 'نودژ'}, - {'id': 911, 'name': 'نرماشیر'}, - {'id': 912, 'name': 'نظام شهر'}], - 'en_name': 'kerman', - 'name': 'کرمان'}, - 22: {'cities': [{'id': 913, 'name': 'اسلام آبادغرب'}, - {'id': 914, 'name': 'حمیل'}, - {'id': 915, 'name': 'بانوره'}, - {'id': 916, 'name': 'باینگان'}, - {'id': 917, 'name': 'پاوه'}, - {'id': 918, 'name': 'نودشه'}, - {'id': 919, 'name': 'نوسود'}, - {'id': 920, 'name': 'ازگله'}, - {'id': 921, 'name': 'تازه آباد'}, - {'id': 922, 'name': 'جوانرود'}, - {'id': 923, 'name': 'ریجاب'}, - {'id': 924, 'name': 'کرند'}, - {'id': 925, 'name': 'گهواره'}, - {'id': 926, 'name': 'روانسر'}, - {'id': 927, 'name': 'شاهو'}, - {'id': 928, 'name': 'سرپل ذهاب'}, - {'id': 929, 'name': 'سطر'}, - {'id': 930, 'name': 'سنقر'}, - {'id': 931, 'name': 'صحنه'}, - {'id': 932, 'name': 'میان راهان'}, - {'id': 933, 'name': 'سومار'}, - {'id': 934, 'name': 'قصرشیرین'}, - {'id': 935, 'name': 'رباط'}, - {'id': 936, 'name': 'کرمانشاه'}, - {'id': 937, 'name': 'کوزران'}, - {'id': 938, 'name': 'هلشی'}, - {'id': 939, 'name': 'کنگاور'}, - {'id': 940, 'name': 'گودین'}, - {'id': 941, 'name': 'سرمست'}, - {'id': 942, 'name': 'گیلانغرب'}, - {'id': 943, 'name': 'بیستون'}, - {'id': 944, 'name': 'هرسین'}], - 'en_name': 'kermanshah', - 'name': 'کرمانشاه'}, - 23: {'cities': [{'id': 945, 'name': 'باشت'}, - {'id': 946, 'name': 'چیتاب'}, - {'id': 947, 'name': 'گراب سفلی'}, - {'id': 948, 'name': 'مادوان'}, - {'id': 949, 'name': 'مارگون'}, - {'id': 950, 'name': 'یاسوج'}, - {'id': 951, 'name': 'لیکک'}, - {'id': 952, 'name': 'چرام'}, - {'id': 953, 'name': 'سرفاریاب'}, - {'id': 954, 'name': 'پاتاوه'}, - {'id': 955, 'name': 'سی سخت'}, - {'id': 956, 'name': 'دهدشت'}, - {'id': 957, 'name': 'دیشموک'}, - {'id': 958, 'name': 'سوق'}, - {'id': 959, 'name': 'قلعه رییسی'}, - {'id': 960, 'name': 'دوگنبدان'}, - {'id': 961, 'name': 'لنده'}], - 'en_name': 'kohgiluyeh-boyer-ahmad', - 'name': 'کهگیلویه وبویراحمد'}, - 24: {'cities': [{'id': 962, 'name': 'آزادشهر'}, - {'id': 963, 'name': 'نگین شهر'}, - {'id': 964, 'name': 'نوده خاندوز'}, - {'id': 965, 'name': 'آق قلا'}, - {'id': 966, 'name': 'انبارآلوم'}, - {'id': 967, 'name': 'بندرگز'}, - {'id': 968, 'name': 'نوکنده'}, - {'id': 969, 'name': 'بندرترکمن'}, - {'id': 970, 'name': 'تاتارعلیا'}, - {'id': 971, 'name': 'خان ببین'}, - {'id': 972, 'name': 'دلند'}, - {'id': 973, 'name': 'رامیان'}, - {'id': 974, 'name': 'سنگدوین'}, - {'id': 975, 'name': 'علی اباد'}, - {'id': 976, 'name': 'فاضل آباد'}, - {'id': 977, 'name': 'مزرعه'}, - {'id': 978, 'name': 'کردکوی'}, - {'id': 979, 'name': 'فراغی'}, - {'id': 980, 'name': 'کلاله'}, - {'id': 981, 'name': 'گالیکش'}, - {'id': 982, 'name': 'جلین'}, - {'id': 983, 'name': 'سرخنکلاته'}, - {'id': 984, 'name': 'گرگان'}, - {'id': 985, 'name': 'سیمین شهر'}, - {'id': 986, 'name': 'گمیش تپه'}, - {'id': 987, 'name': 'اینچه برون'}, - {'id': 988, 'name': 'گنبدکاووس'}, - {'id': 989, 'name': 'مراوه'}, - {'id': 990, 'name': 'مینودشت'}], - 'en_name': 'golestan', - 'name': 'گلستان'}, - 25: {'cities': [{'id': 991, 'name': 'آستارا'}, - {'id': 992, 'name': 'لوندویل'}, - {'id': 993, 'name': 'آستانه اشرفیه'}, - {'id': 994, 'name': 'کیاشهر'}, - {'id': 995, 'name': 'املش'}, - {'id': 996, 'name': 'رانکوه'}, - {'id': 997, 'name': 'بندرانزلی'}, - {'id': 998, 'name': 'خشکبیجار'}, - {'id': 999, 'name': 'خمام'}, - {'id': 1000, 'name': 'رشت'}, - {'id': 1001, 'name': 'سنگر'}, - {'id': 1002, 'name': 'کوچصفهان'}, - {'id': 1003, 'name': 'لشت نشاء'}, - {'id': 1004, 'name': 'لولمان'}, - {'id': 1005, 'name': 'پره سر'}, - {'id': 1006, 'name': 'رضوانشهر'}, - {'id': 1007, 'name': 'بره سر'}, - {'id': 1008, 'name': 'توتکابن'}, - {'id': 1009, 'name': 'جیرنده'}, - {'id': 1010, 'name': 'رستم آباد'}, - {'id': 1011, 'name': 'رودبار'}, - {'id': 1012, 'name': 'لوشان'}, - {'id': 1013, 'name': 'منجیل'}, - {'id': 1014, 'name': 'چابکسر'}, - {'id': 1015, 'name': 'رحیم آباد'}, - {'id': 1016, 'name': 'رودسر'}, - {'id': 1017, 'name': 'کلاچای'}, - {'id': 1018, 'name': 'واجارگاه'}, - {'id': 1019, 'name': 'دیلمان'}, - {'id': 1020, 'name': 'سیاهکل'}, - {'id': 1021, 'name': 'احمدسرگوراب'}, - {'id': 1022, 'name': 'شفت'}, - {'id': 1023, 'name': 'صومعه سرا'}, - {'id': 1024, 'name': 'گوراب زرمیخ'}, - {'id': 1025, 'name': 'مرجقل'}, - {'id': 1026, 'name': 'اسالم'}, - {'id': 1027, 'name': 'چوبر'}, - {'id': 1028, 'name': 'حویق'}, - {'id': 1029, 'name': 'لیسار'}, - {'id': 1030, 'name': 'هشتپر (تالش)'}, - {'id': 1031, 'name': 'فومن'}, - {'id': 1032, 'name': 'ماسوله'}, - {'id': 1033, 'name': 'ماکلوان'}, - {'id': 1034, 'name': 'رودبنه'}, - {'id': 1035, 'name': 'لاهیجان'}, - {'id': 1036, 'name': 'اطاقور'}, - {'id': 1037, 'name': 'چاف و چمخاله'}, - {'id': 1038, 'name': 'شلمان'}, - {'id': 1039, 'name': 'کومله'}, - {'id': 1040, 'name': 'لنگرود'}, - {'id': 1041, 'name': 'بازار جمعه'}, - {'id': 1042, 'name': 'ماسال'}], - 'en_name': 'gilan', - 'name': 'گیلان'}, - 26: {'cities': [{'id': 1043, 'name': 'ازنا'}, - {'id': 1044, 'name': 'مومن آباد'}, - {'id': 1045, 'name': 'الیگودرز'}, - {'id': 1046, 'name': 'شول آباد'}, - {'id': 1047, 'name': 'اشترینان'}, - {'id': 1048, 'name': 'بروجرد'}, - {'id': 1049, 'name': 'پلدختر'}, - {'id': 1050, 'name': 'معمولان'}, - {'id': 1051, 'name': 'بیران شهر'}, - {'id': 1052, 'name': 'خرم آباد'}, - {'id': 1053, 'name': 'زاغه'}, - {'id': 1054, 'name': 'سپیددشت'}, - {'id': 1055, 'name': 'نورآباد'}, - {'id': 1056, 'name': 'هفت چشمه'}, - {'id': 1057, 'name': 'چالانچولان'}, - {'id': 1058, 'name': 'دورود'}, - {'id': 1059, 'name': 'سراب دوره'}, - {'id': 1060, 'name': 'ویسیان'}, - {'id': 1061, 'name': 'چقابل'}, - {'id': 1062, 'name': 'الشتر'}, - {'id': 1063, 'name': 'فیروزآباد'}, - {'id': 1064, 'name': 'درب گنبد'}, - {'id': 1065, 'name': 'کوهدشت'}, - {'id': 1066, 'name': 'کوهنانی'}, - {'id': 1067, 'name': 'گراب'}], - 'en_name': 'lorestan', - 'name': 'لرستان'}, - 27: {'cities': [{'id': 1068, 'name': 'آمل'}, - {'id': 1069, 'name': 'امامزاده عبدالله'}, - {'id': 1070, 'name': 'دابودشت'}, - {'id': 1071, 'name': 'رینه'}, - {'id': 1072, 'name': 'گزنک'}, - {'id': 1073, 'name': 'امیرکلا'}, - {'id': 1074, 'name': 'بابل'}, - {'id': 1075, 'name': 'خوش رودپی'}, - {'id': 1076, 'name': 'زرگرمحله'}, - {'id': 1077, 'name': 'گتاب'}, - {'id': 1078, 'name': 'گلوگاه'}, - {'id': 1079, 'name': 'مرزیکلا'}, - {'id': 1080, 'name': 'بابلسر'}, - {'id': 1081, 'name': 'بهنمیر'}, - {'id': 1082, 'name': 'هادی شهر'}, - {'id': 1083, 'name': 'بهشهر'}, - {'id': 1084, 'name': 'خلیل شهر'}, - {'id': 1085, 'name': 'رستمکلا'}, - {'id': 1086, 'name': 'تنکابن'}, - {'id': 1087, 'name': 'خرم آباد'}, - {'id': 1088, 'name': 'شیرود'}, - {'id': 1089, 'name': 'نشتارود'}, - {'id': 1090, 'name': 'جویبار'}, - {'id': 1091, 'name': 'کوهی خیل'}, - {'id': 1092, 'name': 'چالوس'}, - {'id': 1093, 'name': 'مرزن آباد'}, - {'id': 1094, 'name': 'هچیرود'}, - {'id': 1095, 'name': 'رامسر'}, - {'id': 1096, 'name': 'کتالم وسادات شهر'}, - {'id': 1097, 'name': 'پایین هولار'}, - {'id': 1098, 'name': 'ساری'}, - {'id': 1099, 'name': 'فریم'}, - {'id': 1100, 'name': 'کیاسر'}, - {'id': 1101, 'name': 'آلاشت'}, - {'id': 1102, 'name': 'پل سفید'}, - {'id': 1103, 'name': 'زیرآب'}, - {'id': 1104, 'name': 'شیرگاه'}, - {'id': 1105, 'name': 'کیاکلا'}, - {'id': 1106, 'name': 'سلمان شهر'}, - {'id': 1107, 'name': 'عباس اباد'}, - {'id': 1108, 'name': 'کلارآباد'}, - {'id': 1109, 'name': 'فریدونکنار'}, - {'id': 1110, 'name': 'ارطه'}, - {'id': 1111, 'name': 'قایم شهر'}, - {'id': 1112, 'name': 'کلاردشت'}, - {'id': 1113, 'name': 'گلوگاه'}, - {'id': 1114, 'name': 'سرخرود'}, - {'id': 1115, 'name': 'محمودآباد'}, - {'id': 1116, 'name': 'سورک'}, - {'id': 1117, 'name': 'نکا'}, - {'id': 1118, 'name': 'ایزدشهر'}, - {'id': 1119, 'name': 'بلده'}, - {'id': 1120, 'name': 'چمستان'}, - {'id': 1121, 'name': 'رویان'}, - {'id': 1122, 'name': 'نور'}, - {'id': 1123, 'name': 'پول'}, - {'id': 1124, 'name': 'کجور'}, - {'id': 1125, 'name': 'نوشهر'}], - 'en_name': 'mazandaran', - 'name': 'مازندران'}, - 28: {'cities': [{'id': 1126, 'name': 'آشتیان'}, - {'id': 1127, 'name': 'اراک'}, - {'id': 1128, 'name': 'داودآباد'}, - {'id': 1129, 'name': 'ساروق'}, - {'id': 1130, 'name': 'کارچان'}, - {'id': 1131, 'name': 'تفرش'}, - {'id': 1132, 'name': 'خمین'}, - {'id': 1133, 'name': 'قورچی باشی'}, - {'id': 1134, 'name': 'جاورسیان'}, - {'id': 1135, 'name': 'خنداب'}, - {'id': 1136, 'name': 'دلیجان'}, - {'id': 1137, 'name': 'نراق'}, - {'id': 1138, 'name': 'پرندک'}, - {'id': 1139, 'name': 'خشکرود'}, - {'id': 1140, 'name': 'رازقان'}, - {'id': 1141, 'name': 'زاویه'}, - {'id': 1142, 'name': 'مامونیه'}, - {'id': 1143, 'name': 'آوه'}, - {'id': 1144, 'name': 'ساوه'}, - {'id': 1145, 'name': 'غرق آباد'}, - {'id': 1146, 'name': 'نوبران'}, - {'id': 1147, 'name': 'آستانه'}, - {'id': 1148, 'name': 'توره'}, - {'id': 1149, 'name': 'شازند'}, - {'id': 1150, 'name': 'شهباز'}, - {'id': 1151, 'name': 'مهاجران'}, - {'id': 1152, 'name': 'هندودر'}, - {'id': 1153, 'name': 'خنجین'}, - {'id': 1154, 'name': 'فرمهین'}, - {'id': 1155, 'name': 'کمیجان'}, - {'id': 1156, 'name': 'میلاجرد'}, - {'id': 1157, 'name': 'محلات'}, - {'id': 1158, 'name': 'نیمور'}], - 'en_name': 'markazi', - 'name': 'مرکزی'}, - 29: {'cities': [{'id': 1159, 'name': 'ابوموسی'}, - {'id': 1160, 'name': 'بستک'}, - {'id': 1161, 'name': 'جناح'}, - {'id': 1162, 'name': 'سردشت'}, - {'id': 1163, 'name': 'گوهران'}, - {'id': 1164, 'name': 'بندرعباس'}, - {'id': 1165, 'name': 'تازیان پایین'}, - {'id': 1166, 'name': 'تخت'}, - {'id': 1167, 'name': 'فین'}, - {'id': 1168, 'name': 'قلعه قاضی'}, - {'id': 1169, 'name': 'بندرلنگه'}, - {'id': 1170, 'name': 'چارک'}, - {'id': 1171, 'name': 'کنگ'}, - {'id': 1172, 'name': 'کیش'}, - {'id': 1173, 'name': 'لمزان'}, - {'id': 1174, 'name': 'پارسیان'}, - {'id': 1175, 'name': 'دشتی'}, - {'id': 1176, 'name': 'کوشکنار'}, - {'id': 1177, 'name': 'بندرجاسک'}, - {'id': 1178, 'name': 'حاجی اباد'}, - {'id': 1179, 'name': 'سرگز'}, - {'id': 1180, 'name': 'فارغان'}, - {'id': 1181, 'name': 'خمیر'}, - {'id': 1182, 'name': 'رویدر'}, - {'id': 1183, 'name': 'بیکاء'}, - {'id': 1184, 'name': 'دهبارز'}, - {'id': 1185, 'name': 'زیارتعلی'}, - {'id': 1186, 'name': 'سیریک'}, - {'id': 1187, 'name': 'کوهستک'}, - {'id': 1188, 'name': 'گروک'}, - {'id': 1189, 'name': 'درگهان'}, - {'id': 1190, 'name': 'سوزا'}, - {'id': 1191, 'name': 'قشم'}, - {'id': 1192, 'name': 'هرمز'}, - {'id': 1193, 'name': 'تیرور'}, - {'id': 1194, 'name': 'سندرک'}, - {'id': 1195, 'name': 'میناب'}, - {'id': 1196, 'name': 'هشتبندی'}], - 'en_name': 'hormozgan', - 'name': 'هرمزگان'}, - 30: {'cities': [{'id': 1197, 'name': 'آجین'}, - {'id': 1198, 'name': 'اسدآباد'}, - {'id': 1199, 'name': 'بهار'}, - {'id': 1200, 'name': 'صالح آباد'}, - {'id': 1201, 'name': 'لالجین'}, - {'id': 1202, 'name': 'مهاجران'}, - {'id': 1203, 'name': 'تویسرکان'}, - {'id': 1204, 'name': 'سرکان'}, - {'id': 1205, 'name': 'فرسفج'}, - {'id': 1206, 'name': 'دمق'}, - {'id': 1207, 'name': 'رزن'}, - {'id': 1208, 'name': 'قروه درجزین'}, - {'id': 1209, 'name': 'فامنین'}, - {'id': 1210, 'name': 'شیرین سو'}, - {'id': 1211, 'name': 'کبودرآهنگ'}, - {'id': 1212, 'name': 'گل تپه'}, - {'id': 1213, 'name': 'ازندریان'}, - {'id': 1214, 'name': 'جوکار'}, - {'id': 1215, 'name': 'زنگنه'}, - {'id': 1216, 'name': 'سامن'}, - {'id': 1217, 'name': 'ملایر'}, - {'id': 1218, 'name': 'برزول'}, - {'id': 1219, 'name': 'فیروزان'}, - {'id': 1220, 'name': 'گیان'}, - {'id': 1221, 'name': 'نهاوند'}, - {'id': 1222, 'name': 'جورقان'}, - {'id': 1223, 'name': 'قهاوند'}, - {'id': 1224, 'name': 'مریانج'}, - {'id': 1225, 'name': 'همدان'}], - 'en_name': 'hamadan', - 'name': 'همدان'}, - 31: {'cities': [{'id': 1226, 'name': 'ابرکوه'}, - {'id': 1227, 'name': 'مهردشت'}, - {'id': 1228, 'name': 'احمدآباد'}, - {'id': 1229, 'name': 'اردکان'}, - {'id': 1230, 'name': 'عقدا'}, - {'id': 1231, 'name': 'اشکذر'}, - {'id': 1232, 'name': 'خضرآباد'}, - {'id': 1233, 'name': 'بافق'}, - {'id': 1234, 'name': 'بهاباد'}, - {'id': 1235, 'name': 'تفت'}, - {'id': 1236, 'name': 'نیر'}, - {'id': 1237, 'name': 'مروست'}, - {'id': 1238, 'name': 'هرات'}, - {'id': 1239, 'name': 'مهریز'}, - {'id': 1240, 'name': 'بفروییه'}, - {'id': 1241, 'name': 'میبد'}, - {'id': 1242, 'name': 'ندوشن'}, - {'id': 1243, 'name': 'حمیدیا'}, - {'id': 1244, 'name': 'زارچ'}, - {'id': 1245, 'name': 'شاهدیه'}, - {'id': 1246, 'name': 'یزد'}], - 'en_name': 'yazd', - 'name': 'یزد'} -} - -class State: - def __init__(self): - self.provinces = PROVINCES - - def province_list(self): - return self.provinces - - # TODO: performance issue - def get_city_by_id(self, city_id): - for province_id, province in self.provinces.items(): - for city in province.get('cities'): - if city.get('id') == city_id: - return city.get('name') - return None - - def get_province_by_id(self, province_id): - return self.provinces.get(province_id, {}).get('name') - - def validate_city(self, province_id, city_id): - cities_id = [item.get('id') for item in self.provinces.get(province_id).get('cities')] - return True if city_id in cities_id else False - - def validate_province(self, province_id): - return True if province_id in self.provinces.keys() else False - -state = State() diff --git a/apps/users/serializers.py b/apps/users/serializers.py index 0b11cc3..ca52773 100644 --- a/apps/users/serializers.py +++ b/apps/users/serializers.py @@ -5,117 +5,13 @@ from apps.users.models import User from django.utils import timezone from django.utils.translation import gettext_lazy as _ -phone_number_validator = RegexValidator(regex=r'^\+98\d{10,10}$', message=_( - "Phone number must be entered in the format: '+989999999999'." - ),) -class PublicUserSerializer(serializers.ModelSerializer): - class Meta: - model = User - fields = ('avatar', 'name', 'username', "first_name", "last_name", "uuid") - read_only_fields = ['avatar', 'name', 'username', 'first_name', 'last_name', "uuid"] - class AccountSerializer(serializers.ModelSerializer): - phone_number = serializers.CharField(validators=[phone_number_validator], required=False, read_only=True) class Meta: model = User fields = ( "uuid", - 'username', - 'email', - 'phone_number', - "first_name", - "last_name", - 'name', - 'avatar', 'iban', 'iban_verified', - # 'country', - 'province', - 'province_name', - 'city', - 'city_name', - 'postal_code', - 'address', ) - read_only_fields = ['uuid', 'email', 'phone_number', 'iban_verified', 'province_name', 'city_name',] - - -class RequestOTPSerializer(serializers.ModelSerializer): - phone_number = serializers.CharField(required=True, validators=[phone_number_validator]) - ttl = serializers.SerializerMethodField() - - class Meta: - model = User - fields = ( - 'phone_number', - 'otp_expire', - 'ttl' - ) - - read_only_fields = ['otp_expire', 'ttl'] - - def save(self, **kwargs): - # TODO: move it to query set - validated_data = self.validated_data - user = User.objects.filter(phone_number=validated_data['phone_number']).first() - if not user: - validated_data['username'] = validated_data['phone_number'] - user = User.objects.create_user(**validated_data) - - if not user.otp_is_valid(): - user.set_otp() - user.save() - - user.send_otp() - - self.instance = user - return user - - def get_ttl(self, obj: User): - if obj.otp_expire > timezone.now(): - ttl = obj.otp_expire - timezone.now() - result = ttl.total_seconds() - - else: - result = 0 - - return result - - -class RequestOTTSerializer(serializers.ModelSerializer): - application = serializers.CharField(required=True, write_only=True) - class Meta: - model = User - fields = ( - 'application', - 'ott', - 'ott_expire', - ) - - read_only_fields = ['ott', 'ott_expire'] - - -class UserInquirySerializer(serializers.ModelSerializer): - phone_number = serializers.CharField(required=True, validators=[phone_number_validator]) - - class Meta: - model = User - fields = ('avatar', 'name', 'username', "first_name", "last_name", "uuid", 'phone_number') - read_only_fields = ['avatar', 'name', 'username', 'first_name', 'last_name', "uuid"] - - def save(self, **kwargs): - # TODO: move it to query set - validated_data = self.validated_data - user = User.objects.filter(phone_number=validated_data['phone_number']).first() - if not user: - user = User.objects.create_user(**validated_data) - - self.instance = user - return user - - -class ChangePasswordSerializer(serializers.Serializer): - old_password = serializers.CharField(required=True) - old_password_field = serializers.CharField(default='password') - new_password = serializers.CharField(required=True) + read_only_fields = ['uuid', 'iban_verified'] diff --git a/apps/users/tasks.py b/apps/users/tasks.py deleted file mode 100644 index f11dd9b..0000000 --- a/apps/users/tasks.py +++ /dev/null @@ -1,28 +0,0 @@ -# from celery import shared_task -# from django.conf import settings -# # from service_clients import Client, AccountsClient -# -# # SCOPES = ['notifications.notification:submit'] -# # client = Client(client_id=settings.CLIENT_ID, -# # client_secret=settings.CLIENT_SECRET, -# # scopes=SCOPES, -# # grant_type=AccountsClient.GRANT_CLIENT_CREDENTIALS) -# -# @shared_task -# def send_notification(phone_number=None, -# title=None, -# body=None, -# user_uuid=None, -# email=None, -# notification_type=1): -# -# data = { -# 'phone_number': phone_number, -# "body": f'{title}:\n{body}' -# } -# try: -# print(client.request(required_scopes=SCOPES, url=f'{settings.BASE_NOTIFICATION_URL}/notifications/', data=data, -# method='post', timeout=5).text) -# except Exception as e: -# print(e) -# diff --git a/apps/users/urls.py b/apps/users/urls.py index 78b6ae9..b17be2f 100644 --- a/apps/users/urls.py +++ b/apps/users/urls.py @@ -1,19 +1,8 @@ from django.urls import path -from django.contrib.auth.views import LogoutView -from .views import UserListView, UserDetailView, AccountView, RequestOTPView, ChangePasswordView, \ - OTPLoginView, ProfileDetailView, ProfileUpdateView, RequestOTTView +from .views import AccountView app_name = "users" urlpatterns = [ - path('login/', OTPLoginView.as_view(), name='login'), - path('logout/', LogoutView.as_view(), name='logout'), - path('profile/', ProfileDetailView.as_view(), name='profile_detail'), - path('profile/update/', ProfileUpdateView.as_view(), name='profile_update'), path('api/account/', AccountView.as_view(), name='account_api'), - path('api/users/', UserListView.as_view(), name='user_list_api'), - path('api/users//', UserDetailView.as_view(), name='user_detail_api'), - path('api/request_otp/', RequestOTPView.as_view(), name='request_otp_api'), - path('api/request_ott/', RequestOTTView.as_view(), name='request_ott_api'), - path('api/change_password/', ChangePasswordView.as_view(), name='change_password_api'), ] diff --git a/apps/users/views.py b/apps/users/views.py index a2b5431..79594c1 100644 --- a/apps/users/views.py +++ b/apps/users/views.py @@ -11,35 +11,13 @@ from rest_framework import generics, status, permissions from rest_framework.response import Response from rest_framework.views import APIView -from apps.accounts_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements -from apps.users.forms import OTPAuthenticationForm, ProfileUpdateForm +from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements from apps.users.models import User -from apps.users.provinces_and_cities import State -from apps.users.serializers import PublicUserSerializer, AccountSerializer, RequestOTPSerializer, RequestOTTSerializer, \ - ChangePasswordSerializer, UserInquirySerializer +from apps.users.serializers import AccountSerializer UserModel = get_user_model() -class UserListView(generics.ListAPIView): - permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements] - queryset = User.objects.all() - serializer_class = PublicUserSerializer - required_alternate_scopes = { - "GET": [["accounts.profile:list"]], - } - - -class UserDetailView(generics.RetrieveAPIView): - permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements] - queryset = User.objects.all() - serializer_class = PublicUserSerializer - lookup_field = 'uuid' - required_alternate_scopes = { - "GET": [["accounts.profile:retrieve"]], - } - - class AccountView(generics.RetrieveUpdateAPIView): serializer_class = AccountSerializer permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements] @@ -57,72 +35,6 @@ class AccountView(generics.RetrieveUpdateAPIView): serializer.save(last_update=timezone.now()) -class RequestOTPView(generics.CreateAPIView): - permission_classes = [] - serializer_class = RequestOTPSerializer - required_scopes = [] - - -class RequestOTTView(generics.CreateAPIView): - permission_classes = [IsAuthenticatedOrTokenHasScope] - required_scopes = ['accounts.account:request_ott'] - serializer_class = RequestOTTSerializer - - def get_object(self): - return self.request.user - - def create(self, request, *args, **kwargs): - instance: User = self.get_object() - serializer = self.get_serializer(instance, data=request.data) - serializer.is_valid(raise_exception=True) - instance.set_ott() - instance.save() - return Response(serializer.data) - - -class UserInquiryView(generics.CreateAPIView): - serializer_class = UserInquirySerializer - permission_classes = [IsAuthenticatedOrTokenHasScope] - required_scopes = ['accounts.profile:inquiry'] - - -class ChangePasswordView(generics.UpdateAPIView): - permission_classes = [IsAuthenticatedOrTokenHasScope] - serializer_class = ChangePasswordSerializer - required_scopes = ["accounts.account:change_password"] - model = User - - def get_object(self, queryset=None): - obj = self.request.user - return obj - - def update(self, request, *args, **kwargs): - self.object = self.get_object() - serializer = self.get_serializer(data=request.data) - - if serializer.is_valid(): - pass_field = serializer.data.get("old_password_field") - old_password = serializer.data.get("old_password") - new_password = serializer.data.get("new_password") - if not self.object.check_auth(pass_field, old_password): - return Response({"old_password": ["Wrong password/otp."]}, status=status.HTTP_400_BAD_REQUEST) - - self.object.set_password(new_password) - self.object.last_update = timezone.now() - self.object.save() - return Response({"state": 'success'}, status=status.HTTP_200_OK) - - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - -class OTPLoginView(LoginView): - template_name = 'users/login.html' - """ - Display the login form and handle the login action. - """ - form_class = OTPAuthenticationForm - - @method_decorator(login_required, name='dispatch') class ProfileDetailView(DetailView): model = User @@ -131,20 +43,3 @@ class ProfileDetailView(DetailView): def get_object(self, queryset=None): return self.request.user - - -@method_decorator(login_required, name='dispatch') -class ProfileUpdateView(UpdateView): - model = User - template_name = 'users/profile_update.html' - pk_url_kwarg = 'uuid' - form_class = ProfileUpdateForm - - def get_success_url(self): - return reverse('users:profile_update') - - def get_object(self, queryset=None): - return self.request.user - - def form_valid(self, form): - return super().form_valid(form) diff --git a/apps/wallet/admin.py b/apps/wallet/admin.py index 1c59e67..e430446 100755 --- a/apps/wallet/admin.py +++ b/apps/wallet/admin.py @@ -1,21 +1,4 @@ from django.contrib import admin from .models import Transaction - -class TransactionAdmin(admin.ModelAdmin): - fields = [ - 'uuid', - 'amount', - 'payer', - 'payee', - 'create', - 'last_update', - 'state', - 'delay', - 'detail_id', - 'application' - ] - readonly_fields = ['uuid', 'last_update', 'create', 'detail_id', 'application'] - - -admin.site.register(Transaction, TransactionAdmin) +admin.site.register(Transaction) diff --git a/apps/wallet/migrations/0001_initial.py b/apps/wallet/migrations/0001_initial.py index 6e02dfd..c93b62f 100644 --- a/apps/wallet/migrations/0001_initial.py +++ b/apps/wallet/migrations/0001_initial.py @@ -1,9 +1,9 @@ -# Generated by Django 4.1 on 2022-09-04 12:55 +# Generated by Django 5.0.6 on 2024-07-03 14:33 -from django.conf import settings -from django.db import migrations, models import django.db.models.deletion import uuid +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): @@ -12,6 +12,7 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), + migrations.swappable_dependency(settings.OAUTH2_PROVIDER_APPLICATION_MODEL), ] operations = [ @@ -24,8 +25,8 @@ class Migration(migrations.Migration): ('state', models.IntegerField(choices=[(1, 'created'), (2, 'delayed'), (3, 'pending'), (4, 'incomplete'), (5, 'success'), (6, 'failed'), (7, 'expected_failure')], default=1)), ('create', models.DateTimeField(auto_now=True, null=True, verbose_name='create')), ('last_update', models.DateTimeField(auto_now=True, null=True, verbose_name='last update')), - ('application_client_id', models.CharField(db_index=True, max_length=100, null=True)), ('detail_id', models.IntegerField(blank=True, null=True)), + ('application', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='transactions', to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL)), ('payee', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='receipts', to=settings.AUTH_USER_MODEL)), ('payer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='payments', to=settings.AUTH_USER_MODEL)), ], diff --git a/apps/wallet/migrations/0002_remove_transaction_application_client_id_and_more.py b/apps/wallet/migrations/0002_remove_transaction_application_client_id_and_more.py deleted file mode 100644 index 13fb061..0000000 --- a/apps/wallet/migrations/0002_remove_transaction_application_client_id_and_more.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 4.1 on 2022-09-08 12:16 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.OAUTH2_PROVIDER_APPLICATION_MODEL), - ('wallet', '0001_initial'), - ] - - operations = [ - migrations.RemoveField( - model_name='transaction', - name='application_client_id', - ), - migrations.AddField( - model_name='transaction', - name='application', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='transactions', to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL), - ), - ] diff --git a/apps/wallet/models.py b/apps/wallet/models.py index 0b6b83f..e29c25e 100755 --- a/apps/wallet/models.py +++ b/apps/wallet/models.py @@ -7,6 +7,7 @@ from django.db.models import F from django.utils.translation import gettext_lazy as _ from rest_framework.exceptions import APIException, ValidationError from django.conf import settings +from oauth2_provider import settings as oauth2_settings from .constans import StateChoices from apps.users.models import User @@ -58,7 +59,7 @@ class Transaction(models.Model): state = models.IntegerField(choices=StateChoices.choices, default=StateChoices.CREATED) create = models.DateTimeField(_('create'), blank=True, null=True, auto_now=True) last_update = models.DateTimeField(_('last update'), blank=True, null=True, auto_now=True) - application = models.ForeignKey(settings.OAUTH2_PROVIDER_APPLICATION_MODEL, on_delete=models.PROTECT, + application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT, related_name='transactions', null=True) detail_id = models.IntegerField(null=True, blank=True) # TODO: or better detail url diff --git a/apps/wallet/serializers.py b/apps/wallet/serializers.py index c714718..84e657e 100755 --- a/apps/wallet/serializers.py +++ b/apps/wallet/serializers.py @@ -3,15 +3,11 @@ from django.db.transaction import atomic from rest_framework import serializers from rest_framework.exceptions import APIException -from apps.users.serializers import PublicUserSerializer from .models import Transaction from ..users.models import User class TransactionSerializer(serializers.ModelSerializer): - payer = PublicUserSerializer(required=False) - payee = PublicUserSerializer(required=False, read_only=True) - class Meta: model = Transaction fields = ('uuid', diff --git a/apps/wallet/views.py b/apps/wallet/views.py index b85931d..c2f26de 100755 --- a/apps/wallet/views.py +++ b/apps/wallet/views.py @@ -9,7 +9,7 @@ from oauth2_provider.contrib.rest_framework import TokenHasScope, IsAuthenticate from rest_framework import generics, permissions from rest_framework.response import Response -from apps.accounts_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements +from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements from apps.wallet.models import Transaction from apps.wallet.serializers import TransactionSerializer, DepositSerializer, WithdrawSerializer, WalletSerializer from .constans import StateChoices diff --git a/templates/base.html b/templates/base.html index f57d078..f321bcf 100644 --- a/templates/base.html +++ b/templates/base.html @@ -35,18 +35,7 @@ aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> - +
{% include "include/message_frame.html" %} diff --git a/wallet/settings.py b/wallet/settings.py index c96fb54..34434dd 100644 --- a/wallet/settings.py +++ b/wallet/settings.py @@ -52,8 +52,7 @@ INSTALLED_APPS = [ # local apps 'apps.users', 'apps.wallet', - 'apps.accounts_oauth2', - 'apps.superapp', + 'apps.gooyal_oauth2', ] MIDDLEWARE = [ @@ -68,22 +67,25 @@ MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', ] -# Tell Django OAuth Toolkit to use the Gooyal Application model -OAUTH2_PROVIDER_ID_TOKEN_MODEL = 'accounts_oauth2.IDToken' -OAUTH2_PROVIDER_APPLICATION_MODEL = 'accounts_oauth2.Application' -OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL = 'accounts_oauth2.AccessToken' -OAUTH2_PROVIDER_GRANT_MODEL = 'accounts_oauth2.Grant' -OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL = 'accounts_oauth2.RefreshToken' + +OAUTH2_PROVIDER_APPLICATION_MODEL = "oauth2_provider.Application" +OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL = "oauth2_provider.AccessToken" +OAUTH2_PROVIDER_ID_TOKEN_MODEL = "oauth2_provider.IDToken" +OAUTH2_PROVIDER_GRANT_MODEL = "oauth2_provider.Grant" +OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL = "oauth2_provider.RefreshToken" + OAUTH2_PROVIDER = { # this is the list of available scopes - 'SCOPES_BACKEND_CLASS': 'apps.accounts_oauth2.scopes.Scopes', - 'SCOPES': {'read': 'Read scope', - 'write': 'Write scope', - 'groups': 'Access to your groups', - 'introspection': 'Introspect token scope'}, - 'OAUTH2_VALIDATOR_CLASS': 'apps.accounts_oauth2.validators.OAuth2Validator', - # "INTROSPECTION_USER_CREATE_METHOD": 'apps.users.models.create_user_in_introspection', + # 'SCOPES_BACKEND_CLASS': 'apps.gooyal_oauth2.scopes.Scopes', + # 'SCOPES': {'read': 'Read scope', + # 'write': 'Write scope', + # 'groups': 'Access to your groups', + # 'introspection': 'Introspect token scope'}, + 'OAUTH2_VALIDATOR_CLASS': 'apps.gooyal_oauth2.validators.OAuth2Validator', + 'RESOURCE_SERVER_INTROSPECTION_URL': 'https://accounts.gooyal.com/oauth2/introspect/', + # 'RESOURCE_SERVER_AUTH_TOKEN': '3yUqsWtwKYKHnfivFcJu', # OR this but not both: + 'RESOURCE_SERVER_INTROSPECTION_CREDENTIALS': ('dCHm08iIoXvbRSgOI79SV6kIs0aoUzwJehtoczvJ','QBGwaye4Mvr2JHAtn5lQpZhd3RfSVw4XZNrgt6P4UBuV7u6IhsJXUV5QYv3v6rQYEuzjZdKYMjDQuXPmjHeF5UI5fFx080E7FPxFfYHIYBr3ZyvuPi1u7zDRF0EQbzbH'), } # GOOYAL_DYNAMIC_SCOPES @@ -99,7 +101,7 @@ REST_FRAMEWORK = { 'rest_framework.permissions.IsAuthenticated', ), 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', - 'PAGE_SIZE': 10, + 'PAGE_SIZE': 20, "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", } @@ -112,7 +114,7 @@ SPECTACULAR_SETTINGS = { } -ROOT_URLCONF = 'accounts.urls' +ROOT_URLCONF = 'wallet.urls' TEMPLATES = [ { @@ -132,7 +134,6 @@ TEMPLATES = [ ] AUTHENTICATION_BACKENDS = ( - 'apps.users.backends.OTPBackend', 'django.contrib.auth.backends.ModelBackend', ) diff --git a/wallet/urls.py b/wallet/urls.py index 41fd070..0cb0e8e 100644 --- a/wallet/urls.py +++ b/wallet/urls.py @@ -22,7 +22,7 @@ from rest_framework import permissions from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView -# from apps.accounts_oauth2.views.introspect import introspect_token +# from apps.gooyal_oauth2.views.introspect import introspect_token from apps.wallet.views import TransactionList, TransactionDetail, TransactionPay, TransactionReceipt, \ WithdrawVerify, WithdrawSubmit, DepositSubmit, DepositVerify from django.contrib.auth import urls as auth_urls @@ -36,7 +36,6 @@ urlpatterns = [ path('admin/', admin.site.urls), path('wallet/', include('apps.wallet.urls')), path('users/', include('apps.users.urls')), - path('superapp/', include('apps.superapp.urls')), path('oauth2/', include('oauth2_provider.urls', namespace='oauth2_provider')),