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
پارامترنوعاجباریپیش‌فرضتوضیحات
pageinteger✗1شماره صفحه
page_sizeinteger✗20حداکثر ۱۰۰
show_statusstring✗—ok | pending | sold | not
transaction_typestring✗—sale | rent
cityinteger✗—ID شهر
locationinteger✗—ID منطقه
property_typeinteger✗—ID نوع ملک
agentinteger✗—ID مشاور
ownerinteger✗—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
فیلدنوعتوضیحات
countintکل رکوردها
page / page_size / total_pagesintصفحه‌بندی
next / previousint|nullشماره صفحه بعدی/قبلی (نه URL)
resultsarrayآیتم‌های صفحه
مثال 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

پارامترهای اجباری
پارامترنوعاجباریتوضیحات
ownerint✓شناسه مالک
cityint✓شناسه شهر
locationint✓باید متعلق به city باشد
floor_numberstring✓کلید duplicate
property_typeint✓نوع ملک
titlestring(68)✓عنوان
uploaded_imagesfile[]✓حداقل یک فایل
main_image_indexint✓ایندکس از ۰
سایر فیلدهای اختیاری رایج
پارامترنوعتوضیحات
transaction_typesale|rentپیش‌فرض sale
price / price_per_meter / mortgage / rentbigintمبالغ
measure / areadecimalمساحت / زیربنا
bedrooms / bathroomsintفضاها
address / phone / descriptionstring/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_imagesfile[]تصاویر جدید
main_image_indexintایندکس در لیست جدید
deleted_image_idsint[]حذف تصاویر موجود
set_main_image_idintاصلی کردن تصویر موجود
سایر فیلدهای مدل—اختیاری (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نوعاجباریتوضیح
cityint✗فیلتر مناطق همان شهر
[{"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}/
فیلدنوعتوضیح
idintشناسه
first_name / last_namestringنام
full_namestringمحاسبه‌شده (فقط read)
phone_numberstringتماس
addresstextآدرس
bank_account_number / ibanstringبانکی
descriptiontextتوضیحات
is_activeboolفعال
properties_countintمحاسبه‌شده
{"first_name": "محمد", "last_name": "رضایی", "phone_number": "09121234567", "is_active": true}
مدل Property — جدول کامل فیلدها
نام فیلدنوعتوضیحات
روابط
agentFKمشاور (در create خودکار)
ownerFKمالک
city / location / property_typeFKموقعیت و نوع
پایه
property_codestringخودکار اگر خالی باشد
titlestring(68)عنوان
slugslug uniqueاز عنوان
transaction_typesale|rentنوع معامله
floor_numberstringطبقه / کلید duplicate
show_statusok|pending|sold|notcreate=pending
قیمت و ابعاد
price, price_per_meter, mortgage, rentbigintمبالغ
measure, areadecimalمساحت / زیربنا
floors_count, units_count, bedrooms, bathroomsintفضا
امکانات 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, gymboolامکانات
has_gas, has_electricity, has_water, has_wastewater, has_well, has_irrigationboolزیرساخت
heating_system, cooling_systemstringسیستم‌ها
construction_year, facade, structure, usage, document_type, document_status, eviction_date, reformedmixedساخت/سند
descriptionHTMLCKEditor5
created_at / updated_atdatetimeمتا
PropertyImage
فیلدنوعتوضیح
idintشناسه
propertyFKملک
imagefileفایل
is_mainboolتصویر اصلی
کش داشبورد
موردمقدار
سرویسProperties/services/cache_service.py
کلیدproperty_dashboard:v{version}:{scope}:{md5(query)}
scopeadmin | staff | agent_{user.id}
TTL۳۰۰ ثانیه
Invalidationpost_save / post_delete روی PropertyModel (version bump)
Backend لوکالLocMem — بدون Redis
دسترسی‌ها
عملیاتسطح دسترسی
لیست املاکIsAuthenticated (+ فیلتر نقش)
ثبت ملکIsAuthenticated
جزئیات/ویرایش/حذفAgent صاحب فایل یا Admin/Staff
خواندن شهر/منطقه/نوع/مالکAdmin / Staff / Agent
نوشتن شهر/منطقه/نوع/مالکفقط Admin / Staff
جریان کامل ثبت ملک در فرانت
  1. بارگذاری cities / property-types / property-owners
  2. با انتخاب شهر: locations?city=
  3. ارسال فقط به /properties/create/ (بدون check-duplicate جدا)
  4. 201 موفق / 200+can_edit → edit / 403 پیام عدم دسترسی
  5. refetch لیست — بک‌اند کش را باطل کرده است
ملک جدید pending است؛ staff فقط ok را در لیست پیش‌فرض می‌بیند. agent صاحب فایل، pending خودش را می‌بیند.
ماتریس خطاها
کدعلت رایج
400اعتبارسنجی فیلدها / city-location ناسازگار / index اشتباه
401توکن نیست یا منقضی
403دسترسی شیء / duplicate بدون can_edit / agent روی write پایه
404slug یا id نامعتبر
خلاصه برای تیم فرانت: برای ثبت ملک فقط create کافی است؛ lookups را با نقش agent هم می‌توانید بخوانید؛ مناطق را با city فیلتر کنید؛ و پاسخ‌های 201/200/403 را جدا مدیریت کنید.