-
Notifications
You must be signed in to change notification settings - Fork 0
Lau/listing view #58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Lau/listing view #58
Changes from 8 commits
fd9c0e7
5a10647
28cb881
34ae14f
9226799
5bbc124
8a00698
b553214
da542e0
35175a8
ca8ac1c
075c60b
bd8f917
79cafc1
35ed950
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| from decimal import Decimal | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file is a script to artificially add offers to one listing
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok maybe make a comment in the file. Also not sure |
||
|
|
||
| from django.contrib.auth import get_user_model | ||
| from django.core.management.base import BaseCommand | ||
| from django.utils import timezone | ||
|
|
||
| from market.models import Category, Item, Listing, Offer | ||
|
|
||
|
|
||
| User = get_user_model() | ||
|
|
||
|
|
||
| class Command(BaseCommand): | ||
| help = "Seed a listing with two pending offers for testing" | ||
|
|
||
| def add_arguments(self, parser): | ||
| parser.add_argument( | ||
| "--listing-id", | ||
| type=int, | ||
| default=None, | ||
| help="Add offers to an existing listing by ID", | ||
| ) | ||
|
|
||
| def handle(self, *args, **options): | ||
| alice, _ = User.objects.get_or_create( | ||
| username="alice", | ||
| defaults={ | ||
| "email": "alice@example.com", | ||
| "first_name": "Alice", | ||
| "last_name": "Johnson", | ||
| "phone_number": "+12155551234", | ||
| "phone_verified": True, | ||
| }, | ||
| ) | ||
| alice.set_password("testpassword123") | ||
| alice.save() | ||
|
|
||
| bob, _ = User.objects.get_or_create( | ||
| username="bob", | ||
| defaults={ | ||
| "email": "bob@example.com", | ||
| "first_name": "Bob", | ||
| "last_name": "Williams", | ||
| "phone_number": "+12155555678", | ||
| "phone_verified": True, | ||
| }, | ||
| ) | ||
| bob.set_password("testpassword123") | ||
| bob.save() | ||
|
|
||
| self.stdout.write( | ||
| self.style.SUCCESS("Buyers ready: Alice Johnson, Bob Williams") | ||
| ) | ||
|
|
||
| listing_id = options["listing_id"] | ||
| if listing_id: | ||
| try: | ||
| listing = Listing.objects.get(pk=listing_id) | ||
| except Listing.DoesNotExist: | ||
| self.stdout.write( | ||
| self.style.ERROR(f"Listing with id={listing_id} not found") | ||
| ) | ||
| return | ||
| self.stdout.write( | ||
| self.style.SUCCESS( | ||
| f"Using existing listing: {listing.title} (id={listing.id})" | ||
| ) | ||
| ) | ||
| else: | ||
| seller, _ = User.objects.get_or_create( | ||
| username="lautaro", | ||
| defaults={ | ||
| "email": "lautaro@example.com", | ||
| "first_name": "Lautaro", | ||
| "last_name": "Beck", | ||
| }, | ||
| ) | ||
| seller.set_password("testpassword123") | ||
| seller.save() | ||
| self.stdout.write( | ||
| self.style.SUCCESS(f"Seller ready: {seller.get_full_name()}") | ||
| ) | ||
|
|
||
| category, _ = Category.objects.get_or_create(name="Furniture") | ||
| listing = Item.objects.create( | ||
| seller=seller, | ||
| title="New offer", | ||
| description="ASDFASFAFS", | ||
| price=Decimal("12312.00"), | ||
| negotiable=True, | ||
| expires_at=timezone.now() + timezone.timedelta(days=60), | ||
| condition=Item.Condition.GOOD, | ||
| category=category, | ||
| ) | ||
| self.stdout.write(self.style.SUCCESS(f"Created listing: {listing.title}")) | ||
|
|
||
| _, created_alice = Offer.objects.get_or_create( | ||
| user=alice, | ||
| listing=listing, | ||
| defaults={ | ||
| "offered_price": Decimal("40.00"), | ||
| "message": "Would you take $40? I can pick up today.", | ||
| }, | ||
| ) | ||
|
|
||
| _, created_bob = Offer.objects.get_or_create( | ||
| user=bob, | ||
| listing=listing, | ||
| defaults={ | ||
| "offered_price": Decimal("45.00"), | ||
| "message": "Interested! Is the price negotiable?", | ||
| }, | ||
| ) | ||
|
|
||
| new_count = sum([created_alice, created_bob]) | ||
| skipped = 2 - new_count | ||
|
|
||
| self.stdout.write( | ||
| self.style.SUCCESS( | ||
| f"Created {new_count} offers, skipped {skipped} (already existed)" | ||
| ) | ||
| ) | ||
| self.stdout.write( | ||
| self.style.SUCCESS(f"\nDone! Offers added to listing id={listing.id}") | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| # Generated by Django 5.0.2 on 2026-03-27 21:34 | ||
|
|
||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ("market", "0004_rename_address_sublet_street_address_and_more"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AddField( | ||
| model_name="offer", | ||
| name="status", | ||
| field=models.CharField( | ||
| choices=[ | ||
| ("pending", "Pending"), | ||
| ("accepted", "Accepted"), | ||
| ("rejected", "Rejected"), | ||
| ], | ||
| default="pending", | ||
| max_length=10, | ||
| ), | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -43,16 +43,22 @@ def has_object_permission(self, request, view, obj): | |||||||||||||||||
| ) | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| class OfferOwnerPermission(permissions.BasePermission): | ||||||||||||||||||
| class ListingOwnerOffersPermission(permissions.BasePermission): | ||||||||||||||||||
| """ | ||||||||||||||||||
| Custom permission to allow owner of an offer to delete it. | ||||||||||||||||||
| Permission for listing-owner offer interactions: | ||||||||||||||||||
| - GET: listing seller can view offers on their listing | ||||||||||||||||||
| - PATCH/PUT: listing seller can update offer status | ||||||||||||||||||
| - DELETE: offer creator can delete/withdraw their own offer | ||||||||||||||||||
| """ | ||||||||||||||||||
|
|
||||||||||||||||||
| def has_permission(self, request, view): | ||||||||||||||||||
| return request.user.is_authenticated | ||||||||||||||||||
|
|
||||||||||||||||||
| def has_object_permission(self, request, view, obj): | ||||||||||||||||||
| if request.method in permissions.SAFE_METHODS: # GET | ||||||||||||||||||
| if request.method in permissions.SAFE_METHODS: | ||||||||||||||||||
| return obj.listing.seller == request.user | ||||||||||||||||||
|
|
||||||||||||||||||
| if request.method in ("PATCH", "PUT"): | ||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can prob combine these 2 conditions since they are doing the same thing |
||||||||||||||||||
| return obj.listing.seller == request.user | ||||||||||||||||||
|
|
||||||||||||||||||
| return obj.user == request.user | ||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,7 +11,10 @@ | |
| OffersReceived, | ||
| Tags, | ||
| UserFavorites, | ||
| change_offer_details, | ||
| change_offer_status, | ||
| get_current_user, | ||
| get_my_offer_for_listing, | ||
| get_phone_status, | ||
| send_verification_code, | ||
| verify_phone_code, | ||
|
|
@@ -46,9 +49,24 @@ | |
| # post: create an offer for an listing | ||
| # delete: delete an offer for an listing | ||
| path( | ||
| "listings/<listing_id>/offers/", | ||
| "listings/<int:listing_id>/offers/", | ||
| Offers.as_view({"get": "list", "post": "create", "delete": "destroy"}), | ||
| ), | ||
| # Current user's offer for an individual listing | ||
| # (Returns 404 when the user has no offer for that listing.) | ||
| path( | ||
| "listings/<int:listing_id>/offers/mine/", | ||
| get_my_offer_for_listing, | ||
| name="offers-mine", | ||
| ), | ||
| # Update offer status (PATCH) | ||
| path("offers/<int:offer_id>/", change_offer_status, name="offer-status"), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make it clear that this is for modifying status by adding /status at the end (like how you did for details) |
||
| # Update offer offered_price + message (PATCH) | ||
| path( | ||
| "offers/<int:offer_id>/details/", | ||
| change_offer_details, | ||
| name="offer-details", | ||
| ), | ||
| # Image Creation | ||
| path("listings/<listing_id>/images/", CreateImages.as_view()), | ||
| # Image Deletion | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.