Properties API
مستندات کامل API مدیریت املاک — تمام endpointها، پارامترها، نمونههای درخواست/پاسخ، خطاها، مدلها و کش
معرفی
این ماژول شامل CRUD ملک، شهر، منطقه، نوع ملک و مالک است.
همه مسیرها با پیشوند /properties/ شروع میشوند (نه /api/properties/).
احراز هویت: Authorization: Bearer <access_token>
مهم: اندپوینت جدا برای check-duplicate وجود ندارد.
بررسی تکراری بودن فقط داخل POST /properties/create/ بر اساس
owner + city + location + floor_number انجام میشود.
لیست Endpointها
1. دریافت لیست املاک
Endpoint
GET /properties/list/
توضیحات
لیست نقشمحور با pagination و فیلتر. نتایج از کش نسخهدار با TTL ۳۰۰ ثانیه سرو میشوند.
- admin/superuser: همه فایلها
- staff: فقط
show_status=ok
- agent: فقط فایلهای خودش
احراز هویت
IsAuthenticated
پارامترهای Query String
| پارامتر | نوع | اجباری | پیشفرض | توضیحات |
page | integer | ✗ | 1 | شماره صفحه |
page_size | integer | ✗ | 20 | حداکثر ۱۰۰ |
show_status | string | ✗ | — | ok | pending | sold | not |
transaction_type | string | ✗ | — | sale | rent |
city | integer | ✗ | — | ID شهر |
location | integer | ✗ | — | ID منطقه |
property_type | integer | ✗ | — | ID نوع ملک |
agent | integer | ✗ | — | ID مشاور |
owner | integer | ✗ | — | ID مالک |
نمونه درخواست
curl -X GET "http://127.0.0.1:8000/properties/list/?page=1&page_size=20&city=1&show_status=pending" \
-H "Authorization: Bearer ACCESS_TOKEN"
نمونه پاسخ موفق (200 OK)
{
"count": 34,
"page": 1,
"page_size": 20,
"total_pages": 2,
"next": 2,
"previous": null,
"results": [
{
"id": 1,
"slug": "apartment-tehran-1",
"title": "آپارتمان ۱۲۰ متری",
"property_code": "26_1000_1",
"transaction_type": "sale",
"price": 8500000000,
"price_per_meter": 70000000,
"mortgage": null,
"rent": null,
"area": "100.00",
"measure": "120.00",
"bedrooms": 3,
"floor_number": "2",
"city_name": "تهران",
"location_name": "منطقه ۱",
"property_type_title": "آپارتمان زیر ۱۰۰ متر",
"agent_name": "علی احمدی",
"owner_name": "محمد رضایی",
"show_status": "pending",
"main_image": "http://127.0.0.1:8000/media/property_images/1.jpg",
"images": [
{"id": 1, "image": "/media/property_images/1.jpg", "image_url": "http://127.0.0.1:8000/media/property_images/1.jpg", "is_main": true}
],
"created_at": "2026-07-08T10:00:00Z",
"updated_at": "2026-07-08T10:00:00Z"
}
]
}
توضیحات فیلدهای pagination
| فیلد | نوع | توضیحات |
| count | int | کل رکوردها |
| page / page_size / total_pages | int | صفحهبندی |
| next / previous | int|null | شماره صفحه بعدی/قبلی (نه URL) |
| results | array | آیتمهای صفحه |
مثال Frontend
async function getProperties(token, page = 1, pageSize = 20, filters = {}) {
const qs = new URLSearchParams({ page, page_size: pageSize, ...filters });
const res = await fetch(`/properties/list/?${qs}`, {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) throw await res.json();
return res.json();
}
2. ایجاد ملک جدید (+ چک تکراری)
POST /properties/create/
توضیحات
ثبت ملک با multipart/form-data. قبل از ذخیره، تکراری بودن روی
owner + city + location + floor_number بررسی میشود.
show_status همیشه pending ست میشود و slug از عنوان ساخته میشود.
اگر کاربر agent_profile داشته باشد، agent خودکار ست میشود.
احراز هویت
IsAuthenticated
پارامترهای اجباری
| پارامتر | نوع | اجباری | توضیحات |
| owner | int | ✓ | شناسه مالک |
| city | int | ✓ | شناسه شهر |
| location | int | ✓ | باید متعلق به city باشد |
| floor_number | string | ✓ | کلید duplicate |
| property_type | int | ✓ | نوع ملک |
| title | string(68) | ✓ | عنوان |
| uploaded_images | file[] | ✓ | حداقل یک فایل |
| main_image_index | int | ✓ | ایندکس از ۰ |
سایر فیلدهای اختیاری رایج
| پارامتر | نوع | توضیحات |
| transaction_type | sale|rent | پیشفرض sale |
| price / price_per_meter / mortgage / rent | bigint | مبالغ |
| measure / area | decimal | مساحت / زیربنا |
| bedrooms / bathrooms | int | فضاها |
| address / phone / description | string/HTML | اطلاعات تکمیلی |
| has_parking و سایر boolها | boolean | امکانات و زیرساخت |
فیلدهای slug و show_status از writable حذف شدهاند و توسط سرور کنترل میشوند.
نمونه JavaScript
const formData = new FormData();
formData.append('owner', '5');
formData.append('city', '1');
formData.append('location', '3');
formData.append('floor_number', '2');
formData.append('property_type', '1');
formData.append('title', 'آپارتمان ۱۲۰ متری');
formData.append('transaction_type', 'sale');
formData.append('price', '8500000000');
formData.append('uploaded_images', file1);
formData.append('uploaded_images', file2);
formData.append('main_image_index', '0');
const res = await fetch('/properties/create/', {
method: 'POST',
headers: { Authorization: `Bearer ${accessToken}` },
body: formData,
});
const data = await res.json();
if (res.status === 201) {
// ساخته شد
} else if (res.status === 200 && data.is_duplicate && data.can_edit) {
window.location.href = data.edit_endpoint;
} else if (res.status === 403 && data.is_duplicate) {
alert(data.message);
}
نمونه cURL
curl -X POST http://127.0.0.1:8000/properties/create/ \
-H "Authorization: Bearer ACCESS_TOKEN" \
-F "owner=5" -F "city=1" -F "location=3" -F "floor_number=2" \
-F "property_type=1" -F "title=آپارتمان ۱۲۰ متری" -F "transaction_type=sale" \
-F "price=8500000000" -F "uploaded_images=@image1.jpg" -F "main_image_index=0"
پاسخ ایجاد موفق (201)
{
"id": 16,
"slug": "apartment-120-metri",
"title": "آپارتمان ۱۲۰ متری",
"show_status": "pending",
"property_code": "26_1000_3",
"owner": {"id": 5, "full_name": "محمد رضایی"},
"city": {"id": 1, "name": "تهران", "province": "تهران"},
"location": {"id": 3, "name": "منطقه ۱", "city": 1, "city_name": "تهران"},
"images": [{"id": 1, "is_main": true}]
}
پاسخ تکراری قابل ویرایش (200)
{
"is_duplicate": true,
"can_edit": true,
"duplicate": true,
"message": "این ملک قبلاً ثبت شده، به صفحه ویرایش منتقل شوید.",
"slug": "existing-property-slug",
"edit_endpoint": "/properties/existing-property-slug/edit/"
}
پاسخ تکراری غیرقابل ویرایش (403)
{
"is_duplicate": true,
"can_edit": false,
"duplicate": true,
"message": "این ملک قبلاً ثبت شده و شما اجازه ویرایش آن را ندارید.",
"slug": "existing-property-slug",
"edit_endpoint": "/properties/existing-property-slug/edit/"
}
can_edit برای superuser، admin، staff، یا agent صاحب همان فایل تکراری true است.
خطاها
- 400 — فیلد اجباری خالی، index نامعتبر، location متعلق به city نیست
- 401 — بدون توکن
- 403 — duplicate بدون دسترسی
3. جزئیات ملک
GET /properties/<slug>/
دسترسی: authenticated + object permission (agent صاحب فایل یا admin/staff).
curl -X GET http://127.0.0.1:8000/properties/apartment-120-metri/ \
-H "Authorization: Bearer ACCESS_TOKEN"
{
"id": 16,
"slug": "apartment-120-metri",
"title": "آپارتمان ۱۲۰ متری",
"show_status": "pending",
"floor_number": "2",
"price": 8500000000,
"location_iframe": "<iframe src=\\"https://maps...\\"></iframe>",
"agent": {"id": 4, "full_name": "علی احمدی", "phone": null},
"owner": {"id": 5, "full_name": "محمد رضایی", "phone_number": "0912..."},
"city": {"id": 1, "name": "تهران", "province": "تهران"},
"location": {"id": 3, "name": "منطقه ۱", "city": 1, "city_name": "تهران"},
"property_type": {"id": 1, "title": "آپارتمان زیر ۱۰۰ متر", "base_code": 1000},
"images": [{"id": 1, "image": "/media/...", "image_url": "http://...", "is_main": true}],
"has_parking": true,
"description": "<p>...</p>",
"created_at": "2026-07-08T10:00:00Z",
"updated_at": "2026-07-08T10:00:00Z"
}
- 404 — slug نامعتبر
- 403 — دسترسی شیء ندارید
4. ویرایش ملک (PUT)
PUT /properties/<slug>/
از UpdateSerializer با partial=True استفاده میکند. دسترسی object-level همان جزئیات.
5. حذف ملک
DELETE /properties/<slug>/
پاسخ موفق: 204 بدون بدنه.
6. ویرایش تخصصی (PATCH /edit/)
PATCH /properties/<slug>/edit/
| پارامتر | نوع | توضیحات |
| uploaded_images | file[] | تصاویر جدید |
| main_image_index | int | ایندکس در لیست جدید |
| deleted_image_ids | int[] | حذف تصاویر موجود |
| set_main_image_id | int | اصلی کردن تصویر موجود |
| سایر فیلدهای مدل | — | اختیاری (slug exclude است؛ show_status قابل تغییر) |
- میتوان همزمان افزود و حذف کرد
- همیشه حداقل یک is_main میماند
- نمیتوان تصویر در حال حذف را اصلی کرد
{
"message": "ملک با موفقیت بروزرسانی شد",
"data": { "...": "PropertyDetailSerializer" }
}
const fd = new FormData();
fd.append('price', '9000000000');
fd.append('uploaded_images', newFile);
fd.append('main_image_index', '0');
fd.append('deleted_image_ids', '4');
await fetch('/properties/apartment-120-metri/edit/', {
method: 'PATCH',
headers: { Authorization: `Bearer ${token}` },
body: fd,
});
7. مدیریت شهرها
GET /properties/cities/
POST /properties/cities/
GET /properties/cities/{id}/
PUT /properties/cities/{id}/
DELETE /properties/cities/{id}/
- خواندن: admin / staff / agent
- نوشتن: فقط admin / staff
[{"id": 1, "name": "تهران", "province": "تهران"}]
{
"id": 1, "name": "تهران", "province": "تهران", "population": 9000000,
"description": "...", "locations_count": 22, "properties_count": 145,
"created_at": "...", "updated_at": "..."
}
{"name": "کرج", "province": "البرز", "population": 2000000, "description": ""}
8. مدیریت مناطق
GET /properties/locations/
GET /properties/locations/?city=1
POST /properties/locations/
GET|PUT|DELETE /properties/locations/{id}/
برای dropdown فرم ملک بعد از انتخاب شهر از ?city=<id> استفاده کنید.
| پارامتر query | نوع | اجباری | توضیح |
| city | int | ✗ | فیلتر مناطق همان شهر |
[{"id": 1, "name": "منطقه ۱", "city": 1, "city_name": "تهران"}]
{
"id": 1, "name": "منطقه ۱", "city": 1, "city_name": "تهران", "city_province": "تهران",
"description": "...", "latitude": 35.77, "longitude": 51.41,
"land_use": "مسکونی", "average_price": 150000000,
"iframe": "<iframe ...></iframe>", "properties_count": 45
}
const cities = await fetch('/properties/cities/', { headers }).then(r => r.json());
const locations = await fetch(`/properties/locations/?city=${selectedCityId}`, { headers }).then(r => r.json());
9. انواع ملک
GET|POST /properties/property-types/
GET|PUT|DELETE /properties/property-types/{id}/
[{"id": 1, "title": "آپارتمان زیر ۱۰۰ متر", "base_code": 1000, "properties_count": 12}]
| base_code | معنی |
| 1000 | آپارتمان زیر ۱۰۰ متر |
| 2000 | آپارتمان بالای ۱۰۰ متر |
| 3000 | زمین و ویلایی خارج بافت |
| 4000 | زمین مسکونی |
| 5000 | تجاری و اداری |
| 6000 | ویلا |
10. مالکها
GET|POST /properties/property-owners/
GET|PUT|DELETE /properties/property-owners/{id}/
| فیلد | نوع | توضیح |
| id | int | شناسه |
| first_name / last_name | string | نام |
| full_name | string | محاسبهشده (فقط read) |
| phone_number | string | تماس |
| address | text | آدرس |
| bank_account_number / iban | string | بانکی |
| description | text | توضیحات |
| is_active | bool | فعال |
| properties_count | int | محاسبهشده |
{"first_name": "محمد", "last_name": "رضایی", "phone_number": "09121234567", "is_active": true}
مدل Property — جدول کامل فیلدها
| نام فیلد | نوع | توضیحات |
| روابط |
| agent | FK | مشاور (در create خودکار) |
| owner | FK | مالک |
| city / location / property_type | FK | موقعیت و نوع |
| پایه |
| property_code | string | خودکار اگر خالی باشد |
| title | string(68) | عنوان |
| slug | slug unique | از عنوان |
| transaction_type | sale|rent | نوع معامله |
| floor_number | string | طبقه / کلید duplicate |
| show_status | ok|pending|sold|not | create=pending |
| قیمت و ابعاد |
| price, price_per_meter, mortgage, rent | bigint | مبالغ |
| measure, area | decimal | مساحت / زیربنا |
| floors_count, units_count, bedrooms, bathrooms | int | فضا |
| امکانات boolean |
| has_parking, has_storage, has_elevator, has_balcony, has_suite, has_yard, has_pool, has_jacuzzi, has_sauna, has_barbecue, has_security, has_janitor, has_conference_hall, gym | bool | امکانات |
| has_gas, has_electricity, has_water, has_wastewater, has_well, has_irrigation | bool | زیرساخت |
| heating_system, cooling_system | string | سیستمها |
| construction_year, facade, structure, usage, document_type, document_status, eviction_date, reformed | mixed | ساخت/سند |
| description | HTML | CKEditor5 |
| created_at / updated_at | datetime | متا |
PropertyImage
| فیلد | نوع | توضیح |
| id | int | شناسه |
| property | FK | ملک |
| image | file | فایل |
| is_main | bool | تصویر اصلی |
کش داشبورد
| مورد | مقدار |
| سرویس | Properties/services/cache_service.py |
| کلید | property_dashboard:v{version}:{scope}:{md5(query)} |
| scope | admin | staff | agent_{user.id} |
| TTL | ۳۰۰ ثانیه |
| Invalidation | post_save / post_delete روی PropertyModel (version bump) |
| Backend لوکال | LocMem — بدون Redis |
دسترسیها
| عملیات | سطح دسترسی |
| لیست املاک | IsAuthenticated (+ فیلتر نقش) |
| ثبت ملک | IsAuthenticated |
| جزئیات/ویرایش/حذف | Agent صاحب فایل یا Admin/Staff |
| خواندن شهر/منطقه/نوع/مالک | Admin / Staff / Agent |
| نوشتن شهر/منطقه/نوع/مالک | فقط Admin / Staff |
جریان کامل ثبت ملک در فرانت
- بارگذاری cities / property-types / property-owners
- با انتخاب شهر: locations?city=
- ارسال فقط به /properties/create/ (بدون check-duplicate جدا)
- 201 موفق / 200+can_edit → edit / 403 پیام عدم دسترسی
- refetch لیست — بکاند کش را باطل کرده است
ملک جدید pending است؛ staff فقط ok را در لیست پیشفرض میبیند.
agent صاحب فایل، pending خودش را میبیند.
ماتریس خطاها
| کد | علت رایج |
| 400 | اعتبارسنجی فیلدها / city-location ناسازگار / index اشتباه |
| 401 | توکن نیست یا منقضی |
| 403 | دسترسی شیء / duplicate بدون can_edit / agent روی write پایه |
| 404 | slug یا id نامعتبر |
خلاصه برای تیم فرانت:
برای ثبت ملک فقط create کافی است؛ lookups را با نقش agent هم میتوانید بخوانید؛
مناطق را با city فیلتر کنید؛ و پاسخهای 201/200/403 را جدا مدیریت کنید.