5 Commits

Author SHA1 Message Date
arvinbehbahani562@gmail.com cb0ff19355 cleaned urls 2026-05-13 17:36:11 +03:30
arvinbehbahani562@gmail.com 2d5311702d finished the addresses app 2026-05-13 16:46:50 +03:30
arvinbehbahani562@gmail.com bb4186f3b5 cleaning code a bit 2026-05-13 05:11:59 +03:30
arvinbehbahani562@gmail.com 73cefcdeb8 almost finished with the addresses 2026-05-13 05:07:13 +03:30
arvinbehbahani562@gmail.com cf99039c8d added venv to .gitignore and initiated the app 2026-05-13 00:41:18 +03:30
25 changed files with 3114 additions and 17 deletions
+1
View File
@@ -28,6 +28,7 @@ wheels/
# Virtual environments # Virtual environments
.venv/ .venv/
venvArvin/
venv/ venv/
ENV/ ENV/
env/ env/
View File
+33
View File
@@ -0,0 +1,33 @@
from django.contrib import admin
from .models import Address, Province, City
class ProvinceAdmin(admin.ModelAdmin):
model = Province
list_display = (
"province_name",
"province_id",
)
admin.site.register(Province, ProvinceAdmin)
class CityAdmin(admin.ModelAdmin):
model = City
list_display = (
"city_name",
"city_local_id",
"province",
)
admin.site.register(City, CityAdmin)
class AddressAdmin(admin.ModelAdmin):
model = Address
list_display = [
"address_detail",
"province",
"city",
"user__email",
]
admin.site.register(Address, AddressAdmin)
View File
+45
View File
@@ -0,0 +1,45 @@
from rest_framework import serializers
from ..models import Address, City, Province
class ProvinceSerializer(serializers.ModelSerializer):
class Meta:
model = Province
fields = ["province_id", "province_name"]
class CitySerializer(serializers.ModelSerializer):
province_id = serializers.CharField(source="province.province_id")
class Meta:
model = City
fields = ["city_local_id", "city_name", "province_id"]
class AddressSerializer(serializers.ModelSerializer):
province_name = serializers.CharField(source="province.province_name", read_only=True)
city_name = serializers.CharField(source="city.city_name", read_only=True)
user_email = serializers.EmailField(source="user.email", read_only=True)
absolute_url = serializers.SerializerMethodField(method_name="get_absolute_url", read_only=True)
relative_url = serializers.URLField(source="get_absolute_relative_url")
class Meta:
model = Address
fields = ["province", "province_name", "city", "city_name", "postal_code", "address_detail", "relative_url", "absolute_url", "user_email", "created_at", "updated_at"]
read_only_fields = ["user_email", "absolute_url", "province_name", "city_name", "created_at", "updated_at"]
def to_representation(self, instance):
rep = super().to_representation(instance)
request = self.context.get("request")
if not request.parser_context.get("kwargs"):
rep.pop("province_name")
rep.pop("city_name")
rep.pop("created_at")
rep.pop("updated_at")
else:
rep.pop("absolute_url")
rep.pop("relative_url")
return rep
def get_absolute_url(self, obj):
request = self.context.get("request")
return request.build_absolute_uri(obj.pk)
+15
View File
@@ -0,0 +1,15 @@
from django.urls import path
from rest_framework.routers import DefaultRouter
from . import views
app_name = "address-api-urls"
router = DefaultRouter()
router.register("info", views.AddressViewSet, basename="address-viewset")
urlpatterns = [
path("province/", views.ProvinceListAPIView.as_view(), name="get-provinces"),
path("province/<int:province_pk>/cities/", views.CityListAPIView.as_view(), name="get-cities")
]
urlpatterns += router.urls
+35
View File
@@ -0,0 +1,35 @@
from rest_framework.generics import ListAPIView
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from . import serializers
from ..models import Province, City, Address
class ProvinceListAPIView(ListAPIView):
serializer_class = serializers.ProvinceSerializer
queryset = Province.objects.all()
# def get(self, request, *args, **kwargs):
# return Response()
class CityListAPIView(ListAPIView):
serializer_class = serializers.CitySerializer
def get_queryset(self):
province_id = self.kwargs["province_pk"]
return City.objects.filter(province_id=province_id)
class AddressViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated,]
serializer_class = serializers.AddressSerializer
def get_queryset(self):
user = self.request.user
return Address.objects.filter(user=user)
def perform_create(self, serializer):
serializer.save(
user=self.request.user
)
+6
View File
@@ -0,0 +1,6 @@
from django.apps import AppConfig
class AddressesConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'addresses'
View File
@@ -0,0 +1,76 @@
from django.core.management.base import BaseCommand
from faker import Faker
from django.contrib.auth import get_user_model
from random import choice, randint
from django.db.models import Q
from ...models import Address, Province, City
user = get_user_model()
class Command(BaseCommand):
help = "fake address creator"
def __init__(self, stdout=None, stderr=None, no_color=False, force_color=False):
super().__init__(stdout, stderr, no_color, force_color)
self.faker = Faker()
def handle(self, *args, **options):
print("creating fake users...")
user1 = user.objects.create_user(
phone_number=self.faker.phone_number(),
email=self.faker.email(),
username=self.faker.user_name(),
password="string123",
)
user2 = user.objects.create_user(
phone_number=self.faker.phone_number(),
email=self.faker.email(),
username=self.faker.user_name(),
password="string123",
)
print("operation successful...")
# print(City.objects.values_list("city_local_id", flat=True)[:10])
print("creating fake addresses")
for _ in range(10):
province = Province.objects.get(pk=randint(0, 30))
city_list = list(City.objects.filter(province=province).values_list(
"city_local_id",
flat=True,))
city = City.objects.get(Q(province=province) & Q(city_local_id=choice(city_list)))
address = Address.objects.create(
user=user1,
province=province,
city=city,
postal_code=self.faker.postalcode(),
address_detail=self.faker.address(),
)
for _ in range(5):
province = Province.objects.get(pk=randint(0, 30))
city_list = City.objects.filter(province=province).values_list(
"city_local_id",
flat=True,
)
city = City.objects.get(Q(province=province) & Q(city_local_id=choice(city_list)))
address = Address.objects.create(
user=user2,
province=province,
city=city,
postal_code=self.faker.postalcode(),
address_detail=self.faker.address(),
)
print("operation successful...")
self.stdout.write(
self.style.SUCCESS("fake addresses and accounts created successfully...")
)
@@ -0,0 +1,41 @@
from django.core.management.base import BaseCommand #, CommandError
import json
from pathlib import Path
from ...models import Province, City
class Command(BaseCommand):
help = "push all provinces and cities to database"
def handle(self, *args, **options):
json_file_path = Path("/app/addresses/provinces_cities.json")
with open(json_file_path, "r", encoding="utf-8") as json_file:
all_data = json.load(json_file)
print("injecting all provinces to the database...")
for province_data in all_data:
province = Province.objects.get_or_create(
province_id=int(province_data.get("provinceId")),
province_name=str(province_data.get("provinceName")),
)
print("task complete :)")
print("injecting all the cities to the database ")
for city_data in all_data:
city = City.objects.get_or_create(
city_local_id=str(city_data.get("cityId")),
city_name=str(city_data.get("cityName")),
province_id=int(city_data.get("provinceId"))
)
print("task complete :)")
self.stdout.write(
self.style.SUCCESS(r"all the data successfully injected in the database :>")
)
+49
View File
@@ -0,0 +1,49 @@
# Generated by Django 5.1.15 on 2026-05-12 23:43
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Province',
fields=[
('province_id', models.IntegerField(primary_key=True, serialize=False)),
('province_name', models.CharField(max_length=255)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
),
migrations.CreateModel(
name='City',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('city_local_id', models.CharField(blank=True, max_length=3, null=True)),
('city_name', models.CharField(max_length=255)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('province', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='city', to='addresses.province')),
],
),
migrations.CreateModel(
name='Address',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('address_detail', models.TextField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('city', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='city', to='addresses.city')),
('province', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='province', to='addresses.province')),
],
),
]
@@ -0,0 +1,18 @@
# Generated by Django 5.1.15 on 2026-05-13 00:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('addresses', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='address',
name='postal_code',
field=models.CharField(max_length=15, null=True),
),
]
View File
+43
View File
@@ -0,0 +1,43 @@
from django.db import models
from config.settings import AUTH_USER_MODEL
from django.urls import reverse
class Province(models.Model):
province_id = models.IntegerField(primary_key=True)
province_name = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self) -> str:
return f"{self.province_id}: {self.province_name}"
class City(models.Model):
province = models.ForeignKey("Province", on_delete=models.CASCADE, related_name="city")
city_local_id = models.CharField(max_length=3, null=True, blank=True)
city_name = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self) -> str:
return f"{self.city_local_id}: {self.city_name}"
class Address(models.Model):
user = models.ForeignKey(AUTH_USER_MODEL, on_delete=models.CASCADE)
province = models.ForeignKey("Province", on_delete=models.CASCADE, related_name="province")
city = models.ForeignKey("City", on_delete=models.CASCADE, related_name="city")
postal_code = models.CharField(max_length=15, null=True)
address_detail = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self) -> str:
return f"{self.address_detail[:10]}..."
def get_absolute_relative_url(self):
return reverse("addresses:address-api-urls:address-viewset-detail", kwargs={"pk": self.pk})
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.
+7
View File
@@ -0,0 +1,7 @@
from django.urls import path, include
app_name = "addresses"
urlpatterns = [
path("", include("addresses.api.urls"), name="address-api")
]
+3
View File
@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.
Binary file not shown.
+1
View File
@@ -63,6 +63,7 @@ INSTALLED_APPS = [
"drf_spectacular", "drf_spectacular",
"drf_spectacular_sidecar", "drf_spectacular_sidecar",
"corsheaders", "corsheaders",
"addresses",
] ]
MIDDLEWARE = [ MIDDLEWARE = [
+2
View File
@@ -3,6 +3,7 @@ from django.urls import include, path
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
urlpatterns = [ urlpatterns = [
path("api-auth/", include("rest_framework.urls")),
path("admin/", admin.site.urls), path("admin/", admin.site.urls),
path("api/schema/", SpectacularAPIView.as_view(), name="schema"), path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
path("api/docs/swagger/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui"), path("api/docs/swagger/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui"),
@@ -41,4 +42,5 @@ urlpatterns = [
path("api/farmer-todos/", include("farmer_todos.urls")), path("api/farmer-todos/", include("farmer_todos.urls")),
path("api/sensor-external-api/", include("device_hub.sensor_external_api_urls")), path("api/sensor-external-api/", include("device_hub.sensor_external_api_urls")),
path("api/address/", include("addresses.urls"))
] ]
+3 -3
View File
@@ -66,7 +66,7 @@ services:
ports: ports:
- "8000:8000" - "8000:8000"
env_file: env_file:
- .env - .env.example
environment: environment:
DOCKER_VERSION: ${DOCKER_VERSION:-develop} DOCKER_VERSION: ${DOCKER_VERSION:-develop}
ALLOWED_HOSTS: ${ALLOWED_HOSTS:-localhost,127.0.0.1,0.0.0.0,web,backend-web} ALLOWED_HOSTS: ${ALLOWED_HOSTS:-localhost,127.0.0.1,0.0.0.0,web,backend-web}
@@ -101,7 +101,7 @@ services:
- .:/app - .:/app
- ./logs:/app/logs - ./logs:/app/logs
env_file: env_file:
- .env - .env.example
environment: environment:
DOCKER_VERSION: ${DOCKER_VERSION:-develop} DOCKER_VERSION: ${DOCKER_VERSION:-develop}
ALLOWED_HOSTS: ${ALLOWED_HOSTS:-localhost,127.0.0.1,0.0.0.0,web,backend-web} ALLOWED_HOSTS: ${ALLOWED_HOSTS:-localhost,127.0.0.1,0.0.0.0,web,backend-web}
@@ -135,7 +135,7 @@ services:
- .:/app - .:/app
- ./logs:/app/logs - ./logs:/app/logs
env_file: env_file:
- .env - .env.example
environment: environment:
DOCKER_VERSION: ${DOCKER_VERSION:-develop} DOCKER_VERSION: ${DOCKER_VERSION:-develop}
ALLOWED_HOSTS: ${ALLOWED_HOSTS:-localhost,127.0.0.1,0.0.0.0,web,backend-web} ALLOWED_HOSTS: ${ALLOWED_HOSTS:-localhost,127.0.0.1,0.0.0.0,web,backend-web}
+44 -14
View File
@@ -1,14 +1,44 @@
Django>=5.0,<5.2 amqp==5.3.1
djangorestframework>=3.14,<3.16 asgiref==3.11.1
djangorestframework-simplejwt>=5.3,<5.4 async-timeout==5.0.1
django-cors-headers>=4.3,<4.5 attrs==26.1.0
drf-spectacular>=0.27,<0.28 billiard==4.2.4
drf-spectacular-sidecar>=2024.7.1,<2025 celery==5.3.6
celery[redis]>=5.3,<5.4 certifi==2026.4.22
redis>=5.0,<5.1 charset-normalizer==3.4.7
click==8.3.3
mysqlclient>=2.2,<2.3 click-didyoumean==0.3.1
gunicorn>=22,<23 click-plugins==1.1.1.2
python-dotenv>=1.0,<1.1 click-repl==0.3.0
requests>=2.31,<2.33 Django==5.1.15
django-cors-headers==4.4.0
djangorestframework==3.15.2
djangorestframework-simplejwt==5.3.1
drf-spectacular==0.27.2
drf-spectacular-sidecar==2024.12.1
Faker==40.15.0
gunicorn==22.0.0
idna==3.14
inflection==0.5.1
jsonschema==4.26.0
jsonschema-specifications==2025.9.1
kombu==5.6.2
mysqlclient==2.2.8
packaging==26.2
prompt_toolkit==3.0.52
PyJWT==2.12.1
python-dateutil==2.9.0.post0
python-dotenv==1.0.1
PyYAML==6.0.3
redis==5.0.8
referencing==0.37.0
requests==2.32.5
rpds-py==0.30.0
six==1.17.0
sqlparse==0.5.5
typing_extensions==4.15.0
tzdata==2026.2
uritemplate==4.2.0
urllib3==2.7.0
vine==5.1.0
wcwidth==0.7.0