Compare commits

...

10 Commits

13 changed files with 242 additions and 60 deletions

View File

@ -42,6 +42,7 @@ THIRD_PARTY_APPS = [
LOCAL_APPS = [
"core.apps.CoreConfig",
"crawl.apps.CrawlConfig",
"graph.apps.GraphConfig",
"market.apps.MarketConfig",
"user.apps.UserConfig",

View File

@ -6,6 +6,11 @@ DEBUG = False
ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS").split(" ")
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOWED_ORIGINS = os.environ.get("CORS_ALLOWED_ORIGINS").split(" ")
CSRF_COOKIE_DOMAIN = os.environ.get("CSRF_COOKIE_DOMAIN")
CSRF_TRUSTED_ORIGINS = os.environ.get("CSRF_TRUSTED_ORIGINS").split(" ")
# Database
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases

View File

@ -1,3 +1 @@
from django.contrib import admin
# Register your models here.

View File

@ -37,17 +37,14 @@ def search_bunjang(keyword, page=1):
result.append(
{
"title": item["name"],
"price": item["price"],
"price": int(item["price"]),
"year": update_time.year,
"month": update_time.month,
"day": update_time.day,
}
)
except Exception:
print("--------------------------------------------")
print(url)
print(data)
print("--------------------------------------------")
pass
finally:
return result
@ -56,14 +53,32 @@ def get_bunjang(keyword):
result = []
page = 1
while True:
print(f"page: {page}")
print(f"b {keyword} p{page}")
page_result = search_bunjang(keyword, page)
if not page_result:
break
result += page_result
filtered_result = []
for item in page_result:
price = int(item["price"])
if not (
"매입" in item["title"]
or "삽니다" in item["title"]
or "사요" in item["title"]
or "케이스" in item["title"]
or price % 10 != 0
or price < 100000
or price > 2000000
):
filtered_result.append(item)
result += filtered_result
page += 1
time.sleep(0.1)
# with open("bunjang.json", "w", encoding="utf-8") as file:
# json.dump(result, file, ensure_ascii=False, indent=2)
return result
sum = 0
for item in result:
sum += item["price"]
if len(result) == 0:
return 0
avg = round(sum // len(result), -3)
return avg

View File

@ -1,6 +1,7 @@
import requests
import re
import time
import json
# from bs4 import BeautifulSoup
@ -15,16 +16,15 @@ def get_api_id():
js_url = base_url + re.findall(pattern, text)[0]
response = requests.get(js_url)
text = response.text
index = text.find('iO.SENTRY_RELEASE={id:"') + 24
id = text[index : index + 20]
index = text.find('iO.SENTRY_RELEASE={id:"') + 23
index_length = text[index:].find('"')
id = text[index : index + index_length]
return id
def get_url(api_id, keyword, page=1):
base = f"https://web.joongna.com/_next/data/{api_id}/search"
return (
f"{base}/{keyword}.json?page={page}&sort=RECENT_SORT&keyword={keyword}"
)
return f"{base}/{keyword}.json?page={page}&sort=RECENT_SORT&keyword={keyword}"
def search_joongna(api_id, keyword, page):
@ -36,9 +36,9 @@ def search_joongna(api_id, keyword, page):
queries = data["pageProps"]["dehydratedState"]["queries"]
if len(queries) == 0:
return False
items = data["pageProps"]["dehydratedState"]["queries"][0]["state"][
items = data["pageProps"]["dehydratedState"]["queries"][0]["state"]["data"][
"data"
]["data"]["items"]
]["items"]
item_length = len(items)
if item_length == 0:
return False
@ -73,14 +73,31 @@ def get_joongna(keyword):
result = []
page = 1
while True:
print(f"page: {page}")
print(f"j {keyword} p{page}")
page_result = search_joongna(api_id, keyword, page)
if not page_result:
break
result += page_result
filtered_result = []
for item in page_result:
if not (
"매입" in item["title"]
or "삽니다" in item["title"]
or "사요" in item["title"]
or "케이스" in item["title"]
or item["price"] % 10 != 0
or item["price"] < 100000
or item["price"] > 2000000
):
filtered_result.append(item)
result += filtered_result
page += 1
time.sleep(0.1)
# with open("joongna.json", "w", encoding="utf-8") as file:
# json.dump(result, file, ensure_ascii=False, indent=2)
return result
sum = 0
for item in result:
sum += item["price"]
if len(result) == 0:
return 0
avg = round(sum // len(result), -3)
return avg

30
crawl/methods.py Normal file
View File

@ -0,0 +1,30 @@
from django.utils import timezone
from crawl.bunjang import get_bunjang
from crawl.joongna import get_joongna
from market.models import Product
from graph.models import MonthlyTransaction
def crawl():
today = timezone.now()
month = today.month - 1
year = today.year
if month == 0:
month = 12
year -= 1
for product in Product.objects.all():
name = product.name
print(name)
bunjang_result = get_bunjang(name)
joongna_result = get_joongna(name)
print("----------------")
print(bunjang_result, joongna_result)
print("----------------")
avg = (bunjang_result + joongna_result) / 2
MonthlyTransaction.objects.create(
product=product,
year=year,
month=month,
price=avg,
)

View File

@ -3,4 +3,10 @@ from django.contrib import admin
from .models import Transaction, MonthlyTransaction
admin.site.register(Transaction)
admin.site.register(MonthlyTransaction)
@admin.register(MonthlyTransaction)
class MonthlyTransactionAdmin(admin.ModelAdmin):
list_display = ("product", "year", "month", "price")
list_filter = ("year", "month")
search_fields = ("product",)

View File

@ -1,11 +1,36 @@
from django.contrib import admin
from .models import Brand, Product, Post, Image
from .models import Brand, Product, ItemIssues, Post, Image
admin.site.register(Brand)
admin.site.register(Product)
admin.site.register(Image)
@admin.register(ItemIssues)
class ItemIssuesAdmin(admin.ModelAdmin):
list_display = (
"display",
"frame",
"button",
"biometric",
"camera",
"speaker",
"others",
)
@admin.register(Image)
class ImageAdmin(admin.ModelAdmin):
list_display = ("post", "image")
raw_id_fields = ("post",)
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ("name", "brand", "release_date")
list_filter = ("brand", "release_date")
search_fields = ("name",)
date_hierarchy = "release_date"
@admin.register(Post)
@ -13,12 +38,15 @@ class PostAdmin(admin.ModelAdmin):
list_display = (
"product",
"price",
"text",
"author",
"status",
"item_issues",
"done",
"written_at",
)
list_filter = ("status", "written_at")
list_filter = ("done", "written_at")
search_fields = ("product", "text")
date_hierarchy = "written_at"
raw_id_fields = ("author",)
raw_id_fields = (
"author",
"product",
"item_issues",
)

View File

@ -13,7 +13,9 @@ class Brand(models.Model):
class Product(models.Model):
name = models.CharField(max_length=50)
brand = models.ForeignKey(
Brand, on_delete=models.CASCADE, related_name="products"
Brand,
on_delete=models.CASCADE,
related_name="products",
)
release_date = models.DateField(blank=True, null=True)
@ -24,23 +26,31 @@ class Product(models.Model):
return self.name
class ItemIssues(models.Model):
display = models.BooleanField(default=False)
frame = models.BooleanField(default=False)
button = models.BooleanField(default=False)
biometric = models.BooleanField(default=False)
camera = models.BooleanField(default=False)
speaker = models.BooleanField(default=False)
others = models.BooleanField(default=False)
class Post(models.Model):
STATUS_CHOICES = (
("s", "selling"),
("r", "reserved"),
("d", "done"),
)
product = models.ForeignKey(
Product, on_delete=models.CASCADE, related_name="posts"
Product,
on_delete=models.CASCADE,
related_name="posts",
)
price = models.IntegerField()
text = models.TextField()
author = models.ForeignKey(
User, on_delete=models.SET_NULL, null=True, related_name="posts"
)
status = models.CharField(
max_length=1, choices=STATUS_CHOICES, default="s"
item_issues = models.OneToOneField(
ItemIssues, on_delete=models.CASCADE, null=True, related_name="post"
)
done = models.BooleanField(default=False)
written_at = models.DateTimeField(auto_now_add=True)
class Meta:
@ -52,7 +62,9 @@ class Post(models.Model):
class Image(models.Model):
post = models.ForeignKey(
Post, on_delete=models.CASCADE, related_name="images"
Post,
on_delete=models.CASCADE,
related_name="images",
)
image = models.ImageField(upload_to="images", blank=True, null=True)

View File

@ -6,6 +6,7 @@ from market.models import (
Product,
Post,
Image,
ItemIssues,
)
@ -42,10 +43,31 @@ class ImageSerializer(ModelSerializer):
fields = ("image",)
class ImageCreateSerializer(ModelSerializer):
class Meta:
model = Image
fields = ("post", "image")
class ItemIssuesSerializer(ModelSerializer):
class Meta:
model = ItemIssues
fields = (
"display",
"frame",
"button",
"biometric",
"camera",
"speaker",
"others",
)
class PostListSerializer(ModelSerializer):
product = ProductListSerializer(read_only=True)
nickname = serializers.CharField(source="author.nickname")
image = serializers.SerializerMethodField()
item_issues = ItemIssuesSerializer(read_only=True)
class Meta:
model = Post
@ -55,7 +77,8 @@ class PostListSerializer(ModelSerializer):
"price",
"text",
"nickname",
"status",
"item_issues",
"done",
"written_at",
"image",
)
@ -63,30 +86,56 @@ class PostListSerializer(ModelSerializer):
def get_image(self, obj):
first_image = obj.images.first()
return ImageSerializer(
instance=first_image, context=self.context
instance=first_image,
context=self.context,
).data["image"]
class PostCreateSerializer(ModelSerializer):
item_issues = ItemIssuesSerializer(write_only=True)
# item_issues = serializers.JSONField(write_only=True)
photos = serializers.ListField(
child=serializers.ImageField(),
write_only=True,
)
def create(self, validated_data):
item_issues_data = validated_data.pop("item_issues")
item_issues = ItemIssues.objects.create(**item_issues_data)
validated_data["item_issues"] = item_issues
photos = validated_data.pop("photos", [])
post = super().create(validated_data)
for photo in photos:
Image.objects.create(post=post, image=photo)
return post
class Meta:
model = Post
fields = (
"product",
"price",
"text",
"item_issues",
"photos",
)
class PostStatusUpdateSerializer(ModelSerializer):
class PostUpdateSerializer(ModelSerializer):
class Meta:
model = Post
fields = ("status",)
fields = (
"price",
"text",
"done",
)
class PostSerializer(ModelSerializer):
product = ProductListSerializer(read_only=True)
nickname = serializers.CharField(source="author.nickname")
images = ImageSerializer(many=True, read_only=True)
item_issues = ItemIssuesSerializer(read_only=True)
class Meta:
model = Post
@ -96,7 +145,8 @@ class PostSerializer(ModelSerializer):
"price",
"text",
"nickname",
"status",
"item_issues",
"done",
"written_at",
"images",
)

View File

@ -5,14 +5,14 @@ from rest_framework.viewsets import ModelViewSet
from core.mixins import ActionBasedMixin
from core.permissions import IsAuthorOrReadOnly, IsAdminUserOrReadOnly
from market.models import Brand, Product, Post
from market.models import Brand, Product, Post, ItemIssues, Image
from market.serializers import (
BrandSerializer,
ProductSerializer,
PostSerializer,
PostCreateSerializer,
PostListSerializer,
PostStatusUpdateSerializer,
PostUpdateSerializer,
)
from graph.models import Transaction
from graph.methods import create_transaction
@ -62,7 +62,7 @@ class PostViewset(ActionBasedMixin, ModelViewSet):
serializer_class_map = {
"list": PostListSerializer,
"create": PostCreateSerializer,
"update": PostStatusUpdateSerializer,
"update": PostUpdateSerializer,
}
permission_classes = [IsAuthenticated, IsAuthorOrReadOnly]
permission_classes_map = {
@ -85,19 +85,31 @@ class PostViewset(ActionBasedMixin, ModelViewSet):
return Response(serializer.data)
def perform_create(self, serializer):
# item_issues = ItemIssues.objects.create()
# serializer.save(author=self.request.user, item_issues=item_issues)
serializer.save(author=self.request.user)
# images = self.request.data.getlist("images")
# for image in images:
# Image.objects.create(post=serializer.instance, image=image)
def partial_update(self, request, *args, **kwargs):
object = self.get_object()
done = request.data.get("done")
if not object.done and done:
create_transaction(object)
elif object.done and not done:
Transaction.objects.filter(post=object).delete()
if object.done != done:
object.done = done
object.save()
return Response(status=200)
def perform_update(self, serializer):
object = self.get_object()
object_unsold = object.status == "s" or object.status == "r"
instance = serializer.instance
if object_unsold and serializer.validated_data.get("status") == "d":
done = serializer.validated_data.get("done")
if not object.done and done:
create_transaction(instance)
elif (
not object_unsold
and serializer.validated_data.get("status") == "s"
):
Transaction.objects.filter(
price=instance.price, product=instance.product
).delete()
elif object.done and not done:
Transaction.objects.filter(post=object).delete()
return super().perform_update(serializer)

View File

@ -5,10 +5,14 @@ Django==4.2
django-cors-headers==3.14.0
django-debug-toolbar==4.0.0
djangorestframework==3.14.0
gunicorn==21.2.0
mypy-extensions==1.0.0
packaging==23.0
pathspec==0.11.1
Pillow==9.5.0
platformdirs==3.2.0
psycopg2==2.9.9
pytz==2022.7.1
setuptools==68.2.2
sqlparse==0.4.3
wheel==0.41.3

View File

@ -1,4 +1,7 @@
from django.contrib.auth import authenticate, login, logout
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from rest_framework.decorators import action
from rest_framework.permissions import AllowAny, IsAdminUser, IsAuthenticated
@ -77,6 +80,7 @@ class UserViewset(ActionBasedMixin, ModelViewSet):
return Response(serializer.data)
@action(detail=False, methods=["POST"])
@method_decorator(csrf_exempt)
def login(self, request):
username = request.data["username"]
password = request.data["password"]