From 8cd8e7603d50d8ddda3a3d97675d6987a247806c Mon Sep 17 00:00:00 2001 From: Noman Akram Date: Wed, 6 May 2026 12:46:10 +0500 Subject: [PATCH 1/3] implement auth --- .gitignore | 2 + pak_wheels/.DS_Store | Bin 6148 -> 6148 bytes .../__pycache__/settings.cpython-314.pyc | Bin 3397 -> 3370 bytes pak_wheels/settings.py | 19 +++++++ pak_wheels/users/.DS_Store | Bin 0 -> 6148 bytes pak_wheels/users/__init__.py | 0 pak_wheels/users/admin.py | 3 ++ pak_wheels/users/apps.py | 5 ++ pak_wheels/users/migrations/0001_initial.py | 47 ++++++++++++++++++ pak_wheels/users/migrations/__init__.py | 0 pak_wheels/users/models.py | 14 ++++++ pak_wheels/users/serializers.py | 41 +++++++++++++++ pak_wheels/users/tests.py | 3 ++ pak_wheels/users/urls.py | 12 +++++ pak_wheels/users/views.py | 21 ++++++++ 15 files changed, 167 insertions(+) create mode 100644 pak_wheels/users/.DS_Store create mode 100644 pak_wheels/users/__init__.py create mode 100644 pak_wheels/users/admin.py create mode 100644 pak_wheels/users/apps.py create mode 100644 pak_wheels/users/migrations/0001_initial.py create mode 100644 pak_wheels/users/migrations/__init__.py create mode 100644 pak_wheels/users/models.py create mode 100644 pak_wheels/users/serializers.py create mode 100644 pak_wheels/users/tests.py create mode 100644 pak_wheels/users/urls.py create mode 100644 pak_wheels/users/views.py diff --git a/.gitignore b/.gitignore index 6973ddf..9574478 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ BASIC_COMMANDS.md /DJANGO_GUIDE phase1-api-starter.md +/pak_wheels/users/__pycache__ +/pak_wheels/users/migrations/__pycache__ diff --git a/pak_wheels/.DS_Store b/pak_wheels/.DS_Store index 99d5f2298891ee1022d1b6d0c44e60c75b1a86c2..4de642a60bc7d067b997bf817a1e8267083ae1b8 100644 GIT binary patch delta 194 zcmZoMXfc=|#>B)qu~2NHo+2a1#DLw4FEBDPa!lr7++5GfP|8rukjhZRP@I!)7@VA+ zTfhJY32s0NNj5j%#U&{xKM5$xp_*2mJ>lwcM|2e_1XL7cAlblZ0Mx$u2;)hX&Fmcf d96%RtKFIi;c{0CB`mu~2NHo+2a5#DLw5ER%VdHg9%iKE$%Ip^b4fI|n}pP{n3P=I_jt U`9&-_7=VD0fq`jrfXEhR08=CoA^-pY diff --git a/pak_wheels/__pycache__/settings.cpython-314.pyc b/pak_wheels/__pycache__/settings.cpython-314.pyc index 0b6a522e9c919b2a02c936612b669e65ca57d297..f811c4cb52678fb96faee38c7925e94dc5e358e8 100644 GIT binary patch delta 333 zcmYjK%}N4c6rHP6>Wrg~|1*w%nVG5cZGzsQTm-^d77CgJi4jpZ+Et6zk@y5{-MWo- zJwh+g3*@?0pGweyd(ORw!?_>!%r+VkLsKNazJErC6T_nCN$ttp=@5rq1vRM#He8y# zS08re5yK{%2yh%hgrLcY4i#Y-a;tYpkWgLX1R~f&6b-~+BHl|1m6J$t3Q44pMy4A? zwy7`wt|5GaI1Q_-mX#jCKA^Ra+{!5|os+*PXW(#FR^an)}Vhj d_AZChd%Zy~)0uu6TKN4@-=n_J=4N=GegOv(Mlb*X delta 359 zcmYj~%}PR16ov1xQ<~>q|E_=c`k$GZb`L?&Q-rW)6&ezjmZ;>0XjFq9AiGb{w5c?S zraeN`V>oKisG}0KVXwXS;$!V6`^`400mHAUdVl`zw#J6V?#Jajvs`DSI~q3CG_Z^n z$1{E{-Bi>Wf|tVZQ3R{-LnufGhk=0Fw(3kW$sI-}f~X>d8p4RcY(^!gD54ZY3~?lo zZ1`Z+#O(8;q(?G}!*1|`)?`TSv4xNH+{wx$c8F(m%O1UFJd0t(fYQFR{yh0M!Igw*UYD diff --git a/pak_wheels/settings.py b/pak_wheels/settings.py index ce60467..a595dff 100644 --- a/pak_wheels/settings.py +++ b/pak_wheels/settings.py @@ -11,6 +11,7 @@ """ from pathlib import Path +from datetime import timedelta # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -47,12 +48,14 @@ ] CUSTOM_APPS = [ + 'pak_wheels.users', ] INSTALLED_APPS = DEFAULT_APPS + THIRD_PARTY_APPS + CUSTOM_APPS MIDDLEWARE = [ + 'corsheaders.middleware.CorsMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', @@ -62,6 +65,7 @@ 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] +AUTH_USER_MODEL = 'users.User' ROOT_URLCONF = 'pak_wheels.urls' @@ -80,6 +84,16 @@ }, ] +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework_simplejwt.authentication.JWTAuthentication', + ), + 'DEFAULT_PERMISSION_CLASSES': ( + 'rest_framework.permissions.IsAuthenticated', + ), + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', + 'PAGE_SIZE': 20, +} WSGI_APPLICATION = 'pak_wheels.wsgi.application' @@ -95,6 +109,11 @@ } +# JWT settings +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(days=1), + 'REFRESH_TOKEN_LIFETIME': timedelta(days=30), +} MEDIA_URL = '/media/' MEDIA_ROOT = BASE_DIR / 'media' diff --git a/pak_wheels/users/.DS_Store b/pak_wheels/users/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..c35d988fe432905380257f3ddc685407144fcaab GIT binary patch literal 6148 zcmeHK%}T>S5T1=iqCME7;K6+a`v$SZCn%ny{h<{a6Z`?aMeu1n`VxY7pT>*d>*v$Q+v#`g$uHRD zd_9Yh%s%CGMKiiVws%++bWOMQY>!^B|3=<=uFvNO?+=n|3wM?2PuzN*C)nQ16fgx$ zfvr^lHJdH&3fgQ6m;$E2QUU%xcnrowu@VfQ4osm10CwOGhBfyR9OD%e#Yzx&U`|4T z66&6sRk(BbPm{|7Y#}e?7>q zOaW8iUn$@^=`bDOm9(}tUXE+6$GE^?V_qe=48bClVsN<>A7TW9-}3^PC{}{-!0bmL M$Y7Hx@TUrV0nX7{f&c&j literal 0 HcmV?d00001 diff --git a/pak_wheels/users/__init__.py b/pak_wheels/users/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pak_wheels/users/admin.py b/pak_wheels/users/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/pak_wheels/users/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/pak_wheels/users/apps.py b/pak_wheels/users/apps.py new file mode 100644 index 0000000..46574ae --- /dev/null +++ b/pak_wheels/users/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class UsersConfig(AppConfig): + name = 'pak_wheels.users' diff --git a/pak_wheels/users/migrations/0001_initial.py b/pak_wheels/users/migrations/0001_initial.py new file mode 100644 index 0000000..9c262cf --- /dev/null +++ b/pak_wheels/users/migrations/0001_initial.py @@ -0,0 +1,47 @@ +# Generated by Django 6.0.4 on 2026-05-06 03:48 + +import django.contrib.auth.models +import django.contrib.auth.validators +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('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')), + ('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')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('email', models.EmailField(max_length=254, unique=True)), + ('full_name', models.CharField(max_length=255)), + ('phone', models.CharField(max_length=20)), + ('city', models.CharField(blank=True, max_length=100)), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + ] diff --git a/pak_wheels/users/migrations/__init__.py b/pak_wheels/users/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pak_wheels/users/models.py b/pak_wheels/users/models.py new file mode 100644 index 0000000..d76b3bc --- /dev/null +++ b/pak_wheels/users/models.py @@ -0,0 +1,14 @@ +from django.contrib.auth.models import AbstractUser +from django.db import models + +class User(AbstractUser): + email = models.EmailField(unique=True) + full_name = models.CharField(max_length=255) + phone = models.CharField(max_length=20) + city = models.CharField(max_length=100, blank=True) + + USERNAME_FIELD = 'email' + REQUIRED_FIELDS = ['username', 'full_name'] + + def __str__(self): + return self.email \ No newline at end of file diff --git a/pak_wheels/users/serializers.py b/pak_wheels/users/serializers.py new file mode 100644 index 0000000..6d64cf8 --- /dev/null +++ b/pak_wheels/users/serializers.py @@ -0,0 +1,41 @@ +from rest_framework import serializers +from django.contrib.auth.password_validation import validate_password +from .models import User +from rest_framework_simplejwt.serializers import TokenObtainPairSerializer + +class UserTokenObtainPairSerializer(TokenObtainPairSerializer): + def validate(self, attrs): + data = super().validate(attrs) + serializer = UserSerializer(self.user).data + data['user'] = serializer + return data + +class UserRegisterationSerializer(serializers.ModelSerializer): + password = serializers.CharField(write_only=True, validators=[validate_password]) + + class Meta: + model = User + fields = ['email', 'username', 'password', 'full_name', 'phone', 'city'] + + def validate(self, attrs): + email = attrs.get('email') + if User.objects.filter(email=email).exists(): + raise serializers.ValidationError("User with this email already exists") + return attrs + + def create(self, validated_data): + user = User.objects.create_user( + username=validated_data['username'], + email=validated_data['email'], + password=validated_data['password'], + full_name=validated_data['full_name'], + phone=validated_data['phone'], + city=validated_data.get('city', '') + ) + return user + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ('id', 'email', 'username', 'full_name', 'phone', 'city') + read_only_fields = ('id', 'email') \ No newline at end of file diff --git a/pak_wheels/users/tests.py b/pak_wheels/users/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/pak_wheels/users/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/pak_wheels/users/urls.py b/pak_wheels/users/urls.py new file mode 100644 index 0000000..21abf0d --- /dev/null +++ b/pak_wheels/users/urls.py @@ -0,0 +1,12 @@ +from django.urls import path +from rest_framework_simplejwt.views import ( + TokenRefreshView, +) +from .views import UserRegistrationView, ProfileView, UserTokenObtainPairView + +urlpatterns = [ + path('register/', UserRegistrationView.as_view(), name='register'), + path('login/', UserTokenObtainPairView.as_view(), name='login'), + path('refresh/', TokenRefreshView.as_view(), name='token_refresh'), + path('profile/', ProfileView.as_view(), name='profile'), +] \ No newline at end of file diff --git a/pak_wheels/users/views.py b/pak_wheels/users/views.py new file mode 100644 index 0000000..c8e535a --- /dev/null +++ b/pak_wheels/users/views.py @@ -0,0 +1,21 @@ +from rest_framework import generics, status +from rest_framework.response import Response +from rest_framework.permissions import AllowAny, IsAuthenticated +from .models import User +from .serializers import UserRegisterationSerializer, UserSerializer, UserTokenObtainPairSerializer +from rest_framework_simplejwt.views import TokenObtainPairView + +class UserRegistrationView(generics.CreateAPIView): + queryset = User.objects.all() + permission_classes = [AllowAny] + serializer_class = UserRegisterationSerializer + +class UserTokenObtainPairView(TokenObtainPairView): + serializer_class = UserTokenObtainPairSerializer + +class ProfileView(generics.RetrieveUpdateAPIView): + permission_classes = [IsAuthenticated] + serializer_class = UserSerializer + + def get_object(self): + return self.request.user \ No newline at end of file From efd6ab63bb20a9c370533f0bbe01576e3cc26629 Mon Sep 17 00:00:00 2001 From: Noman Akram Date: Thu, 7 May 2026 15:23:47 +0500 Subject: [PATCH 2/3] removed unused imports --- pak_wheels/users/views.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pak_wheels/users/views.py b/pak_wheels/users/views.py index c8e535a..5fe3496 100644 --- a/pak_wheels/users/views.py +++ b/pak_wheels/users/views.py @@ -1,5 +1,4 @@ -from rest_framework import generics, status -from rest_framework.response import Response +from rest_framework import generics from rest_framework.permissions import AllowAny, IsAuthenticated from .models import User from .serializers import UserRegisterationSerializer, UserSerializer, UserTokenObtainPairSerializer From 36ca222a586b491e1b4dc5cfdd32ef37fbbb08db Mon Sep 17 00:00:00 2001 From: Noman Akram Date: Thu, 7 May 2026 18:59:09 +0500 Subject: [PATCH 3/3] Added the Car listings --- .gitignore | 9 +++ db.sqlite3 | Bin 135168 -> 151552 bytes pak_wheels/listings/__init__.py | 0 pak_wheels/listings/admin.py | 3 + pak_wheels/listings/apps.py | 5 ++ .../listings/migrations/0001_initial.py | 48 ++++++++++++ pak_wheels/listings/migrations/__init__.py | 0 pak_wheels/listings/models.py | 46 ++++++++++++ pak_wheels/listings/serializers.py | 29 +++++++ pak_wheels/listings/tests.py | 3 + pak_wheels/listings/urls.py | 23 ++++++ pak_wheels/listings/views.py | 71 ++++++++++++++++++ pak_wheels/settings.py | 2 +- pak_wheels/urls.py | 10 ++- 14 files changed, 247 insertions(+), 2 deletions(-) create mode 100644 pak_wheels/listings/__init__.py create mode 100644 pak_wheels/listings/admin.py create mode 100644 pak_wheels/listings/apps.py create mode 100644 pak_wheels/listings/migrations/0001_initial.py create mode 100644 pak_wheels/listings/migrations/__init__.py create mode 100644 pak_wheels/listings/models.py create mode 100644 pak_wheels/listings/serializers.py create mode 100644 pak_wheels/listings/tests.py create mode 100644 pak_wheels/listings/urls.py create mode 100644 pak_wheels/listings/views.py diff --git a/.gitignore b/.gitignore index 9574478..ee11ee2 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,12 @@ BASIC_COMMANDS.md phase1-api-starter.md /pak_wheels/users/__pycache__ /pak_wheels/users/migrations/__pycache__ +/media/listings +/pak_wheels/__pycache__ +PHASE1_REVIEW_GUIDE.md +PHASE1_REVIEW_GUIDE.md +*.pyc +*.pyc +/pak_wheels/__pycache__ +pak_wheels/__pycache__/urls.cpython-314.pyc +deep_dive_api_dry_run.md diff --git a/db.sqlite3 b/db.sqlite3 index a309275c56de0ebb2dfbe18230d5c9ab32023075..500c104c05909a052c3fd9bf150875c044554ed8 100644 GIT binary patch delta 2792 zcmb7GO>7%Q6yDigd)Ic><1}flG;uO3d86|mU07~As36wS#?%UDn>g)Nh{nsglwaz0U)2qPsiqz?5vv0shKn?F1o{@ zy?E0-U}w`gGnUT6!vy>tF2Nh{BbbMWOEzII=JFxh%{WBB-{6n%TlgV-8@>v2@DNUC81kD~nsD^$=v=)(?F{xi8vI&;O0ULV7AORx zMdp%dgA?!od=^Gw1~)ejk6^}st%899fI*t=(QLP79c9aQX|_|dn>E`}I`Q5cyC`Jo zDl)f4Tg#IzHp?B$7nXM{Y0I#s3opA{=0YDb5TIXNS@vN$jmkwsST)k*xq2{c!1r(` z%D_q;8=&14XuXUYLm66e)uCG)1&r-;uV`8sh)Z2Z^>vjAhaPTYP2@;r%eXo*%q_0! zYqCLP6}q_sjZg2XE+M@i4Jvi)mJW<{4LsESsz@DFI{NFC+}2iNhA^FBKV^HE3&x9v zZ|Dcyf82P>mzJ+u&R7P?KfqOBA!fkUqG;P$Sa1w7_6avwh^Xm=@{&G(eb>fxPfSh3 zg5H4OpYlknKz+kw(a3ldJw3WR`T}yUiZ>&1qWz=DSy+X2qODZ|Jg3vO;2dD=lM+~n zt`b<4mQzwGC0kY~1pM)1KCj%!s{enYsm6kq>DojUl}+f%GHdfXUFWuMW9)7>IPc2K zQz@l!fVS*bIkr^fbfEFbQzOwPH7iEUK7%&4xX)l89z;$x7n>`;O0)A$G?mGu(CGeg zH2VC=2#&?Gikw#xPL#kud39E44C<3FzMcWSZH-E*f$tKaf8sptV(gLxF1lBxpyQeh zb?Q}Zl7)^g=RxPye5ze>Rg)kEv@_r`*M&3=UrZ zD*<>TYuskkj>+qTGvL;hw{FucX}wRt)7Ja=?e~kdu&_XqEyR%BPEsAwNCfQ7q!Th4 zR!^#Ngtmecw95EpyAbPCsykNu!Z?#T2O>!R~{2jVkHfbS59GFiJy0 zC?@A)q9>LU5AMz=e%>46CE>Y!{9HP@V%D(N6P7}JFdzg3FF9$z)a?4yg&OysvFM02 zA6%PT3WY@ubzBtj0x=?9nncK^U0)DSINKR6Z!F^&TA$q*e zG~=#Q3%n-~@_Qu`FWIqKHe#x7QFmdTa<9V~&`J^XNI`KwFcLGS2FkS=c`?BIq!oSZ z(bu!&5BoyAFBk}jV)+tBLcE#&hWAUYXYazw^?{@*U}DVh0bzhk1pEgs!+ZEzxdfLm Z=mzX9-y%q(W(}I9HA`VO14&UP)BmRXHRJ#Q delta 376 zcmZozz}c{XV}i7x76Su=A`ruX_Cy_HMy-trOZb_Ex#cFa3#e~aRQSNPSx)*HivSyc z6D$92{-^v+{A>89^IzI5*pSCBD$LBR&6rqPk`Z5!T9lhvT%4JoCoy@tz7?bJK#QL9ZkG3Z$_TWm zlY##&|2_Uw{A>AV@OJ{$mhx}zjITCemf$a)%sv6AxWJZwbLsqk2WAPLgvsm=K-x8U zHYdDUQmnzt@|}URhGQFtJbNo!JL?IS@4WwcLwF8yALgmy)`5AIal>Xqffhy~Hf1)j zP@{knySTqDW252pxLiiD=?`-l6&U5F&&_3&pRSn8Xw1TOi)qdDh+IaO>5RFI3e)%G zGCG6A7^gGmF=|hrlgr34-7JsMXZpi@M!D_vd5rOl+rOnS>ak6a$z/', CarListingDetailView.as_view(), name='car-detail'), + + # Images + path('/images/', CarImageUploadView.as_view(), name='image-upload'), + path('images//', CarImageDeleteView.as_view(), name='image-delete'), +] diff --git a/pak_wheels/listings/views.py b/pak_wheels/listings/views.py new file mode 100644 index 0000000..c35c5ac --- /dev/null +++ b/pak_wheels/listings/views.py @@ -0,0 +1,71 @@ +from rest_framework import generics, status, permissions +from rest_framework.parsers import MultiPartParser, FormParser +from django.shortcuts import get_object_or_404 +from rest_framework.exceptions import PermissionDenied +from rest_framework.views import APIView +from .models import CarListing, CarImage +from .serializers import ( + CarListingSerializer, + CarListingCreateSerializer, + CarImageSerializer, +) + + +class IsOwnerOrReadOnly(permissions.BasePermission): + """Only the seller can edit/delete their own listing.""" + def has_object_permission(self, request, view, obj): + if request.method in permissions.SAFE_METHODS: + return True + return obj.seller == request.user + + + +class CarListCreateView(generics.ListCreateAPIView): + queryset = CarListing.objects.all() + permission_classes = [permissions.IsAuthenticatedOrReadOnly] + + def get_serializer_class(self): + if self.request.method == "POST": + return CarListingCreateSerializer + return CarListingSerializer + + def perform_create(self, serializer): + serializer.save(seller=self.request.user) + +class CarListingDetailView(generics.RetrieveUpdateDestroyAPIView): + queryset = CarListing.objects.all() + serializer_class = CarListingSerializer + permission_classes = [IsOwnerOrReadOnly] + + +class MyListingsView(generics.ListAPIView): + serializer_class = CarListingSerializer + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + return CarListing.objects.filter(seller=self.request.user) + +class CarImageUploadView(generics.CreateAPIView): + serializer_class = CarImageSerializer + permission_classes = [permissions.IsAuthenticated] + parser_classes = [MultiPartParser, FormParser] + + def perform_create(self, serializer): + listing_id = self.kwargs["listing_id"] + + listing = get_object_or_404(CarListing, id=listing_id) + + if listing.seller != self.request.user: + raise PermissionDenied("You don't own this listing") + + serializer.save(listing=listing) + +class CarImageDeleteView(generics.DestroyAPIView): + queryset = CarImage.objects.all() + serializer_class = CarImageSerializer + permission_classes = [permissions.IsAuthenticated] + + def perform_destroy(self, instance): + if instance.listing.seller != self.request.user: + raise PermissionDenied("You don't own this listing") + instance.delete() \ No newline at end of file diff --git a/pak_wheels/settings.py b/pak_wheels/settings.py index a595dff..93f3fbf 100644 --- a/pak_wheels/settings.py +++ b/pak_wheels/settings.py @@ -49,7 +49,7 @@ CUSTOM_APPS = [ 'pak_wheels.users', - + 'pak_wheels.listings', ] INSTALLED_APPS = DEFAULT_APPS + THIRD_PARTY_APPS + CUSTOM_APPS diff --git a/pak_wheels/urls.py b/pak_wheels/urls.py index 1f078be..c2c0b7b 100644 --- a/pak_wheels/urls.py +++ b/pak_wheels/urls.py @@ -16,8 +16,16 @@ """ from django.contrib import admin from django.urls import path, include +from django.conf import settings +from django.conf.urls.static import static urlpatterns = [ path('admin/', admin.site.urls), - path('', include('pak_wheels.users.urls')), + path('api/auth/', include('pak_wheels.users.urls')), + path('api/listings/', include('pak_wheels.listings.urls')), ] + + +# Serve uploaded images during development +if settings.DEBUG: + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file