4 changed files with 153 additions and 10 deletions
-
74backend/ads/migrations/0001_initial.py
-
51backend/ads/serializers.py
-
11backend/ads/urls.py
-
27backend/ads/views.py
@ -0,0 +1,74 @@ |
|||||
|
# Generated by Django 6.0.8 on 2026-08-08 07:53 |
||||
|
|
||||
|
import django.db.models.deletion |
||||
|
import uuid |
||||
|
from django.db import migrations, models |
||||
|
|
||||
|
|
||||
|
class Migration(migrations.Migration): |
||||
|
|
||||
|
initial = True |
||||
|
|
||||
|
dependencies = [ |
||||
|
('crawler', '0001_initial'), |
||||
|
] |
||||
|
|
||||
|
operations = [ |
||||
|
migrations.CreateModel( |
||||
|
name='Ad', |
||||
|
fields=[ |
||||
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), |
||||
|
('divar_token', models.CharField(max_length=50, unique=True)), |
||||
|
('title', models.CharField(max_length=255)), |
||||
|
('description', models.TextField()), |
||||
|
('price', models.CharField(blank=True, max_length=100, null=True)), |
||||
|
('category', models.CharField(blank=True, max_length=100, null=True)), |
||||
|
('images', models.JSONField(default=list)), |
||||
|
('url', models.URLField()), |
||||
|
('created_at', models.DateTimeField(auto_now_add=True)), |
||||
|
('published_at', models.DateTimeField(blank=True, null=True)), |
||||
|
], |
||||
|
options={ |
||||
|
'indexes': [models.Index(fields=['created_at'], name='ads_ad_created_9f5b83_idx')], |
||||
|
}, |
||||
|
), |
||||
|
migrations.CreateModel( |
||||
|
name='AdEvaluation', |
||||
|
fields=[ |
||||
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), |
||||
|
('is_flagged', models.BooleanField(default=False)), |
||||
|
('reason', models.TextField(blank=True, null=True)), |
||||
|
('confidence', models.FloatField(blank=True, null=True)), |
||||
|
('extracted_fields', models.JSONField(default=dict)), |
||||
|
('evaluated_at', models.DateTimeField(auto_now_add=True)), |
||||
|
('ad', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='evaluations', to='ads.ad')), |
||||
|
('crawl_task', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='evaluations', to='crawler.crawltask')), |
||||
|
], |
||||
|
), |
||||
|
migrations.CreateModel( |
||||
|
name='NotificationLog', |
||||
|
fields=[ |
||||
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), |
||||
|
('channel_id', models.CharField(max_length=100)), |
||||
|
('sent_at', models.DateTimeField(auto_now_add=True)), |
||||
|
('status', models.CharField(choices=[('SENT', 'Sent'), ('FAILED', 'Failed')], max_length=20)), |
||||
|
('error_message', models.TextField(blank=True, null=True)), |
||||
|
('evaluation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to='ads.adevaluation')), |
||||
|
], |
||||
|
options={ |
||||
|
'abstract': False, |
||||
|
}, |
||||
|
), |
||||
|
migrations.AddIndex( |
||||
|
model_name='adevaluation', |
||||
|
index=models.Index(fields=['is_flagged'], name='ads_adevalu_is_flag_1e9b55_idx'), |
||||
|
), |
||||
|
migrations.AddIndex( |
||||
|
model_name='adevaluation', |
||||
|
index=models.Index(fields=['evaluated_at'], name='ads_adevalu_evaluat_9e55c2_idx'), |
||||
|
), |
||||
|
migrations.AddConstraint( |
||||
|
model_name='adevaluation', |
||||
|
constraint=models.UniqueConstraint(fields=('crawl_task', 'ad'), name='unique_crawl_task_ad'), |
||||
|
), |
||||
|
] |
||||
@ -0,0 +1,51 @@ |
|||||
|
from rest_framework import serializers |
||||
|
from .models import Ad, AdEvaluation, NotificationLog |
||||
|
|
||||
|
class AdSerializer(serializers.ModelSerializer): |
||||
|
class Meta: |
||||
|
model = Ad |
||||
|
fields = [ |
||||
|
'id', |
||||
|
'divar_token', |
||||
|
'title', |
||||
|
'description', |
||||
|
'price', |
||||
|
'category', |
||||
|
'images', |
||||
|
'url', |
||||
|
'created_at', |
||||
|
'published_at', |
||||
|
] |
||||
|
read_only_fields = ['id', 'created_at'] |
||||
|
|
||||
|
|
||||
|
class AdEvaluationSerializer(serializers.ModelSerializer): |
||||
|
ad = AdSerializer(read_only=True) |
||||
|
|
||||
|
class Meta: |
||||
|
model = AdEvaluation |
||||
|
fields = [ |
||||
|
'id', |
||||
|
'crawl_task', |
||||
|
'ad', |
||||
|
'is_flagged', |
||||
|
'reason', |
||||
|
'confidence', |
||||
|
'extracted_fields', |
||||
|
'evaluated_at', |
||||
|
] |
||||
|
read_only_fields = ['id', 'evaluated_at'] |
||||
|
|
||||
|
|
||||
|
class NotificationLogSerializer(serializers.ModelSerializer): |
||||
|
class Meta: |
||||
|
model = NotificationLog |
||||
|
fields = [ |
||||
|
'id', |
||||
|
'evaluation', |
||||
|
'channel_id', |
||||
|
'sent_at', |
||||
|
'status', |
||||
|
'error_message', |
||||
|
] |
||||
|
read_only_fields = ['id', 'sent_at'] |
||||
@ -1,6 +1,11 @@ |
|||||
from django.urls import path |
|
||||
from .views import AdsView |
|
||||
|
from django.urls import path, include |
||||
|
from rest_framework.routers import DefaultRouter |
||||
|
from .views import AdEvaluationViewSet |
||||
|
|
||||
|
router = DefaultRouter() |
||||
|
router.register(r'', AdEvaluationViewSet, basename='ads') |
||||
|
|
||||
urlpatterns = [ |
urlpatterns = [ |
||||
path('', AdsView.as_view()), |
|
||||
|
path('', include(router.urls)), |
||||
] |
] |
||||
|
|
||||
@ -1,10 +1,23 @@ |
|||||
from django.shortcuts import render |
|
||||
from rest_framework.views import APIView |
|
||||
from rest_framework.response import Response |
|
||||
|
from rest_framework import viewsets |
||||
|
from .models import AdEvaluation |
||||
|
from .serializers import AdEvaluationSerializer |
||||
|
|
||||
# Create your views here. |
|
||||
|
class AdEvaluationViewSet(viewsets.ReadOnlyModelViewSet): |
||||
|
""" |
||||
|
ViewSet for listing and viewing Ad Evaluations. |
||||
|
Supports filtering by crawl_task (UUID) and is_flagged (boolean). |
||||
|
""" |
||||
|
serializer_class = AdEvaluationSerializer |
||||
|
|
||||
class AdsView(APIView): |
|
||||
def get(self, request): |
|
||||
return Response({'message': 'Hello from Ads'}) |
|
||||
|
def get_queryset(self): |
||||
|
queryset = AdEvaluation.objects.all().select_related('ad', 'crawl_task').order_by('-evaluated_at') |
||||
|
crawl_task_id = self.request.query_params.get('crawl_task') |
||||
|
is_flagged = self.request.query_params.get('is_flagged') |
||||
|
|
||||
|
if crawl_task_id: |
||||
|
queryset = queryset.filter(crawl_task_id=crawl_task_id) |
||||
|
if is_flagged is not None: |
||||
|
is_flagged_bool = is_flagged.lower() in ('true', '1', 't') |
||||
|
queryset = queryset.filter(is_flagged=is_flagged_bool) |
||||
|
|
||||
|
return queryset |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue